Linux wireless drivers development
 help / color / mirror / Atom feed
* Re: [PATCH] wifi: ath11k: apply existing PM quirk to ThinkPad P14s Gen 5 AMD
From: Kyle Farnung @ 2026-03-31 15:40 UTC (permalink / raw)
  To: kfarnung
  Cc: jjohnson, mpearson-lenovo, stable, linux-wireless, ath11k,
	linux-kernel
In-Reply-To: <20260330-p14s-pm-quirk-v1-1-cf2fa39cc2d5@gmail.com>

On Mon, Mar 30, 2026 at 11:18 PM Kyle Farnung via B4 Relay
<devnull+kfarnung.gmail.com@kernel.org> wrote:
>
> From: Kyle Farnung <kfarnung@gmail.com>
>
> Some ThinkPad P14s Gen 5 AMD systems experience suspend/resume
> reliability issues similar to those reported in [1]. These platforms
> were not previously included in the ath11k PM quirk table.
>
> Add DMI matches for product IDs 21ME and 21MF to apply the existing
> ATH11K_PM_WOW override, improving suspend/resume behavior on these
> systems.
>
> Tested on a ThinkPad P14s Gen 5 AMD (21ME) running 6.19.9.
>
> [1] https://bugzilla.kernel.org/show_bug.cgi?id=219196
> [2] https://pcsupport.lenovo.com/us/en/products/laptops-and-netbooks/thinkpad-p-series-laptops/thinkpad-p14s-gen-5-type-21me-21mf/
>
> Fixes: ce8669a27016 ("wifi: ath11k: determine PM policy based on machine model")
> Cc: stable@vger.kernel.org
> Signed-off-by: Kyle Farnung <kfarnung@gmail.com>
> ---
>  drivers/net/wireless/ath/ath11k/core.c | 14 ++++++++++++++
>  1 file changed, 14 insertions(+)
>
> diff --git a/drivers/net/wireless/ath/ath11k/core.c b/drivers/net/wireless/ath/ath11k/core.c
> index 3f6f4db5b7ee1aba79fd7526e5d59d068e0f4a2e..21d366224e75904feeae6cb9c93d9ef692d127fe 100644
> --- a/drivers/net/wireless/ath/ath11k/core.c
> +++ b/drivers/net/wireless/ath/ath11k/core.c
> @@ -1041,6 +1041,20 @@ static const struct dmi_system_id ath11k_pm_quirk_table[] = {
>                         DMI_MATCH(DMI_PRODUCT_NAME, "21D5"),
>                 },
>         },
> +       {
> +               .driver_data = (void *)ATH11K_PM_WOW,
> +               .matches = { /* P14s G5 AMD #1 */
> +                       DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
> +                       DMI_MATCH(DMI_PRODUCT_NAME, "21ME"),
> +               },
> +       },
> +       {
> +               .driver_data = (void *)ATH11K_PM_WOW,
> +               .matches = { /* P14s G5 AMD #2 */
> +                       DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
> +                       DMI_MATCH(DMI_PRODUCT_NAME, "21MF"),
> +               },
> +       },
>         {}
>  };
>
>
> ---
> base-commit: dbd94b9831bc52a1efb7ff3de841ffc3457428ce
> change-id: 20260330-p14s-pm-quirk-0a51ba19235f
>
> Best regards,
> --
> Kyle Farnung <kfarnung@gmail.com>
>
>

Apologies to everyone, I realized that I lost my CC list in the sending
process. I wasn't sure what the right fix was so I ended up posting a v2
patch [1] instead.

[1] https://lore.kernel.org/linux-wireless/20260330-p14s-pm-quirk-v2-1-ef18ce07996b@gmail.com/

Thanks,
Kyle

^ permalink raw reply

* Re: [PATCH ath-next v3 1/6] dt-bindings: net: wireless: add ath12k wifi device IPQ5424
From: Krzysztof Kozlowski @ 2026-03-31 14:42 UTC (permalink / raw)
  To: Jeff Johnson, Raj Kumar Bhagat
  Cc: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jeff Johnson, linux-wireless, devicetree, linux-kernel, ath12k
In-Reply-To: <b1194bdb-75fa-4f2e-b4a3-9565b11bb3e9@oss.qualcomm.com>

On 31/03/2026 16:23, Jeff Johnson wrote:
> On 3/31/2026 12:24 AM, Krzysztof Kozlowski wrote:
>> On Tue, Mar 31, 2026 at 02:09:06AM +0530, Raj Kumar Bhagat wrote:
>>>  $id: http://devicetree.org/schemas/net/wireless/qcom,ipq5332-wifi.yaml#
>>> @@ -17,6 +17,7 @@ properties:
>>>    compatible:
>>>      enum:
>>>        - qcom,ipq5332-wifi
>>> +      - qcom,ipq5424-wifi
>>
>> No, use previous patch.
>>
>> I am annoyed that you keep making changes even for such trivialities and
>> require re-review from the community.  Previous patch was correct. This
>> one doing whatever you want to do in copyrights is too much. You don't
>> change copyrights just because you wrote one device model.
> 
> Krzysztof,
> 
> FYI here is the guidance I received from Qualcomm legal (links to internal
> documentation, removed -- I've forwarded the entire e-mail to your Qualcomm
> mailbox):

As I explained already more than once, legal can engage in open source
discussions directly. I am not going to discuss with them via proxies.

> 
> ... Repos under copyleft license [...] QTI copyright must be added when we
> make significant changes.
> 
> ... Repos under friendly license (BSD, Apache, MIT, ...) [...] QTI copyright
> must be added for any changes, not just significant ones.
> 
> ... under the regular QUIC to QTI open-source copyright transitioning [...]
> all QUIC Copyright instances should be replaced with year-less QTI OSS Copyright.
> 
> I'll follow up with them on this case where there is a dual-license file.

You nicely removed the quote where they ask to follow what the upstream
maintainer asks for. So as one of the maintainers I ask not to change
it, because it is churn and pointless waste of my time.

Best regards,
Krzysztof

^ permalink raw reply

* [PATCH ath-next 5/5] wifi: ath12k: add thermal cooling device support
From: Maharaja Kennadyrajan @ 2026-03-31 14:24 UTC (permalink / raw)
  To: ath12k; +Cc: linux-wireless, Maharaja Kennadyrajan
In-Reply-To: <20260331142446.2951809-1-maharaja.kennadyrajan@oss.qualcomm.com>

Add thermal cooling device support to control the temperature by throttling data
transmission. Throttling is performed by suspending data TX queues according to
a configured duty-cycle off percentage. The thermal cooling device allows users
to configure the duty-cycle off percentage and operate the device with the
selected value.

User configuration updates a single duty-cycle off percentage, which is applied
uniformly by the host and treated as only one temperature level. This value
remains in effect until updated again by the user. All other thermal throttling
parameters continue to use their default firmware provided values.

Reject invalid duty-cycle off percentage values that fall outside the supported
range. Register a cooling device to allow the thermal framework to query and set
the current throttle state, report the maximum supported state, and keep the
host state in sync with successful firmware updates. A throttle state of zero
restores the default firmware thermal configuration.

Command to set the duty-cycle off percent:
echo 40 > /sys/devices/pci0000:00/0000:00:1d.1/0000:58:00.0/ieee80211/phyX/cooling_device0/cur_state

Command to read duty-cycle off percent:
cat /sys/devices/pci0000:00/0000:00:1d.1/0000:58:00.0/ieee80211/phyX/cooling_device0/cur_state

Command to read the maximum duty-cycle off percent:
cat /sys/devices/pci0000:00/0000:00:1d.1/0000:58:00.0/ieee80211/phyX/cooling_device0/max_state

Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1
Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3

Signed-off-by: Maharaja Kennadyrajan <maharaja.kennadyrajan@oss.qualcomm.com>
---
 drivers/net/wireless/ath/ath12k/mac.c     |   1 +
 drivers/net/wireless/ath/ath12k/thermal.c | 107 +++++++++++++++++++++-
 drivers/net/wireless/ath/ath12k/thermal.h |  14 +++
 3 files changed, 121 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
index 21430a70aa7c..40395c9a8bea 100644
--- a/drivers/net/wireless/ath/ath12k/mac.c
+++ b/drivers/net/wireless/ath/ath12k/mac.c
@@ -14821,6 +14821,7 @@ static void ath12k_mac_setup(struct ath12k *ar)
 	init_completion(&ar->completed_11d_scan);
 	init_completion(&ar->regd_update_completed);
 	init_completion(&ar->thermal.wmi_sync);
+	mutex_init(&ar->thermal.lock);
 
 	ar->thermal.temperature = 0;
 	ar->thermal.hwmon_dev = NULL;
diff --git a/drivers/net/wireless/ath/ath12k/thermal.c b/drivers/net/wireless/ath/ath12k/thermal.c
index 6f70c11c1098..97fc49c40ac1 100644
--- a/drivers/net/wireless/ath/ath12k/thermal.c
+++ b/drivers/net/wireless/ath/ath12k/thermal.c
@@ -76,6 +76,73 @@ void ath12k_thermal_init_configs(struct ath12k *ar)
 	ar->thermal.tt_level_configs = &tt_level_configs[cfg_idx][0];
 }
 
+static int
+ath12k_thermal_get_max_throttle_state(struct thermal_cooling_device *cdev,
+				      unsigned long *state)
+{
+	*state = ATH12K_THERMAL_THROTTLE_MAX;
+
+	return 0;
+}
+
+static int
+ath12k_thermal_get_cur_throttle_state(struct thermal_cooling_device *cdev,
+				      unsigned long *state)
+{
+	struct ath12k *ar = cdev->devdata;
+
+	mutex_lock(&ar->thermal.lock);
+	*state = ar->thermal.throttle_state;
+	mutex_unlock(&ar->thermal.lock);
+
+	return 0;
+}
+
+int ath12k_thermal_set_throttling(struct ath12k *ar, u32 throttle_state)
+{
+	struct ath12k_wmi_thermal_mitigation_arg param = {};
+	struct ath12k_wmi_tt_level_config_param cfg = {};
+	int ret;
+
+	param.num_levels = 1;
+	cfg.dcoffpercent = throttle_state;
+	param.levelconf = &cfg;
+
+	ret = ath12k_wmi_send_thermal_mitigation_cmd(ar, &param);
+	if (ret)
+		ath12k_warn(ar->ab, "failed to send thermal mitigation cmd: %d\n",
+			    ret);
+
+	return ret;
+}
+
+static int
+ath12k_thermal_set_cur_throttle_state(struct thermal_cooling_device *cdev,
+				      unsigned long throttle_state)
+{
+	struct ath12k *ar = cdev->devdata;
+
+	if (throttle_state > ATH12K_THERMAL_THROTTLE_MAX)
+		return -EINVAL;
+
+	scoped_guard(mutex, &ar->thermal.lock) {
+		if (ar->thermal.throttle_state == throttle_state)
+			return 0;
+		ar->thermal.throttle_state = throttle_state;
+	}
+
+	if (throttle_state == 0)
+		return ath12k_thermal_throttling_config_default(ar);
+
+	return ath12k_thermal_set_throttling(ar, throttle_state);
+}
+
+static const struct thermal_cooling_device_ops ath12k_thermal_ops = {
+	.get_max_state = ath12k_thermal_get_max_throttle_state,
+	.get_cur_state = ath12k_thermal_get_cur_throttle_state,
+	.set_cur_state = ath12k_thermal_set_cur_throttle_state,
+};
+
 static ssize_t ath12k_thermal_temp_show(struct device *dev,
 					struct device_attribute *attr,
 					char *buf)
@@ -132,6 +199,7 @@ ATTRIBUTE_GROUPS(ath12k_hwmon);
 
 static int ath12k_thermal_setup_radio(struct ath12k_base *ab, int i)
 {
+	char pdev_name[20];
 	struct ath12k *ar;
 	int ret;
 
@@ -139,6 +207,28 @@ static int ath12k_thermal_setup_radio(struct ath12k_base *ab, int i)
 	if (!ar)
 		return 0;
 
+	ar->thermal.cdev =
+		thermal_cooling_device_register("ath12k_thermal", ar,
+						&ath12k_thermal_ops);
+	if (IS_ERR(ar->thermal.cdev)) {
+		ret = PTR_ERR(ar->thermal.cdev);
+		ar->thermal.cdev = NULL;
+		ath12k_err(ar->ab, "failed to register cooling device: %d\n",
+			   ret);
+		return ret;
+	}
+
+	scnprintf(pdev_name, sizeof(pdev_name), "cooling_device%u",
+		  ar->hw_link_id);
+
+	ret = sysfs_create_link(&ar->ah->hw->wiphy->dev.kobj,
+				&ar->thermal.cdev->device.kobj, pdev_name);
+	if (ret) {
+		ath12k_err(ab, "failed to create cooling device symlink: %d\n",
+			   ret);
+		goto unregister_cdev;
+	}
+
 	ar->thermal.hwmon_dev =
 		hwmon_device_register_with_groups(&ar->ah->hw->wiphy->dev,
 						  "ath12k_hwmon", ar,
@@ -148,14 +238,22 @@ static int ath12k_thermal_setup_radio(struct ath12k_base *ab, int i)
 		ar->thermal.hwmon_dev = NULL;
 		ath12k_err(ar->ab, "failed to register hwmon device: %d\n",
 			   ret);
-		return ret;
+		goto remove_sysfs;
 	}
 
 	return 0;
+
+remove_sysfs:
+	sysfs_remove_link(&ar->ah->hw->wiphy->dev.kobj, pdev_name);
+unregister_cdev:
+	thermal_cooling_device_unregister(ar->thermal.cdev);
+	ar->thermal.cdev = NULL;
+	return ret;
 }
 
 static void ath12k_thermal_cleanup_radio(struct ath12k_base *ab, int i)
 {
+	char pdev_name[20];
 	struct ath12k *ar;
 
 	ar = ab->pdevs[i].ar;
@@ -164,6 +262,13 @@ static void ath12k_thermal_cleanup_radio(struct ath12k_base *ab, int i)
 
 	hwmon_device_unregister(ar->thermal.hwmon_dev);
 	ar->thermal.hwmon_dev = NULL;
+
+	scnprintf(pdev_name, sizeof(pdev_name), "cooling_device%u",
+		  ar->hw_link_id);
+	sysfs_remove_link(&ar->ah->hw->wiphy->dev.kobj, pdev_name);
+
+	thermal_cooling_device_unregister(ar->thermal.cdev);
+	ar->thermal.cdev = NULL;
 }
 
 int ath12k_thermal_register(struct ath12k_base *ab)
diff --git a/drivers/net/wireless/ath/ath12k/thermal.h b/drivers/net/wireless/ath/ath12k/thermal.h
index 33231bb3683c..30e7b0880e05 100644
--- a/drivers/net/wireless/ath/ath12k/thermal.h
+++ b/drivers/net/wireless/ath/ath12k/thermal.h
@@ -7,9 +7,12 @@
 #ifndef _ATH12K_THERMAL_
 #define _ATH12K_THERMAL_
 
+#include <linux/mutex.h>
+
 #define ATH12K_THERMAL_SYNC_TIMEOUT_HZ (5 * HZ)
 
 #define ATH12K_THERMAL_DEFAULT_DUTY_CYCLE 100
