Linux wireless drivers development
 help / color / mirror / Atom feed
* Re: [PATCH] wifi: rtl8xxxu: Enable AP mode for RTL8188EU
From: Bitterblue Smith @ 2026-03-12 15:58 UTC (permalink / raw)
  To: Georg Müller, Jes.Sorensen; +Cc: linux-wireless, linux-kernel
In-Reply-To: <20260312142155.2642993-1-georgmueller@gmx.net>

On 12/03/2026 16:21, Georg Müller wrote:
> Allow devices with this driver to be used as a wireless access point.
> 
> I successfully tested this with a TP-Link TP-Link TL-WN725N adapter.
> 
> Experiments two years ago failed, but some other improvements to the
> driver seemed to have resolved theses issues.
> 

The rate control code still doesn't handle more than one station.
It's not going to work right.

It shouldn't be too complicated. The ra_info member of rtl8xxxu_priv
needs to become an array.

> The max_macid_num is conservative. The old driver used 32 [1], some
> other sources said 64. I have not enough adapters to test any of the
> higher values. Given the usage of this chipset in nano dongles, I think
> the 16 might be high enough.
> 
> Signed-off-by: Georg Müller <georgmueller@gmx.net>
> ---
>  drivers/net/wireless/realtek/rtl8xxxu/8188e.c | 2 ++
>  1 file changed, 2 insertions(+)
> 
> diff --git a/drivers/net/wireless/realtek/rtl8xxxu/8188e.c b/drivers/net/wireless/realtek/rtl8xxxu/8188e.c
> index 766a7a7c7d28..c6aecb28d28b 100644
> --- a/drivers/net/wireless/realtek/rtl8xxxu/8188e.c
> +++ b/drivers/net/wireless/realtek/rtl8xxxu/8188e.c
> @@ -1867,6 +1867,8 @@ struct rtl8xxxu_fileops rtl8188eu_fops = {
>  	.init_reg_pkt_life_time = 1,
>  	.gen2_thermal_meter = 1,
>  	.max_sec_cam_num = 32,
> +	.supports_ap = 1,
> +	.max_macid_num = 16,
>  	.adda_1t_init = 0x0b1b25a0,
>  	.adda_1t_path_on = 0x0bdb25a0,
>  	/*


^ permalink raw reply

* Re: [PATCH 00/61] treewide: Use IS_ERR_OR_NULL over manual NULL check - refactor
From: James Bottomley @ 2026-03-12 15:32 UTC (permalink / raw)
  To: Jason Gunthorpe, Kuan-Wei Chiu
  Cc: Philipp Hahn, amd-gfx, apparmor, bpf, ceph-devel, cocci, dm-devel,
	dri-devel, gfs2, intel-gfx, intel-wired-lan, iommu, kvm,
	linux-arm-kernel, linux-block, linux-bluetooth, linux-btrfs,
	linux-cifs, linux-clk, linux-erofs, linux-ext4, linux-fsdevel,
	linux-gpio, linux-hyperv, linux-input, linux-kernel, linux-leds,
	linux-media, linux-mips, linux-mm, linux-modules, linux-mtd,
	linux-nfs, linux-omap, linux-phy, linux-pm, linux-rockchip,
	linux-s390, linux-scsi, linux-sctp, linux-security-module,
	linux-sh, linux-sound, linux-stm32, linux-trace-kernel, linux-usb,
	linux-wireless, netdev, ntfs3, samba-technical, sched-ext,
	target-devel, tipc-discussion, v9fs
In-Reply-To: <20260312125730.GI1469476@ziepe.ca>

On Thu, 2026-03-12 at 09:57 -0300, Jason Gunthorpe wrote:
> On Wed, Mar 11, 2026 at 02:40:36AM +0800, Kuan-Wei Chiu wrote:
> 
> > IMHO, the necessity of IS_ERR_OR_NULL() often highlights a
> > confusing or flawed API design. It usually implies that the caller
> > is unsure whether a failure results in an error pointer or a NULL
> > pointer. 
> 
> +1
> 
> IS_ERR_OR_NULL() should always be looked on with suspicion. Very
> little should be returning some tri-state 'ERR' 'NULL' 'SUCCESS'
> pointer. What does the middle condition even mean? IS_ERR_OR_NULL()
> implies ERR and NULL are semanticly the same, so fix the things to
> always use ERR.

Not in any way supporting the original patch.  However, the pattern
ERR, NULL, PTR is used extensively in the dentry code of filesystems. 
See the try_lookup..() set of functions in fs/namei.c

The meaning is

PTR - I found it
NULL - It definitely doesn't exist
ERR - something went wrong during the lookup.

So I don't think you can blanket say this pattern is wrong.

Regards,

James


^ permalink raw reply

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

On Mon Mar 9, 2026 at 4:20 PM CET, 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, stored in the struct
> ath12k_base, that is reused accros calls. The ath12k_wmi_tlv_parse_alloc()
> is also renamed into / merged with ath12k_wmi_tlv_parse() as it no longer
> allocs memory 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 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

And I just realized that this part did not make it to the actual commit
but stayed in the stage area. I'll add it in the v2

>   - used sizeof(*tb) in ath12k_wmi_tlv_parse()
>
> Note I did try to move to a DEFINE_PER_CPU() allocated array but the module
> loader complained about the array size. So I stuck to the per ab alloc. 

^ permalink raw reply

* Re: [PATCH ath-next] wifi: ath12k: avoid dynamic alloc when parsing wmi tb
From: Nicolas Escande @ 2026-03-12 15:26 UTC (permalink / raw)
  To: Baochen Qiang, Nicolas Escande, ath12k; +Cc: linux-wireless
In-Reply-To: <adcb0245-6514-42f7-a47d-f6d8b3f79dff@oss.qualcomm.com>

On Wed Mar 11, 2026 at 6:46 AM CET, Baochen Qiang wrote:
[...]
>> 
>> But if you guys don't want it that way, I can rework it. Just tell me in more
>> details what you think is the right way and I can modify it.
>
> then how about adding module init path to ath12k module and do percpu allocation there:
>
> in ath12k/core.c
>
> +void __percpu *wmi_tb;
> +
> +static int ath12k_core_module_init(void)
> +{
> +       wmi_tb  = __alloc_percpu(WMI_TAG_MAX * sizeof(void *),
> +                             __alignof__(void *));
> +       if (!wmi_tb)
> +               return -ENOMEM;
> +
> +       return 0;
> +}
> +
> +static void ath12k_core_module_exit(void)
> +{
> +       free_percpu(wmi_tb);
> +}
> +
> +module_init(ath12k_core_module_init);
> +module_exit(ath12k_core_module_exit);

That should work, I'll try something along those lines then.

>
>
>> 
>> Thanks

Thanks for the review Baochen

^ permalink raw reply

* Re: [syzbot ci] Re: iwlwifi + mac80211 stability
From: Ben Greear @ 2026-03-12 15:25 UTC (permalink / raw)
  To: syzbot ci, linux-wireless; +Cc: syzbot, syzkaller-bugs
In-Reply-To: <69b2ccdb.a00a0220.707e5.0016.GAE@google.com>

On 3/12/26 07:25, syzbot ci wrote:
> syzbot ci has tested the following series

Thank you syzbot.

The logs show this:

[   74.595871][   T64] wlan0: Created IBSS using preconfigured BSSID 50:50:50:50:50:50
[   74.604375][   T64] wlan0: Creating new IBSS network, BSSID 50:50:50:50:50:50
[   74.621865][ T1095] wlan1: Created IBSS using preconfigured BSSID 50:50:50:50:50:50
[   74.624268][ T1095] wlan1: Creating new IBSS network, BSSID 50:50:50:50:50:50
[   74.667157][ T5962] Failed to create local keys debugfs dir, rv: -13 phyd: 0xfffffffffffffff3
[   74.673187][ T5962] wlan2: Failed to create netdev dir, rv: -13 name: netdev:wlan2 wiphy dir: 0xfffffffffffffff3
[   74.885583][ T5553] ------------[ cut here ]------------


Which would be triggered by this from patch 0004, I guess.   The phyd
pointer appears to be an error code -13 instead of clean NULL, so I guess I
need to add checks for where that is created as well.

--- a/net/mac80211/debugfs.c
+++ b/net/mac80211/debugfs.c
@@ -680,6 +680,12 @@ void debugfs_hw_add(struct ieee80211_local *local)
  		return;

  	local->debugfs.keys = debugfs_create_dir("keys", phyd);
+	if (IS_ERR(local->debugfs.keys)) {
+		pr_err("Failed to create local keys debugfs dir, rv: %ld phyd: 0x%px\n",
+		       (long)(local->debugfs.keys), phyd);
+		local->debugfs.keys = NULL;
+		return;
+	}

Thanks,
Ben

-- 
Ben Greear <greearb@candelatech.com>
Candela Technologies Inc  http://www.candelatech.com



^ permalink raw reply

* [PATCH] carl9170: rx: gate data frame delivery on STARTED state
From: Masi Osmani @ 2026-03-12 15:16 UTC (permalink / raw)
  To: Christian Lamparter; +Cc: linux-wireless, Masi Osmani

Do not deliver data frames to mac80211 unless the device is fully
started.  After carl9170_op_stop() the driver state drops to IDLE,
but the USB RX path can still receive frames from the hardware.
Without this gate, ieee80211_rx() may reference station data that
sta_info_destroy_part2() is concurrently freeing during interface
teardown, causing a use-after-free kernel panic.

The race occurs when ieee80211_handle_reconfig_failure() clears
IN_DRIVER flags without stopping the hardware: cfg80211 then tears
down interfaces via ieee80211_do_stop() which calls sta_info_flush()
while the driver's RX path still delivers frames.  This was observed
when carl9170 firmware deadlocks during a restart attempt and
ieee80211_reconfig() fails at drv_add_interface().

The gate is placed in carl9170_rx_untie_data() just before the
ieee80211_rx() call, not in the USB layer, because firmware command
responses (including CARL9170_RSP_BOOT needed for firmware upload)
must still flow through the shared RX path via
carl9170_handle_command_response() which returns before reaching
this point.

Signed-off-by: Masi Osmani <mas-i@hotmail.de>
---
 drivers/net/wireless/ath/carl9170/rx.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/net/wireless/ath/carl9170/rx.c b/drivers/net/wireless/ath/carl9170/rx.c
--- a/drivers/net/wireless/ath/carl9170/rx.c
+++ b/drivers/net/wireless/ath/carl9170/rx.c
@@ -683,6 +683,14 @@

 	carl9170_ba_check(ar, buf, len);

+	/*
+	 * Do not deliver data frames to mac80211 unless the device is
+	 * fully started.  After carl9170_op_stop() the state drops to
+	 * IDLE, preventing a use-after-free when sta_info_destroy_part2()
+	 * races with ieee80211_rx() during interface teardown.
+	 */
+	if (!IS_STARTED(ar))
+		return 0;
 	skb = carl9170_rx_copy_data(buf, len);
 	if (!skb)
 		return -ENOMEM;

^ permalink raw reply

* [PATCH v3] wifi: ath9k: Obtain system GPIOS from descriptors
From: Linus Walleij @ 2026-03-12 15:09 UTC (permalink / raw)
  To: Kalle Valo, Andy Shevchenko, Arnd Bergmann, Alban Bedel,
	Bartosz Golaszewski, Toke Høiland-Jørgensen,
	Michał Kępień
  Cc: linux-wireless, brcm80211-dev-list.pdl, linux-gpio, Linus Walleij,
	Linus Walleij

The ath9k has an odd use of system-wide GPIOs: if the chip
does not have internal GPIO capability, it will try to obtain a
GPIO line from the system GPIO controller:

  if (BIT(gpio) & ah->caps.gpio_mask)
        ath9k_hw_gpio_cfg_wmac(...);
  else if (AR_SREV_SOC(ah))
        ath9k_hw_gpio_cfg_soc(ah, gpio, out, label);

Where ath9k_hw_gpio_cfg_soc() will attempt to issue
gpio_request_one() passing the local GPIO number of the controller
(0..31) to gpio_request_one().

This is somewhat peculiar and possibly even dangerous: there is
nowadays no guarantee of the numbering of these system-wide
GPIOs, and assuming that GPIO 0..31 as used by ath9k would
correspond to GPIOs 0..31 on the system as a whole seems a bit
wild.

Register all 32 GPIOs at index 0..31 directly in the ATH79K
GPIO driver and associate with the NULL device (making them
widely available) if and only if we are probing ATH79K wifi
from the AHB bus (used for SoCs). We obtain these offsets from
the NULL device if necessary.

These GPIOs should ideally be defined in the device tree
instead, but we have no control over that for the legacy
code path.

Testcompiled with the ath79 defconfig.

Reported-by: Michał Kępień <kernel@kempniu.pl>
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
This patch set is a long standing attempt to get rid of the global
GPIO numbers from the ath9k Wireless driver.

Maybe Kalle can merge this to the Wireless tree if we agree on this
hack solution.

Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
---
Changes in v3:
- Rebased on kernel v7.0-rc1
- Fix up issues as pointed out by Michał Kępień
- Link to v2: https://lore.kernel.org/r/20240423-descriptors-wireless-v2-1-6d1d03b30bfa@linaro.org

Changes in v2:
- Define all the descriptors directly in the ATH79K
  GPIO driver in case the driver want to request them directly.
- Link to v1: https://lore.kernel.org/r/20240131-descriptors-wireless-v1-0-e1c7c5d68746@linaro.org
---
 drivers/gpio/gpio-ath79.c           | 57 ++++++++++++++++++++++++++++++++++++-
 drivers/net/wireless/ath/ath9k/hw.c | 36 +++++++++++++++--------
 drivers/net/wireless/ath/ath9k/hw.h |  3 +-
 3 files changed, 82 insertions(+), 14 deletions(-)

diff --git a/drivers/gpio/gpio-ath79.c b/drivers/gpio/gpio-ath79.c
index 2ad9f6ac6636..00d8e877d1d5 100644
--- a/drivers/gpio/gpio-ath79.c
+++ b/drivers/gpio/gpio-ath79.c
@@ -11,6 +11,7 @@
 #include <linux/device.h>
 #include <linux/gpio/driver.h>
 #include <linux/gpio/generic.h>
+#include <linux/gpio/machine.h> /* For WLAN GPIOs */
 #include <linux/interrupt.h>
 #include <linux/irq.h>
 #include <linux/mod_devicetable.h>
@@ -214,6 +215,56 @@ static const struct of_device_id ath79_gpio_of_match[] = {
 };
 MODULE_DEVICE_TABLE(of, ath79_gpio_of_match);
 
+#if IS_ENABLED(CONFIG_ATH9K_AHB)
+/*
+ * This registers all of the ath79k GPIOs as descriptors to be picked
+ * directly from the ATH79K wifi driver if the two are jitted together
+ * in the same SoC.
+ */
+#define ATH79K_WIFI_DESCS 32
+static int ath79_gpio_register_wifi_descriptors(struct device *dev,
+						const char *label)
+{
+	struct gpiod_lookup_table *lookup;
+	int i;
+
+	/* Create a gpiod lookup using gpiochip-local offsets + 1 for NULL */
+	lookup = devm_kzalloc(dev,
+			      struct_size(lookup, table, ATH79K_WIFI_DESCS + 1),
+			      GFP_KERNEL);
+
+	if (!lookup)
+		return -ENOMEM;
+
+	/*
+	 * Ugly system-wide lookup for the NULL device: we know this
+	 * is already NULL but explicitly assign it here for people to
+	 * know what is going on. (Yes this is an ugly legacy hack, live
+	 * with it.)
+	 */
+	lookup->dev_id = NULL;
+
+	for (i = 0; i < ATH79K_WIFI_DESCS; i++) {
+		lookup->table[i] = (struct gpiod_lookup)
+			/*
+			 * Set the HW offset on the chip and the lookup
+			 * index to the same value, so looking up index 0
+			 * will get HW offset 0, index 1 HW offset 1 etc.
+			 */
+			GPIO_LOOKUP_IDX(label, i, "ath9k", i, GPIO_ACTIVE_HIGH);
+	}
+
+	gpiod_add_lookup_table(lookup);
+
+	return 0;
+}
+#else
+static int ath79_gpio_register_wifi_descriptors(struct device *dev,
+						const char *label)
+{
+}
+#endif
+
 static int ath79_gpio_probe(struct platform_device *pdev)
 {
 	struct gpio_generic_chip_config config;
@@ -276,7 +327,11 @@ static int ath79_gpio_probe(struct platform_device *pdev)
 		girq->handler = handle_simple_irq;
 	}
 
-	return devm_gpiochip_add_data(dev, &ctrl->chip.gc, ctrl);
+	err = devm_gpiochip_add_data(dev, &ctrl->chip.gc, ctrl);
+	if (err)
+		return err;
+
+	return ath79_gpio_register_wifi_descriptors(dev, ctrl->chip.gc.label);
 }
 
 static struct platform_driver ath79_gpio_driver = {
diff --git a/drivers/net/wireless/ath/ath9k/hw.c b/drivers/net/wireless/ath/ath9k/hw.c
index a45351afcf6e..04b4f0347cd9 100644
--- a/drivers/net/wireless/ath/ath9k/hw.c
+++ b/drivers/net/wireless/ath/ath9k/hw.c
@@ -21,7 +21,7 @@
 #include <linux/time.h>
 #include <linux/bitops.h>
 #include <linux/etherdevice.h>
-#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
 #include <linux/unaligned.h>
 
 #include "hw.h"
@@ -2719,19 +2719,29 @@ static void ath9k_hw_gpio_cfg_output_mux(struct ath_hw *ah, u32 gpio, u32 type)
 static void ath9k_hw_gpio_cfg_soc(struct ath_hw *ah, u32 gpio, bool out,
 				  const char *label)
 {
+	enum gpiod_flags flags = out ? GPIOD_OUT_LOW : GPIOD_IN;
+	struct gpio_desc *gpiod;
 	int err;
 
-	if (ah->caps.gpio_requested & BIT(gpio))
+	if (ah->gpiods[gpio])
 		return;
 
-	err = devm_gpio_request_one(ah->dev, gpio, out ? GPIOF_OUT_INIT_LOW : GPIOF_IN, label);
-	if (err) {
+	/*
+	 * Obtains a system specific GPIO descriptor from another GPIO controller.
+	 * Ideally this should come from the device tree, this is a legacy code
+	 * path.
+	 */
+	gpiod = gpiod_get_index(NULL, "ath9k", gpio, flags);
+
+	if (IS_ERR(gpiod)) {
+		err = PTR_ERR(gpiod);
 		ath_err(ath9k_hw_common(ah), "request GPIO%d failed:%d\n",
 			gpio, err);
 		return;
 	}
 
-	ah->caps.gpio_requested |= BIT(gpio);
+	gpiod_set_consumer_name(gpiod, label);
+	ah->gpiods[gpio] = gpiod;
 }
 
 static void ath9k_hw_gpio_cfg_wmac(struct ath_hw *ah, u32 gpio, bool out,
@@ -2791,10 +2801,12 @@ void ath9k_hw_gpio_free(struct ath_hw *ah, u32 gpio)
 	if (!AR_SREV_SOC(ah))
 		return;
 
-	WARN_ON(gpio >= ah->caps.num_gpio_pins);
+	if (ah->gpiods[gpio]) {
+		gpiod_put(ah->gpiods[gpio]);
+		ah->gpiods[gpio] = NULL;
+	}
 
-	if (ah->caps.gpio_requested & BIT(gpio))
-		ah->caps.gpio_requested &= ~BIT(gpio);
+	WARN_ON(gpio >= ah->caps.num_gpio_pins);
 }
 EXPORT_SYMBOL(ath9k_hw_gpio_free);
 
@@ -2822,8 +2834,8 @@ u32 ath9k_hw_gpio_get(struct ath_hw *ah, u32 gpio)
 			val = REG_READ(ah, AR_GPIO_IN(ah)) & BIT(gpio);
 		else
 			val = MS_REG_READ(AR, gpio);
-	} else if (BIT(gpio) & ah->caps.gpio_requested) {
-		val = gpio_get_value(gpio) & BIT(gpio);
+	} else if (ah->gpiods[gpio]) {
+		val = gpiod_get_value(ah->gpiods[gpio]);
 	} else {
 		WARN_ON(1);
 	}
@@ -2846,8 +2858,8 @@ void ath9k_hw_set_gpio(struct ath_hw *ah, u32 gpio, u32 val)
 			AR7010_GPIO_OUT : AR_GPIO_IN_OUT(ah);
 
 		REG_RMW(ah, out_addr, val << gpio, BIT(gpio));
-	} else if (BIT(gpio) & ah->caps.gpio_requested) {
-		gpio_set_value(gpio, val);
+	} else if (ah->gpiods[gpio]) {
+		gpiod_set_value(ah->gpiods[gpio], val);
 	} else {
 		WARN_ON(1);
 	}