+#define ATH12K_THERMAL_THROTTLE_MAX 100
 
 enum ath12k_thermal_cfg_idx {
 	/* Internal Power Amplifier Device */
@@ -26,6 +29,10 @@ struct ath12k_thermal {
 	int temperature;
 	struct device *hwmon_dev;
 	const struct ath12k_wmi_tt_level_config_param *tt_level_configs;
+	struct thermal_cooling_device *cdev;
+	/* Serialize thermal operations and hwmon reads */
+	struct mutex lock;
+	u32 throttle_state;
 };
 
 #if IS_REACHABLE(CONFIG_THERMAL)
@@ -34,6 +41,7 @@ void ath12k_thermal_unregister(struct ath12k_base *ab);
 void ath12k_thermal_event_temperature(struct ath12k *ar, int temperature);
 int ath12k_thermal_throttling_config_default(struct ath12k *ar);
 void ath12k_thermal_init_configs(struct ath12k *ar);
+int ath12k_thermal_set_throttling(struct ath12k *ar, u32 throttle_state);
 #else
 static inline int ath12k_thermal_register(struct ath12k_base *ab)
 {
@@ -57,5 +65,11 @@ static inline int ath12k_thermal_throttling_config_default(struct ath12k *ar)
 static inline void ath12k_thermal_init_configs(struct ath12k *ar)
 {
 }
+
+static inline int ath12k_thermal_set_throttling(struct ath12k *ar,
+						u32 throttle_state)
+{
+	return 0;
+}
 #endif
 #endif /* _ATH12K_THERMAL_ */
-- 
2.34.1


^ permalink raw reply related

* [PATCH ath-next 4/5] wifi: ath12k: reorder group start/stop for safe thermal sysfs cleanup
From: Maharaja Kennadyrajan @ 2026-03-31 14:24 UTC (permalink / raw)
  To: ath12k; +Cc: linux-wireless, Maharaja Kennadyrajan
In-Reply-To: <20260331142446.2951809-1-maharaja.kennadyrajan@oss.qualcomm.com>

A later change adds thermal cooling device sysfs under the wiphy device kobject.
With the current teardown order, MAC/wiphy are unregistered before per-device
cleanup, so any subsequent thermal sysfs removal would run after the wiphy kobject
is gone. That ordering is asymmetric with setup and would risk kernfs issues during
removal.

This change also adjusts the position of ath12k_mac_mlo_teardown(). Previously
it ran before per-device cleanup/MAC unregister. MLO teardown issues WMI to teardown
multi-link state and is part of the MAC teardown sequence. Placing it alongside
MAC unregister (after per-device cleanup) preserves setup/teardown symmetry and
avoids racing with remaining netdev/wiphy state.

Reorder hw_group_stop() so per-device cleanup (including thermal/hwmon sysfs
removal) runs while the wiphy still exists. After per-device cleanup completes,
unregister the MAC (dropping wiphys), run ath12k_mac_mlo_teardown(), and finally
destroy the MAC. This mirrors the setup sequence and keeps sysfs cleanup safe
when introduced in a later patch.

To keep start/stop symmetry, add ath12k_core_device_setup() to encapsulate the
per-device bring-up steps (pdev create, IRQ enable, rfkill config) that were
previously open-coded in hw_group_start(). Use this helper in hw_group_start()
to match the existing per-device cleanup helper used by hw_group_stop().

Note that set_bit(ATH12K_FLAG_REGISTERED, &ab->dev_flags) is now executed outside
the ab->core_lock critical section. The core_lock has not provided protection for
the REGISTERED flag, readers do not rely on core_lock for this bit, and the flag
is only toggled in the serialized group start/stop path using atomic bitops.

Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1

Signed-off-by: Maharaja Kennadyrajan <maharaja.kennadyrajan@oss.qualcomm.com>
---
 drivers/net/wireless/ath/ath12k/core.c | 50 +++++++++++++++-----------
 1 file changed, 29 insertions(+), 21 deletions(-)

diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c
index c31c47fb5a73..d21db889794a 100644
--- a/drivers/net/wireless/ath/ath12k/core.c
+++ b/drivers/net/wireless/ath/ath12k/core.c
@@ -1006,6 +1006,27 @@ static void ath12k_core_device_cleanup(struct ath12k_base *ab)
 	mutex_unlock(&ab->core_lock);
 }
 
+static int ath12k_core_device_setup(struct ath12k_base *ab)
+{
+	int ret;
+
+	guard(mutex)(&ab->core_lock);
+
+	ret = ath12k_core_pdev_create(ab);
+	if (ret) {
+		ath12k_err(ab, "failed to create pdev core %d\n", ret);
+		return ret;
+	}
+
+	ath12k_hif_irq_enable(ab);
+
+	ret = ath12k_core_rfkill_config(ab);
+	if (ret && ret != -EOPNOTSUPP)
+		return ret;
+
+	return 0;
+}
+
 static void ath12k_core_hw_group_stop(struct ath12k_hw_group *ag)
 {
 	struct ath12k_base *ab;
@@ -1015,10 +1036,6 @@ static void ath12k_core_hw_group_stop(struct ath12k_hw_group *ag)
 
 	clear_bit(ATH12K_GROUP_FLAG_REGISTERED, &ag->flags);
 
-	ath12k_mac_unregister(ag);
-
-	ath12k_mac_mlo_teardown(ag);
-
 	for (i = ag->num_devices - 1; i >= 0; i--) {
 		ab = ag->ab[i];
 		if (!ab)
@@ -1029,6 +1046,12 @@ static void ath12k_core_hw_group_stop(struct ath12k_hw_group *ag)
 		ath12k_core_device_cleanup(ab);
 	}
 
+	/* Unregister MAC (drops wiphys) only after per-device cleanup */
+	ath12k_mac_unregister(ag);
+
+	/* Teardown MLO state after MAC unregister for symmetry */
+	ath12k_mac_mlo_teardown(ag);
+
 	ath12k_mac_destroy(ag);
 }
 
@@ -1165,26 +1188,11 @@ static int ath12k_core_hw_group_start(struct ath12k_hw_group *ag)
 		if (!ab)
 			continue;
 
-		mutex_lock(&ab->core_lock);
-
 		set_bit(ATH12K_FLAG_REGISTERED, &ab->dev_flags);
 
-		ret = ath12k_core_pdev_create(ab);
-		if (ret) {
-			ath12k_err(ab, "failed to create pdev core %d\n", ret);
-			mutex_unlock(&ab->core_lock);
-			goto err;
-		}
-
-		ath12k_hif_irq_enable(ab);
-
-		ret = ath12k_core_rfkill_config(ab);
-		if (ret && ret != -EOPNOTSUPP) {
-			mutex_unlock(&ab->core_lock);
+		ret = ath12k_core_device_setup(ab);
+		if (ret)
 			goto err;
-		}
-
-		mutex_unlock(&ab->core_lock);
 	}
 
 	return 0;
-- 
2.34.1


^ permalink raw reply related

* [PATCH ath-next 3/5] wifi: ath12k: refactor per-radio thermal hwmon setup and cleanup
From: Maharaja Kennadyrajan @ 2026-03-31 14:24 UTC (permalink / raw)
  To: ath12k; +Cc: linux-wireless, Maharaja Kennadyrajan
In-Reply-To: <20260331142446.2951809-1-maharaja.kennadyrajan@oss.qualcomm.com>

Both the error path in thermal registration and the normal thermal unregister
path performed the same hwmon device unregistration and pointer cleanup.
Consolidate this logic into a single helper to reduce code duplication and ensure
consistent cleanup across all paths. Add a helper to set up the hwmon registration
during thermal registration to keep symmetry with thermal cleanup.

Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1
Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3

Signed-off-by: Maharaja Kennadyrajan <maharaja.kennadyrajan@oss.qualcomm.com>
---
 drivers/net/wireless/ath/ath12k/thermal.c | 83 +++++++++++++----------
 1 file changed, 47 insertions(+), 36 deletions(-)

diff --git a/drivers/net/wireless/ath/ath12k/thermal.c b/drivers/net/wireless/ath/ath12k/thermal.c
index 4f76622e8117..6f70c11c1098 100644
--- a/drivers/net/wireless/ath/ath12k/thermal.c
+++ b/drivers/net/wireless/ath/ath12k/thermal.c
@@ -130,59 +130,70 @@ static struct attribute *ath12k_hwmon_attrs[] = {
 };
 ATTRIBUTE_GROUPS(ath12k_hwmon);
 
-int ath12k_thermal_register(struct ath12k_base *ab)
+static int ath12k_thermal_setup_radio(struct ath12k_base *ab, int i)
+{
+	struct ath12k *ar;
+	int ret;
+
+	ar = ab->pdevs[i].ar;
+	if (!ar)
+		return 0;
+
+	ar->thermal.hwmon_dev =
+		hwmon_device_register_with_groups(&ar->ah->hw->wiphy->dev,
+						  "ath12k_hwmon", ar,
+						  ath12k_hwmon_groups);
+	if (IS_ERR(ar->thermal.hwmon_dev)) {
+		ret = PTR_ERR(ar->thermal.hwmon_dev);
+		ar->thermal.hwmon_dev = NULL;
+		ath12k_err(ar->ab, "failed to register hwmon device: %d\n",
+			   ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void ath12k_thermal_cleanup_radio(struct ath12k_base *ab, int i)
 {
 	struct ath12k *ar;
-	int i, j, ret;
+
+	ar = ab->pdevs[i].ar;
+	if (!ar)
+		return;
+
+	hwmon_device_unregister(ar->thermal.hwmon_dev);
+	ar->thermal.hwmon_dev = NULL;
+}
+
+int ath12k_thermal_register(struct ath12k_base *ab)
+{
+	int i, ret;
 
 	if (!IS_REACHABLE(CONFIG_HWMON))
 		return 0;
 
 	for (i = 0; i < ab->num_radios; i++) {
-		ar = ab->pdevs[i].ar;
-		if (!ar)
-			continue;
-
-		ar->thermal.hwmon_dev =
-			hwmon_device_register_with_groups(&ar->ah->hw->wiphy->dev,
-							  "ath12k_hwmon", ar,
-							  ath12k_hwmon_groups);
-		if (IS_ERR(ar->thermal.hwmon_dev)) {
-			ret = PTR_ERR(ar->thermal.hwmon_dev);
-			ar->thermal.hwmon_dev = NULL;
-			ath12k_err(ar->ab, "failed to register hwmon device: %d\n",
-				   ret);
-			for (j = i - 1; j >= 0; j--) {
-				ar = ab->pdevs[j].ar;
-				if (!ar)
-					continue;
-
-				hwmon_device_unregister(ar->thermal.hwmon_dev);
-				ar->thermal.hwmon_dev = NULL;
-			}
-			return ret;
-		}
+		ret = ath12k_thermal_setup_radio(ab, i);
+		if (ret)
+			goto out;
 	}
 
 	return 0;
+out:
+	for (i--; i >= 0; i--)
+		ath12k_thermal_cleanup_radio(ab, i);
+
+	return ret;
 }
 
 void ath12k_thermal_unregister(struct ath12k_base *ab)
 {
-	struct ath12k *ar;
 	int i;
 
 	if (!IS_REACHABLE(CONFIG_HWMON))
 		return;
 
-	for (i = 0; i < ab->num_radios; i++) {
-		ar = ab->pdevs[i].ar;
-		if (!ar)
-			continue;
-
-		if (ar->thermal.hwmon_dev) {
-			hwmon_device_unregister(ar->thermal.hwmon_dev);
-			ar->thermal.hwmon_dev = NULL;
-		}
-	}
+	for (i = 0; i < ab->num_radios; i++)
+		ath12k_thermal_cleanup_radio(ab, i);
 }
-- 
2.34.1


^ permalink raw reply related

* [PATCH ath-next 2/5] wifi: ath12k: configure firmware thermal throttling via WMI
From: Maharaja Kennadyrajan @ 2026-03-31 14:24 UTC (permalink / raw)
  To: ath12k; +Cc: linux-wireless, Maharaja Kennadyrajan
In-Reply-To: <20260331142446.2951809-1-maharaja.kennadyrajan@oss.qualcomm.com>

Ath12k firmware supports thermal-throttling but requires the host to explicitly
program throttle levels and mitigation actions via WMI. Without this configuration,
firmware-driven thermal mitigation remains inactive or relies on platform-specific
defaults.

Add host-side support to build and send thermal-throttle configuration using
WMI_THERM_THROT_SET_CONF_CMDID during MAC radio start, ensuring thermal parameters
are programmed before data traffic begins.

Maintain per-radio storage for thermal throttle levels and provide conservative
default level tables for Internal Power Amplifier Device (IPA) and External Power
Amplifier Device or External Front End Module (XFEM) targets. The appropriate
default table is selected based on firmware-advertised service bits, allowing the
host to align with target thermal mitigation capabilities. If the WMI TLV service
WMI_TLV_SERVICE_IS_TARGET_IPA bit is set, then host selects the thermal throttle
values from IPA index from the table and selects values from XFEM index from the
table if this WMI TLV service bit is not set.

Build and send the thermal throttle configuration request with either 4 or
5 levels depending on firmware capability, and populate optional fields
(pout reduction and tx chain mask) only when the corresponding service bits
are advertised.

Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1
Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3

Signed-off-by: Maharaja Kennadyrajan <maharaja.kennadyrajan@oss.qualcomm.com>
---
 drivers/net/wireless/ath/ath12k/mac.c     |  8 +++
 drivers/net/wireless/ath/ath12k/thermal.c | 64 +++++++++++++++++++++
 drivers/net/wireless/ath/ath12k/thermal.h | 21 +++++++
 drivers/net/wireless/ath/ath12k/wmi.c     | 69 +++++++++++++++++++++++
 drivers/net/wireless/ath/ath12k/wmi.h     | 42 ++++++++++++++
 5 files changed, 204 insertions(+)

diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
index 553ec28b6aaa..21430a70aa7c 100644
--- a/drivers/net/wireless/ath/ath12k/mac.c
+++ b/drivers/net/wireless/ath/ath12k/mac.c
@@ -9673,6 +9673,12 @@ static int ath12k_mac_start(struct ath12k *ar)
 		}
 	}
 
+	ret = ath12k_thermal_throttling_config_default(ar);
+	if (ret) {
+		ath12k_err(ab, "failed to set thermal throttle: %d\n", ret);
+		goto err;
+	}
+
 	rcu_assign_pointer(ab->pdevs_active[ar->pdev_idx],
 			   &ab->pdevs[ar->pdev_idx]);
 
@@ -14461,6 +14467,8 @@ static int ath12k_mac_setup_register(struct ath12k *ar,
 	ar->rssi_info.temp_offset = 0;
 	ar->rssi_info.noise_floor = ar->rssi_info.min_nf_dbm + ar->rssi_info.temp_offset;
 
+	ath12k_thermal_init_configs(ar);
+
 	return 0;
 }
 
diff --git a/drivers/net/wireless/ath/ath12k/thermal.c b/drivers/net/wireless/ath/ath12k/thermal.c
index a764d2112a3c..4f76622e8117 100644
--- a/drivers/net/wireless/ath/ath12k/thermal.c
+++ b/drivers/net/wireless/ath/ath12k/thermal.c
@@ -12,6 +12,70 @@
 #include "core.h"
 #include "debug.h"
 
+static const struct ath12k_wmi_tt_level_config_param
+tt_level_configs[ATH12K_TT_CFG_IDX_MAX][ENHANCED_THERMAL_LEVELS] = {
+	[ATH12K_TT_CFG_IDX_IPA] = {
+		[0] = {	.tmplwm = -100, .tmphwm = 115, .dcoffpercent = 0,
+			.pout_reduction_db = 0 },
+		[1] = { .tmplwm = 110, .tmphwm = 120, .dcoffpercent = 0,
+			.pout_reduction_db = 12	},
+		[2] = { .tmplwm = 115, .tmphwm = 125, .dcoffpercent = 50,
+			.pout_reduction_db = 12	},
+		[3] = { .tmplwm = 120, .tmphwm = 130, .dcoffpercent = 90,
+			.pout_reduction_db = 12	},
+		[4] = { .tmplwm = 125, .tmphwm = 130, .dcoffpercent = 100,
+			.pout_reduction_db = 12	},
+	},
+	[ATH12K_TT_CFG_IDX_XFEM] = {
+		[0] = {	.tmplwm = -100,	.tmphwm = 105, .dcoffpercent = 0,
+			.pout_reduction_db = 0 },
+		[1] = { .tmplwm = 100, .tmphwm = 110, .dcoffpercent = 0,
+			.pout_reduction_db = 0 },
+		[2] = { .tmplwm = 105, .tmphwm = 115, .dcoffpercent = 50,
+			.pout_reduction_db = 0 },
+		[3] = {	.tmplwm = 110, .tmphwm = 120, .dcoffpercent = 90,
+			.pout_reduction_db = 0 },
+		[4] = { .tmplwm = 115, .tmphwm = 120, .dcoffpercent = 100,
+			.pout_reduction_db = 0 },
+	},
+};
+
+static enum ath12k_thermal_cfg_idx ath12k_thermal_cfg_index(struct ath12k *ar)
+{
+	if (test_bit(WMI_TLV_SERVICE_IS_TARGET_IPA, ar->ab->wmi_ab.svc_map))
+		return ATH12K_TT_CFG_IDX_IPA;
+
+	return ATH12K_TT_CFG_IDX_XFEM;
+}
+
+int ath12k_thermal_throttling_config_default(struct ath12k *ar)
+{
+	struct ath12k_wmi_thermal_mitigation_arg param = {};
+	int ret;
+
+	if (test_bit(WMI_TLV_SERVICE_THERM_THROT_5_LEVELS, ar->ab->wmi_ab.svc_map))
+		param.num_levels = ENHANCED_THERMAL_LEVELS;
+	else
+		param.num_levels = THERMAL_LEVELS;
+
+	param.levelconf = ar->thermal.tt_level_configs;
+
+	ret = ath12k_wmi_send_thermal_mitigation_cmd(ar, &param);
+	if (ret)
+		ath12k_warn(ar->ab,
+			    "failed to send thermal mitigation cmd for default config: %d\n",
+			    ret);
+	return ret;
+}
+
+void ath12k_thermal_init_configs(struct ath12k *ar)
+{
+	enum ath12k_thermal_cfg_idx cfg_idx;
+
+	cfg_idx = ath12k_thermal_cfg_index(ar);
+	ar->thermal.tt_level_configs = &tt_level_configs[cfg_idx][0];
+}
+
 static ssize_t ath12k_thermal_temp_show(struct device *dev,
 					struct device_attribute *attr,
 					char *buf)
diff --git a/drivers/net/wireless/ath/ath12k/thermal.h b/drivers/net/wireless/ath/ath12k/thermal.h
index 9d84056188e1..33231bb3683c 100644
--- a/drivers/net/wireless/ath/ath12k/thermal.h
+++ b/drivers/net/wireless/ath/ath12k/thermal.h
@@ -9,18 +9,31 @@
 
 #define ATH12K_THERMAL_SYNC_TIMEOUT_HZ (5 * HZ)
 
+#define ATH12K_THERMAL_DEFAULT_DUTY_CYCLE 100
+
+enum ath12k_thermal_cfg_idx {
+	/* Internal Power Amplifier Device */
+	ATH12K_TT_CFG_IDX_IPA,
+	/* External Power Amplifier Device or External Front End Module */
+	ATH12K_TT_CFG_IDX_XFEM,
+	ATH12K_TT_CFG_IDX_MAX,
+};
+
 struct ath12k_thermal {
 	struct completion wmi_sync;
 
 	/* temperature value in Celsius degree protected by data_lock. */
 	int temperature;
 	struct device *hwmon_dev;
+	const struct ath12k_wmi_tt_level_config_param *tt_level_configs;
 };
 
 #if IS_REACHABLE(CONFIG_THERMAL)
 int ath12k_thermal_register(struct ath12k_base *ab);
 void ath12k_thermal_unregister(struct ath12k_base *ab);
 void ath12k_thermal_event_temperature(struct ath12k *ar, int temperature);
+int ath12k_thermal_throttling_config_default(struct ath12k *ar);
+void ath12k_thermal_init_configs(struct ath12k *ar);
 #else
 static inline int ath12k_thermal_register(struct ath12k_base *ab)
 {
@@ -36,5 +49,13 @@ static inline void ath12k_thermal_event_temperature(struct ath12k *ar,
 {
 }
 
+static inline int ath12k_thermal_throttling_config_default(struct ath12k *ar)
+{
+	return 0;
+}
+
+static inline void ath12k_thermal_init_configs(struct ath12k *ar)
+{
+}
 #endif
 #endif /* _ATH12K_THERMAL_ */
diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c
index 34184d0d03ff..b239b436b745 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.c
+++ b/drivers/net/wireless/ath/ath12k/wmi.c
@@ -3384,6 +3384,75 @@ int ath12k_wmi_send_set_current_country_cmd(struct ath12k *ar,
 	return ret;
 }
 
+int
+ath12k_wmi_send_thermal_mitigation_cmd(struct ath12k *ar,
+				       struct ath12k_wmi_thermal_mitigation_arg *arg)
+{
+	struct ath12k_wmi_therm_throt_level_config_param *lvl_conf;
+	struct ath12k_wmi_therm_throt_config_request_cmd *cmd;
+	struct ath12k_wmi_pdev *wmi = ar->wmi;
+	struct wmi_tlv *tlv;
+	struct sk_buff *skb;
+	int i, ret, len;
+
+	len = sizeof(*cmd) + TLV_HDR_SIZE + (arg->num_levels * sizeof(*lvl_conf));
+
+	skb = ath12k_wmi_alloc_skb(wmi->wmi_ab, len);
+	if (!skb)
+		return -ENOMEM;
+
+	cmd = (struct ath12k_wmi_therm_throt_config_request_cmd *)skb->data;
+	cmd->tlv_header = ath12k_wmi_tlv_cmd_hdr(WMI_TAG_THERM_THROT_CONFIG_REQUEST,
+						 sizeof(*cmd));
+	cmd->pdev_id = cpu_to_le32(ar->pdev->pdev_id);
+	cmd->enable = cpu_to_le32(1);
+	cmd->dc = cpu_to_le32(100);
+	cmd->dc_per_event = cpu_to_le32(0xffffffff);
+	cmd->therm_throt_levels = cpu_to_le32(arg->num_levels);
+
+	tlv = (struct wmi_tlv *)(skb->data + sizeof(*cmd));
+	tlv->header = ath12k_wmi_tlv_hdr(WMI_TAG_ARRAY_STRUCT,
+					 arg->num_levels * sizeof(*lvl_conf));
+
+	lvl_conf = (struct ath12k_wmi_therm_throt_level_config_param *)tlv->value;
+
+	for (i = 0; i < arg->num_levels; i++) {
+		lvl_conf->tlv_header =
+			ath12k_wmi_tlv_cmd_hdr(WMI_TAG_THERM_THROT_LEVEL_CONFIG_INFO,
+					       sizeof(*lvl_conf));
+
+		lvl_conf->temp_lwm = a_cpu_to_sle32(arg->levelconf[i].tmplwm);
+		lvl_conf->temp_hwm = a_cpu_to_sle32(arg->levelconf[i].tmphwm);
+		lvl_conf->dc_off_percent = cpu_to_le32(arg->levelconf[i].dcoffpercent);
+
+		if (test_bit(WMI_TLV_SERVICE_THERM_THROT_POUT_REDUCTION,
+			     ar->ab->wmi_ab.svc_map))
+			lvl_conf->pout_reduction_25db =
+				cpu_to_le32(arg->levelconf[i].pout_reduction_db);
+
+		if (test_bit(WMI_TLV_SERVICE_THERM_THROT_TX_CHAIN_MASK,
+			     ar->ab->wmi_ab.svc_map))
+			lvl_conf->tx_chain_mask = cpu_to_le32(ar->cfg_tx_chainmask);
+
+		lvl_conf->duty_cycle = cpu_to_le32(ATH12K_THERMAL_DEFAULT_DUTY_CYCLE);
+		lvl_conf++;
+	}
+
+	ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
+		   "WMI vdev set thermal throt pdev_id %u enable dc 100 dc_per_event 0xffffffff levels %d\n",
+		   ar->pdev->pdev_id, arg->num_levels);
+
+	ret = ath12k_wmi_cmd_send(wmi, skb, WMI_THERM_THROT_SET_CONF_CMDID);
+	if (ret) {
+		ath12k_warn(ar->ab,
+			    "failed to send WMI_THERM_THROT_SET_CONF cmd: %d\n",
+			    ret);
+		dev_kfree_skb(skb);
+	}
+
+	return ret;
+}
+
 int ath12k_wmi_send_11d_scan_start_cmd(struct ath12k *ar,
 				       struct wmi_11d_scan_start_arg *arg)
 {
diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h
index 8539435c292d..59b2b42161e1 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.h
+++ b/drivers/net/wireless/ath/ath12k/wmi.h
@@ -2274,6 +2274,10 @@ enum wmi_tlv_service {
 	WMI_TLV_SERVICE_WMSK_COMPACTION_RX_TLVS = 361,
 
 	WMI_TLV_SERVICE_PEER_METADATA_V1A_V1B_SUPPORT = 365,
+	WMI_TLV_SERVICE_THERM_THROT_POUT_REDUCTION = 410,
+	WMI_TLV_SERVICE_IS_TARGET_IPA = 425,
+	WMI_TLV_SERVICE_THERM_THROT_TX_CHAIN_MASK = 426,
+	WMI_TLV_SERVICE_THERM_THROT_5_LEVELS = 429,
 	WMI_TLV_SERVICE_ETH_OFFLOAD = 461,
 
 	WMI_MAX_EXT2_SERVICE,
@@ -4128,6 +4132,42 @@ struct wmi_therm_throt_stats_event {
 	__le32 therm_throt_levels;
 } __packed;
 
+#define THERMAL_LEVELS  4
+#define ENHANCED_THERMAL_LEVELS 5
+
+struct ath12k_wmi_tt_level_config_param {
+	s32 tmplwm;
+	s32 tmphwm;
+	u32 dcoffpercent;
+	u32 pout_reduction_db;
+};
+
+struct ath12k_wmi_therm_throt_config_request_cmd {
+	__le32 tlv_header;
+	__le32 pdev_id;
+	__le32 enable;
+	__le32 dc;
+	/* After how many duty cycles the firmware sends stats to host */
+	__le32 dc_per_event;
+	__le32 therm_throt_levels;
+} __packed;
+
+struct ath12k_wmi_therm_throt_level_config_param {
+	__le32 tlv_header;
+	a_sle32 temp_lwm;
+	a_sle32 temp_hwm;
+	__le32 dc_off_percent;
+	__le32 prio;
+	__le32 pout_reduction_25db;
+	__le32 tx_chain_mask;
+	__le32 duty_cycle;
+} __packed;
+
+struct ath12k_wmi_thermal_mitigation_arg {
+	int num_levels;
+	const struct ath12k_wmi_tt_level_config_param *levelconf;
+};
+
 struct ath12k_wmi_init_country_arg {
 	union {
 		u16 country_code;
@@ -6522,6 +6562,8 @@ __le32 ath12k_wmi_tlv_hdr(u32 cmd, u32 len);
 int ath12k_wmi_send_tpc_stats_request(struct ath12k *ar,
 				      enum wmi_halphy_ctrl_path_stats_id tpc_stats_type);
 void ath12k_wmi_free_tpc_stats_mem(struct ath12k *ar);
+int ath12k_wmi_send_thermal_mitigation_cmd(struct ath12k *ar,
+					   struct ath12k_wmi_thermal_mitigation_arg *arg);
 
 static inline u32
 ath12k_wmi_caps_ext_get_pdev_id(const struct ath12k_wmi_caps_ext_params *param)
-- 
2.34.1


^ permalink raw reply related

* [PATCH ath-next 1/5] wifi: ath12k: handle thermal throttle stats WMI event
From: Maharaja Kennadyrajan @ 2026-03-31 14:24 UTC (permalink / raw)
  To: ath12k; +Cc: linux-wireless, Maharaja Kennadyrajan
In-Reply-To: <20260331142446.2951809-1-maharaja.kennadyrajan@oss.qualcomm.com>

Add handling for WMI_THERM_THROT_STATS_EVENTID by defining the
wmi_therm_throt_stats_event TLV and parsing pdev_id, temperature and
throttle level.

The firmware can emit this event periodically, including when the
throttle level is 0.

Log the received thermal throttle stats to get the current temperature level,
temperature and thermal throttling levels.

Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1
Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3

Signed-off-by: Maharaja Kennadyrajan <maharaja.kennadyrajan@oss.qualcomm.com>
---
 drivers/net/wireless/ath/ath12k/wmi.c | 38 +++++++++++++++++++++++++++
 drivers/net/wireless/ath/ath12k/wmi.h |  8 ++++++
 2 files changed, 46 insertions(+)

diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c
index 65a05a9520ff..34184d0d03ff 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.c
+++ b/drivers/net/wireless/ath/ath12k/wmi.c
@@ -8819,6 +8819,41 @@ ath12k_wmi_pdev_temperature_event(struct ath12k_base *ab,
 	rcu_read_unlock();
 }
 
+static void ath12k_wmi_thermal_throt_stats_event(struct ath12k_base *ab,
+						 struct sk_buff *skb)
+{
+	const struct wmi_therm_throt_stats_event *ev;
+	struct ath12k *ar;
+
+	const void **tb __free(kfree) = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+	if (IS_ERR(tb)) {
+		ath12k_err(ab, "failed to parse thermal throttling stats tlv: %ld\n",
+			   PTR_ERR(tb));
+		return;
+	}
+
+	ev = tb[WMI_TAG_THERM_THROT_STATS_EVENT];
+	if (!ev) {
+		ath12k_err(ab, "failed to fetch thermal throt stats ev\n");
+		return;
+	}
+
+	rcu_read_lock();
+	ar = ath12k_mac_get_ar_by_pdev_id(ab, le32_to_cpu(ev->pdev_id));
+	if (!ar) {
+		ath12k_warn(ab, "received thermal_throt_stats in invalid pdev %u\n",
+			    le32_to_cpu(ev->pdev_id));
+		rcu_read_unlock();
+		return;
+	}
+	rcu_read_unlock();
+
+	ath12k_dbg(ab, ATH12K_DBG_WMI,
+		   "thermal stats ev level %u pdev_id %u temp %u throt_levels %u\n",
+		   le32_to_cpu(ev->level), le32_to_cpu(ev->pdev_id),
+		   le32_to_cpu(ev->temp), le32_to_cpu(ev->therm_throt_levels));
+}
+
 static void ath12k_fils_discovery_event(struct ath12k_base *ab,
 					struct sk_buff *skb)
 {
@@ -9900,6 +9935,9 @@ static void ath12k_wmi_op_rx(struct ath12k_base *ab, struct sk_buff *skb)
 	case WMI_PDEV_TEMPERATURE_EVENTID:
 		ath12k_wmi_pdev_temperature_event(ab, skb);
 		break;
+	case WMI_THERM_THROT_STATS_EVENTID:
+		ath12k_wmi_thermal_throt_stats_event(ab, skb);
+		break;
 	case WMI_PDEV_DMA_RING_BUF_RELEASE_EVENTID:
 		ath12k_wmi_pdev_dma_ring_buf_release_event(ab, skb);
 		break;
diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h
index 5ba9b7d3a888..8539435c292d 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.h
+++ b/drivers/net/wireless/ath/ath12k/wmi.h
@@ -870,6 +870,7 @@ enum wmi_tlv_event_id {
 	WMI_READ_DATA_FROM_FLASH_EVENTID,
 	WMI_REPORT_RX_AGGR_FAILURE_EVENTID,
 	WMI_PKGID_EVENTID,
+	WMI_THERM_THROT_STATS_EVENTID,
 	WMI_GPIO_INPUT_EVENTID = WMI_TLV_CMD(WMI_GRP_GPIO),
 	WMI_UPLOADH_EVENTID,
 	WMI_CAPTUREH_EVENTID,
@@ -4120,6 +4121,13 @@ enum set_init_cc_flags {
 	ALPHA_IS_SET,
 };
 
+struct wmi_therm_throt_stats_event {
+	__le32 pdev_id;
+	__le32 temp;
+	__le32 level;
+	__le32 therm_throt_levels;
+} __packed;
+
 struct ath12k_wmi_init_country_arg {
 	union {
 		u16 country_code;
-- 
2.34.1


^ permalink raw reply related

* [PATCH ath-next 0/5] wifi: ath12k: thermal throttling and cooling device support
From: Maharaja Kennadyrajan @ 2026-03-31 14:24 UTC (permalink / raw)
  To: ath12k; +Cc: linux-wireless, Maharaja Kennadyrajan

Patch 1 handles the firmware stats event so we can track the current
temperature and throttle level per pdev without spamming logs.

Patch 2 enables thermal throttling at bring-up and programs default level
tables to firmware via WMI_THERM_THROT_SET_CONF_CMDID; the driver picks
IPA/XFEM defaults based on the firmware WMI service bitmap, supports 4 or 5
levels as advertised, and only fills optional fields (pout reduction,
tx chain mask) when the corresponding WMI service bits are present.

Patch 3 refactors per-radio thermal hwmon cleanup to reduce code duplication and
ensure consistent cleanup across thermal register and unregister paths.

Patch 4 reorders the group teardown logic symmetric for safe thermal sysfs cleanup.

Patch 5 exposes a thermal cooling device per radio so the kernel thermal
framework or userspace can set the TX duty-cycle off percentage; writes
are validated against the throttling state range and host state is kept in
sync with successful firmware updates.

Examples:
echo 40 > /sys/devices/pci0000:00/0000:00:1d.1/0000:58:00.0/ieee80211/phyX/cooling_device/cur_stat
cat /sys/devices/pci0000:00/0000:00:1d.1/0000:58:00.0/ieee80211/phyX/cooling_device/cur_state
cat /sys/devices/pci0000:00/0000:00:1d.1/0000:58:00.0/ieee80211/phyX/cooling_device/max_state

Maharaja Kennadyrajan (5):
  wifi: ath12k: handle thermal throttle stats WMI event
  wifi: ath12k: configure firmware thermal throttling via WMI
  wifi: ath12k: refactor per-radio thermal hwmon setup and cleanup
  wifi: ath12k: reorder group start/stop for safe thermal sysfs cleanup
  wifi: ath12k: add thermal cooling device support

 drivers/net/wireless/ath/ath12k/core.c    |  50 +++--
 drivers/net/wireless/ath/ath12k/mac.c     |   9 +
 drivers/net/wireless/ath/ath12k/thermal.c | 252 ++++++++++++++++++----
 drivers/net/wireless/ath/ath12k/thermal.h |  35 +++
 drivers/net/wireless/ath/ath12k/wmi.c     | 107 +++++++++
 drivers/net/wireless/ath/ath12k/wmi.h     |  50 +++++
 6 files changed, 446 insertions(+), 57 deletions(-)


base-commit: dbd94b9831bc52a1efb7ff3de841ffc3457428ce
-- 
2.34.1


^ permalink raw reply

* Re: [PATCH ath-next v3 1/6] dt-bindings: net: wireless: add ath12k wifi device IPQ5424
From: Jeff Johnson @ 2026-03-31 14:23 UTC (permalink / raw)
  To: Krzysztof Kozlowski, Raj Kumar Bhagat
  Cc: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jeff Johnson, linux-wireless, devicetree, linux-kernel, ath12k
In-Reply-To: <20260331-fanatic-elegant-wallaby-913e35@quoll>

On 3/31/2026 12:24 AM, Krzysztof Kozlowski wrote:
> On Tue, Mar 31, 2026 at 02:09:06AM +0530, Raj Kumar Bhagat wrote:
>>  $id: http://devicetree.org/schemas/net/wireless/qcom,ipq5332-wifi.yaml#
>> @@ -17,6 +17,7 @@ properties:
>>    compatible:
>>      enum:
>>        - qcom,ipq5332-wifi
>> +      - qcom,ipq5424-wifi
> 
> No, use previous patch.
> 
> I am annoyed that you keep making changes even for such trivialities and
> require re-review from the community.  Previous patch was correct. This
> one doing whatever you want to do in copyrights is too much. You don't
> change copyrights just because you wrote one device model.

Krzysztof,

FYI here is the guidance I received from Qualcomm legal (links to internal
documentation, removed -- I've forwarded the entire e-mail to your Qualcomm
mailbox):

... Repos under copyleft license [...] QTI copyright must be added when we
make significant changes.

... Repos under friendly license (BSD, Apache, MIT, ...) [...] QTI copyright
must be added for any changes, not just significant ones.

... under the regular QUIC to QTI open-source copyright transitioning [...]
all QUIC Copyright instances should be replaced with year-less QTI OSS Copyright.

I'll follow up with them on this case where there is a dual-license file.

/jeff

^ permalink raw reply

* Re: [PATCH 1/3] wifi: mt76: connac: use a helper to cache txpower_cur
From: Jonas Gorski @ 2026-03-31  9:30 UTC (permalink / raw)
  To: Felix Baumann
  Cc: lucid_duck, linux-mediatek, linux-wireless, lorenzo.bianconi,
	morrownr, nbd, satadru, sean.wang
In-Reply-To: <3f4103d9-4871-4ae8-93a7-d286fce37443@freifunk-aachen.de>

Hi Felix,

On Tue, Mar 31, 2026 at 10:24 AM Felix Baumann
<felix.baumann@freifunk-aachen.de> wrote:
>
> Hi Lucid Duck,
>
> thanks a lot for your contribution. Very much appreciated :)
> The kernel has a strict policy for contributions: real names only.
> https://www.kernel.org/doc/html/v4.11/process/submitting-patches.html#sign-your-work-the-developer-s-certificate-of-origin
>
> May I kindly ask whether it would be okay for Sean to use your real name?
> https://github.com/Lucid-Duck

You should update your link, the real name policy was relaxed a while ago:

https://www.kernel.org/doc/html/latest/process/submitting-patches.html#developer-s-certificate-of-origin-1-1

"(...) then you just add a line saying: (...) using a known identity
(sorry, no anonymous contributions.)."

See https://github.com/torvalds/linux/commit/d4563201f33a022fc0353033d9dfeb1606a88330
for details.

Having said that, I don't think the real name policy would have even
applied to Tested-by / Reported-by tags. For these it really doesn't
matter, as these are not code contributions.

Best regards,
Jonas

^ permalink raw reply

* Re: [PATCH 1/3] wifi: mt76: connac: use a helper to cache txpower_cur
From: Felix Baumann @ 2026-03-31  7:59 UTC (permalink / raw)
  To: lucid_duck
  Cc: linux-mediatek, linux-wireless, lorenzo.bianconi, morrownr, nbd,
	satadru, sean.wang
In-Reply-To: <20260327021534.448552-1-lucid_duck@justthetip.ca>

Hi Lucid Duck,

thanks a lot for your contribution. Very much appreciated :)
The kernel has a strict policy for contributions: real names only.
https://www.kernel.org/doc/html/v4.11/process/submitting-patches.html#sign-your-work-the-developer-s-certificate-of-origin

May I kindly ask whether it would be okay for Sean to use your real name?
https://github.com/Lucid-Duck

-- 
Regards
Felix Baumann


^ permalink raw reply

* Re: [BUG] mt7921e: Intermittent connection failure
From: silverducks @ 2026-03-31  7:33 UTC (permalink / raw)
  To: linux-wireless
  Cc: nbd, lorenzo, ryder.lee, shayne.chen, sean.wang, matthias.bgg,
	angelogioacchino.delregno
In-Reply-To: <59f58f03-6dee-4380-80d6-7e2778b5f14b@altlinux.org>

[-- Attachment #1: Type: text/plain, Size: 449 bytes --]

Greetings!

I apologize for poor formatting in the previous email. I did not realize all
plain text files' contents would be visible on the mailing list.
I am attaching an archive containing the same files as in previous email for
convenience.
Given compression, I can also avoid using external hosting, which I 
presume is
preferred, so I am including all relevant logs in the archive as well.
I am also including original email text just in case.

[-- Attachment #2: attachments.tar.gz --]
[-- Type: application/gzip, Size: 574228 bytes --]

^ permalink raw reply

* Re: [PATCH ath-next v3 1/6] dt-bindings: net: wireless: add ath12k wifi device IPQ5424
From: Krzysztof Kozlowski @ 2026-03-31  7:24 UTC (permalink / raw)
  To: Raj Kumar Bhagat
  Cc: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jeff Johnson, linux-wireless, devicetree, linux-kernel, ath12k
In-Reply-To: <20260331-ath12k-ipq5424-v3-1-1455b9cae29c@oss.qualcomm.com>

On Tue, Mar 31, 2026 at 02:09:06AM +0530, Raj Kumar Bhagat wrote:
>  $id: http://devicetree.org/schemas/net/wireless/qcom,ipq5332-wifi.yaml#
> @@ -17,6 +17,7 @@ properties:
>    compatible:
>      enum:
>        - qcom,ipq5332-wifi
> +      - qcom,ipq5424-wifi

No, use previous patch.

I am annoyed that you keep making changes even for such trivialities and
require re-review from the community.  Previous patch was correct. This
one doing whatever you want to do in copyrights is too much. You don't
change copyrights just because you wrote one device model.

Best regards,
Krzysztof


^ permalink raw reply

* [PATCH v2] wifi: ath11k: apply existing PM quirk to ThinkPad P14s Gen 5 AMD
From: Kyle Farnung via B4 Relay @ 2026-03-31  6:32 UTC (permalink / raw)
  To: Jeff Johnson, Baochen Qiang
  Cc: Jeff Johnson, linux-wireless, ath11k, linux-kernel, stable,
	Kyle Farnung

From: Kyle Farnung <kfarnung@gmail.com>

Some ThinkPad P14s Gen 5 AMD systems experience suspend/resume
reliability issues similar to those reported in [1]. These platforms
were not previously included in the ath11k PM quirk table.

Add DMI matches for product IDs 21ME and 21MF to apply the existing
ATH11K_PM_WOW override, improving suspend/resume behavior on these
systems.

Tested on a ThinkPad P14s Gen 5 AMD (21ME) running 6.19.9.

[1] https://bugzilla.kernel.org/show_bug.cgi?id=219196
[2] https://pcsupport.lenovo.com/us/en/products/laptops-and-netbooks/thinkpad-p-series-laptops/thinkpad-p14s-gen-5-type-21me-21mf/

Fixes: ce8669a27016 ("wifi: ath11k: determine PM policy based on machine model")
Cc: stable@vger.kernel.org
Signed-off-by: Kyle Farnung <kfarnung@gmail.com>
---
Changes in v2:
- Fix missing mailing list recipients (linux-wireless, ath11k, linux-kernel)
- Link to v1: https://lore.kernel.org/r/20260330-p14s-pm-quirk-v1-1-cf2fa39cc2d5@gmail.com
---
 drivers/net/wireless/ath/ath11k/core.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/drivers/net/wireless/ath/ath11k/core.c b/drivers/net/wireless/ath/ath11k/core.c
index 3f6f4db5b7ee1aba79fd7526e5d59d068e0f4a2e..21d366224e75904feeae6cb9c93d9ef692d127fe 100644
--- a/drivers/net/wireless/ath/ath11k/core.c
+++ b/drivers/net/wireless/ath/ath11k/core.c
@@ -1041,6 +1041,20 @@ static const struct dmi_system_id ath11k_pm_quirk_table[] = {
 			DMI_MATCH(DMI_PRODUCT_NAME, "21D5"),
 		},
 	},
+	{
+		.driver_data = (void *)ATH11K_PM_WOW,
+		.matches = { /* P14s G5 AMD #1 */
+			DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "21ME"),
+		},
+	},
+	{
+		.driver_data = (void *)ATH11K_PM_WOW,
+		.matches = { /* P14s G5 AMD #2 */
+			DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "21MF"),
+		},
+	},
 	{}
 };
 

---
base-commit: dbd94b9831bc52a1efb7ff3de841ffc3457428ce
change-id: 20260330-p14s-pm-quirk-0a51ba19235f

Best regards,
-- 
Kyle Farnung <kfarnung@gmail.com>



^ permalink raw reply related

* Re: [PATCH 1/2] ath10k: skip quiet mode for WCN3990 to prevent firmware crash
From: Baochen Qiang @ 2026-03-31  5:09 UTC (permalink / raw)
  To: Malte Schababerle, Jeff Johnson; +Cc: linux-wireless, ath10k
In-Reply-To: <20260322124822.230492-2-m.schababerle@gmail.com>



On 3/22/2026 8:48 PM, Malte Schababerle wrote:
> WCN3990 firmware (WLAN.HL.3.2) crashes deterministically when the
> quiet mode WMI command is sent during ath10k_start(). The crash occurs
> at PC=0xb0008e20 in wlanmdsp.mbn, ~17ms after the subsequent
> vdev_create command, and cascades into a full modem crash.
> 
> Commit 53884577fbcef ("ath10k: skip sending quiet mode cmd for
> WCN3990") addressed this for HL2.0 firmware by gating quiet mode on
> WMI_SERVICE_THERM_THROT. HL2.0 did not advertise the service bit, so
> the guard was effective. However, newer WCN3990 firmware (HL3.2)
> erroneously advertises WMI_SERVICE_THERM_THROT via its TLV service
> map despite still being unable to handle the quiet mode command.
> 
> Skip quiet mode unconditionally for WCN3990 using QCA_REV_WCN3990()
> instead of relying on the service bit.
> 
> Tested on OnePlus 7T (SM8150/WCN3990) with WLAN.HL.3.2.0.c2-00006
> and WLAN.HL.3.2.0.c2-00011 (both crash without patch, both work with):
> - wlan0 comes up without crash
> - WiFi scanning works
> - NetworkManager recognizes the interface
> 
> Fixes: 53884577fbcef ("ath10k: skip sending quiet mode cmd for WCN3990")
> Signed-off-by: Malte Schababerle <m.schababerle@gmail.com>
> ---
>  drivers/net/wireless/ath/ath10k/thermal.c | 10 ++++++++++
>  1 file changed, 10 insertions(+)
> 
> diff --git a/drivers/net/wireless/ath/ath10k/thermal.c b/drivers/net/wireless/ath/ath10k/thermal.c
> index 8b15ec07b1071..33f299f414710 100644
> --- a/drivers/net/wireless/ath/ath10k/thermal.c
> +++ b/drivers/net/wireless/ath/ath10k/thermal.c
> @@ -136,6 +136,16 @@ void ath10k_thermal_set_throttling(struct ath10k *ar)
>  	if (!ar->wmi.ops->gen_pdev_set_quiet_mode)
>  		return;
>  
> +	/* WCN3990 firmware crashes on quiet mode despite advertising support.
> +	 * See also commit 53884577fbcef ("ath10k: skip sending quiet mode
> +	 * cmd for WCN3990").
> +	 */
> +	if (QCA_REV_WCN3990(ar)) {
> +		ath10k_dbg(ar, ATH10K_DBG_BOOT,
> +			   "skip quiet mode for WCN3990 (known crash trigger)\n");
> +		return;
> +	}
> +
>  	if (ar->state != ATH10K_STATE_ON)
>  		return;
>  

Malte, the firmware team needs firmware dump to understand this issue, would you be able
to help collect it?


^ permalink raw reply

* Re: [PATCH ath-next] wifi: ath12k: Rename hw_link_id to radio_idx in ath12k_ah_to_ar()
From: Rameshkumar Sundaram @ 2026-03-31  5:03 UTC (permalink / raw)
  To: Roopni Devanathan, ath12k; +Cc: linux-wireless
In-Reply-To: <20260331045834.1181924-1-roopni.devanathan@oss.qualcomm.com>



On 3/31/2026 10:28 AM, Roopni Devanathan wrote:
> ath12k_ah_to_ar() is returning radio from the given hardware based on the
> radio index passed. But, the variable that radio index is received at is
> wrongly named 'hw_link_id', which points to the hardware link index that
> comes from the firmware. This affects readability.
> 
> Resolve this by renaming 'hw_link_id' to 'radio_idx'.
> 
> Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ-1
> 
> Signed-off-by: Roopni Devanathan <roopni.devanathan@oss.qualcomm.com>
> ---
>   drivers/net/wireless/ath/ath12k/core.h | 10 +++++-----
>   1 file changed, 5 insertions(+), 5 deletions(-)
> 
> diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h
> index 59c193b24764..5eff86827e9c 100644
> --- a/drivers/net/wireless/ath/ath12k/core.h
> +++ b/drivers/net/wireless/ath/ath12k/core.h
> @@ -1366,13 +1366,13 @@ static inline struct ath12k_hw *ath12k_hw_to_ah(struct ieee80211_hw  *hw)
>   	return hw->priv;
>   }
>   
> -static inline struct ath12k *ath12k_ah_to_ar(struct ath12k_hw *ah, u8 hw_link_id)
> +static inline struct ath12k *ath12k_ah_to_ar(struct ath12k_hw *ah, u8 radio_idx)
>   {
> -	if (WARN(hw_link_id >= ah->num_radio,
> -		 "bad hw link id %d, so switch to default link\n", hw_link_id))
> -		hw_link_id = 0;
> +	if (WARN(radio_idx >= ah->num_radio,
> +		 "bad radio index %d, use default radio\n", radio_idx))
> +		radio_idx = 0;
>   
> -	return &ah->radio[hw_link_id];
> +	return &ah->radio[radio_idx];
>   }
>   
>   static inline struct ath12k_hw *ath12k_ar_to_ah(struct ath12k *ar)
> 
> base-commit: 15551ababf6d4e857f2101366a0c3eaa86dd822c

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


^ permalink raw reply

* [PATCH ath-next] wifi: ath12k: Rename hw_link_id to radio_idx in ath12k_ah_to_ar()
From: Roopni Devanathan @ 2026-03-31  4:58 UTC (permalink / raw)
  To: ath12k; +Cc: linux-wireless, Roopni Devanathan

ath12k_ah_to_ar() is returning radio from the given hardware based on the
radio index passed. But, the variable that radio index is received at is
wrongly named 'hw_link_id', which points to the hardware link index that
comes from the firmware. This affects readability.

Resolve this by renaming 'hw_link_id' to 'radio_idx'.

Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ-1

Signed-off-by: Roopni Devanathan <roopni.devanathan@oss.qualcomm.com>
---
 drivers/net/wireless/ath/ath12k/core.h | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h
index 59c193b24764..5eff86827e9c 100644
--- a/drivers/net/wireless/ath/ath12k/core.h
+++ b/drivers/net/wireless/ath/ath12k/core.h
@@ -1366,13 +1366,13 @@ static inline struct ath12k_hw *ath12k_hw_to_ah(struct ieee80211_hw  *hw)
 	return hw->priv;
 }
 
-static inline struct ath12k *ath12k_ah_to_ar(struct ath12k_hw *ah, u8 hw_link_id)
+static inline struct ath12k *ath12k_ah_to_ar(struct ath12k_hw *ah, u8 radio_idx)
 {
-	if (WARN(hw_link_id >= ah->num_radio,
-		 "bad hw link id %d, so switch to default link\n", hw_link_id))
-		hw_link_id = 0;
+	if (WARN(radio_idx >= ah->num_radio,
+		 "bad radio index %d, use default radio\n", radio_idx))
+		radio_idx = 0;
 
-	return &ah->radio[hw_link_id];
+	return &ah->radio[radio_idx];
 }
 
 static inline struct ath12k_hw *ath12k_ar_to_ah(struct ath12k *ar)

base-commit: 15551ababf6d4e857f2101366a0c3eaa86dd822c
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH ath-current] wifi: ath10k: fix station lookup failure during disconnect
From: Rameshkumar Sundaram @ 2026-03-31  4:54 UTC (permalink / raw)
  To: Baochen Qiang, Jeff Johnson; +Cc: linux-wireless, ath10k, pmenzel
In-Reply-To: <20260325-ath10k-station-lookup-failure-v1-1-2e0c970f25d5@oss.qualcomm.com>



On 3/25/2026 8:35 AM, Baochen Qiang wrote:
> Recent commit [1] moved station statistics collection to an earlier stage
> of the disconnect flow. With this change in place, ath10k fails to resolve
> the station entry when handling a peer stats event triggered during
> disconnect, resulting in log messages such as:
> 
> wlp58s0: deauthenticating from 74:1a:e0:e7:b4:c8 by local choice (Reason: 3=DEAUTH_LEAVING)
> ath10k_pci 0000:3a:00.0: not found station for peer stats
> ath10k_pci 0000:3a:00.0: failed to parse stats info tlv: -22
> 
> The failure occurs because ath10k relies on ieee80211_find_sta_by_ifaddr()
> for station lookup. That function uses local->sta_hash, but by the time
> the peer stats request is triggered during disconnect, mac80211 has
> already removed the station from that hash table, leading to lookup
> failure.
> 
> Before commit [1], this issue was not visible because the transition from
> IEEE80211_STA_NONE to IEEE80211_STA_NOTEXIST prevented ath10k from sending
> a peer stats request at all: ath10k_mac_sta_get_peer_stats_info() would
> fail early to find the peer and skip requesting statistics.
> 
> Fix this by switching the lookup path to ath10k_peer_find(), which queries
> ath10k's internal peer table. At the point where the firmware emits the
> peer stats event, the peer entry is still present in the driver's list,
> ensuring lookup succeeds.
> 
> Tested-on: QCA6174 hw3.2 PCI WLAN.RM.4.4.1-00309-QCARMSWPZ-1
> 
> Fixes: a203dbeeca15 ("wifi: mac80211: collect station statistics earlier when disconnect") # [1]
> Reported-by: Paul Menzel <pmenzel@molgen.mpg.de>
> Closes: https://lore.kernel.org/ath10k/57671b89-ec9f-4e6c-992c-45eb8e75929c@molgen.mpg.de
> Signed-off-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
> ---
>   drivers/net/wireless/ath/ath10k/wmi-tlv.c | 26 +++++++++++++++-----------
>   1 file changed, 15 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/net/wireless/ath/ath10k/wmi-tlv.c b/drivers/net/wireless/ath/ath10k/wmi-tlv.c
> index ec8e91707f84..01f2d1fa9d7d 100644
> --- a/drivers/net/wireless/ath/ath10k/wmi-tlv.c
> +++ b/drivers/net/wireless/ath/ath10k/wmi-tlv.c
> @@ -3,7 +3,7 @@
>    * Copyright (c) 2005-2011 Atheros Communications Inc.
>    * Copyright (c) 2011-2017 Qualcomm Atheros, Inc.
>    * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
> - * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
>    */
>   #include "core.h"
>   #include "debug.h"
> @@ -14,6 +14,7 @@
>   #include "wmi-tlv.h"
>   #include "p2p.h"
>   #include "testmode.h"
> +#include "txrx.h"
>   #include <linux/bitfield.h>
>   
>   /***************/
> @@ -224,8 +225,9 @@ static int ath10k_wmi_tlv_parse_peer_stats_info(struct ath10k *ar, u16 tag, u16
>   						const void *ptr, void *data)
>   {
>   	const struct wmi_tlv_peer_stats_info *stat = ptr;
> -	struct ieee80211_sta *sta;
> +	u32 vdev_id = *(u32 *)data;
>   	struct ath10k_sta *arsta;
> +	struct ath10k_peer *peer;
>   
>   	if (tag != WMI_TLV_TAG_STRUCT_PEER_STATS_INFO)
>   		return -EPROTO;
> @@ -241,20 +243,20 @@ static int ath10k_wmi_tlv_parse_peer_stats_info(struct ath10k *ar, u16 tag, u16
>   		   __le32_to_cpu(stat->last_tx_rate_code),
>   		   __le32_to_cpu(stat->last_tx_bitrate_kbps));
>   
> -	rcu_read_lock();
> -	sta = ieee80211_find_sta_by_ifaddr(ar->hw, stat->peer_macaddr.addr, NULL);
> -	if (!sta) {
> -		rcu_read_unlock();
> -		ath10k_warn(ar, "not found station for peer stats\n");
> +	guard(spinlock_bh)(&ar->data_lock);
> +
> +	peer = ath10k_peer_find(ar, vdev_id, stat->peer_macaddr.addr);
> +	if (!peer || !peer->sta) {
> +		ath10k_warn(ar, "not found %s with vdev id %u mac addr %pM for peer stats\n",
> +			    peer ? "sta" : "peer", vdev_id, stat->peer_macaddr.addr);
>   		return -EINVAL;
>   	}
>   
> -	arsta = (struct ath10k_sta *)sta->drv_priv;
> +	arsta = (struct ath10k_sta *)peer->sta->drv_priv;
>   	arsta->rx_rate_code = __le32_to_cpu(stat->last_rx_rate_code);
>   	arsta->rx_bitrate_kbps = __le32_to_cpu(stat->last_rx_bitrate_kbps);
>   	arsta->tx_rate_code = __le32_to_cpu(stat->last_tx_rate_code);
>   	arsta->tx_bitrate_kbps = __le32_to_cpu(stat->last_tx_bitrate_kbps);
> -	rcu_read_unlock();
>   
>   	return 0;
>   }
> @@ -266,6 +268,7 @@ static int ath10k_wmi_tlv_op_pull_peer_stats_info(struct ath10k *ar,
>   	const struct wmi_tlv_peer_stats_info_ev *ev;
>   	const void *data;
>   	u32 num_peer_stats;
> +	u32 vdev_id;
>   	int ret;
>   
>   	tb = ath10k_wmi_tlv_parse_alloc(ar, skb->data, skb->len, GFP_ATOMIC);
> @@ -284,15 +287,16 @@ static int ath10k_wmi_tlv_op_pull_peer_stats_info(struct ath10k *ar,
>   	}
>   
>   	num_peer_stats = __le32_to_cpu(ev->num_peers);
> +	vdev_id = __le32_to_cpu(ev->vdev_id);
>   
>   	ath10k_dbg(ar, ATH10K_DBG_WMI,
>   		   "wmi tlv peer stats info update peer vdev id %d peers %i more data %d\n",
> -		   __le32_to_cpu(ev->vdev_id),
> +		   vdev_id,
>   		   num_peer_stats,
>   		   __le32_to_cpu(ev->more_data));
>   
>   	ret = ath10k_wmi_tlv_iter(ar, data, ath10k_wmi_tlv_len(data),
> -				  ath10k_wmi_tlv_parse_peer_stats_info, NULL);
> +				  ath10k_wmi_tlv_parse_peer_stats_info, &vdev_id);
>   	if (ret)
>   		ath10k_warn(ar, "failed to parse stats info tlv: %d\n", ret);
>   
> 
> ---
> base-commit: 4242625f272974dd1947f73b10d884eab3b277cd
> change-id: 20260325-ath10k-station-lookup-failure-be4dd6e81100
> 
Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>

^ permalink raw reply

* Re: [PATCH ath-next v5] wifi: ath12k: avoid dynamic alloc when parsing wmi tb
From: Baochen Qiang @ 2026-03-31  2:44 UTC (permalink / raw)
  To: Nicolas Escande, ath12k; +Cc: linux-wireless
In-Reply-To: <20260330102434.13136-1-nico.escande@gmail.com>



On 3/30/2026 6:24 PM, Nicolas Escande wrote:
> On each WMI message received from the hardware, we alloc a temporary array
> of WMI_TAG_MAX entries of type void *. This array is then populated with
> pointers of parsed structs depending on the WMI type, and then freed. This
> alloc can fail when memory pressure in the system is high enough.
> 
> Given the fact that it is scheduled in softirq with the system_bh_wq, we
> should not be able to parse more than one WMI message per CPU at any time.
> 
> So instead lets move to a per cpu allocated array, that is reused across
> calls. This memory is allocated as needed and refcounted to exist only
> as long as one struct ath12k_base lives.
> 
> ath12k_wmi_tlv_parse_alloc() and ath12k_wmi_tlv_parse() are merged
> together as it no longer allocs mem but returns the existing per-cpu one.
> 
> Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.3.1-00218-QCAHKSWPL_SILICONZ-1
> 
> Signed-off-by: Nicolas Escande <nico.escande@gmail.com>
> ---
> changes from v4:
>   - moved to a single instance, refcounted per cpu memory alloc
>   
> changes from v3:
>   - simplified ath12k_core_init() with a single statement
>   - move perpcu.h include directly to wmi.c
> 
> changes from v2:
>   - removed now superfluous return in ath12k_wmi_event_teardown_complete()
>   - moved ath12k_wmi_tb declaration to wmi.c & added two functions to
>     alloc / free it
>   - removed useless error message on memory allocation failure
> 
> changes from v1:
>   - rebased on ath-next 27401c9b1432
>   - changed wording according to Jeff's comment
>   - moved alloc/cleanup to new module_init/exit functions in the
>     ath12k module as per Baochen's comment
> 
> changes from RFC:
>   - rebased on ath-next 8e0ab5b9adb7
>   - converted missing call sites ath12k_wmi_obss_color_collision_event()
>     & ath12k_wmi_pdev_temperature_event()
>   - changed alloc order & cleanup path in ath12k_core_alloc() as it seems
>     it confused people
>   - used sizeof(*tb) in ath12k_wmi_tlv_parse()
> ---
>  drivers/net/wireless/ath/ath12k/core.c |   6 +
>  drivers/net/wireless/ath/ath12k/wmi.c  | 215 +++++++++----------------
>  drivers/net/wireless/ath/ath12k/wmi.h  |   3 +
>  3 files changed, 85 insertions(+), 139 deletions(-)
> 
> diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c
> index c31c47fb5a73..76060d448920 100644
> --- a/drivers/net/wireless/ath/ath12k/core.c
> +++ b/drivers/net/wireless/ath/ath12k/core.c
> @@ -2258,6 +2258,7 @@ void ath12k_core_free(struct ath12k_base *ab)
>  	timer_delete_sync(&ab->rx_replenish_retry);
>  	destroy_workqueue(ab->workqueue_aux);
>  	destroy_workqueue(ab->workqueue);
> +	ath12k_wmi_free();
>  	kfree(ab);
>  }
>  
> @@ -2280,6 +2281,9 @@ struct ath12k_base *ath12k_core_alloc(struct device *dev, size_t priv_size,
>  	if (!ab->workqueue_aux)
>  		goto err_free_wq;
>  
> +	if (ath12k_wmi_alloc() < 0)
> +		goto err_free_wq_aux;
> +
>  	mutex_init(&ab->core_lock);
>  	spin_lock_init(&ab->base_lock);
>  	init_completion(&ab->reset_complete);
> @@ -2314,6 +2318,8 @@ struct ath12k_base *ath12k_core_alloc(struct device *dev, size_t priv_size,
>  
>  	return ab;
>  
> +err_free_wq_aux:
> +	destroy_workqueue(ab->workqueue_aux);
>  err_free_wq:
>  	destroy_workqueue(ab->workqueue);
>  err_sc_free:
> diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c
> index 65a05a9520ff..b75491d51831 100644
> --- a/drivers/net/wireless/ath/ath12k/wmi.c
> +++ b/drivers/net/wireless/ath/ath12k/wmi.c
> @@ -15,6 +15,7 @@
>  #include <linux/time.h>
>  #include <linux/of.h>
>  #include <linux/cleanup.h>
> +#include <linux/percpu.h>
>  #include "core.h"
>  #include "debugfs.h"
>  #include "debug.h"
> @@ -134,6 +135,10 @@ struct wmi_pdev_set_obss_bitmap_arg {
>  	const char *label;
>  };
>  
> +static DEFINE_MUTEX(ath12k_wmi_mutex);
> +static int ath12k_wmi_refcount;
> +static void __percpu *ath12k_wmi_tb;
> +
>  static const struct ath12k_wmi_tlv_policy ath12k_wmi_tlv_policies[] = {
>  	[WMI_TAG_ARRAY_BYTE] = { .min_len = 0 },
>  	[WMI_TAG_ARRAY_UINT32] = { .min_len = 0 },
> @@ -289,29 +294,19 @@ static int ath12k_wmi_tlv_iter_parse(struct ath12k_base *ab, u16 tag, u16 len,
>  	return 0;
>  }
>  
> -static int ath12k_wmi_tlv_parse(struct ath12k_base *ar, const void **tb,
> -				const void *ptr, size_t len)
> -{
> -	return ath12k_wmi_tlv_iter(ar, ptr, len, ath12k_wmi_tlv_iter_parse,
> -				   (void *)tb);
> -}
> -
>  static const void **
> -ath12k_wmi_tlv_parse_alloc(struct ath12k_base *ab,
> -			   struct sk_buff *skb, gfp_t gfp)
> +ath12k_wmi_tlv_parse(struct ath12k_base *ab, struct sk_buff *skb)
>  {
>  	const void **tb;
>  	int ret;
>  
> -	tb = kzalloc_objs(*tb, WMI_TAG_MAX, gfp);
> -	if (!tb)
> -		return ERR_PTR(-ENOMEM);
> +	tb = this_cpu_ptr(ath12k_wmi_tb);
> +	memset(tb, 0, WMI_TAG_MAX * sizeof(*tb));
>  
> -	ret = ath12k_wmi_tlv_parse(ab, tb, skb->data, skb->len);
> -	if (ret) {
> -		kfree(tb);
> +	ret = ath12k_wmi_tlv_iter(ab, skb->data, skb->len,
> +				  ath12k_wmi_tlv_iter_parse, (void *)tb);
> +	if (ret)
>  		return ERR_PTR(ret);
> -	}
>  
>  	return tb;
>  }
> @@ -3911,9 +3906,10 @@ ath12k_wmi_obss_color_collision_event(struct ath12k_base *ab, struct sk_buff *sk
>  	const struct wmi_obss_color_collision_event *ev;
>  	struct ath12k_link_vif *arvif;
>  	u32 vdev_id, evt_type;
> +	const void **tb;
>  	u64 bitmap;
>  
> -	const void **tb __free(kfree) = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ath12k_warn(ab, "failed to parse OBSS color collision tlv %ld\n",
>  			    PTR_ERR(tb));
> @@ -5714,7 +5710,7 @@ static int ath12k_pull_vdev_start_resp_tlv(struct ath12k_base *ab, struct sk_buf
>  	const struct wmi_vdev_start_resp_event *ev;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
> @@ -5724,13 +5720,11 @@ static int ath12k_pull_vdev_start_resp_tlv(struct ath12k_base *ab, struct sk_buf
>  	ev = tb[WMI_TAG_VDEV_START_RESPONSE_EVENT];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch vdev start resp ev");
> -		kfree(tb);
>  		return -EPROTO;
>  	}
>  
>  	*vdev_rsp = *ev;
>  
> -	kfree(tb);
>  	return 0;
>  }
>  
> @@ -5809,7 +5803,7 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
>  
>  	ath12k_dbg(ab, ATH12K_DBG_WMI, "processing regulatory ext channel list\n");
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
> @@ -5819,7 +5813,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
>  	ev = tb[WMI_TAG_REG_CHAN_LIST_CC_EXT_EVENT];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch reg chan list ext update ev\n");
> -		kfree(tb);
>  		return -EPROTO;
>  	}
>  
> @@ -5849,7 +5842,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
>  	if (num_2g_reg_rules > MAX_REG_RULES || num_5g_reg_rules > MAX_REG_RULES) {
>  		ath12k_warn(ab, "Num reg rules for 2G/5G exceeds max limit (num_2g_reg_rules: %d num_5g_reg_rules: %d max_rules: %d)\n",
>  			    num_2g_reg_rules, num_5g_reg_rules, MAX_REG_RULES);
> -		kfree(tb);
>  		return -EINVAL;
>  	}
>  
> @@ -5859,7 +5851,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
>  		if (num_6g_reg_rules_ap[i] > MAX_6GHZ_REG_RULES) {
>  			ath12k_warn(ab, "Num 6G reg rules for AP mode(%d) exceeds max limit (num_6g_reg_rules_ap: %d, max_rules: %d)\n",
>  				    i, num_6g_reg_rules_ap[i], MAX_6GHZ_REG_RULES);
> -			kfree(tb);
>  			return -EINVAL;
>  		}
>  
> @@ -5884,14 +5875,12 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
>  		    num_6g_reg_rules_cl[WMI_REG_VLP_AP][i] >  MAX_6GHZ_REG_RULES) {
>  			ath12k_warn(ab, "Num 6g client reg rules exceeds max limit, for client(type: %d)\n",
>  				    i);
> -			kfree(tb);
>  			return -EINVAL;
>  		}
>  	}
>  
>  	if (!total_reg_rules) {
>  		ath12k_warn(ab, "No reg rules available\n");
> -		kfree(tb);
>  		return -EINVAL;
>  	}
>  
> @@ -5993,7 +5982,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
>  						      ext_wmi_reg_rule);
>  
>  		if (!reg_info->reg_rules_2g_ptr) {
> -			kfree(tb);
>  			ath12k_warn(ab, "Unable to Allocate memory for 2g rules\n");
>  			return -ENOMEM;
>  		}
> @@ -6027,7 +6015,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
>  						      ext_wmi_reg_rule);
>  
>  		if (!reg_info->reg_rules_5g_ptr) {
> -			kfree(tb);
>  			ath12k_warn(ab, "Unable to Allocate memory for 5g rules\n");
>  			return -ENOMEM;
>  		}
> @@ -6046,7 +6033,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
>  						      ext_wmi_reg_rule);
>  
>  		if (!reg_info->reg_rules_6g_ap_ptr[i]) {
> -			kfree(tb);
>  			ath12k_warn(ab, "Unable to Allocate memory for 6g ap rules\n");
>  			return -ENOMEM;
>  		}
> @@ -6061,7 +6047,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
>  							      ext_wmi_reg_rule);
>  
>  			if (!reg_info->reg_rules_6g_client_ptr[j][i]) {
> -				kfree(tb);
>  				ath12k_warn(ab, "Unable to Allocate memory for 6g client rules\n");
>  				return -ENOMEM;
>  			}
> @@ -6096,7 +6081,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
>  
>  	ath12k_dbg(ab, ATH12K_DBG_WMI, "processed regulatory ext channel list\n");
>  
> -	kfree(tb);
>  	return 0;
>  }
>  
> @@ -6107,7 +6091,7 @@ static int ath12k_pull_peer_del_resp_ev(struct ath12k_base *ab, struct sk_buff *
>  	const struct wmi_peer_delete_resp_event *ev;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
> @@ -6117,7 +6101,6 @@ static int ath12k_pull_peer_del_resp_ev(struct ath12k_base *ab, struct sk_buff *
>  	ev = tb[WMI_TAG_PEER_DELETE_RESP_EVENT];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch peer delete resp ev");
> -		kfree(tb);
>  		return -EPROTO;
>  	}
>  
> @@ -6127,7 +6110,6 @@ static int ath12k_pull_peer_del_resp_ev(struct ath12k_base *ab, struct sk_buff *
>  	ether_addr_copy(peer_del_resp->peer_macaddr.addr,
>  			ev->peer_macaddr.addr);
>  
> -	kfree(tb);
>  	return 0;
>  }
>  
> @@ -6139,7 +6121,7 @@ static int ath12k_pull_vdev_del_resp_ev(struct ath12k_base *ab,
>  	const struct wmi_vdev_delete_resp_event *ev;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
> @@ -6149,13 +6131,11 @@ static int ath12k_pull_vdev_del_resp_ev(struct ath12k_base *ab,
>  	ev = tb[WMI_TAG_VDEV_DELETE_RESP_EVENT];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch vdev delete resp ev");
> -		kfree(tb);
>  		return -EPROTO;
>  	}
>  
>  	*vdev_id = le32_to_cpu(ev->vdev_id);
>  
> -	kfree(tb);
>  	return 0;
>  }
>  
> @@ -6167,7 +6147,7 @@ static int ath12k_pull_bcn_tx_status_ev(struct ath12k_base *ab,
>  	const struct wmi_bcn_tx_status_event *ev;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
> @@ -6177,14 +6157,12 @@ static int ath12k_pull_bcn_tx_status_ev(struct ath12k_base *ab,
>  	ev = tb[WMI_TAG_OFFLOAD_BCN_TX_STATUS_EVENT];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch bcn tx status ev");
> -		kfree(tb);
>  		return -EPROTO;
>  	}
>  
>  	*vdev_id = le32_to_cpu(ev->vdev_id);
>  	*tx_status = le32_to_cpu(ev->tx_status);
>  
> -	kfree(tb);
>  	return 0;
>  }
>  
> @@ -6195,7 +6173,7 @@ static int ath12k_pull_vdev_stopped_param_tlv(struct ath12k_base *ab, struct sk_
>  	const struct wmi_vdev_stopped_event *ev;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
> @@ -6205,13 +6183,11 @@ static int ath12k_pull_vdev_stopped_param_tlv(struct ath12k_base *ab, struct sk_
>  	ev = tb[WMI_TAG_VDEV_STOPPED_EVENT];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch vdev stop ev");
> -		kfree(tb);
>  		return -EPROTO;
>  	}
>  
>  	*vdev_id = le32_to_cpu(ev->vdev_id);
>  
> -	kfree(tb);
>  	return 0;
>  }
>  
> @@ -6350,7 +6326,7 @@ static int ath12k_pull_mgmt_tx_compl_param_tlv(struct ath12k_base *ab,
>  	const struct wmi_mgmt_tx_compl_event *ev;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
> @@ -6360,7 +6336,6 @@ static int ath12k_pull_mgmt_tx_compl_param_tlv(struct ath12k_base *ab,
>  	ev = tb[WMI_TAG_MGMT_TX_COMPL_EVENT];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch mgmt tx compl ev");
> -		kfree(tb);
>  		return -EPROTO;
>  	}
>  
> @@ -6370,7 +6345,6 @@ static int ath12k_pull_mgmt_tx_compl_param_tlv(struct ath12k_base *ab,
>  	param->ppdu_id = ev->ppdu_id;
>  	param->ack_rssi = ev->ack_rssi;
>  
> -	kfree(tb);
>  	return 0;
>  }
>  
> @@ -6533,7 +6507,7 @@ static int ath12k_pull_scan_ev(struct ath12k_base *ab, struct sk_buff *skb,
>  	const struct wmi_scan_event *ev;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
> @@ -6543,7 +6517,6 @@ static int ath12k_pull_scan_ev(struct ath12k_base *ab, struct sk_buff *skb,
>  	ev = tb[WMI_TAG_SCAN_EVENT];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch scan ev");
> -		kfree(tb);
>  		return -EPROTO;
>  	}
>  
> @@ -6555,7 +6528,6 @@ static int ath12k_pull_scan_ev(struct ath12k_base *ab, struct sk_buff *skb,
>  	scan_evt_param->vdev_id = ev->vdev_id;
>  	scan_evt_param->tsf_timestamp = ev->tsf_timestamp;
>  
> -	kfree(tb);
>  	return 0;
>  }
>  
> @@ -6566,7 +6538,7 @@ static int ath12k_pull_peer_sta_kickout_ev(struct ath12k_base *ab, struct sk_buf
>  	const struct wmi_peer_sta_kickout_event *ev;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
> @@ -6576,7 +6548,6 @@ static int ath12k_pull_peer_sta_kickout_ev(struct ath12k_base *ab, struct sk_buf
>  	ev = tb[WMI_TAG_PEER_STA_KICKOUT_EVENT];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch peer sta kickout ev");
> -		kfree(tb);
>  		return -EPROTO;
>  	}
>  
> @@ -6584,7 +6555,6 @@ static int ath12k_pull_peer_sta_kickout_ev(struct ath12k_base *ab, struct sk_buf
>  	arg->reason = le32_to_cpu(ev->reason);
>  	arg->rssi = le32_to_cpu(ev->rssi);
>  
> -	kfree(tb);
>  	return 0;
>  }
>  
> @@ -6595,7 +6565,7 @@ static int ath12k_pull_roam_ev(struct ath12k_base *ab, struct sk_buff *skb,
>  	const struct wmi_roam_event *ev;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
> @@ -6605,7 +6575,6 @@ static int ath12k_pull_roam_ev(struct ath12k_base *ab, struct sk_buff *skb,
>  	ev = tb[WMI_TAG_ROAM_EVENT];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch roam ev");
> -		kfree(tb);
>  		return -EPROTO;
>  	}
>  
> @@ -6613,7 +6582,6 @@ static int ath12k_pull_roam_ev(struct ath12k_base *ab, struct sk_buff *skb,
>  	roam_ev->reason = ev->reason;
>  	roam_ev->rssi = ev->rssi;
>  
> -	kfree(tb);
>  	return 0;
>  }
>  
> @@ -6647,7 +6615,7 @@ static int ath12k_pull_chan_info_ev(struct ath12k_base *ab, struct sk_buff *skb,
>  	const struct wmi_chan_info_event *ev;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
> @@ -6657,7 +6625,6 @@ static int ath12k_pull_chan_info_ev(struct ath12k_base *ab, struct sk_buff *skb,
>  	ev = tb[WMI_TAG_CHAN_INFO_EVENT];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch chan info ev");
> -		kfree(tb);
>  		return -EPROTO;
>  	}
>  
> @@ -6674,7 +6641,6 @@ static int ath12k_pull_chan_info_ev(struct ath12k_base *ab, struct sk_buff *skb,
>  	ch_info_ev->mac_clk_mhz = ev->mac_clk_mhz;
>  	ch_info_ev->vdev_id = ev->vdev_id;
>  
> -	kfree(tb);
>  	return 0;
>  }
>  
> @@ -6686,7 +6652,7 @@ ath12k_pull_pdev_bss_chan_info_ev(struct ath12k_base *ab, struct sk_buff *skb,
>  	const struct wmi_pdev_bss_chan_info_event *ev;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
> @@ -6696,7 +6662,6 @@ ath12k_pull_pdev_bss_chan_info_ev(struct ath12k_base *ab, struct sk_buff *skb,
>  	ev = tb[WMI_TAG_PDEV_BSS_CHAN_INFO_EVENT];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch pdev bss chan info ev");
> -		kfree(tb);
>  		return -EPROTO;
>  	}
>  
> @@ -6714,7 +6679,6 @@ ath12k_pull_pdev_bss_chan_info_ev(struct ath12k_base *ab, struct sk_buff *skb,
>  	bss_ch_info_ev->rx_bss_cycle_count_low = ev->rx_bss_cycle_count_low;
>  	bss_ch_info_ev->rx_bss_cycle_count_high = ev->rx_bss_cycle_count_high;
>  
> -	kfree(tb);
>  	return 0;
>  }
>  
> @@ -6726,7 +6690,7 @@ ath12k_pull_vdev_install_key_compl_ev(struct ath12k_base *ab, struct sk_buff *sk
>  	const struct wmi_vdev_install_key_compl_event *ev;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
> @@ -6736,7 +6700,6 @@ ath12k_pull_vdev_install_key_compl_ev(struct ath12k_base *ab, struct sk_buff *sk
>  	ev = tb[WMI_TAG_VDEV_INSTALL_KEY_COMPLETE_EVENT];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch vdev install key compl ev");
> -		kfree(tb);
>  		return -EPROTO;
>  	}
>  
> @@ -6746,7 +6709,6 @@ ath12k_pull_vdev_install_key_compl_ev(struct ath12k_base *ab, struct sk_buff *sk
>  	arg->key_flags = le32_to_cpu(ev->key_flags);
>  	arg->status = le32_to_cpu(ev->status);
>  
> -	kfree(tb);
>  	return 0;
>  }
>  
> @@ -6757,7 +6719,7 @@ static int ath12k_pull_peer_assoc_conf_ev(struct ath12k_base *ab, struct sk_buff
>  	const struct wmi_peer_assoc_conf_event *ev;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
> @@ -6767,14 +6729,12 @@ static int ath12k_pull_peer_assoc_conf_ev(struct ath12k_base *ab, struct sk_buff
>  	ev = tb[WMI_TAG_PEER_ASSOC_CONF_EVENT];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch peer assoc conf ev");
> -		kfree(tb);
>  		return -EPROTO;
>  	}
>  
>  	peer_assoc_conf->vdev_id = le32_to_cpu(ev->vdev_id);
>  	peer_assoc_conf->macaddr = ev->peer_macaddr.addr;
>  
> -	kfree(tb);
>  	return 0;
>  }
>  
> @@ -6792,7 +6752,7 @@ static int ath12k_reg_11d_new_cc_event(struct ath12k_base *ab, struct sk_buff *s
>  	const void **tb;
>  	int ret, i;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
> @@ -6801,7 +6761,6 @@ static int ath12k_reg_11d_new_cc_event(struct ath12k_base *ab, struct sk_buff *s
>  
>  	ev = tb[WMI_TAG_11D_NEW_COUNTRY_EVENT];
>  	if (!ev) {
> -		kfree(tb);
>  		ath12k_warn(ab, "failed to fetch 11d new cc ev");
>  		return -EPROTO;
>  	}
> @@ -6814,8 +6773,6 @@ static int ath12k_reg_11d_new_cc_event(struct ath12k_base *ab, struct sk_buff *s
>  		   ab->new_alpha2[0],
>  		   ab->new_alpha2[1]);
>  
> -	kfree(tb);
> -
>  	for (i = 0; i < ab->num_radios; i++) {
>  		pdev = &ab->pdevs[i];
>  		ar = pdev->ar;
> @@ -8557,7 +8514,7 @@ static void ath12k_pdev_ctl_failsafe_check_event(struct ath12k_base *ab,
>  	const struct wmi_pdev_ctl_failsafe_chk_event *ev;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
> @@ -8567,7 +8524,6 @@ static void ath12k_pdev_ctl_failsafe_check_event(struct ath12k_base *ab,
>  	ev = tb[WMI_TAG_PDEV_CTL_FAILSAFE_CHECK_EVENT];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch pdev ctl failsafe check ev");
> -		kfree(tb);
>  		return;
>  	}
>  
> @@ -8581,8 +8537,6 @@ static void ath12k_pdev_ctl_failsafe_check_event(struct ath12k_base *ab,
>  	if (ev->ctl_failsafe_status != 0)
>  		ath12k_warn(ab, "pdev ctl failsafe failure status %d",
>  			    ev->ctl_failsafe_status);
> -
> -	kfree(tb);
>  }
>  
>  static void
> @@ -8654,7 +8608,7 @@ ath12k_wmi_pdev_csa_switch_count_status_event(struct ath12k_base *ab,
>  	const u32 *vdev_ids;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
> @@ -8666,7 +8620,6 @@ ath12k_wmi_pdev_csa_switch_count_status_event(struct ath12k_base *ab,
>  
>  	if (!ev || !vdev_ids) {
>  		ath12k_warn(ab, "failed to fetch pdev csa switch count ev");
> -		kfree(tb);
>  		return;
>  	}
>  
> @@ -8676,8 +8629,6 @@ ath12k_wmi_pdev_csa_switch_count_status_event(struct ath12k_base *ab,
>  		   ev->num_vdevs);
>  
>  	ath12k_wmi_process_csa_switch_count_event(ab, ev, vdev_ids);
> -
> -	kfree(tb);
>  }
>  
>  static void
> @@ -8689,7 +8640,7 @@ ath12k_wmi_pdev_dfs_radar_detected_event(struct ath12k_base *ab, struct sk_buff
>  	struct ath12k *ar;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
> @@ -8700,7 +8651,6 @@ ath12k_wmi_pdev_dfs_radar_detected_event(struct ath12k_base *ab, struct sk_buff
>  
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch pdev dfs radar detected ev");
> -		kfree(tb);
>  		return;
>  	}
>  
> @@ -8739,8 +8689,6 @@ ath12k_wmi_pdev_dfs_radar_detected_event(struct ath12k_base *ab, struct sk_buff
>  
>  exit:
>  	rcu_read_unlock();
> -
> -	kfree(tb);
>  }
>  
>  static void ath12k_tm_wmi_event_segmented(struct ath12k_base *ab, u32 cmd_id,
> @@ -8751,7 +8699,7 @@ static void ath12k_tm_wmi_event_segmented(struct ath12k_base *ab, u32 cmd_id,
>  	int ret;
>  	u16 length;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
> @@ -8762,14 +8710,11 @@ static void ath12k_tm_wmi_event_segmented(struct ath12k_base *ab, u32 cmd_id,
>  	ev = tb[WMI_TAG_ARRAY_BYTE];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch ftm msg\n");
> -		kfree(tb);
>  		return;
>  	}
>  
>  	length = skb->len - TLV_HDR_SIZE;
>  	ath12k_tm_process_event(ab, cmd_id, ev, length);
> -	kfree(tb);
> -	tb = NULL;
>  }
>  
>  static void
> @@ -8782,7 +8727,7 @@ ath12k_wmi_pdev_temperature_event(struct ath12k_base *ab,
>  	int temp;
>  	u32 pdev_id;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ath12k_warn(ab, "failed to parse tlv: %ld\n", PTR_ERR(tb));
>  		return;
> @@ -8791,15 +8736,12 @@ ath12k_wmi_pdev_temperature_event(struct ath12k_base *ab,
>  	ev = tb[WMI_TAG_PDEV_TEMPERATURE_EVENT];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch pdev temp ev\n");
> -		kfree(tb);
>  		return;
>  	}
>  
>  	temp = a_sle32_to_cpu(ev->temp);
>  	pdev_id = le32_to_cpu(ev->pdev_id);
>  
> -	kfree(tb);
> -
>  	ath12k_dbg(ab, ATH12K_DBG_WMI,
>  		   "pdev temperature ev temp %d pdev_id %u\n",
>  		   temp, pdev_id);
> @@ -8826,7 +8768,7 @@ static void ath12k_fils_discovery_event(struct ath12k_base *ab,
>  	const struct wmi_fils_discovery_event *ev;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab,
> @@ -8838,15 +8780,12 @@ static void ath12k_fils_discovery_event(struct ath12k_base *ab,
>  	ev = tb[WMI_TAG_HOST_SWFDA_EVENT];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch FILS discovery event\n");
> -		kfree(tb);
>  		return;
>  	}
>  
>  	ath12k_warn(ab,
>  		    "FILS discovery frame expected from host for vdev_id: %u, transmission scheduled at %u, next TBTT: %u\n",
>  		    ev->vdev_id, ev->fils_tt, ev->tbtt);
> -
> -	kfree(tb);
>  }
>  
>  static void ath12k_probe_resp_tx_status_event(struct ath12k_base *ab,
> @@ -8856,7 +8795,7 @@ static void ath12k_probe_resp_tx_status_event(struct ath12k_base *ab,
>  	const struct wmi_probe_resp_tx_status_event *ev;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab,
> @@ -8869,7 +8808,6 @@ static void ath12k_probe_resp_tx_status_event(struct ath12k_base *ab,
>  	if (!ev) {
>  		ath12k_warn(ab,
>  			    "failed to fetch probe response transmission status event");
> -		kfree(tb);
>  		return;
>  	}
>  
> @@ -8877,8 +8815,6 @@ static void ath12k_probe_resp_tx_status_event(struct ath12k_base *ab,
>  		ath12k_warn(ab,
>  			    "Probe response transmission failed for vdev_id %u, status %u\n",
>  			    ev->vdev_id, ev->tx_status);
> -
> -	kfree(tb);
>  }
>  
>  static int ath12k_wmi_p2p_noa_event(struct ath12k_base *ab,
> @@ -8890,7 +8826,7 @@ static int ath12k_wmi_p2p_noa_event(struct ath12k_base *ab,
>  	struct ath12k *ar;
>  	int ret, vdev_id;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse P2P NoA TLV: %d\n", ret);
> @@ -8900,10 +8836,8 @@ static int ath12k_wmi_p2p_noa_event(struct ath12k_base *ab,
>  	ev = tb[WMI_TAG_P2P_NOA_EVENT];
>  	noa = tb[WMI_TAG_P2P_NOA_INFO];
>  
> -	if (!ev || !noa) {
> -		ret = -EPROTO;
> -		goto out;
> -	}
> +	if (!ev || !noa)
> +		return -EPROTO;
>  
>  	vdev_id = __le32_to_cpu(ev->vdev_id);
>  
> @@ -8926,8 +8860,6 @@ static int ath12k_wmi_p2p_noa_event(struct ath12k_base *ab,
>  
>  unlock:
>  	rcu_read_unlock();
> -out:
> -	kfree(tb);
>  	return ret;
>  }
>  
> @@ -8938,7 +8870,7 @@ static void ath12k_rfkill_state_change_event(struct ath12k_base *ab,
>  	const void **tb;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
> @@ -8946,10 +8878,8 @@ static void ath12k_rfkill_state_change_event(struct ath12k_base *ab,
>  	}
>  
>  	ev = tb[WMI_TAG_RFKILL_EVENT];
> -	if (!ev) {
> -		kfree(tb);
> +	if (!ev)
>  		return;
> -	}
>  
>  	ath12k_dbg(ab, ATH12K_DBG_MAC,
>  		   "wmi tlv rfkill state change gpio %d type %d radio_state %d\n",
> @@ -8962,7 +8892,6 @@ static void ath12k_rfkill_state_change_event(struct ath12k_base *ab,
>  	spin_unlock_bh(&ab->base_lock);
>  
>  	queue_work(ab->workqueue, &ab->rfkill_work);
> -	kfree(tb);
>  }
>  
>  static void
> @@ -8978,7 +8907,7 @@ static void ath12k_wmi_twt_enable_event(struct ath12k_base *ab,
>  	const struct wmi_twt_enable_event *ev;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse wmi twt enable status event tlv: %d\n",
> @@ -8989,15 +8918,12 @@ static void ath12k_wmi_twt_enable_event(struct ath12k_base *ab,
>  	ev = tb[WMI_TAG_TWT_ENABLE_COMPLETE_EVENT];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch twt enable wmi event\n");
> -		goto exit;
> +		return;
>  	}
>  
>  	ath12k_dbg(ab, ATH12K_DBG_MAC, "wmi twt enable event pdev id %u status %u\n",
>  		   le32_to_cpu(ev->pdev_id),
>  		   le32_to_cpu(ev->status));
> -
> -exit:
> -	kfree(tb);
>  }
>  
>  static void ath12k_wmi_twt_disable_event(struct ath12k_base *ab,
> @@ -9007,7 +8933,7 @@ static void ath12k_wmi_twt_disable_event(struct ath12k_base *ab,
>  	const struct wmi_twt_disable_event *ev;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse wmi twt disable status event tlv: %d\n",
> @@ -9018,15 +8944,12 @@ static void ath12k_wmi_twt_disable_event(struct ath12k_base *ab,
>  	ev = tb[WMI_TAG_TWT_DISABLE_COMPLETE_EVENT];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch twt disable wmi event\n");
> -		goto exit;
> +		return;
>  	}
>  
>  	ath12k_dbg(ab, ATH12K_DBG_MAC, "wmi twt disable event pdev id %d status %u\n",
>  		   le32_to_cpu(ev->pdev_id),
>  		   le32_to_cpu(ev->status));
> -
> -exit:
> -	kfree(tb);
>  }
>  
>  static int ath12k_wmi_wow_wakeup_host_parse(struct ath12k_base *ab,
> @@ -9099,7 +9022,7 @@ static void ath12k_wmi_gtk_offload_status_event(struct ath12k_base *ab,
>  	const void **tb;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
> @@ -9109,7 +9032,6 @@ static void ath12k_wmi_gtk_offload_status_event(struct ath12k_base *ab,
>  	ev = tb[WMI_TAG_GTK_OFFLOAD_STATUS_EVENT];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch gtk offload status ev");
> -		kfree(tb);
>  		return;
>  	}
>  
> @@ -9119,7 +9041,6 @@ static void ath12k_wmi_gtk_offload_status_event(struct ath12k_base *ab,
>  		rcu_read_unlock();
>  		ath12k_warn(ab, "failed to get arvif for vdev_id:%d\n",
>  			    le32_to_cpu(ev->vdev_id));
> -		kfree(tb);
>  		return;
>  	}
>  
> @@ -9135,8 +9056,6 @@ static void ath12k_wmi_gtk_offload_status_event(struct ath12k_base *ab,
>  				   (void *)&replay_ctr_be, GFP_ATOMIC);
>  
>  	rcu_read_unlock();
> -
> -	kfree(tb);
>  }
>  
>  static void ath12k_wmi_event_mlo_setup_complete(struct ath12k_base *ab,
> @@ -9148,7 +9067,7 @@ static void ath12k_wmi_event_mlo_setup_complete(struct ath12k_base *ab,
>  	const void **tb;
>  	int ret, i;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse mlo setup complete event tlv: %d\n",
> @@ -9159,7 +9078,6 @@ static void ath12k_wmi_event_mlo_setup_complete(struct ath12k_base *ab,
>  	ev = tb[WMI_TAG_MLO_SETUP_COMPLETE_EVENT];
>  	if (!ev) {
>  		ath12k_warn(ab, "failed to fetch mlo setup complete event\n");
> -		kfree(tb);
>  		return;
>  	}
>  
> @@ -9178,14 +9096,11 @@ static void ath12k_wmi_event_mlo_setup_complete(struct ath12k_base *ab,
>  	if (!ar) {
>  		ath12k_warn(ab, "invalid pdev_id %d status %u in setup complete event\n",
>  			    ev->pdev_id, ev->status);
> -		goto out;
> +		return;
>  	}
>  
>  	ar->mlo_setup_status = le32_to_cpu(ev->status);
>  	complete(&ar->mlo_setup_done);
> -
> -out:
> -	kfree(tb);
>  }
>  
>  static void ath12k_wmi_event_teardown_complete(struct ath12k_base *ab,
> @@ -9195,7 +9110,7 @@ static void ath12k_wmi_event_teardown_complete(struct ath12k_base *ab,
>  	const void **tb;
>  	int ret;
>  
> -	tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	tb = ath12k_wmi_tlv_parse(ab, skb);
>  	if (IS_ERR(tb)) {
>  		ret = PTR_ERR(tb);
>  		ath12k_warn(ab, "failed to parse teardown complete event tlv: %d\n", ret);
> @@ -9203,13 +9118,8 @@ static void ath12k_wmi_event_teardown_complete(struct ath12k_base *ab,
>  	}
>  
>  	ev = tb[WMI_TAG_MLO_TEARDOWN_COMPLETE];
> -	if (!ev) {
> +	if (!ev)
>  		ath12k_warn(ab, "failed to fetch teardown complete event\n");
> -		kfree(tb);
> -		return;
> -	}
> -
> -	kfree(tb);
>  }
>  
>  #ifdef CONFIG_ATH12K_DEBUGFS
> @@ -11239,3 +11149,30 @@ int ath12k_wmi_send_mlo_link_set_active_cmd(struct ath12k_base *ab,
>  	dev_kfree_skb(skb);
>  	return ret;
>  }
> +
> +int ath12k_wmi_alloc(void)
> +{
> +	guard(mutex)(&ath12k_wmi_mutex);
> +
> +	if (!ath12k_wmi_tb)
> +		ath12k_wmi_tb = __alloc_percpu(WMI_TAG_MAX * sizeof(void *),
> +					       __alignof__(void *));
> +	if (!ath12k_wmi_tb)
> +		return -ENOMEM;

better move the second testing inside ?

	if (!ath12k_wmi_tb) {
		ath12k_wmi_tb = alloc();
		if (!ath12k_wmi_tb)
			return -ENOMEM;
	}

> +
> +	ath12k_wmi_refcount++;
> +	return 0;
> +}
> +
> +void ath12k_wmi_free(void)
> +{
> +	guard(mutex)(&ath12k_wmi_mutex);
> +
> +	if (!WARN_ON(ath12k_wmi_refcount - 1 < 0))

better to use refcount_t and its APIs?

> +		ath12k_wmi_refcount--;
> +
> +	if (!ath12k_wmi_refcount) {
> +		free_percpu(ath12k_wmi_tb);
> +		ath12k_wmi_tb = NULL;
> +	}
> +}
> diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h
> index 5ba9b7d3a888..4a34b2ca99ea 100644
> --- a/drivers/net/wireless/ath/ath12k/wmi.h
> +++ b/drivers/net/wireless/ath/ath12k/wmi.h
> @@ -6576,4 +6576,7 @@ int ath12k_wmi_send_vdev_set_tpc_power(struct ath12k *ar,
>  				       struct ath12k_reg_tpc_power_info *param);
>  int ath12k_wmi_send_mlo_link_set_active_cmd(struct ath12k_base *ab,
>  					    struct wmi_mlo_link_set_active_arg *param);
> +int ath12k_wmi_alloc(void);
> +void ath12k_wmi_free(void);
> +
>  #endif


^ permalink raw reply

* RE: [BUG] wifi: rtw88: Hard system freeze on RTL8821CE when power_save is enabled (LPS/ASPM conflict)
From: Ping-Ke Shih @ 2026-03-31  0:32 UTC (permalink / raw)
  To: LB F
  Cc: Bitterblue Smith, linux-wireless@vger.kernel.org,
	linux-kernel@vger.kernel.org
In-Reply-To: <CALdGYqT2e+jt+mK-o_bL8hfdEwqZ44fUt9_N6-H4jYp8FpqQJw@mail.gmail.com>


LB F <goainwo@gmail.com> wrote:
> 
> Could you advise on how to investigate this further? For example:
> 
>   - Is there a debug flag or register dump we could capture right
>     before the first corrupted frame in a burst?
>   - Would it help to log C2H (chip-to-host) traffic around the
>     time of these events?

I have never heard about this symptom from internal, so no clear
idea for that. Sorry.

> 
> I am ready to run any specific tests you need. In the meantime,
> I agree that filtering by DRV_INFO_SIZE is the right practical
> solution, and I'm waiting for your official patch to test locally.

As malformed frames happen randomly, more validations like
DRV_INFO_SIZE are needed. I think Bitterblue is working on the
test patch. :)

Ping-Ke


^ permalink raw reply

* [PATCH ath-next v3 6/6] wifi: ath12k: Enable IPQ5424 WiFi device support
From: Raj Kumar Bhagat @ 2026-03-30 20:39 UTC (permalink / raw)
  To: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jeff Johnson
  Cc: linux-wireless, devicetree, linux-kernel, ath12k,
	Raj Kumar Bhagat, Sowmiya Sree Elavalagan,
	Saravanakumar Duraisamy
In-Reply-To: <20260331-ath12k-ipq5424-v3-0-1455b9cae29c@oss.qualcomm.com>

From: Sowmiya Sree Elavalagan <sowmiya.elavalagan@oss.qualcomm.com>

Currently, ath12k AHB (in IPQ5332) uses SCM calls to authenticate the
firmware image to bring up userpd. From IPQ5424 onwards, Q6 firmware can
directly communicate with the Trusted Management Engine - Lite (TME-L),
eliminating the need for SCM calls for userpd bring-up.

Hence, to enable IPQ5424 device support, use qcom_mdt_load_no_init() and
skip the SCM call as Q6 will directly authenticate the userpd firmware.

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
Tested-on: IPQ5424 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1

Signed-off-by: Sowmiya Sree Elavalagan <sowmiya.elavalagan@oss.qualcomm.com>
Co-developed-by: Saravanakumar Duraisamy <quic_saradura@quicinc.com>
Signed-off-by: Saravanakumar Duraisamy <quic_saradura@quicinc.com>
Co-developed-by: Raj Kumar Bhagat <raj.bhagat@oss.qualcomm.com>
Signed-off-by: Raj Kumar Bhagat <raj.bhagat@oss.qualcomm.com>
---
 drivers/net/wireless/ath/ath12k/ahb.c       | 36 ++++++++++++++++++-----------
 drivers/net/wireless/ath/ath12k/ahb.h       |  1 +
 drivers/net/wireless/ath/ath12k/wifi7/ahb.c |  8 +++++++
 3 files changed, 31 insertions(+), 14 deletions(-)

diff --git a/drivers/net/wireless/ath/ath12k/ahb.c b/drivers/net/wireless/ath/ath12k/ahb.c
index 9a4d34e49104..2dcf0a52e4c1 100644
--- a/drivers/net/wireless/ath/ath12k/ahb.c
+++ b/drivers/net/wireless/ath/ath12k/ahb.c
@@ -382,8 +382,12 @@ static int ath12k_ahb_power_up(struct ath12k_base *ab)
 		ATH12K_AHB_UPD_SWID;
 
 	/* Load FW image to a reserved memory location */
-	ret = qcom_mdt_load(dev, fw, fw_name, pasid, mem_region, mem_phys, mem_size,
-			    &mem_phys);
+	if (ab_ahb->scm_auth_enabled)
+		ret = qcom_mdt_load(dev, fw, fw_name, pasid, mem_region,
+				    mem_phys, mem_size, &mem_phys);
+	else
+		ret = qcom_mdt_load_no_init(dev, fw, fw_name, mem_region,
+					    mem_phys, mem_size, &mem_phys);
 	if (ret) {
 		ath12k_err(ab, "Failed to load MDT segments: %d\n", ret);
 		goto err_fw;
@@ -414,11 +418,13 @@ static int ath12k_ahb_power_up(struct ath12k_base *ab)
 		goto err_fw2;
 	}
 
-	/* Authenticate FW image using peripheral ID */
-	ret = qcom_scm_pas_auth_and_reset(pasid);
-	if (ret) {
-		ath12k_err(ab, "failed to boot the remote processor %d\n", ret);
-		goto err_fw2;
+	if (ab_ahb->scm_auth_enabled) {
+		/* Authenticate FW image using peripheral ID */
+		ret = qcom_scm_pas_auth_and_reset(pasid);
+		if (ret) {
+			ath12k_err(ab, "failed to boot the remote processor %d\n", ret);
+			goto err_fw2;
+		}
 	}
 
 	/* Instruct Q6 to spawn userPD thread */
@@ -475,13 +481,15 @@ static void ath12k_ahb_power_down(struct ath12k_base *ab, bool is_suspend)
 
 	qcom_smem_state_update_bits(ab_ahb->stop_state, BIT(ab_ahb->stop_bit), 0);
 
-	pasid = (u32_encode_bits(ab_ahb->userpd_id, ATH12K_USERPD_ID_MASK)) |
-		ATH12K_AHB_UPD_SWID;
-	/* Release the firmware */
-	ret = qcom_scm_pas_shutdown(pasid);
-	if (ret)
-		ath12k_err(ab, "scm pas shutdown failed for userPD%d: %d\n",
-			   ab_ahb->userpd_id, ret);
+	if (ab_ahb->scm_auth_enabled) {
+		pasid = (u32_encode_bits(ab_ahb->userpd_id, ATH12K_USERPD_ID_MASK)) |
+			 ATH12K_AHB_UPD_SWID;
+		/* Release the firmware */
+		ret = qcom_scm_pas_shutdown(pasid);
+		if (ret)
+			ath12k_err(ab, "scm pas shutdown failed for userPD%d\n",
+				   ab_ahb->userpd_id);
+	}
 }
 
 static void ath12k_ahb_init_qmi_ce_config(struct ath12k_base *ab)
diff --git a/drivers/net/wireless/ath/ath12k/ahb.h b/drivers/net/wireless/ath/ath12k/ahb.h
index be9e31b3682d..0fa15daaa3e6 100644
--- a/drivers/net/wireless/ath/ath12k/ahb.h
+++ b/drivers/net/wireless/ath/ath12k/ahb.h
@@ -68,6 +68,7 @@ struct ath12k_ahb {
 	int userpd_irq_num[ATH12K_USERPD_MAX_IRQ];
 	const struct ath12k_ahb_ops *ahb_ops;
 	const struct ath12k_ahb_device_family_ops *device_family_ops;
+	bool scm_auth_enabled;
 };
 
 struct ath12k_ahb_driver {
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/ahb.c b/drivers/net/wireless/ath/ath12k/wifi7/ahb.c
index a6c5f7689edd..6a8b8b2a56f9 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/ahb.c
+++ b/drivers/net/wireless/ath/ath12k/wifi7/ahb.c
@@ -19,6 +19,9 @@ static const struct of_device_id ath12k_wifi7_ahb_of_match[] = {
 	{ .compatible = "qcom,ipq5332-wifi",
 	  .data = (void *)ATH12K_HW_IPQ5332_HW10,
 	},
+	{ .compatible = "qcom,ipq5424-wifi",
+	  .data = (void *)ATH12K_HW_IPQ5424_HW10,
+	},
 	{ }
 };
 
@@ -38,6 +41,11 @@ static int ath12k_wifi7_ahb_probe(struct platform_device *pdev)
 	switch (hw_rev) {
 	case ATH12K_HW_IPQ5332_HW10:
 		ab_ahb->userpd_id = ATH12K_IPQ5332_USERPD_ID;
+		ab_ahb->scm_auth_enabled = true;
+		break;
+	case ATH12K_HW_IPQ5424_HW10:
+		ab_ahb->userpd_id = ATH12K_IPQ5332_USERPD_ID;
+		ab_ahb->scm_auth_enabled = false;
 		break;
 	default:
 		return -EOPNOTSUPP;

-- 
2.34.1


^ permalink raw reply related

* [PATCH ath-next v3 5/6] wifi: ath12k: Add CE remap hardware parameters for IPQ5424
From: Raj Kumar Bhagat @ 2026-03-30 20:39 UTC (permalink / raw)
  To: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jeff Johnson
  Cc: linux-wireless, devicetree, linux-kernel, ath12k,
	Raj Kumar Bhagat, Saravanakumar Duraisamy
In-Reply-To: <20260331-ath12k-ipq5424-v3-0-1455b9cae29c@oss.qualcomm.com>

From: Saravanakumar Duraisamy <quic_saradura@quicinc.com>

Add CE remap hardware parameters for Ath12k AHB device IPQ5424.

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
Tested-on: IPQ5424 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1

Signed-off-by: Saravanakumar Duraisamy <quic_saradura@quicinc.com>
Signed-off-by: Raj Kumar Bhagat <raj.bhagat@oss.qualcomm.com>
---
 drivers/net/wireless/ath/ath12k/ce.h       | 13 +++++++++----
 drivers/net/wireless/ath/ath12k/wifi7/hw.c | 22 +++++++++++++++++-----
 2 files changed, 26 insertions(+), 9 deletions(-)

diff --git a/drivers/net/wireless/ath/ath12k/ce.h b/drivers/net/wireless/ath/ath12k/ce.h
index df4f2a4f8480..009cddf2d68d 100644
--- a/drivers/net/wireless/ath/ath12k/ce.h
+++ b/drivers/net/wireless/ath/ath12k/ce.h
@@ -38,10 +38,15 @@
 #define PIPEDIR_INOUT		3 /* bidirectional */
 #define PIPEDIR_INOUT_H2H	4 /* bidirectional, host to host */
 
-/* CE address/mask */
-#define CE_HOST_IE_ADDRESS	0x75804C
-#define CE_HOST_IE_2_ADDRESS	0x758050
-#define CE_HOST_IE_3_ADDRESS	CE_HOST_IE_ADDRESS
+/* IPQ5332 CE address/mask */
+#define CE_HOST_IPQ5332_IE_ADDRESS	0x75804C
+#define CE_HOST_IPQ5332_IE_2_ADDRESS	0x758050
+#define CE_HOST_IPQ5332_IE_3_ADDRESS	CE_HOST_IPQ5332_IE_ADDRESS
+
+/* IPQ5424 CE address/mask */
+#define CE_HOST_IPQ5424_IE_ADDRESS	0x21804C
+#define CE_HOST_IPQ5424_IE_2_ADDRESS	0x218050
+#define CE_HOST_IPQ5424_IE_3_ADDRESS	CE_HOST_IPQ5424_IE_ADDRESS
 
 #define CE_HOST_IE_3_SHIFT	0xC
 
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
index 9b9ca06a9f45..a2c98cc1e348 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c
+++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
@@ -329,9 +329,15 @@ static const struct ath12k_hw_ring_mask ath12k_wifi7_hw_ring_mask_wcn7850 = {
 };
 
 static const struct ce_ie_addr ath12k_wifi7_ce_ie_addr_ipq5332 = {
-	.ie1_reg_addr = CE_HOST_IE_ADDRESS - HAL_IPQ5332_CE_WFSS_REG_BASE,
-	.ie2_reg_addr = CE_HOST_IE_2_ADDRESS - HAL_IPQ5332_CE_WFSS_REG_BASE,
-	.ie3_reg_addr = CE_HOST_IE_3_ADDRESS - HAL_IPQ5332_CE_WFSS_REG_BASE,
+	.ie1_reg_addr = CE_HOST_IPQ5332_IE_ADDRESS - HAL_IPQ5332_CE_WFSS_REG_BASE,
+	.ie2_reg_addr = CE_HOST_IPQ5332_IE_2_ADDRESS - HAL_IPQ5332_CE_WFSS_REG_BASE,
+	.ie3_reg_addr = CE_HOST_IPQ5332_IE_3_ADDRESS - HAL_IPQ5332_CE_WFSS_REG_BASE,
+};
+
+static const struct ce_ie_addr ath12k_wifi7_ce_ie_addr_ipq5424 = {
+	.ie1_reg_addr = CE_HOST_IPQ5424_IE_ADDRESS - HAL_IPQ5424_CE_WFSS_REG_BASE,
+	.ie2_reg_addr = CE_HOST_IPQ5424_IE_2_ADDRESS - HAL_IPQ5424_CE_WFSS_REG_BASE,
+	.ie3_reg_addr = CE_HOST_IPQ5424_IE_3_ADDRESS - HAL_IPQ5424_CE_WFSS_REG_BASE,
 };
 
 static const struct ce_remap ath12k_wifi7_ce_remap_ipq5332 = {
@@ -340,6 +346,12 @@ static const struct ce_remap ath12k_wifi7_ce_remap_ipq5332 = {
 	.cmem_offset = HAL_SEQ_WCSS_CMEM_OFFSET,
 };
 
+static const struct ce_remap ath12k_wifi7_ce_remap_ipq5424 = {
+	.base = HAL_IPQ5424_CE_WFSS_REG_BASE,
+	.size = HAL_IPQ5424_CE_SIZE,
+	.cmem_offset = HAL_SEQ_WCSS_CMEM_OFFSET,
+};
+
 static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = {
 	{
 		.name = "qcn9274 hw1.0",
@@ -822,8 +834,8 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = {
 		.iova_mask = 0,
 		.supports_aspm = false,
 
-		.ce_ie_addr = NULL,
-		.ce_remap = NULL,
+		.ce_ie_addr = &ath12k_wifi7_ce_ie_addr_ipq5424,
+		.ce_remap = &ath12k_wifi7_ce_remap_ipq5424,
 		.bdf_addr_offset = 0x940000,
 
 		.dp_primary_link_only = true,

-- 
2.34.1


^ permalink raw reply related

* [PATCH ath-next v3 4/6] wifi: ath12k: add ath12k_hw_regs for IPQ5424
From: Raj Kumar Bhagat @ 2026-03-30 20:39 UTC (permalink / raw)
  To: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jeff Johnson
  Cc: linux-wireless, devicetree, linux-kernel, ath12k,
	Raj Kumar Bhagat, Saravanakumar Duraisamy
In-Reply-To: <20260331-ath12k-ipq5424-v3-0-1455b9cae29c@oss.qualcomm.com>

From: Saravanakumar Duraisamy <quic_saradura@quicinc.com>

Add register addresses (ath12k_hw_regs) for ath12k AHB based
WiFi 7 device IPQ5424.

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
Tested-on: IPQ5424 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1

Signed-off-by: Saravanakumar Duraisamy <quic_saradura@quicinc.com>
Signed-off-by: Raj Kumar Bhagat <raj.bhagat@oss.qualcomm.com>
---
 drivers/net/wireless/ath/ath12k/wifi7/hal.c        |  2 +-
 drivers/net/wireless/ath/ath12k/wifi7/hal.h        |  3 +
 .../net/wireless/ath/ath12k/wifi7/hal_qcn9274.c    | 88 ++++++++++++++++++++++
 .../net/wireless/ath/ath12k/wifi7/hal_qcn9274.h    |  1 +
 4 files changed, 93 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal.c b/drivers/net/wireless/ath/ath12k/wifi7/hal.c
index c2cc99a83f09..a0a1902fb491 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/hal.c
+++ b/drivers/net/wireless/ath/ath12k/wifi7/hal.c
@@ -55,7 +55,7 @@ static const struct ath12k_hw_version_map ath12k_wifi7_hw_ver_map[] = {
 		.hal_desc_sz = sizeof(struct hal_rx_desc_qcn9274_compact),
 		.tcl_to_wbm_rbm_map = ath12k_hal_tcl_to_wbm_rbm_map_qcn9274,
 		.hal_params = &ath12k_hw_hal_params_ipq5332,
-		.hw_regs = NULL,
+		.hw_regs = &ipq5424_regs,
 	},
 };
 
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal.h b/drivers/net/wireless/ath/ath12k/wifi7/hal.h
index 9337225a5253..3d9386198893 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/hal.h
+++ b/drivers/net/wireless/ath/ath12k/wifi7/hal.h
@@ -364,6 +364,9 @@
 #define HAL_IPQ5332_CE_WFSS_REG_BASE	0x740000
 #define HAL_IPQ5332_CE_SIZE		0x100000
 
+#define HAL_IPQ5424_CE_WFSS_REG_BASE	0x200000
+#define HAL_IPQ5424_CE_SIZE		0x100000
+
 #define HAL_RX_MAX_BA_WINDOW	256
 
 #define HAL_DEFAULT_BE_BK_VI_REO_TIMEOUT_USEC	(100 * 1000)
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c
index 41c918eb1767..ba9ce1e718e8 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c
+++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.c
@@ -484,6 +484,94 @@ const struct ath12k_hw_regs ipq5332_regs = {
 		HAL_IPQ5332_CE_WFSS_REG_BASE,
 };
 
+const struct ath12k_hw_regs ipq5424_regs = {
+	/* SW2TCL(x) R0 ring configuration address */
+	.tcl1_ring_id = 0x00000918,
+	.tcl1_ring_misc = 0x00000920,
+	.tcl1_ring_tp_addr_lsb = 0x0000092c,
+	.tcl1_ring_tp_addr_msb = 0x00000930,
+	.tcl1_ring_consumer_int_setup_ix0 = 0x00000940,
+	.tcl1_ring_consumer_int_setup_ix1 = 0x00000944,
+	.tcl1_ring_msi1_base_lsb = 0x00000958,
+	.tcl1_ring_msi1_base_msb = 0x0000095c,
+	.tcl1_ring_base_lsb = 0x00000910,
+	.tcl1_ring_base_msb = 0x00000914,
+	.tcl1_ring_msi1_data = 0x00000960,
+	.tcl2_ring_base_lsb = 0x00000988,
+	.tcl_ring_base_lsb = 0x00000b68,
+
+	/* TCL STATUS ring address */
+	.tcl_status_ring_base_lsb = 0x00000d48,
+
+	/* REO DEST ring address */
+	.reo2_ring_base = 0x00000578,
+	.reo1_misc_ctrl_addr = 0x00000b9c,
+	.reo1_sw_cookie_cfg0 = 0x0000006c,
+	.reo1_sw_cookie_cfg1 = 0x00000070,
+	.reo1_qdesc_lut_base0 = 0x00000074,
+	.reo1_qdesc_lut_base1 = 0x00000078,
+	.reo1_ring_base_lsb = 0x00000500,
+	.reo1_ring_base_msb = 0x00000504,
+	.reo1_ring_id = 0x00000508,
+	.reo1_ring_misc = 0x00000510,
+	.reo1_ring_hp_addr_lsb = 0x00000514,
+	.reo1_ring_hp_addr_msb = 0x00000518,
+	.reo1_ring_producer_int_setup = 0x00000524,
+	.reo1_ring_msi1_base_lsb = 0x00000548,
+	.reo1_ring_msi1_base_msb = 0x0000054C,
+	.reo1_ring_msi1_data = 0x00000550,
+	.reo1_aging_thres_ix0 = 0x00000B28,
+	.reo1_aging_thres_ix1 = 0x00000B2C,
+	.reo1_aging_thres_ix2 = 0x00000B30,
+	.reo1_aging_thres_ix3 = 0x00000B34,
+
+	/* REO Exception ring address */
+	.reo2_sw0_ring_base = 0x000008c0,
+
+	/* REO Reinject ring address */
+	.sw2reo_ring_base = 0x00000320,
+	.sw2reo1_ring_base = 0x00000398,
+
+	/* REO cmd ring address */
+	.reo_cmd_ring_base = 0x000002A8,
+
+	/* REO status ring address */
+	.reo_status_ring_base = 0x00000aa0,
+
+	/* WBM idle link ring address */
+	.wbm_idle_ring_base_lsb = 0x00000d3c,
+	.wbm_idle_ring_misc_addr = 0x00000d4c,
+	.wbm_r0_idle_list_cntl_addr = 0x00000240,
+	.wbm_r0_idle_list_size_addr = 0x00000244,
+	.wbm_scattered_ring_base_lsb = 0x00000250,
+	.wbm_scattered_ring_base_msb = 0x00000254,
+	.wbm_scattered_desc_head_info_ix0 = 0x00000260,
+	.wbm_scattered_desc_head_info_ix1	= 0x00000264,
+	.wbm_scattered_desc_tail_info_ix0 = 0x00000270,
+	.wbm_scattered_desc_tail_info_ix1 = 0x00000274,
+	.wbm_scattered_desc_ptr_hp_addr = 0x0000027c,
+
+	/* SW2WBM release ring address */
+	.wbm_sw_release_ring_base_lsb = 0x0000037c,
+
+	/* WBM2SW release ring address */
+	.wbm0_release_ring_base_lsb = 0x00000e08,
+	.wbm1_release_ring_base_lsb = 0x00000e80,
+
+	/* PPE release ring address */
+	.ppe_rel_ring_base = 0x0000046c,
+
+	/* CE address */
+	.umac_ce0_src_reg_base = 0x00200000 -
+		HAL_IPQ5424_CE_WFSS_REG_BASE,
+	.umac_ce0_dest_reg_base = 0x00201000 -
+		HAL_IPQ5424_CE_WFSS_REG_BASE,
+	.umac_ce1_src_reg_base = 0x00202000 -
+		HAL_IPQ5424_CE_WFSS_REG_BASE,
+	.umac_ce1_dest_reg_base = 0x00203000 -
+		HAL_IPQ5424_CE_WFSS_REG_BASE,
+};
+
 static inline
 bool ath12k_hal_rx_desc_get_first_msdu_qcn9274(struct hal_rx_desc *desc)
 {
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.h b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.h
index 08c0a0469474..03cf3792d523 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.h
+++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_qcn9274.h
@@ -17,6 +17,7 @@ extern const struct hal_ops hal_qcn9274_ops;
 extern const struct ath12k_hw_regs qcn9274_v1_regs;
 extern const struct ath12k_hw_regs qcn9274_v2_regs;
 extern const struct ath12k_hw_regs ipq5332_regs;
+extern const struct ath12k_hw_regs ipq5424_regs;
 extern const struct ath12k_hal_tcl_to_wbm_rbm_map
 ath12k_hal_tcl_to_wbm_rbm_map_qcn9274[DP_TCL_NUM_RING_MAX];
 extern const struct ath12k_hw_hal_params ath12k_hw_hal_params_qcn9274;

-- 
2.34.1


^ permalink raw reply related

* [PATCH ath-next v3 3/6] wifi: ath12k: add ath12k_hw_version_map entry for IPQ5424
From: Raj Kumar Bhagat @ 2026-03-30 20:39 UTC (permalink / raw)
  To: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jeff Johnson
  Cc: linux-wireless, devicetree, linux-kernel, ath12k,
	Raj Kumar Bhagat
In-Reply-To: <20260331-ath12k-ipq5424-v3-0-1455b9cae29c@oss.qualcomm.com>

Add a new ath12k_hw_version_map entry for the AHB based WiFi 7 device
IPQ5424.

Reuse most of the ath12k_hw_version_map fields such as hal_ops,
hal_desc_sz, tcl_to_wbm_rbm_map, and hal_params from IPQ5332. The
register addresses differ on IPQ5424, hence set hw_regs temporarily
to NULL and populated it in a subsequent patch.

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
Tested-on: IPQ5424 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1

Signed-off-by: Raj Kumar Bhagat <raj.bhagat@oss.qualcomm.com>
---
 drivers/net/wireless/ath/ath12k/wifi7/hal.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal.c b/drivers/net/wireless/ath/ath12k/wifi7/hal.c
index bd1753ca0db6..c2cc99a83f09 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/hal.c
+++ b/drivers/net/wireless/ath/ath12k/wifi7/hal.c
@@ -50,6 +50,13 @@ static const struct ath12k_hw_version_map ath12k_wifi7_hw_ver_map[] = {
 		.hal_params = &ath12k_hw_hal_params_wcn7850,
 		.hw_regs = &qcc2072_regs,
 	},
+	[ATH12K_HW_IPQ5424_HW10] = {
+		.hal_ops = &hal_qcn9274_ops,
+		.hal_desc_sz = sizeof(struct hal_rx_desc_qcn9274_compact),
+		.tcl_to_wbm_rbm_map = ath12k_hal_tcl_to_wbm_rbm_map_qcn9274,
+		.hal_params = &ath12k_hw_hal_params_ipq5332,
+		.hw_regs = NULL,
+	},
 };
 
 int ath12k_wifi7_hal_init(struct ath12k_base *ab)

-- 
2.34.1


^ permalink raw reply related

* [PATCH ath-next v3 2/6] wifi: ath12k: Add ath12k_hw_params for IPQ5424
From: Raj Kumar Bhagat @ 2026-03-30 20:39 UTC (permalink / raw)
  To: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jeff Johnson
  Cc: linux-wireless, devicetree, linux-kernel, ath12k,
	Raj Kumar Bhagat, Saravanakumar Duraisamy
In-Reply-To: <20260331-ath12k-ipq5424-v3-0-1455b9cae29c@oss.qualcomm.com>

From: Saravanakumar Duraisamy <quic_saradura@quicinc.com>

Add ath12k_hw_params for the ath12k AHB-based WiFi 7 device IPQ5424.
The WiFi device IPQ5424 is similar to IPQ5332. Most of the hardware
parameters like hw_ops, wmi_init, ring_mask, etc., are the same between
IPQ5424 and IPQ5332, hence use these same parameters for IPQ5424.
Some parameters are specific to IPQ5424; initially set these to
0 or NULL, and populate them in subsequent patches.

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
Tested-on: IPQ5424 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1

Signed-off-by: Saravanakumar Duraisamy <quic_saradura@quicinc.com>
Signed-off-by: Raj Kumar Bhagat <raj.bhagat@oss.qualcomm.com>
---
 drivers/net/wireless/ath/ath12k/core.h     |  1 +
 drivers/net/wireless/ath/ath12k/wifi7/hw.c | 75 ++++++++++++++++++++++++++++++
 2 files changed, 76 insertions(+)

diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h
index 59c193b24764..68453594eba8 100644
--- a/drivers/net/wireless/ath/ath12k/core.h
+++ b/drivers/net/wireless/ath/ath12k/core.h
@@ -157,6 +157,7 @@ enum ath12k_hw_rev {
 	ATH12K_HW_WCN7850_HW20,
 	ATH12K_HW_IPQ5332_HW10,
 	ATH12K_HW_QCC2072_HW10,
+	ATH12K_HW_IPQ5424_HW10,
 };
 
 enum ath12k_firmware_mode {
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
index ec6dba96640b..9b9ca06a9f45 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c
+++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
@@ -753,6 +753,81 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = {
 
 		.dp_primary_link_only = false,
 	},
+	{
+		.name = "ipq5424 hw1.0",
+		.hw_rev = ATH12K_HW_IPQ5424_HW10,
+		.fw = {
+			.dir = "IPQ5424/hw1.0",
+			.board_size = 256 * 1024,
+			.cal_offset = 128 * 1024,
+			.m3_loader = ath12k_m3_fw_loader_remoteproc,
+			.download_aux_ucode = false,
+		},
+		.max_radios = 1,
+		.single_pdev_only = false,
+		.qmi_service_ins_id = ATH12K_QMI_WLFW_SERVICE_INS_ID_V01_IPQ5332,
+		.internal_sleep_clock = false,
+
+		.hw_ops = &qcn9274_ops,
+		.ring_mask = &ath12k_wifi7_hw_ring_mask_ipq5332,
+
+		.host_ce_config = ath12k_wifi7_host_ce_config_ipq5332,
+		.ce_count = 12,
+		.target_ce_config = ath12k_wifi7_target_ce_config_wlan_ipq5332,
+		.target_ce_count = 12,
+		.svc_to_ce_map =
+			ath12k_wifi7_target_service_to_ce_map_wlan_ipq5332,
+		.svc_to_ce_map_len = 18,
+
+		.rxdma1_enable = true,
+		.num_rxdma_per_pdev = 1,
+		.num_rxdma_dst_ring = 0,
+		.rx_mac_buf_ring = false,
+		.vdev_start_delay = false,
+
+		.interface_modes = BIT(NL80211_IFTYPE_STATION) |
+				   BIT(NL80211_IFTYPE_AP) |
+				   BIT(NL80211_IFTYPE_MESH_POINT),
+		.supports_monitor = true,
+
+		.idle_ps = false,
+		.download_calib = true,
+		.supports_suspend = false,
+		.tcl_ring_retry = true,
+		.reoq_lut_support = false,
+		.supports_shadow_regs = false,
+
+		.num_tcl_banks = 48,
+		.max_tx_ring = 4,
+
+		.wmi_init = &ath12k_wifi7_wmi_init_qcn9274,
+
+		.qmi_cnss_feature_bitmap = BIT(CNSS_QDSS_CFG_MISS_V01),
+
+		.rfkill_pin = 0,
+		.rfkill_cfg = 0,
+		.rfkill_on_level = 0,
+
+		.rddm_size = 0,
+
+		.def_num_link = 0,
+		.max_mlo_peer = 256,
+
+		.otp_board_id_register = 0,
+
+		.supports_sta_ps = false,
+
+		.acpi_guid = NULL,
+		.supports_dynamic_smps_6ghz = false,
+		.iova_mask = 0,
+		.supports_aspm = false,
+
+		.ce_ie_addr = NULL,
+		.ce_remap = NULL,
+		.bdf_addr_offset = 0x940000,
+
+		.dp_primary_link_only = true,
+	},
 };
 
 /* Note: called under rcu_read_lock() */

-- 
2.34.1


^ permalink raw reply related


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