diff --git a/drivers/net/wireless/ath/ath9k/hw.h b/drivers/net/wireless/ath/ath9k/hw.h
index eaa07d6dbde0..d9d2f64c5570 100644
--- a/drivers/net/wireless/ath/ath9k/hw.h
+++ b/drivers/net/wireless/ath/ath9k/hw.h
@@ -19,6 +19,7 @@
 
 #include <linux/if_ether.h>
 #include <linux/delay.h>
+#include <linux/gpio/consumer.h>
 #include <linux/io.h>
 #include <linux/firmware.h>
 
@@ -302,7 +303,6 @@ struct ath9k_hw_capabilities {
 	u8 max_rxchains;
 	u8 num_gpio_pins;
 	u32 gpio_mask;
-	u32 gpio_requested;
 	u8 rx_hp_qdepth;
 	u8 rx_lp_qdepth;
 	u8 rx_status_len;
@@ -783,6 +783,7 @@ struct ath_hw {
 	struct ath9k_hw_capabilities caps;
 	struct ath9k_channel channels[ATH9K_NUM_CHANNELS];
 	struct ath9k_channel *curchan;
+	struct gpio_desc *gpiods[32];
 
 	union {
 		struct ar5416_eeprom_def def;

---
base-commit: 6de23f81a5e08be8fbf5e8d7e9febc72a5b5f27f
change-id: 20240122-descriptors-wireless-b8da95dcab35

Best regards,
-- 
Linus Walleij <linusw@kernel.org>


^ permalink raw reply related

* [PATCH] mac80211: stop hardware before clearing driver state on reconfig failure
From: Masi Osmani @ 2026-03-12 14:30 UTC (permalink / raw)
  To: Johannes Berg; +Cc: linux-wireless, Christian Lamparter, Masi Osmani

When ieee80211_handle_reconfig_failure() is called after a failed HW
reconfiguration, it clears IEEE80211_SDATA_IN_DRIVER flags on all
interfaces but does not stop the hardware. This creates a race window:
cfg80211_shutdown_all_interfaces() subsequently calls ieee80211_do_stop()
which runs sta_info_flush() to destroy stations, while the driver's RX
path may still be delivering frames that reference station data being
freed.

This race was observed with the carl9170 driver: when firmware
deadlocks during a restart attempt, ieee80211_reconfig() fails
at drv_add_interface(). The subsequent interface teardown triggers
sta_info_destroy_part2() while the USB RX tasklet still calls
ieee80211_rx_napi(), causing a use-after-free kernel panic.

The fix stops the hardware in ieee80211_handle_reconfig_failure() before
clearing IN_DRIVER state, ensuring no driver can deliver RX frames once
the teardown begins. The drv_stop() call is guarded by local->started
since some call sites reach this function after drv_start() has already
failed (where the hardware was never started).

Signed-off-by: Masi Osmani <mas-i@hotmail.de>
---
 net/mac80211/util.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/net/mac80211/util.c b/net/mac80211/util.c
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1614,6 +1614,18 @@ static void ieee80211_handle_reconfig_failure(struct ieee80211_local *local)

 	local->resuming = false;
 	local->suspended = false;
+
+	/*
+	 * Stop the hardware before clearing IN_DRIVER state. Without this,
+	 * cfg80211_shutdown_all_interfaces() tears down stations via
+	 * sta_info_flush() while the driver's RX path may still deliver
+	 * frames referencing station data being freed, causing use-after-free.
+	 * Guard with local->started since this function can be reached when
+	 * drv_start() itself failed (hardware never started).
+	 */
+	if (local->started)
+		drv_stop(local, false);
+
 	local->in_reconfig = false;
 	local->reconfig_failure = true;


^ permalink raw reply

* Re: [PATCH] wifi: rtl8xxxu: Enable AP mode for RTL8188EU
From: Georg Müller @ 2026-03-12 14:29 UTC (permalink / raw)
  To: Jes.Sorensen; +Cc: linux-wireless, linux-kernel
In-Reply-To: <20260312142155.2642993-1-georgmueller@gmx.net>

Sorry, I missed the link:

Am 12.03.26 um 15:21 schrieb Georg Müller:
> The max_macid_num is conservative. The old driver used 32 [1], some
> other sources said 64. I have not enough adapters to test any of the
> higher values. Given the usage of this chipset in nano dongles, I think
> the 16 might be high enough.
> 

[1] https://github.com/lwfinger/rtl8188eu/blob/f5d1c8df2e2d8b217ea0113bf2cf3e37df8cb716/include/sta_info.h#L28

Link for max_macid_num = 64:
https://lore.kernel.org/linux-wireless/27e83382-4c84-1841-c428-d1e5143ea20c@gmail.com/


^ permalink raw reply

* [syzbot ci] Re: iwlwifi + mac80211 stability
From: syzbot ci @ 2026-03-12 14:25 UTC (permalink / raw)
  To: greearb, linux-wireless; +Cc: syzbot, syzkaller-bugs
In-Reply-To: <20260311230730.163348-1-greearb@candelatech.com>

syzbot ci has tested the following series

[v1] iwlwifi + mac80211 stability
https://lore.kernel.org/all/20260311230730.163348-1-greearb@candelatech.com
* [PATCH wireless-next 01/28] wifi: iwlwifi: mld:  Check for NULL before lookup.
* [PATCH wireless-next 02/28] wifi: iwlwifi: mld: Fix un-set return value in error case.
* [PATCH wireless-next 03/28] wifi: iwlwifi: mld: Add check for null vif in stats callback.
* [PATCH wireless-next 04/28] wifi: mac80211:  Check debugfs creation return values.
* [PATCH wireless-next 05/28] wifi: mac80211: do not fail taking sta to lower state.
* [PATCH wireless-next 06/28] wifi: mac80211: Mark sta as uploaded if single transition succeeds.
* [PATCH wireless-next 07/28] wifi: mac80211:  Fix use-after-free of debugfs inodes.
* [PATCH wireless-next 08/28] wifi: mac80211: Debugfs safety checks.
* [PATCH wireless-next 09/28] wifi: mac80211: Use warn-on-once in drv_remove_chanctxt
* [PATCH wireless-next 10/28] wifi: mac80211: Ensure sta debugfs is not double-freed.
* [PATCH wireless-next 11/28] wifi: iwlwifi: mld: Fix stale reference in fw_id_to_link_sta
* [PATCH wireless-next 12/28] wifi: iwlwifi: mld:  Improve logging in error cases.
* [PATCH wireless-next 13/28] wifi: iwlwifi: mld: Remove warning about BAID.
* [PATCH wireless-next 14/28] wifi: mac80211: Add dmesg log regarding warn-on in drv-stop.
* [PATCH wireless-next 15/28] wifi: iwlwifi: mld: Fix use-after-free of bss_conf
* [PATCH wireless-next 16/28] wifi: iwlwifi: mld: Check for null in iwl_mld_wait_sta_txqs_empty
* [PATCH wireless-next 17/28] wifi: iwlwifi: mld: use warn-on-once in error path.
* [PATCH wireless-next 18/28] wifi: iwlwifi: mld: Use warn-on-once in emlsr exit logic.
* [PATCH wireless-next 19/28] wifi: iwlwifi: mld: Improve error message in rx path.
* [PATCH wireless-next 20/28] wifi: iwlwifi: mld: Improve logging message.
* [PATCH wireless-next 21/28] wifi: iwlwifi: mld: Protect from null mld_sta
* [PATCH wireless-next 22/28] wifi: mac80211: Add force-cleanup call to driver.
* [PATCH wireless-next 23/28] wifi: iwlwifi: mld: Support force-cleanup op
* [PATCH wireless-next 24/28] wifi: iwlwifi: mld: Fix NPE in flush logic.
* [PATCH wireless-next 25/28] wifi: iwlwifi: mld: Fix bad return address in tx code.
* [PATCH wireless-next 26/28] wifi: mac80211: Ensure link work-items are only initialized once.
* [PATCH wireless-next 27/28] wifi: iwlwifi: mld: Convert to WARN_ONCE in link removal path.
* [PATCH wireless-next 28/28] wifi: mac80211: Decrease WARN spam.

and found the following issue:
WARNING in drv_add_interface

Full report is available here:
https://ci.syzbot.org/series/d3986751-1907-410b-b80c-976f38583b8c

***

WARNING in drv_add_interface

tree:      linux-next
URL:       https://kernel.googlesource.com/pub/scm/linux/kernel/git/next/linux-next
base:      97492c019da4b62df83255e968b23b81c0315530
arch:      amd64
compiler:  Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8
config:    https://ci.syzbot.org/builds/acf234a5-5041-402a-ace9-5766b71cadb4/config
C repro:   https://ci.syzbot.org/findings/1533841d-c00d-4811-84c1-419f7bccc86a/c_repro
syz repro: https://ci.syzbot.org/findings/1533841d-c00d-4811-84c1-419f7bccc86a/syz_repro

------------[ cut here ]------------
!sdata->vif.debugfs_dir
WARNING: net/mac80211/driver-ops.h:510 at drv_vif_add_debugfs net/mac80211/driver-ops.h:510 [inline], CPU#1: dhcpcd/5553
WARNING: net/mac80211/driver-ops.h:510 at drv_add_interface+0x5e5/0x910 net/mac80211/driver-ops.c:84, CPU#1: dhcpcd/5553
Modules linked in:
CPU: 1 UID: 0 PID: 5553 Comm: dhcpcd Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
RIP: 0010:drv_vif_add_debugfs net/mac80211/driver-ops.h:510 [inline]
RIP: 0010:drv_add_interface+0x5e5/0x910 net/mac80211/driver-ops.c:84
Code: f3 fa ff ff e8 9c 22 ae f6 48 8d 3d 85 f2 0a 05 67 48 0f b9 3a e9 c1 fc ff ff e8 86 22 ae f6 e9 19 fb ff ff e8 7c 22 ae f6 90 <0f> 0b 90 eb 94 e8 71 22 ae f6 4c 8d 35 7a f2 0a 05 49 8d bf 28 0a
RSP: 0018:ffffc90003b57678 EFLAGS: 00010293
RAX: ffffffff8b1776f4 RBX: ffff888172594dc0 RCX: ffff8881165657c0
RDX: 0000000000000000 RSI: 0000000000000002 RDI: 0000000000000006
RBP: 0000000000000000 R08: ffffffff901146b7 R09: 1ffffffff20228d6
R10: dffffc0000000000 R11: fffffbfff20228d7 R12: dffffc0000000000
R13: ffff888172597028 R14: ffff8881725957f8 R15: 0000000000000002
FS:  00007ff45a6f6740(0000) GS:ffff8882a9465000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000055d00b0c3161 CR3: 000000010017c000 CR4: 00000000000006f0
Call Trace:
 <TASK>
 ieee80211_do_open+0x929/0x2490 net/mac80211/iface.c:1466
 ieee80211_open+0x15b/0x200 net/mac80211/iface.c:472
 __dev_open+0x44d/0x830 net/core/dev.c:1702
 __dev_change_flags+0x1f7/0x690 net/core/dev.c:9778
 netif_change_flags+0x88/0x1a0 net/core/dev.c:9841
 dev_change_flags+0x130/0x260 net/core/dev_api.c:68
 devinet_ioctl+0x9f2/0x1b30 net/ipv4/devinet.c:1199
 inet_ioctl+0x42a/0x560 net/ipv4/af_inet.c:1004
 sock_do_ioctl+0x101/0x320 net/socket.c:1253
 sock_ioctl+0x5c6/0x7f0 net/socket.c:1374
 vfs_ioctl fs/ioctl.c:51 [inline]
 __do_sys_ioctl fs/ioctl.c:597 [inline]
 __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7ff45a7c4d49
Code: 5c c3 48 8d 44 24 08 48 89 54 24 e0 48 89 44 24 c0 48 8d 44 24 d0 48 89 44 24 c8 b8 10 00 00 00 c7 44 24 b8 10 00 00 00 0f 05 <41> 89 c0 3d 00 f0 ff ff 76 10 48 8b 15 ae 60 0d 00 f7 d8 41 83 c8
RSP: 002b:00007ffff8603cd8 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007ff45a6f66c0 RCX: 00007ff45a7c4d49
RDX: 00007ffff8613ec8 RSI: 0000000000008914 RDI: 0000000000000011
RBP: 00007ffff8624088 R08: 00007ffff8613e88 R09: 00007ffff8613e38
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007ffff8613ec8 R14: 0000000000000028 R15: 0000000000008914
 </TASK>


***

If these findings have caused you to resend the series or submit a
separate fix, please add the following tag to your commit message:
  Tested-by: syzbot@syzkaller.appspotmail.com

---
This report is generated by a bot. It may contain errors.
syzbot ci engineers can be reached at syzkaller@googlegroups.com.

^ permalink raw reply

* [PATCH] wifi: rtl8xxxu: Enable AP mode for RTL8188EU
From: Georg Müller @ 2026-03-12 14:21 UTC (permalink / raw)
  To: Jes.Sorensen; +Cc: linux-wireless, linux-kernel, Georg Müller

Allow devices with this driver to be used as a wireless access point.

I successfully tested this with a TP-Link TP-Link TL-WN725N adapter.

Experiments two years ago failed, but some other improvements to the
driver seemed to have resolved theses issues.

The max_macid_num is conservative. The old driver used 32 [1], some
other sources said 64. I have not enough adapters to test any of the
higher values. Given the usage of this chipset in nano dongles, I think
the 16 might be high enough.

Signed-off-by: Georg Müller <georgmueller@gmx.net>
---
 drivers/net/wireless/realtek/rtl8xxxu/8188e.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/net/wireless/realtek/rtl8xxxu/8188e.c b/drivers/net/wireless/realtek/rtl8xxxu/8188e.c
index 766a7a7c7d28..c6aecb28d28b 100644
--- a/drivers/net/wireless/realtek/rtl8xxxu/8188e.c
+++ b/drivers/net/wireless/realtek/rtl8xxxu/8188e.c
@@ -1867,6 +1867,8 @@ struct rtl8xxxu_fileops rtl8188eu_fops = {
 	.init_reg_pkt_life_time = 1,
 	.gen2_thermal_meter = 1,
 	.max_sec_cam_num = 32,
+	.supports_ap = 1,
+	.max_macid_num = 16,
 	.adda_1t_init = 0x0b1b25a0,
 	.adda_1t_path_on = 0x0bdb25a0,
 	/*
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH 05/10 net-next v2] drivers: net: drop ipv6_stub usage and use direct function calls
From: Jason A. Donenfeld @ 2026-03-12 13:48 UTC (permalink / raw)
  To: Fernando Fernandez Mancera
  Cc: netdev, rbm, Jason Gunthorpe, Leon Romanovsky, Zhu Yanjun,
	Saeed Mahameed, Tariq Toukan, Mark Bloch, Andrew Lunn,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Boris Pismenny, Ido Schimmel, Petr Machata, Simon Horman,
	Edward Cree, Pablo Neira Ayuso, Harald Welte, Antonio Quartulli,
	Sabrina Dubroca, Oliver Neukum, David Ahern, Stanislav Yakovlev,
	Nikolay Aleksandrov, Parav Pandit, Edward Srouji, Vlad Dumitrescu,
	Kees Cook, Jianbo Liu, Gal Pressman, Guillaume Nault,
	Cosmin Ratiu, Carolina Jubran, Alexandre Cassen,
	Stanislav Fomichev, open list:INFINIBAND SUBSYSTEM, open list,
	open list:NETRONOME ETHERNET DRIVERS,
	open list:SFC NETWORK DRIVER,
	open list:GTP (GPRS Tunneling Protocol),
	open list:USB CDC ETHERNET DRIVER,
	open list:WIREGUARD SECURE NETWORK TUNNEL,
	open list:INTEL PRO/WIRELESS 2100, 2200BG, 2915ABG NETWOR...,
	open list:ETHERNET BRIDGE
In-Reply-To: <20260310153506.5181-6-fmancera@suse.de>

On Tue, Mar 10, 2026 at 04:34:28PM +0100, Fernando Fernandez Mancera wrote:
> diff --git a/drivers/net/wireguard/socket.c b/drivers/net/wireguard/socket.c
> index 253488f8c00f..c362c78d908e 100644
> --- a/drivers/net/wireguard/socket.c
> +++ b/drivers/net/wireguard/socket.c
> @@ -136,8 +136,7 @@ static int send6(struct wg_device *wg, struct sk_buff *skb,
>  			if (cache)
>  				dst_cache_reset(cache);
>  		}
> -		dst = ipv6_stub->ipv6_dst_lookup_flow(sock_net(sock), sock, &fl,
> -						      NULL);
> +		dst = ip6_dst_lookup_flow(sock_net(sock), sock, &fl, NULL);
>  		if (IS_ERR(dst)) {
>  			ret = PTR_ERR(dst);
>  			net_dbg_ratelimited("%s: No route to %pISpfsc, error %d\n",

Rest in peace, stub.

For the WireGuard part,

    Reviewed-by: Jason A. Donenfeld <Jason@zx2c4.com>

^ permalink raw reply

* Re: ath12k: handling of HE and EHT capabilities
From: Alexander Wilhelm @ 2026-03-12 13:00 UTC (permalink / raw)
  To: Johannes Berg; +Cc: Jeff Johnson, ath12k, linux-wireless, linux-kernel
In-Reply-To: <e8960517faf04ed4f1bf331e23a95c477113309f.camel@sipsolutions.net>

On Thu, Mar 12, 2026 at 01:10:21PM +0100, Johannes Berg wrote:
> Wait ...
> 
> > > I don’t see this in the function. For example, the MAC capabilities are a
> > > `u16 *` in CPU endianness, which is simply memcpy’d from the parsed
> > > `NL80211_BAND_IFTYPE_ATTR_HE_CAP_MAC`. Later, they are treated as `u16 *`,
> > > as shown in the following code:
> > > 
> > >     printf("%s\t\tHE MAC Capabilities (0x", pre);
> > >     for (i = 0; i < 3; i++)
> > >         printf("%04x", mac_cap[i]);
> > >     printf("):\n");
> 
> That's incorrect for sure. But iw code now actually reads
> 
>         printf("%s\t\tHE MAC Capabilities (0x", pre);
>         for (i = 0; i < 3; i++)
>                 printf("%04x", le16toh(mac_cap[i]));
>         printf("):\n");
> 
> 
> which is correct. HE PHY capabilities are printed as
> 
>         printf("%s\t\tHE PHY Capabilities: (0x", pre);
>         for (i = 0; i < 11; i++)
>                 printf("%02x", ((__u8 *)phy_cap)[i + 1]);
> 
> in my version of the code, and it seems to me the +1 is incorrect either
> way?
> 
> >         printf("%s\t\tEHT MAC Capabilities (0x", pre);
> >         for (i = 0; i < 2; i++)
> >                 printf("%02x", mac_cap[i]);
> 
> This was also correct, not incorrect as I stated, since mac_cap is u8 *,
> and EHT PHY capabilities are cast to u8 * first.
> 
> Maybe your iw is just really old?

Sorry, my fault. I'm using `OpenWrt v24.10.5` with `iw` version 6.9. The
latest master has the `le16toh` implemented. With my `ath12k` fix the PHY
capabilities and the respecitve descriptions are fine now. But I still
cannot get MAC capabilities correct. I'll analyze it further.


Best regards
Alexander wilhelm

^ permalink raw reply

* Re: [PATCH 00/61] treewide: Use IS_ERR_OR_NULL over manual NULL check - refactor
From: Jason Gunthorpe @ 2026-03-12 12:57 UTC (permalink / raw)
  To: Kuan-Wei Chiu
  Cc: Philipp Hahn, amd-gfx, apparmor, bpf, ceph-devel, cocci, dm-devel,
	dri-devel, gfs2, intel-gfx, intel-wired-lan, iommu, kvm,
	linux-arm-kernel, linux-block, linux-bluetooth, linux-btrfs,
	linux-cifs, linux-clk, linux-erofs, linux-ext4, linux-fsdevel,
	linux-gpio, linux-hyperv, linux-input, linux-kernel, linux-leds,
	linux-media, linux-mips, linux-mm, linux-modules, linux-mtd,
	linux-nfs, linux-omap, linux-phy, linux-pm, linux-rockchip,
	linux-s390, linux-scsi, linux-sctp, linux-security-module,
	linux-sh, linux-sound, linux-stm32, linux-trace-kernel, linux-usb,
	linux-wireless, netdev, ntfs3, samba-technical, sched-ext,
	target-devel, tipc-discussion, v9fs
In-Reply-To: <abBlpGKO842B3yl9@google.com>

On Wed, Mar 11, 2026 at 02:40:36AM +0800, Kuan-Wei Chiu wrote:

> IMHO, the necessity of IS_ERR_OR_NULL() often highlights a confusing or
> flawed API design. It usually implies that the caller is unsure whether
> a failure results in an error pointer or a NULL pointer. 

+1

IS_ERR_OR_NULL() should always be looked on with suspicion. Very
little should be returning some tri-state 'ERR' 'NULL' 'SUCCESS'
pointer. What does the middle condition even mean? IS_ERR_OR_NULL()
implies ERR and NULL are semanticly the same, so fix the things to
always use ERR.

If you want to improve things work to get rid of the NULL checks this
script identifies. Remove ERR or NULL because only one can ever
happen, or fix the source to consistently return ERR.

Jason

^ permalink raw reply

* Re: ath12k: handling of HE and EHT capabilities
From: Johannes Berg @ 2026-03-12 12:10 UTC (permalink / raw)
  To: Alexander Wilhelm; +Cc: Jeff Johnson, ath12k, linux-wireless, linux-kernel
In-Reply-To: <40b4b959b2ea5afd55381e6ae2d0c1908456734c.camel@sipsolutions.net>

Wait ...

> > I don’t see this in the function. For example, the MAC capabilities are a
> > `u16 *` in CPU endianness, which is simply memcpy’d from the parsed
> > `NL80211_BAND_IFTYPE_ATTR_HE_CAP_MAC`. Later, they are treated as `u16 *`,
> > as shown in the following code:
> > 
> >     printf("%s\t\tHE MAC Capabilities (0x", pre);
> >     for (i = 0; i < 3; i++)
> >         printf("%04x", mac_cap[i]);
> >     printf("):\n");

That's incorrect for sure. But iw code now actually reads

        printf("%s\t\tHE MAC Capabilities (0x", pre);
        for (i = 0; i < 3; i++)
                printf("%04x", le16toh(mac_cap[i]));
        printf("):\n");


which is correct. HE PHY capabilities are printed as

        printf("%s\t\tHE PHY Capabilities: (0x", pre);
        for (i = 0; i < 11; i++)
                printf("%02x", ((__u8 *)phy_cap)[i + 1]);

in my version of the code, and it seems to me the +1 is incorrect either
way?

>         printf("%s\t\tEHT MAC Capabilities (0x", pre);
>         for (i = 0; i < 2; i++)
>                 printf("%02x", mac_cap[i]);

This was also correct, not incorrect as I stated, since mac_cap is u8 *,
and EHT PHY capabilities are cast to u8 * first.

Maybe your iw is just really old?

johannes

^ permalink raw reply

* Re: ath12k: handling of HE and EHT capabilities
From: Alexander Wilhelm @ 2026-03-12 10:53 UTC (permalink / raw)
  To: Johannes Berg; +Cc: Jeff Johnson, ath12k, linux-wireless, linux-kernel
In-Reply-To: <b7f4c8f1a251ea9cccb32f021828896371953143.camel@sipsolutions.net>

On Thu, Mar 12, 2026 at 10:37:46AM +0100, Johannes Berg wrote:
> Hi,
> > For example, I use the `iw` tool to display the capabilities and their
> > descriptions. The code for that has the following function prototypes:
> > 
> >     * void print_ht_capability(__u16 cap);
> >     * void print_vht_info(__u32 capa, const __u8 *mcs);
> >     * static void __print_he_capa(const __u16 *mac_cap,
> >                                   const __u16 *phy_cap,
> >                                   const __u16 *mcs_set, size_t mcs_len,
> >                                   const __u8 *ppet, int ppet_len,
> >                                   bool indent);
> >     * static void __print_eht_capa(int band,
> >                                    const __u8 *mac_cap,
> >                                    const __u32 *phy_cap,
> >                                    const __u8 *mcs_set, size_t mcs_len,
> >                                    const __u8 *ppet, size_t ppet_len,
> >                                    const __u16 *he_phy_cap,
> >                                    bool indent);
> 
> This is perhaps a bit unfortunate, but note that the HE and EHT __u16
> and __u32 here are really little endian pointers, and the functions do
> byte-order conversion.

I don’t see this in the function. For example, the MAC capabilities are a
`u16 *` in CPU endianness, which is simply memcpy’d from the parsed
`NL80211_BAND_IFTYPE_ATTR_HE_CAP_MAC`. Later, they are treated as `u16 *`,
as shown in the following code:

    printf("%s\t\tHE MAC Capabilities (0x", pre);
    for (i = 0; i < 3; i++)
        printf("%04x", mac_cap[i]);
    printf("):\n");

Here is the result on little‑ vs. big‑endian platforms:

    Little endian:
    HE MAC Capabilities (0x081a010d030f):

    Big endian:
    HE MAC Capabilities (0x0b00189a4010):

For the PHY capabilities, they are also a `u16 *`, but they are treated as
a `u8 *`. However, later in the description printing, `PRINT_HE_CAP` does
not take endianness into account. This prints the correct hex values, but
the interpretation/description is wrong:

    Little endian:
    HE PHY Capabilities: (0x1c604c89ffdb839c110c00):
        HE40/HE80/5GHz
        HE160/5GHz
        HE160/HE80+80/5GHz
        LDPC Coding in Payload
        HE SU PPDU with 1x HE-LTF and 0.8us GI
        STBC Tx <= 80MHz
        STBC Rx <= 80MHz
        Full Bandwidth UL MU-MIMO
        DCM Max Constellation: 1
        DCM Max Constellation Rx: 1
        SU Beamformer
        SU Beamformee
        MU Beamformer
        Beamformee STS <= 80Mhz: 7
        Beamformee STS > 80Mhz: 7
        Sounding Dimensions <= 80Mhz: 3
        Sounding Dimensions > 80Mhz: 3
        Ng = 16 SU Feedback
        Ng = 16 MU Feedback
        Codebook Size SU Feedback
        Codebook Size MU Feedback
        PPE Threshold Present
        HE SU PPDU & HE PPDU 4x HE-LTF 0.8us GI
        Max NC: 3
        STBC Rx > 80MHz
        HE ER SU PPDU 4x HE-LTF 0.8us GI
        HE ER SU PPDU 1x HE-LTF 0.8us GI
        TX 1024-QAM
        RX 1024-QAM

    Big endian:
    HE PHY Capabilities: (0x1c604c89ffda839c110c00):
        Punctured Preamble RX: 12
        HE SU PPDU with 1x HE-LTF and 0.8us GI
        Doppler Rx
        Full Bandwidth UL MU-MIMO
        DCM Max Constellation: 3
        DCM Max NSS Tx: 1
        DCM Max Constellation Rx: 3
        DCM Max NSS Rx: 1
        Rx HE MU PPDU from Non-AP STA
        SU Beamformer
        SU Beamformee
        Beamformee STS <= 80Mhz: 2
        Beamformee STS > 80Mhz: 4
        Sounding Dimensions <= 80Mhz: 3
        Ng = 16 MU Feedback
        Codebook Size MU Feedback
        Triggered MU Beamforming Feedback
        Triggered CQI Feedback
        Partial Bandwidth DL MU-MIMO
        PPE Threshold Present
        SRP-based SR
        Max NC: 2
        20MHz in 160/80+80MHz HE PPDU
        80MHz in 160/80+80MHz HE PPDU
        HE ER SU PPDU 1x HE-LTF 0.8us GI
        DCM Max BW: 2

> >     struct ieee80211_sta_ht_cap {
> >         u16 cap; /* use IEEE80211_HT_CAP_ */
> >         bool ht_supported;
> >         u8 ampdu_factor;
> >         u8 ampdu_density;
> >         struct ieee80211_mcs_info mcs;
> >     };
> > 
> >     struct ieee80211_sta_vht_cap {
> >         bool vht_supported;
> >         u32 cap; /* use IEEE80211_VHT_CAP_ */
> >         struct ieee80211_vht_mcs_info vht_mcs;
> >     };
> > 
> > The structs for HT and VHT use `u16` and `u32` data types for the `cap`
> > variable, matching what `iw` does. That part is consistent.
> 
> Careful. There are different structs used in different places, notably
> HT/VHT and HE/EHT differ.
> 
> For HT and VHT, look at the start of nl80211_send_band_rateinfo(), which
> sends themas individual attributes, defined in enum nl80211_band_attr,
> and the values that are u16 (NL80211_BAND_ATTR_HT_CAPA) or u32
> (NL80211_BAND_ATTR_VHT_CAPA) are in host byte order, though both are
> actually documented misleadingly ("as in [V]HT information IE" is just
> all around wrong.)
> 
> For HE/EHT, you have it in nl80211_send_iftype_data() since it's per
> interface type, and all the individual values are just as they appear in
> the spec, regardless of their size.

Okay, I think I understand so far. I had also initially wondered why HE/EHT
capabilities were treated separately from the HT/VHT capabilities.

> Note that spec is generally in little endian, but sometimes has strange
> field lengths like MAC capabilities being 6 bytes in HE:
> 
> >     struct ieee80211_he_cap_elem {
> >         u8 mac_cap_info[6];
> >         u8 phy_cap_info[11];
> >     } __packed;
> > 
> >     struct ieee80211_he_6ghz_capa {
> >         /* uses IEEE80211_HE_6GHZ_CAP_* below */
> >         __le16 capa; }
> >     __packed;
> > 
> > However, for HE the types differ from the `iw` implementation. Here, `u8`
> > arrays are used instead of `u16` for MAC and PHY capabilities. The 6 GHz
> > capabilities use `u16`, which is also different.
> 
> That doesn't really matter, they're just a set of 6 or 11 bytes, and
> e.g. the HE MAC capabilities are treated by the kernel as a set of 6
> bytes, but by iw as a set of 3 __le16, which results in the same
> interpretation, or at least should.

Sure. I agree with you, it makes no difference whether I use `u8[6]` or
`__le16[3]`, as long as I use `memcpy` and don’t perform any CPU‑endian
swapping at this point.

> >     struct ieee80211_eht_cap_elem_fixed {
> >         u8 mac_cap_info[2];
> >         u8 phy_cap_info[9];
> >     } __packed;
> > 
> > For EHT, `u8` arrays are also used for both MAC and PHY caps, instead of
> > `u32` for the PHY caps as in the `iw` implementation.
> 
> Same thing here.
> 
> > The current `ath12k` implementation always uses `u32` values, which does
> > not work on big‑endian platforms:
> 
> Yeah, that seems problematic and not really fitting for something that's
> 6, 11, 2 or 9 bytes long?
> 
> > I want to address and fix this issue. However, I cannot apply the “never
> > break the userspace” rule here, as it seems, it is already broken.
> 
> I don't think it's broken, why do you say so?

Well, if `ath12k` uses `u32` in CPU‑native order, that’s a bug, and I can’t
get `ieee80211_hw` registered. If I use `__le32` in little-endian order
instead, I end up with incorrect capabilities and mismatched descriptions
shown by the `iw` tool (but I can get the driver running). So neither
approach seems to be a 100% solution at first glance. Did I misinterpret
the rule?

> What's (clearly) broken is how ath12k puts the data into the HE/EHT
> structs that the kernel expects, but per your dmesg:
> 
> >     ath12k_pci 0001:01:00.0: ieee80211 registration failed: -22
> >     ath12k_pci 0001:01:00.0: failed register the radio with mac80211: -22
> 
> it seems that even mac80211 doesn't like the capabilities, so the byte
> order issue already exists there.
> 
> It seems to me the issue is that ath12k_band_cap is in u32, converted,
> but then memcpy()d.

The `ath12k` driver uses `u32` arrays in CPU‑native order for this, so the
swap is effectively happening. Later, in `ieee80211_register_hw`, the
values are compared at the bit level, and that’s where it fails. I
understand that technically `__le32` could be used in `ath12k`, meaning no
swap, but since `u8` arrays are used in `cfg80211`, that might actually be
the better approach. I just wanted to provide a solution that is clean and
acceptable. The `iw` tool still needs to be updated accordingly.


Best regards
Alexander Wilhelm

^ permalink raw reply

* Re: ath12k: handling of HE and EHT capabilities
From: Johannes Berg @ 2026-03-12 11:05 UTC (permalink / raw)
  To: Alexander Wilhelm; +Cc: Jeff Johnson, ath12k, linux-wireless, linux-kernel
In-Reply-To: <abKbLNK0YrT6dr96@FUE-ALEWI-WINX>

On Thu, 2026-03-12 at 11:53 +0100, Alexander Wilhelm wrote:
> On Thu, Mar 12, 2026 at 10:37:46AM +0100, Johannes Berg wrote:
> > Hi,
> > > For example, I use the `iw` tool to display the capabilities and their
> > > descriptions. The code for that has the following function prototypes:
> > > 
> > >     * void print_ht_capability(__u16 cap);
> > >     * void print_vht_info(__u32 capa, const __u8 *mcs);
> > >     * static void __print_he_capa(const __u16 *mac_cap,
> > >                                   const __u16 *phy_cap,
> > >                                   const __u16 *mcs_set, size_t mcs_len,
> > >                                   const __u8 *ppet, int ppet_len,
> > >                                   bool indent);
> > >     * static void __print_eht_capa(int band,
> > >                                    const __u8 *mac_cap,
> > >                                    const __u32 *phy_cap,
> > >                                    const __u8 *mcs_set, size_t mcs_len,
> > >                                    const __u8 *ppet, size_t ppet_len,
> > >                                    const __u16 *he_phy_cap,
> > >                                    bool indent);
> > 
> > This is perhaps a bit unfortunate, but note that the HE and EHT __u16
> > and __u32 here are really little endian pointers, and the functions do
> > byte-order conversion.
> 
> I don’t see this in the function. For example, the MAC capabilities are a
> `u16 *` in CPU endianness, which is simply memcpy’d from the parsed
> `NL80211_BAND_IFTYPE_ATTR_HE_CAP_MAC`. Later, they are treated as `u16 *`,
> as shown in the following code:
> 
>     printf("%s\t\tHE MAC Capabilities (0x", pre);
>     for (i = 0; i < 3; i++)
>         printf("%04x", mac_cap[i]);
>     printf("):\n");
> 
> Here is the result on little‑ vs. big‑endian platforms:
> 
>     Little endian:
>     HE MAC Capabilities (0x081a010d030f):
> 
>     Big endian:
>     HE MAC Capabilities (0x0b00189a4010):

Oh, OK, so _that_ print is definitely wrong in iw. But the individual
prints are converted:

        #define PRINT_HE_CAP(_var, _idx, _bit, _str) \
        do { \
                if (le16toh(_var[_idx]) & BIT(_bit)) \
                        printf("%s\t\t\t" _str "\n", pre); \
        } while (0)

> For the PHY capabilities, they are also a `u16 *`, but they are treated as
> a `u8 *`. However, later in the description printing, `PRINT_HE_CAP` does
> not take endianness into account.

PRINT_HE_CAP *does* convert, there's le16toh() there. Same in
PRINT_HE_CAP_MASK.

It should convert, because it's from a u8[6] kernel API that just
carries the values as they are in the spec.

> > > I want to address and fix this issue. However, I cannot apply the “never
> > > break the userspace” rule here, as it seems, it is already broken.
> > 
> > I don't think it's broken, why do you say so?

Well, I see now that I missed the

        printf("%s\t\tHE MAC Capabilities (0x", pre);
        for (i = 0; i < 3; i++)
                printf("%04x", le16toh(mac_cap[i]));

and

        printf("%s\t\tEHT MAC Capabilities (0x", pre);
        for (i = 0; i < 2; i++)
                printf("%02x", mac_cap[i]);

parts, those are definitely broken in iw on big endian platforms. We
should fix those in iw. The actual individual prints seem fine though.

> Well, if `ath12k` uses `u32` in CPU‑native order, that’s a bug, and I can’t
> get `ieee80211_hw` registered. If I use `__le32` in little-endian order
> instead, I end up with incorrect capabilities and mismatched descriptions
> shown by the `iw` tool (but I can get the driver running). So neither
> approach seems to be a 100% solution at first glance. Did I misinterpret
> the rule?

The *descriptions* should be fine I think? Just the first line with the
hex would be messed up.

> > What's (clearly) broken is how ath12k puts the data into the HE/EHT
> > structs that the kernel expects, but per your dmesg:
> > 
> > >     ath12k_pci 0001:01:00.0: ieee80211 registration failed: -22
> > >     ath12k_pci 0001:01:00.0: failed register the radio with mac80211: -22
> > 
> > it seems that even mac80211 doesn't like the capabilities, so the byte
> > order issue already exists there.
> > 
> > It seems to me the issue is that ath12k_band_cap is in u32, converted,
> > but then memcpy()d.
> 
> The `ath12k` driver uses `u32` arrays in CPU‑native order for this, so the
> swap is effectively happening.

Yeah but the swap is wrong, since HE/EHT capabilities are just byte
arrays in spec byte order in cfg80211/nl80211.

>  Later, in `ieee80211_register_hw`, the
> values are compared at the bit level, and that’s where it fails. I
> understand that technically `__le32` could be used in `ath12k`, meaning no
> swap, but since `u8` arrays are used in `cfg80211`, that might actually be
> the better approach.

Sure, could use u8 in ath12k too, dunno, up to the maintainer. At least
if it was __le32 you could still memcpy() from it since no swap
happened, and wouldn't change the code structure that much.

johannes

^ permalink raw reply

* Re: [PATCH mt76 6/6] wifi: mt76: mt7996: fix issues with manually triggered radar detection
From: Lorenzo Bianconi @ 2026-03-12 10:56 UTC (permalink / raw)
  To: Shayne Chen
  Cc: Felix Fietkau, linux-wireless, Ryder Lee, Evelyn Tsai, Money Wang,
	linux-mediatek, StanleyYP Wang
In-Reply-To: <20260312095724.2117448-6-shayne.chen@mediatek.com>

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

> From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
> 
> Disallow triggering radar detection on non-DFS channels to prevent paused
> TX queues from failing to resume, as a channel switch is not performed in
> this case.

I guess we are missing a Fixes tag here.

> 
> Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
> Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
> ---
>  .../wireless/mediatek/mt76/mt7996/debugfs.c   | 22 +++++++++++++++----
>  1 file changed, 18 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c
> index 76d623b2cafb..e26bed6b97e7 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c
> @@ -226,14 +226,23 @@ mt7996_radar_trigger(void *data, u64 val)
>  #define RADAR_BACKGROUND	2
>  	struct mt7996_dev *dev = data;
>  	struct mt7996_phy *phy = mt7996_band_phy(dev, NL80211_BAND_5GHZ);
> -	int rdd_idx;
> +	struct cfg80211_chan_def *chandef;
> +	int rdd_idx, ret;
>  
>  	if (!phy || !val || val > RADAR_BACKGROUND)
>  		return -EINVAL;
>  
> -	if (val == RADAR_BACKGROUND && !dev->rdd2_phy) {
> -		dev_err(dev->mt76.dev, "Background radar is not enabled\n");
> -		return -EINVAL;
> +	if (test_bit(MT76_SCANNING, &phy->mt76->state))
> +		return -EBUSY;
> +
> +	if (val == RADAR_BACKGROUND) {
> +		if (!dev->rdd2_phy || !cfg80211_chandef_valid(&dev->rdd2_chandef)) {
> +			dev_err(dev->mt76.dev, "Background radar is not enabled\n");

nit: I guess it is better to specify a different error message if rdd2_chandef
is  invalid.

Regards,
Lorenzo

> +			return -EINVAL;
> +		}
> +		chandef = &dev->rdd2_chandef;
> +	} else {
> +		chandef = &phy->mt76->chandef;
>  	}
>  
>  	rdd_idx = mt7996_get_rdd_idx(phy, val == RADAR_BACKGROUND);
> @@ -242,6 +251,11 @@ mt7996_radar_trigger(void *data, u64 val)
>  		return -EINVAL;
>  	}
>  
> +	ret = cfg80211_chandef_dfs_required(dev->mt76.hw->wiphy, chandef,
> +					    NL80211_IFTYPE_AP);
> +	if (ret <= 0)
> +		return ret;
> +
>  	return mt7996_mcu_rdd_cmd(dev, RDD_RADAR_EMULATE, rdd_idx, 0);
>  }
>  
> -- 
> 2.51.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* Re: [PATCH mt76 5/6] wifi: mt76: mt7996: fix the temporary buffer for calibration-free data
From: Lorenzo Bianconi @ 2026-03-12 10:49 UTC (permalink / raw)
  To: Shayne Chen
  Cc: Felix Fietkau, linux-wireless, Ryder Lee, Evelyn Tsai, Money Wang,
	linux-mediatek, StanleyYP Wang
In-Reply-To: <20260312095724.2117448-5-shayne.chen@mediatek.com>

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

> From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
> 
> Move the declaration of buf[] outside the for loop.
> 
> Fixes: 224c7c2be578 ("wifi: mt76: mt7996: apply calibration-free data from OTP")
> Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
> Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
> ---
>  drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> index 2a9c59d15894..4c733f51a03e 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> @@ -4104,6 +4104,7 @@ mt7996_mcu_get_cal_free_data(struct mt7996_dev *dev)
>  	}
>  
>  	for (band = 0; band < __MT_MAX_BAND; band++) {
> +		u8 buf[MT7996_EEPROM_BLOCK_SIZE];

why are you moving buf here? It is only used in the inner block.

>  		const struct cal_free_data *cal;
>  		u16 prev_block_idx = -1;
>  		u16 adie_base;
> @@ -4126,13 +4127,13 @@ mt7996_mcu_get_cal_free_data(struct mt7996_dev *dev)
>  			u16 eep_offset = cal[i].eep_offs;
>  			u16 block_idx = adie_offset / MT7996_EEPROM_BLOCK_SIZE;
>  			u16 offset = adie_offset % MT7996_EEPROM_BLOCK_SIZE;
> -			u8 buf[MT7996_EEPROM_BLOCK_SIZE];

I think we should always define and initialize buf array here, otherwise we
could use it with uninitialized values. Something like:

			u8 buf[MT7996_EEPROM_BLOCK_SIZE] = {};

Regards,
Lorenzo

>  
>  			if (is_mt7996(&dev->mt76) && band == MT_BAND1 &&
>  			    dev->var.type == MT7996_VAR_TYPE_444)
>  				eep_offset -= MT_EE_7977BN_OFFSET;
>  
>  			if (prev_block_idx != block_idx) {
> +				memset(buf, 0, sizeof(buf));
>  				ret = mt7996_mcu_get_eeprom(dev, adie_offset, buf,
>  							    MT7996_EEPROM_BLOCK_SIZE,
>  							    EEPROM_MODE_EFUSE);
> -- 
> 2.51.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* Re: [PATCH mt76 4/6] wifi: mt76: mt7996: adjust timeout value for boot-up calibration commands
From: Lorenzo Bianconi @ 2026-03-12 10:43 UTC (permalink / raw)
  To: Shayne Chen
  Cc: Felix Fietkau, linux-wireless, Ryder Lee, Evelyn Tsai, Money Wang,
	linux-mediatek, Rex Lu
In-Reply-To: <20260312095724.2117448-4-shayne.chen@mediatek.com>

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

> From: Rex Lu <rex.lu@mediatek.com>
> 
> Align the vendor driver by adjusting the timeout values for the
> MCU_UNI_CMD_EFUSE_CTRL and MCU_UNI_CMD_EXT_EEPROM_CTRL commands.
> Without this adjustment, false positive command timeout errors may occur,
> especially on some iPA variants.

I guess we are missing a Fixes tag here.

Regards,
Lorenzo

> 
> Signed-off-by: Rex Lu <rex.lu@mediatek.com>
> Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
> ---
>  drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> index bdb9e30c54c1..2a9c59d15894 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> @@ -271,7 +271,8 @@ mt7996_mcu_set_timeout(struct mt76_dev *mdev, int cmd)
>  		mdev->mcu.timeout = 2 * HZ;
>  		return;
>  	case MCU_UNI_CMD_EFUSE_CTRL:
> -		mdev->mcu.timeout = 20 * HZ;
> +	case MCU_UNI_CMD_EXT_EEPROM_CTRL:
> +		mdev->mcu.timeout = 30 * HZ;
>  		return;
>  	default:
>  		break;
> -- 
> 2.51.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* Re: [PATCH mt76 3/6] wifi: mt76: mt7996: update WFSYS reset flow for MT7990 chipsets
From: Lorenzo Bianconi @ 2026-03-12 10:42 UTC (permalink / raw)
  To: Shayne Chen
  Cc: Felix Fietkau, linux-wireless, Ryder Lee, Evelyn Tsai, Money Wang,
	linux-mediatek, Peter Chiu
In-Reply-To: <20260312095724.2117448-3-shayne.chen@mediatek.com>

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

> From: Peter Chiu <chui-hao.chiu@mediatek.com>
> 
> Skip WFSYS reset during bootup for MT7990 chipsets; only reset if L0.5
> recovery is triggered.
> Without this fix, the following kernel error may occur:
> Internal error: synchronous external abort.

I guess we are missing a Fixes tag here.

Regards,
Lorenzo

> 
> Signed-off-by: Peter Chiu <chui-hao.chiu@mediatek.com>
> Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
> ---
>  .../net/wireless/mediatek/mt76/mt7996/init.c  | 29 +++++++++++++++++--
>  .../net/wireless/mediatek/mt76/mt7996/regs.h  |  8 +++++
>  2 files changed, 34 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c
> index f3239f530aea..8dfb81eabc9a 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c
> @@ -791,11 +791,34 @@ static void mt7996_init_work(struct work_struct *work)
>  
>  void mt7996_wfsys_reset(struct mt7996_dev *dev)
>  {
> -	mt76_set(dev, MT_WF_SUBSYS_RST, 0x1);
> -	msleep(20);
> +	if (!is_mt7990(&dev->mt76)) {
> +		mt76_set(dev, MT_WF_SUBSYS_RST, 0x1);
> +		msleep(20);
> +
> +		mt76_clear(dev, MT_WF_SUBSYS_RST, 0x1);
> +		msleep(20);
> +
> +		return;
> +	}
>  
> -	mt76_clear(dev, MT_WF_SUBSYS_RST, 0x1);
> +	if (!dev->recovery.hw_full_reset)
> +		return;
> +
> +	mt76_set(dev, MT_WF_SUBSYS_RST,
> +		 MT_WF_SUBSYS_RST_WHOLE_PATH_RST_REVERT |
> +		 MT_WF_SUBSYS_RST_BYPASS_WFDMA_SLP_PROT |
> +		 MT_WF_SUBSYS_RST_BYPASS_WFDMA2_SLP_PROT);
> +	mt76_rmw(dev, MT_WF_SUBSYS_RST,
> +		 MT_WF_SUBSYS_RST_WHOLE_PATH_RST_REVERT_CYCLE,
> +		 u32_encode_bits(0x20, MT_WF_SUBSYS_RST_WHOLE_PATH_RST_REVERT_CYCLE));
> +	mt76_clear(dev, MT_WF_L05_RST, MT_WF_L05_RST_WF_RST_MASK);
> +	mt76_set(dev, MT_WF_SUBSYS_RST, MT_WF_SUBSYS_RST_WHOLE_PATH_RST);
>  	msleep(20);
> +
> +	if (mt76_poll(dev, MT_WF_L05_RST, MT_WF_L05_RST_WF_RST_MASK, 0x1a, 1000))
> +		return;
> +
> +	dev_err(dev->mt76.dev, "wfsys reset fail\n");
>  }
>  
>  static void mt7996_rro_hw_init_v3(struct mt7996_dev *dev)
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h
> index 393faae2d52b..c6379933b6c3 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h
> +++ b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h
> @@ -736,7 +736,15 @@ enum offs_rev {
>  #define MT_HW_REV				0x70010204
>  #define MT_HW_REV1				0x8a00
>  
> +#define MT_WF_L05_RST				0x70028550
> +#define MT_WF_L05_RST_WF_RST_MASK		GENMASK(4, 0)
> +
>  #define MT_WF_SUBSYS_RST			0x70028600
> +#define MT_WF_SUBSYS_RST_WHOLE_PATH_RST		BIT(0)
> +#define MT_WF_SUBSYS_RST_WHOLE_PATH_RST_REVERT	BIT(5)
> +#define MT_WF_SUBSYS_RST_BYPASS_WFDMA_SLP_PROT	BIT(6)
> +#define MT_WF_SUBSYS_RST_BYPASS_WFDMA2_SLP_PROT	BIT(16)
> +#define MT_WF_SUBSYS_RST_WHOLE_PATH_RST_REVERT_CYCLE	GENMASK(15, 8)
>  
>  /* PCIE MAC */
>  #define MT_PCIE_MAC_BASE			0x74030000
> -- 
> 2.51.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* Re: [PATCH mt76 2/6] wifi: mt76: mt7996: support critical packet mode for MT7990 chipsets
From: Lorenzo Bianconi @ 2026-03-12 10:42 UTC (permalink / raw)
  To: Shayne Chen
  Cc: Felix Fietkau, linux-wireless, Ryder Lee, Evelyn Tsai, Money Wang,
	linux-mediatek, Howard Hsu
In-Reply-To: <20260312095724.2117448-2-shayne.chen@mediatek.com>

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

> From: Howard Hsu <howard-yh.hsu@mediatek.com>
> 
> For MT7990 chipsets, critical packet mode must be enabled. Without this,
> some higher priority packets may be placed in the wrong AC queue.
> 
> Signed-off-by: Howard Hsu <howard-yh.hsu@mediatek.com>
> Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>

I guess we are missing a Fixes tag here.

Regards,
Lorenzo

> ---
>  drivers/net/wireless/mediatek/mt76/mt7996/main.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
> index e1e51c9a0767..d286dedddd9b 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
> @@ -56,7 +56,7 @@ static int mt7996_start(struct ieee80211_hw *hw)
>  
>  	mutex_lock(&dev->mt76.mutex);
>  	ret = mt7996_mcu_set_hdr_trans(dev, true);
> -	if (!ret && is_mt7992(&dev->mt76)) {
> +	if (!ret && !is_mt7996(&dev->mt76)) {
>  		u8 queue = mt76_connac_lmac_mapping(IEEE80211_AC_VI);
>  
>  		ret = mt7996_mcu_cp_support(dev, queue);
> -- 
> 2.51.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* Re: [PATCH mt76 1/6] wifi: mt76: mt7996: fix RRO EMU configuration
From: Lorenzo Bianconi @ 2026-03-12 10:39 UTC (permalink / raw)
  To: Shayne Chen
  Cc: Felix Fietkau, linux-wireless, Ryder Lee, Evelyn Tsai, Money Wang,
	linux-mediatek, Peter Chiu
In-Reply-To: <20260312095724.2117448-1-shayne.chen@mediatek.com>

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

> From: Peter Chiu <chui-hao.chiu@mediatek.com>
> 
> Use the correct helper to update specific bitfields instead of
> overwriting the entire register.
> 
> Fixes: eedb427eb260 ("wifi: mt76: mt7996: Enable HW RRO for MT7992 chipset")
> Signed-off-by: Peter Chiu <chui-hao.chiu@mediatek.com>
> Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>

Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>

> ---
>  drivers/net/wireless/mediatek/mt76/mt7996/init.c | 3 +--
>  drivers/net/wireless/mediatek/mt76/mt7996/mac.c  | 2 +-
>  2 files changed, 2 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c
> index 5aaa93767109..f3239f530aea 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c
> @@ -873,8 +873,7 @@ void mt7996_rro_hw_init(struct mt7996_dev *dev)
>  			}
>  		} else {
>  			/* set emul 3.0 function */
> -			mt76_wr(dev, MT_RRO_3_0_EMU_CONF,
> -				MT_RRO_3_0_EMU_CONF_EN_MASK);
> +			mt76_set(dev, MT_RRO_3_0_EMU_CONF, MT_RRO_3_0_EMU_CONF_EN_MASK);
>  
>  			mt76_wr(dev, MT_RRO_ADDR_ARRAY_BASE0,
>  				dev->wed_rro.addr_elem[0].phy_addr);
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> index a415cc610eee..86aaf0f29e28 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> @@ -2559,7 +2559,7 @@ void mt7996_mac_reset_work(struct work_struct *work)
>  	mt7996_dma_start(dev, false, false);
>  
>  	if (!is_mt7996(&dev->mt76) && dev->mt76.hwrro_mode == MT76_HWRRO_V3)
> -		mt76_wr(dev, MT_RRO_3_0_EMU_CONF, MT_RRO_3_0_EMU_CONF_EN_MASK);
> +		mt76_set(dev, MT_RRO_3_0_EMU_CONF, MT_RRO_3_0_EMU_CONF_EN_MASK);
>  
>  	if (mtk_wed_device_active(&dev->mt76.mmio.wed)) {
>  		u32 wed_irq_mask = MT_INT_TX_DONE_BAND2 |
> -- 
> 2.51.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* [PATCH 10/10] carl9170: phy: add periodic runtime IQ calibration
From: Masi Osmani @ 2026-03-12 10:38 UTC (permalink / raw)
  To: Christian Lamparter; +Cc: linux-wireless, Masi Osmani, ath9k-devel
In-Reply-To: <cover.1773277728.git.mas-i@hotmail.de>

Add periodic runtime I/Q calibration triggered from the existing
survey statistics work handler (carl9170_stat_work).  The AR9170
hardware performs initial IQ calibration during channel setup, but
I/Q imbalance drifts with temperature over time, degrading EVM
and increasing packet error rate.

The new carl9170_run_iq_calibration() function sets the DO_IQCAL
bit in PHY_TIMING_CTRL4 for both chains, which triggers the
hardware to re-measure I/Q imbalance and update the correction
coefficients automatically.  This is a non-blocking operation --
the hardware runs the calibration in the background without
interrupting normal traffic.

The ath9k driver performs similar periodic calibration via its
longcal timer (every 30s).  carl9170_stat_work runs at a
comparable interval, making it a natural trigger point.

Signed-off-by: Masi Osmani <mas-i@hotmail.de>
---
 drivers/net/wireless/ath/carl9170/carl9170.h |  1 +
 drivers/net/wireless/ath/carl9170/main.c     |  2 ++
 drivers/net/wireless/ath/carl9170/phy.c      | 36 ++++++++++++++++++++
 3 files changed, 39 insertions(+)

diff --git a/drivers/net/wireless/ath/carl9170/carl9170.h b/drivers/net/wireless/ath/carl9170/carl9170.h
index 2eedb2f..0175f8e 100644
--- a/drivers/net/wireless/ath/carl9170/carl9170.h
+++ b/drivers/net/wireless/ath/carl9170/carl9170.h
@@ -605,6 +605,7 @@ int carl9170_set_channel(struct ar9170 *ar, struct ieee80211_channel *channel,
 			 enum nl80211_channel_type bw);
 int carl9170_get_noisefloor(struct ar9170 *ar);
 void carl9170_update_channel_maxpower(struct ar9170 *ar);
+int carl9170_run_iq_calibration(struct ar9170 *ar);
 
 /* FW */
 int carl9170_parse_firmware(struct ar9170 *ar);
diff --git a/drivers/net/wireless/ath/carl9170/main.c b/drivers/net/wireless/ath/carl9170/main.c
index ebf9fa9..50c0922 100644
--- a/drivers/net/wireless/ath/carl9170/main.c
+++ b/drivers/net/wireless/ath/carl9170/main.c
@@ -910,6 +910,8 @@ static void carl9170_stat_work(struct work_struct *work)
 
 	mutex_lock(&ar->mutex);
 	err = carl9170_update_survey(ar, false, true);
+	if (!err)
+		carl9170_run_iq_calibration(ar);
 	mutex_unlock(&ar->mutex);
 
 	if (err)
diff --git a/drivers/net/wireless/ath/carl9170/phy.c b/drivers/net/wireless/ath/carl9170/phy.c
index c294df7..b145e9e 100644
--- a/drivers/net/wireless/ath/carl9170/phy.c
+++ b/drivers/net/wireless/ath/carl9170/phy.c
@@ -1637,6 +1637,42 @@ void carl9170_update_channel_maxpower(struct ar9170 *ar)
 	}
 }
 
+int carl9170_run_iq_calibration(struct ar9170 *ar)
+{
+	u32 val;
+	int err;
+
+	if (!ar->channel)
+		return 0;
+
+	/*
+	 * Trigger runtime IQ calibration.  The hardware measures
+	 * I/Q imbalance and updates the correction coefficients
+	 * automatically when DO_IQCAL is set.  We trigger on both
+	 * chains and re-enable the IQ correction afterwards.
+	 */
+	err = carl9170_read_reg(ar, AR9170_PHY_REG_TIMING_CTRL4(0), &val);
+	if (err)
+		return err;
+
+	val |= AR9170_PHY_TIMING_CTRL4_DO_IQCAL;
+	err = carl9170_write_reg(ar, AR9170_PHY_REG_TIMING_CTRL4(0), val);
+	if (err)
+		return err;
+
+	/* chain 2 */
+	err = carl9170_read_reg(ar, AR9170_PHY_REG_TIMING_CTRL4(2), &val);
+	if (err)
+		return err;
+
+	val |= AR9170_PHY_TIMING_CTRL4_DO_IQCAL;
+	err = carl9170_write_reg(ar, AR9170_PHY_REG_TIMING_CTRL4(2), val);
+	if (err)
+		return err;
+
+	return 0;
+}
+
 static int carl9170_set_radar_detection(struct ar9170 *ar,
 					struct ieee80211_channel *channel)
 {
-- 
2.51.0


^ permalink raw reply related

* [PATCH 09/10] carl9170: fw: enable DFS radar detection
From: Masi Osmani @ 2026-03-12 10:38 UTC (permalink / raw)
  To: Christian Lamparter; +Cc: linux-wireless, Masi Osmani, ath9k-devel
In-Reply-To: <cover.1773277728.git.mas-i@hotmail.de>

Enable DFS (Dynamic Frequency Selection) radar detection on the
AR9170.  The hardware has radar detection registers (RADAR_0,
RADAR_1, RADAR_EXT) and the firmware already sends
CARL9170_RSP_RADAR events, but the driver never programmed the
detection parameters and only logged a "please report" message.

Changes:
- Program radar detection pulse parameters in phy.c when the
  current channel has IEEE80211_CHAN_RADAR set.  Values are
  based on ath9k defaults for FCC/ETSI compliance.
- Advertise radar_detect_widths in the interface combination
  (fw.c) for 20 MHz noHT, 20 MHz HT, and 40 MHz HT.
- Replace the old "please report" message with a call to
  ieee80211_radar_detected() so mac80211 can trigger the
  proper DFS state machine (channel switch / CAC).

Signed-off-by: Masi Osmani <mas-i@hotmail.de>
---
 drivers/net/wireless/ath/carl9170/fw.c  |  3 ++
 drivers/net/wireless/ath/carl9170/phy.c | 45 +++++++++++++++++++++++++
 drivers/net/wireless/ath/carl9170/rx.c  |  7 ++--
 3 files changed, 50 insertions(+), 5 deletions(-)

diff --git a/drivers/net/wireless/ath/carl9170/fw.c b/drivers/net/wireless/ath/carl9170/fw.c
index 419f553..a730593 100644
--- a/drivers/net/wireless/ath/carl9170/fw.c
+++ b/drivers/net/wireless/ath/carl9170/fw.c
@@ -215,6 +215,9 @@ static void carl9170_fw_set_if_combinations(struct ar9170 *ar,
 	ar->if_combs[0].max_interfaces = ar->fw.vif_num;
 	ar->if_combs[0].limits = ar->if_comb_limits;
 	ar->if_combs[0].n_limits = ARRAY_SIZE(ar->if_comb_limits);
+	ar->if_combs[0].radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) |
+					      BIT(NL80211_CHAN_WIDTH_20) |
+					      BIT(NL80211_CHAN_WIDTH_40);
 
 	ar->hw->wiphy->iface_combinations = ar->if_combs;
 	ar->hw->wiphy->n_iface_combinations = ARRAY_SIZE(ar->if_combs);
diff --git a/drivers/net/wireless/ath/carl9170/phy.c b/drivers/net/wireless/ath/carl9170/phy.c
index bcd9066..c294df7 100644
--- a/drivers/net/wireless/ath/carl9170/phy.c
+++ b/drivers/net/wireless/ath/carl9170/phy.c
@@ -1637,6 +1637,47 @@ void carl9170_update_channel_maxpower(struct ar9170 *ar)
 	}
 }
 
+static int carl9170_set_radar_detection(struct ar9170 *ar,
+					struct ieee80211_channel *channel)
+{
+	bool enable = channel->flags & IEEE80211_CHAN_RADAR;
+
+	carl9170_regwrite_begin(ar);
+
+	if (enable) {
+		/*
+		 * Configure radar detection pulse parameters.
+		 * Values based on ath9k's defaults for FCC/ETSI.
+		 */
+		carl9170_regwrite(AR9170_PHY_REG_RADAR_0,
+				  AR9170_PHY_RADAR_0_ENA |
+				  AR9170_PHY_RADAR_0_FFT_ENA |
+				  SET_CONSTVAL(AR9170_PHY_RADAR_0_INBAND, 5) |
+				  SET_CONSTVAL(AR9170_PHY_RADAR_0_PRSSI, 1) |
+				  SET_CONSTVAL(AR9170_PHY_RADAR_0_HEIGHT, 6) |
+				  SET_CONSTVAL(AR9170_PHY_RADAR_0_RRSSI, 12) |
+				  SET_CONSTVAL(AR9170_PHY_RADAR_0_FIRPWR, 33));
+
+		carl9170_regwrite(AR9170_PHY_REG_RADAR_1,
+				  AR9170_PHY_RADAR_1_MAX_RRSSI |
+				  AR9170_PHY_RADAR_1_BLOCK_CHECK |
+				  AR9170_PHY_RADAR_1_RELSTEP_CHECK |
+				  SET_CONSTVAL(AR9170_PHY_RADAR_1_RELSTEP_THRESH, 8) |
+				  SET_CONSTVAL(AR9170_PHY_RADAR_1_RELPWR_THRESH, 12) |
+				  SET_CONSTVAL(AR9170_PHY_RADAR_1_MAXLEN, 255));
+
+		carl9170_regwrite(AR9170_PHY_REG_RADAR_EXT,
+				  AR9170_PHY_RADAR_EXT_ENA);
+	} else {
+		carl9170_regwrite(AR9170_PHY_REG_RADAR_0, 0);
+		carl9170_regwrite(AR9170_PHY_REG_RADAR_1, 0);
+		carl9170_regwrite(AR9170_PHY_REG_RADAR_EXT, 0);
+	}
+
+	carl9170_regwrite_finish();
+	return carl9170_regwrite_result();
+}
+
 int carl9170_get_noisefloor(struct ar9170 *ar)
 {
 	static const u32 phy_regs[] = {
@@ -1739,6 +1780,10 @@ int carl9170_set_channel(struct ar9170 *ar, struct ieee80211_channel *channel,
 	if (err)
 		return err;
 
+	err = carl9170_set_radar_detection(ar, channel);
+	if (err)
+		return err;
+
 	tmp = AR9170_PHY_TURBO_FC_SINGLE_HT_LTF1 |
 	      AR9170_PHY_TURBO_FC_HT_EN;
 
diff --git a/drivers/net/wireless/ath/carl9170/rx.c b/drivers/net/wireless/ath/carl9170/rx.c
index bb909b5..1fe727c 100644
--- a/drivers/net/wireless/ath/carl9170/rx.c
+++ b/drivers/net/wireless/ath/carl9170/rx.c
@@ -259,11 +259,8 @@ void carl9170_handle_command_response(struct ar9170 *ar, void *buf, u32 len)
 		break;
 
 	case CARL9170_RSP_RADAR:
-		if (!net_ratelimit())
-			break;
-
-		wiphy_info(ar->hw->wiphy, "FW: RADAR! Please report this "
-		       "incident to linux-wireless@vger.kernel.org !\n");
+		wiphy_info(ar->hw->wiphy, "FW: radar pulse detected\n");
+		ieee80211_radar_detected(ar->hw, NULL);
 		break;
 
 	case CARL9170_RSP_GPIO:
-- 
2.51.0


^ 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