* [PATCH rtw-next 01/14] wifi: rtw89: coex: Add chip initial info version 11 for RTL8922A/D
From: Ping-Ke Shih @ 2026-07-24 13:56 UTC (permalink / raw)
To: linux-wireless; +Cc: ku920601
In-Reply-To: <20260724135640.3195044-1-pkshih@realtek.com>
From: Ching-Te Ku <ku920601@realtek.com>
The version 11 init info add current RF path control information.
Signed-off-by: Ching-Te Ku <ku920601@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
drivers/net/wireless/realtek/rtw89/coex.c | 2 +
drivers/net/wireless/realtek/rtw89/coex.h | 19 ++++
drivers/net/wireless/realtek/rtw89/core.h | 47 ++++++++++
drivers/net/wireless/realtek/rtw89/fw.c | 75 ++++++++++++++++
drivers/net/wireless/realtek/rtw89/fw.h | 6 ++
drivers/net/wireless/realtek/rtw89/rtw8922a.c | 88 ++++++++++++++++---
drivers/net/wireless/realtek/rtw89/rtw8922d.c | 40 ++++++---
7 files changed, 251 insertions(+), 26 deletions(-)
diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c
index 6a1888d8c476..f94866e1f43a 100644
--- a/drivers/net/wireless/realtek/rtw89/coex.c
+++ b/drivers/net/wireless/realtek/rtw89/coex.c
@@ -3199,6 +3199,8 @@ static void _fw_set_drv_info(struct rtw89_dev *rtwdev, u8 index)
rtw89_fw_h2c_cxdrv_init_v7(rtwdev, index);
else if (ver->fcxinit == 10)
rtw89_fw_h2c_cxdrv_init_v10(rtwdev, index);
+ else if (ver->fcxinit == 11)
+ rtw89_fw_h2c_cxdrv_init_v11(rtwdev, index);
else
rtw89_fw_h2c_cxdrv_init(rtwdev, index);
break;
diff --git a/drivers/net/wireless/realtek/rtw89/coex.h b/drivers/net/wireless/realtek/rtw89/coex.h
index 3238c44429a9..41a68ab21d80 100644
--- a/drivers/net/wireless/realtek/rtw89/coex.h
+++ b/drivers/net/wireless/realtek/rtw89/coex.h
@@ -127,6 +127,11 @@ enum btc_fddt_en {
((__rssi == BTC_RSSI_ST_LOW || \
__rssi == BTC_RSSI_ST_HIGH) ? 1 : 0); })
+/* Antenna TX/RX path position masks and helpers */
+#define BTC_ANT_TX_MASK 0xf0
+#define BTC_ANT_RX_MASK 0x0f
+#define BTC_ANT_SHIFT 4
+
enum btc_ant {
BTC_ANT_SHARED = 0,
BTC_ANT_DEDICATED,
@@ -473,4 +478,18 @@ void btc_dw2b(u8 *buf, size_t idx, u32 val)
buf[idx + 3] = u32_get_bits(val, MASKBYTE3);
}
+static inline
+u8 _btc_get_rf_path_from_ant_num(struct rtw89_btc *btc, u8 antnum)
+{
+ switch (antnum) {
+ default:
+ case 1:
+ return btc->mdinfo.ant.single_pos;
+ case 2:
+ return RF_PATH_AB;
+ case 3:
+ return RF_PATH_ABC;
+ }
+}
+
#endif
diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h
index 7573f1965c98..7147e186b4a5 100644
--- a/drivers/net/wireless/realtek/rtw89/core.h
+++ b/drivers/net/wireless/realtek/rtw89/core.h
@@ -1537,6 +1537,21 @@ struct rtw89_btc_ant_info_v10 {
u8 ant_xmap[2][4];
} __packed;
+struct rtw89_btc_ant_info_v11 {
+ u8 type;
+ u8 num;
+ u8 isolation;
+ u8 single_pos;
+
+ u8 stream_cnt;
+ u8 path_pos; /* WL path position: Tx[7:4], Rx[3:0] */
+ u8 btg_pos;
+ u8 btg1_pos;
+ u8 func[5];
+ u8 ant_xmap[2][4];
+ u8 rsvd0[3];
+} __packed;
+
struct rtw89_btc_ant_info {
u8 type; /* shared, dedicated(non-shared) */
u8 num; /* antenna count */
@@ -1546,6 +1561,7 @@ struct rtw89_btc_ant_info {
u8 stream_cnt; /* spatial_stream count: Tx[7:4], Rx[3:0] */
u8 btg_pos; /* BT0 btg-circuit at 0:WL-S0/1:WL-S1 */
u8 btg1_pos; /* BT1 btg-circuit at 0:WL-S0/1:WL-S1 */
+ u8 path_pos; /* WL path position: Tx[7:4], Rx[3:0] */
u8 func[5]; /* function at 1~5 Ant refer to enum btc_bt_func_type */
u8 ant_xmap[2][4];
@@ -2387,10 +2403,25 @@ struct rtw89_btc_module_v10 {
struct rtw89_btc_ant_info_v10 ant;
} __packed;
+struct rtw89_btc_module_v11 {
+ u8 rfe_type;
+ u8 wa_type;
+ u8 kt_ver;
+ u8 kt_ver_adie;
+
+ u8 bt0_pos;
+ u8 bt0_sw_type;
+ u8 bt1_pos;
+ u8 bt1_sw_type;
+
+ struct rtw89_btc_ant_info_v11 ant;
+} __packed;
+
union rtw89_btc_module_info {
struct rtw89_btc_module_v0 md_v0;
struct rtw89_btc_module_v7 md_v7;
struct rtw89_btc_module_v10 md_v10;
+ struct rtw89_btc_module_v11 md_v11;
};
struct rtw89_btc_module {
@@ -2473,11 +2504,26 @@ struct rtw89_btc_init_info_v10 {
struct rtw89_btc_module_v10 module;
} __packed;
+struct rtw89_btc_init_info_v11 {
+ u8 endian_type; /* 0: little-endian, 1:big-endian */
+ u8 init_mode; /* refer to enum BTC_MODE_xxx */
+ u8 wl_init_ok;
+ u8 bt0_function;
+
+ u8 bt1_function;
+ u8 bt2_function;
+ u8 pta_mode;
+ u8 pta_direction;
+
+ struct rtw89_btc_module_v11 module;
+};
+
union rtw89_btc_init_info_u {
struct rtw89_btc_init_info_v0 init_v0;
struct rtw89_btc_init_info_v7 init_v7;
struct rtw89_btc_init_info_v10 init_v10;
struct rtw89_btc_init_info_v107 init_v107;
+ struct rtw89_btc_init_info_v11 init_v11;
};
struct rtw89_btc_init_info {
@@ -2547,6 +2593,7 @@ enum rtw89_btc_bt_func_type {
#define RTW89_BTC_BT_DEF_BR_TX_PWR 4
#define RTW89_BTC_BT_DEF_LE_TX_PWR 4
#define RTW89_BTC_DEFAULT_ANISO 10
+#define RTW89_BTC_BT_DEF_LE_TX_PWR_1 10
struct rtw89_btc_bt_scan_info_v1 {
__le16 win;
diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c
index 3b8c144e0e98..a3a04697e2e6 100644
--- a/drivers/net/wireless/realtek/rtw89/fw.c
+++ b/drivers/net/wireless/realtek/rtw89/fw.c
@@ -5973,6 +5973,81 @@ int rtw89_fw_h2c_cxdrv_init_v10(struct rtw89_dev *rtwdev, u8 type)
return ret;
}
+int rtw89_fw_h2c_cxdrv_init_v11(struct rtw89_dev *rtwdev, u8 type)
+{
+ struct rtw89_btc *btc = &rtwdev->btc;
+ struct rtw89_btc_dm *dm = &btc->dm;
+ struct rtw89_btc_init_info *init = &dm->init_info;
+ struct rtw89_h2c_cxinit_v11 *h2c;
+ u32 len = sizeof(*h2c);
+ struct sk_buff *skb;
+ int ret;
+
+ skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len);
+ if (!skb) {
+ rtw89_err(rtwdev, "failed to alloc skb for h2c cxdrv_init_v11\n");
+ return -ENOMEM;
+ }
+ skb_put(skb, len);
+ h2c = (struct rtw89_h2c_cxinit_v11 *)skb->data;
+
+ h2c->hdr.type = type;
+ h2c->hdr.ver = 11;
+ h2c->hdr.len = len - H2C_LEN_CXDRVHDR_V7;
+
+ h2c->init.endian_type = init->endian_type;
+ h2c->init.init_mode = init->init_mode;
+ h2c->init.wl_init_ok = init->wl_init_ok;
+ h2c->init.bt0_function = init->bt0_function;
+
+ h2c->init.bt1_function = init->bt1_function;
+ h2c->init.bt2_function = init->bt2_function;
+ h2c->init.pta_mode = init->pta_mode;
+ h2c->init.pta_direction = init->pta_direction;
+
+ h2c->init.module.rfe_type = init->module.rfe_type;
+ h2c->init.module.wa_type = init->module.wa_type;
+ h2c->init.module.kt_ver = init->module.kt_ver;
+ h2c->init.module.kt_ver_adie = init->module.kt_ver_adie;
+
+ h2c->init.module.bt0_pos = init->module.bt0_pos;
+ h2c->init.module.bt0_sw_type = init->module.bt0_sw_type;
+ h2c->init.module.bt1_pos = init->module.bt1_pos;
+ h2c->init.module.bt1_sw_type = init->module.bt1_sw_type;
+
+ h2c->init.module.ant.type = init->module.ant.type;
+ h2c->init.module.ant.num = init->module.ant.num;
+ h2c->init.module.ant.isolation = init->module.ant.isolation;
+ h2c->init.module.ant.single_pos = init->module.ant.single_pos;
+
+ h2c->init.module.ant.stream_cnt = init->module.ant.stream_cnt;
+ h2c->init.module.ant.path_pos = init->module.ant.path_pos;
+ h2c->init.module.ant.btg_pos = init->module.ant.btg_pos;
+ h2c->init.module.ant.btg1_pos = init->module.ant.btg1_pos;
+ memcpy(h2c->init.module.ant.func, init->module.ant.func,
+ sizeof(init->module.ant.func));
+
+ memcpy(h2c->init.module.ant.ant_xmap, init->module.ant.ant_xmap,
+ sizeof(init->module.ant.ant_xmap));
+
+ rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C,
+ H2C_CAT_OUTSRC, BTFC_SET,
+ SET_DRV_INFO, 0, 0,
+ len);
+
+ ret = rtw89_h2c_tx(rtwdev, skb, false);
+ if (ret) {
+ rtw89_err(rtwdev, "failed to send h2c\n");
+ goto fail;
+ }
+
+ return 0;
+fail:
+ dev_kfree_skb_any(skb);
+
+ return ret;
+}
+
#define PORT_DATA_OFFSET 4
#define H2C_LEN_CXDRVINFO_ROLE_DBCC_LEN 12
#define H2C_LEN_CXDRVINFO_ROLE_SIZE(max_role_num) \
diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h
index e86014ca249a..c0a00b721060 100644
--- a/drivers/net/wireless/realtek/rtw89/fw.h
+++ b/drivers/net/wireless/realtek/rtw89/fw.h
@@ -2603,6 +2603,11 @@ struct rtw89_h2c_cxinit_v10 {
struct rtw89_btc_init_info_v10 init;
} __packed;
+struct rtw89_h2c_cxinit_v11 {
+ struct rtw89_h2c_cxhdr_v7 hdr;
+ struct rtw89_btc_init_info_v11 init;
+} __packed;
+
#define RTW89_H2C_CXROLE_V101_ROLE_STAT_CONNTECTED BIT(0)
#define RTW89_H2C_CXROLE_V101_ROLE_STAT_PID GENMASK(3, 1)
#define RTW89_H2C_CXROLE_V101_ROLE_STAT_PHY BIT(4)
@@ -5456,6 +5461,7 @@ int rtw89_fw_h2c_cxdrv_role_v2(struct rtw89_dev *rtwdev, u8 type);
int rtw89_fw_h2c_cxdrv_role_v7(struct rtw89_dev *rtwdev, u8 type);
int rtw89_fw_h2c_cxdrv_role_v8(struct rtw89_dev *rtwdev, u8 type);
int rtw89_fw_h2c_cxdrv_role_v10(struct rtw89_dev *rtwdev, u8 type);
+int rtw89_fw_h2c_cxdrv_init_v11(struct rtw89_dev *rtwdev, u8 type);
int rtw89_fw_h2c_cxdrv_osi_info(struct rtw89_dev *rtwdev, u8 type);
int rtw89_fw_h2c_cxdrv_osi_info_v6(struct rtw89_dev *rtwdev, u8 type);
int rtw89_fw_h2c_cxdrv_ctrl(struct rtw89_dev *rtwdev, u8 type);
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922a.c b/drivers/net/wireless/realtek/rtw89/rtw8922a.c
index 2586fd2df8ab..2d225e009b28 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8922a.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8922a.c
@@ -2721,25 +2721,33 @@ static u32 rtw8922a_chan_to_rf18_val(struct rtw89_dev *rtwdev,
static void rtw8922a_btc_set_rfe(struct rtw89_dev *rtwdev)
{
- struct rtw89_btc_module *md = &rtwdev->btc.mdinfo;
+ struct rtw89_btc *btc = &rtwdev->btc;
+ struct rtw89_btc_module *md = &btc->mdinfo;
+ u8 i, j;
+ u8 tx_path_pos, rx_path_pos;
md->rfe_type = rtwdev->efuse.rfe_type;
md->kt_ver = rtwdev->hal.cv;
- md->bt_solo = 0;
- md->bt0_sw_type = BTC_SWITCH_INTERNAL;
+ md->kt_ver_adie = rtwdev->hal.acv;
md->wa_type = 0;
- md->ant.type = BTC_ANT_SHARED;
md->ant.num = 2;
- md->ant.isolation = 10;
- md->ant.diversity = 0;
- md->ant.single_pos = RF_PATH_A;
- md->ant.btg_pos = RF_PATH_B;
+ md->ant.single_pos = BTC_RF_S0;
if (md->kt_ver <= 1)
md->wa_type |= BTC_WA_HFP_ZB;
- rtwdev->btc.cx.bt_ext.func_type = BTC_3CX_NONE;
+ btc->cx.bt0.ant_iso_to_wl = md->ant.isolation;
+ btc->cx.bt1.ant_iso_to_wl = md->ant.isolation;
+
+ md->ant.stream_cnt = 2;
+ md->ant.btg_pos = RF_PATH_B;
+ md->ant.single_pos = RF_PATH_A;
+
+ btc->cx.bt0.band_56G_support = 0;
+ btc->cx.bt1.band_56G_support = 0;
+ btc->cx.bt0.func_type = BTC_BTF_BT;
+ btc->cx.bt1.func_type = BTC_BTF_NONE;
if (md->rfe_type == 0) {
rtwdev->btc.dm.error.map.rfe_type0 = true;
@@ -2747,17 +2755,69 @@ static void rtw8922a_btc_set_rfe(struct rtw89_dev *rtwdev)
}
md->ant.num = (md->rfe_type % 2) ? 2 : 3;
-
if (md->kt_ver == 0)
md->ant.num = 2;
- if (md->ant.num == 3) {
- md->ant.type = BTC_ANT_DEDICATED;
- md->bt0_pos = BTC_BT_ALONE;
- } else {
+ memset(btc->dm.ant_xmap, 0, sizeof(btc->dm.ant_xmap));
+
+ switch (md->ant.num) {
+ case 1:
md->ant.type = BTC_ANT_SHARED;
md->bt0_pos = BTC_BT_BTG;
+ md->bt0_sw_type = BTC_SWITCH_V1_INTERNAL;
+ md->ant.btg_pos = BTC_RF_S0;
+ btc->dm.ant_xmap[BTC_RF_S0][BTC_BT_1ST] = 1;
+ md->ant.func[0] = BTC_EFMAP_BT0;
+ break;
+ case 2: /* 2-Ant */
+ default:
+ md->ant.type = BTC_ANT_SHARED;
+ md->bt0_pos = BTC_BT_BTG;
+ md->bt0_sw_type = BTC_SWITCH_V1_INTERNAL;
+ btc->dm.wl_trx_nss_en = 1;
+ btc->dm.ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 1;
+ md->ant.func[1] = BTC_EFMAP_BT0;
+ break;
+ case 3: /* 3-Ant, 3 different BT-configuration */
+ /* WL-S0 + WL-S1 + BT0-S0 */
+ md->ant.type = BTC_ANT_DEDICATED;
+ md->bt0_pos = BTC_BT_ALONE;
+ md->bt0_sw_type = BTC_SWITCH_V1_NONE;
+ md->ant.func[2] = BTC_EFMAP_BT0;
+ }
+
+ tx_path_pos = _btc_get_rf_path_from_ant_num(btc, rtwdev->hal.tx_nss);
+ rx_path_pos = _btc_get_rf_path_from_ant_num(btc, rtwdev->hal.rx_nss);
+ /* Combine TX[7:4] and RX[3:0] into path_pos byte */
+ md->ant.path_pos = ((tx_path_pos << BTC_ANT_SHIFT) & BTC_ANT_TX_MASK) |
+ (rx_path_pos & BTC_ANT_RX_MASK);
+
+ switch (btc->cx.bt_ext.chip_id) {
+ default:
+ case BTC_ESOC_NONE:
+ memset(&btc->cx.bt_ext, 0, sizeof(struct rtw89_btc_extsoc_info));
+ btc->cx.bt_ext.max_tx_pwr = RTW89_BTC_BT_DEF_LE_TX_PWR_1;
+ btc->cx.bt_ext.ant_iso_to_wl = RTW89_BTC_DEFAULT_ANISO;
+ break;
+ case BTC_ESOC_8771:
+ btc->cx.bt_ext.func_type = BTC_BTF_THREAD;
+ btc->cx.bt_ext.hw_coex = BTC_EXTSOC_INTF_PTA;
+ btc->cx.bt_ext.rf_band_map = 0x1; /* 2.4G only */
+ btc->cx.bt_ext.link_weight[BTC_BT_B2G] = 10;
+ btc->cx.bt_ext.profile_map[BTC_BT_B2G] |= BTC_BT_THREAD;
+ /* use GPIO 12~15 for Ext-4-wire-PTA */
+ btc->cx.bt_ext.hpta_cfg = BIT(12) | BIT(13) | BIT(14) | BIT(15);
+ md->ant.func[2] = BTC_EFMAP_ZB;
+ break;
}
+
+ btc->dm.ant_xmap[BTC_RF_S0][BTC_BT_EXT] = 0;
+ btc->dm.ant_xmap[BTC_RF_S1][BTC_BT_EXT] = 0;
+
+ for (i = BTC_RF_S0; i <= BTC_RF_S1; i++)
+ for (j = BTC_BT_1ST; j <= BTC_BT_EXT; j++)
+ md->ant.ant_xmap[i][j] = btc->dm.ant_xmap[i][j];
+
rtwdev->btc.btg_pos = md->ant.btg_pos;
rtwdev->btc.ant_type = md->ant.type;
}
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c
index 12c96425a6ff..142e693c87bf 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c
@@ -3174,7 +3174,9 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev)
struct rtw89_btc_dm *dm = &btc->dm;
struct rtw89_btc_cx *cx = &btc->cx;
u8 efuse_bt_func, efuse_ant_info, bt_sw_gpio_pos;
- u8 is_combo, is_bt_share;
+ u8 is_combo, is_bt_share, is_spdt;
+ u8 tx_path_pos, rx_path_pos;
+ u8 i, j;
rtw89_debug(rtwdev, RTW89_DBG_BTC, "[BTC], %s !!\n", __func__);
@@ -3192,7 +3194,8 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev)
cx->bt0.ant_iso_to_wl = md->ant.isolation;
cx->bt1.ant_iso_to_wl = md->ant.isolation;
- md->ant.stream_cnt = 2;
+ md->ant.stream_cnt = (rtwdev->hal.tx_nss << BTC_ANT_SHIFT) +
+ rtwdev->hal.rx_nss;
md->ant.btg_pos = BTC_RF_S1; /* BTG0 at WL-S1 */
md->ant.btg1_pos = BTC_RF_S0; /* BTG1 at WL-S0 if Dual-BTGA */
@@ -3203,19 +3206,20 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev)
efuse_bt_func = rtwdev->efuse.bt_setting_2;
- switch ((efuse_bt_func & 0xe0) >> 5) { /* 0xcd[7:5] */
+ is_spdt = !!(efuse_bt_func & BIT(5));
+
+ switch ((efuse_bt_func & 0xc0) >> 6) { /* 0xcd[7:6] */
default:
case 0:
- case 1:
bt_sw_gpio_pos = 5;
break;
- case 2:
+ case 1:
bt_sw_gpio_pos = 11;
break;
- case 3:
+ case 2:
bt_sw_gpio_pos = 15;
break;
- case 4:
+ case 3:
bt_sw_gpio_pos = 20;
break;
}
@@ -3233,9 +3237,8 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev)
case 1: /* 1-Ant WL-S0 only & BT0 only */
md->ant.type = BTC_ANT_SHARED;
md->bt0_pos = BTC_BT_BTG;
- md->bt0_sw_type = BTC_SWITCH_INTERNAL;
+ md->bt0_sw_type = BTC_SWITCH_V1_INTERNAL;
md->ant.btg_pos = BTC_RF_S0; /* BTG0 at WL-S0 */
- md->ant.stream_cnt = 1;
md->ant.func[0] = BTC_EFMAP_BT0;
dm->ant_xmap[BTC_RF_S0][BTC_BT_1ST] = 1; /* BT0 shared with S0*/
dm->ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 0; /* WL 1T1R no RF-S1 */
@@ -3265,13 +3268,13 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev)
if (md->ant.func[0] == BTC_EFMAP_BT1) { /* if 2nd BT exist */
dm->ant_xmap[BTC_RF_S0][BTC_BT_2ND] = 1;
- if (md->rfe_type == 12) { /* WL-S0 & BT1 by SPDT */
+ if (is_spdt) { /* WL-S0 & BT1 by SPDT */
md->bt1_pos = BTC_BT_ALONE;
/* Todo: set SPDT GPIO-ctrl */
md->bt1_sw_type = bt_sw_gpio_pos;
} else { /* WL-S0 & BT1-S1 by BTGA */
md->bt1_pos = BTC_BT_BTG;
- md->bt1_sw_type = BTC_SWITCH_INTERNAL;
+ md->bt1_sw_type = BTC_SWITCH_V1_INTERNAL;
}
}
@@ -3284,7 +3287,7 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev)
md->ant.func[2] = efuse_bt_func & (~BTC_EFMAP_BT0);
md->ant.type = BTC_ANT_SHARED;
md->bt0_pos = BTC_BT_BTG;
- md->bt0_sw_type = BTC_SWITCH_INTERNAL;
+ md->bt0_sw_type = BTC_SWITCH_V1_INTERNAL;
dm->ant_xmap[BTC_RF_S1][BTC_BT_1ST] = 1;
dm->wl_trx_nss_en = 1; /* 1ss MIMO-PS capability */
} else {
@@ -3324,6 +3327,12 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev)
break;
}
+ tx_path_pos = _btc_get_rf_path_from_ant_num(btc, rtwdev->hal.tx_nss);
+ rx_path_pos = _btc_get_rf_path_from_ant_num(btc, rtwdev->hal.rx_nss);
+ /* Combine TX[7:4] and RX[3:0] into path_pos byte */
+ md->ant.path_pos = ((tx_path_pos << BTC_ANT_SHIFT) & BTC_ANT_TX_MASK) |
+ (rx_path_pos & BTC_ANT_RX_MASK);
+
/*
* if only BT0 at BTGA: 2-Ant, 3-Ant(BT1 used dedicated-ant)
* can setup dm->wl_trx_nss_en = 1, WL MIMO-PS to 1T1R
@@ -3373,6 +3382,13 @@ static void rtw8922d_btc_set_rfe(struct rtw89_dev *rtwdev)
dm->ant_xmap[BTC_RF_S0][BTC_BT_EXT] = 0;
dm->ant_xmap[BTC_RF_S1][BTC_BT_EXT] = 0;
+
+ for (i = BTC_RF_S0; i <= BTC_RF_S1; i++)
+ for (j = BTC_BT_1ST; j <= BTC_BT_EXT; j++)
+ md->ant.ant_xmap[i][j] = dm->ant_xmap[i][j];
+
+ rtwdev->btc.btg_pos = md->ant.btg_pos;
+ rtwdev->btc.ant_type = md->ant.type;
}
static void rtw8922d_btc_init_cfg(struct rtw89_dev *rtwdev)
--
2.25.1
^ permalink raw reply related
* [PATCH rtw-next 00/14] wifi: rtw89: coex: add more command / event formats for coming RTL8922D
From: Ping-Ke Shih @ 2026-07-24 13:56 UTC (permalink / raw)
To: linux-wireless; +Cc: ku920601
To support BT-coexistence including dual Bluetooth for coming RTL8922D,
it adds more command and event formats to carry more information for
the more complex application combinations. More, introduce to show two
Bluetooth states for debugging.
Ching-Te Ku (14):
wifi: rtw89: coex: Add chip initial info version 11 for RTL8922A/D
wifi: rtw89: coex: Add Wi-Fi MLO info version 2 H2C command
wifi: rtw89: coex: Add firmware 0.35.111.X support for RTL8922A/D
wifi: rtw89: coex: Rearrange _ntfy_role_info info
wifi: rtw89: coex: Separate _ntfy_role_info into two function
wifi: rtw89: coex: Decrease Wi-Fi special packet protect time
wifi: rtw89: coex: complete GPIO debug configuration handler
wifi: rtw89: coex: Refine RF calibration notify flow
wifi: rtw89: coex: Fix log dump format with proper newline
wifi: rtw89: coex: Add BTC report version 8 support for BT sub-reports
wifi: rtw89: coex: Add dual Bluetooth debug info dump
wifi: rtw89: coex: Fix TDMA v8 handling to unblock BTC report parsing
wifi: rtw89: coex: Fix H2C command redundant send & version fall
through
wifi: rtw89: coex: Handle Bluetooth LE-Audio related coexistence
feature
drivers/net/wireless/realtek/rtw89/coex.c | 908 +++++++++++++++---
drivers/net/wireless/realtek/rtw89/coex.h | 56 +-
drivers/net/wireless/realtek/rtw89/core.h | 297 +++++-
drivers/net/wireless/realtek/rtw89/fw.c | 133 +++
drivers/net/wireless/realtek/rtw89/fw.h | 22 +
drivers/net/wireless/realtek/rtw89/rtw8922a.c | 88 +-
drivers/net/wireless/realtek/rtw89/rtw8922d.c | 40 +-
7 files changed, 1322 insertions(+), 222 deletions(-)
base-commit: b0034a2e415603daa1cf00363b407cc145bbcd31
--
2.25.1
^ permalink raw reply
* Re: [PATCH v8 5/9] block: implement NVMEM provider
From: Christoph Hellwig @ 2026-07-24 13:10 UTC (permalink / raw)
To: Loic Poulain
Cc: Christoph Hellwig, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Bjorn Andersson, Konrad Dybcio, Jens Axboe,
Johannes Berg, Jeff Johnson, Bartosz Golaszewski, Marcel Holtmann,
Luiz Augusto von Dentz, Balakrishna Godavarthi, Rocky Liao,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Srinivas Kandagatla, Andrew Lunn, Heiner Kallweit,
Russell King, Saravana Kannan, Christian Marangi, linux-mmc,
devicetree, linux-kernel, linux-arm-msm, linux-block,
linux-wireless, ath10k, linux-bluetooth, netdev, daniel,
Bartosz Golaszewski
In-Reply-To: <CAFEp6-1_iariDyqWj0KezHAtULSO+piPHX0F=Z46Rx4KDNLmXw@mail.gmail.com>
On Fri, Jul 24, 2026 at 11:10:58AM +0200, Loic Poulain wrote:
> Right, I was planning to add partition support in a follow-up series.
> But since this is really supposed to be a block-agnostic feature,
> whether it's a disk or a partition, I'll include partition in v8.
Please include the partitions, as including them or not matters for
the interfaces. What is holding that support back now? fwnode
magic?
> > Also once you register the whole devices as nvmem provider, we really
> > should keep it from being used a block device, shouldn't we?
>
>
> I'm not sure, the whole concept is block backed NVMEM, so we still
> need to add that block as base layer, though we could discuss
> suppressing uevent for it. Preventing block usage would make the
> feature much more intrusive than other NVMEM providers like MTD, where
> the underlying device stays accessible. And we may genuinely still
> want to write the disk (e.g. for factory provisioning), updating the
> very data the cells point to, etc. There's also the layering aspect,
> if a disk (e.g. mmcblk0) has logical partitions on top (mmcblk0pX), we
> still register/expose the whole disk mmcblk0 as a block device
> regardless. So if a disk contains NVMEM cells, I think it should still
> be exposed as a block device as well.
So we now get two unsynchronized ways to access the same device?
At least we need to prevent exclusive openers on the block device
side. And document the side channel very well.
^ permalink raw reply
* [PATCH mt76-next 29/29] wifi: mt76: mt7996: hold dev->mutex in remove_interface teardown
From: Felix Fietkau @ 2026-07-24 12:48 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
From: Chad Monroe <chad@monroe.io>
mt7996_remove_interface() destroys the remaining vif links, clearing
omac_mask, vif_mask and mld_idx_mask, with only the wiphy mutex held.
Those masks are modified under dev->mutex everywhere else, so the
unlocked clears can race the scan-link teardown and the reset work
and lose updates.
Take dev->mutex around the link destroy loop, matching
mt7996_add_interface() and mt7915_remove_interface(). The mutex is
released before mt76_vif_cleanup(), which aborts a pending scan and
takes the mutex itself.
Signed-off-by: Chad Monroe <chad@monroe.io>
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7996/main.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
index ce7aede6201e..97be70c23753 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
@@ -606,6 +606,8 @@ static void mt7996_remove_interface(struct ieee80211_hw *hw,
unsigned int link_id;
int i;
+ mutex_lock(&dev->mt76.mutex);
+
/* Remove all active links */
for_each_set_bit(link_id, &rem_links, IEEE80211_MLD_MAX_NUM_LINKS) {
struct mt7996_vif_link *link;
@@ -622,6 +624,8 @@ static void mt7996_remove_interface(struct ieee80211_hw *hw,
mt7996_vif_link_destroy(phy, link, vif, NULL);
}
+ mutex_unlock(&dev->mt76.mutex);
+
ieee80211_iterate_active_interfaces_mtx(hw, 0, mt7996_remove_iter,
&rdata);
mt76_vif_cleanup(&dev->mt76, vif);
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 26/29] wifi: mt76: mt7996: clear stale link state on full reset
From: Felix Fietkau @ 2026-07-24 12:48 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
After a full chip reset, mac80211 reconfig replays interface, link and
channel context setup. mt7996_vif_link_add() short-circuits when the
link_id is still marked in mvif->valid_links, a state introduced for
postponing link teardown to interface removal. The reset path frees the
link structures without clearing those bits, so the replayed setup never
re-creates dev_info/bss_info/STA records in the restarted firmware and
never re-registers the link wcid, leaving the device inoperative.
The reset path also leaks every allocated MLD index: per-link indices
and the per-vif group/remap indices are re-allocated from scratch during
reconfig, but the old bits stay set in the masks, so repeated full
resets exhaust the index space.
Clear valid_links in the reset vif iterator and reset the MLD index
masks alongside the existing omac_mask clearing.
Fixes: ace5d3b6b49e ("wifi: mt76: mt7996: improve hardware restart reliability")
Fixes: 08813703ac41 ("wifi: mt76: mt7996: Destroy vif active links in mt7996_remove_interface()")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
index ce8c5a5ea8cc..4794dacb3756 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
@@ -2449,6 +2449,7 @@ mt7996_mac_reset_vif_iter(void *data, u8 *mac, struct ieee80211_vif *vif)
rcu_assign_pointer(mvif->link[i], NULL);
kfree_rcu(mlink, rcu_head);
}
+ mvif->valid_links = 0;
rcu_read_unlock();
}
@@ -2483,6 +2484,8 @@ mt7996_mac_full_reset(struct mt7996_dev *dev)
mt7996_for_each_phy(dev, phy)
phy->omac_mask = 0;
+ dev->mld_idx_mask = 0;
+ dev->mld_remap_idx_mask = 0;
ieee80211_iterate_stations_atomic(hw, mt7996_mac_reset_sta_iter, dev);
mt76_reset_device(&dev->mt76);
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 27/29] wifi: mt76: set MT76_SCANNING when starting a hw scan
From: Felix Fietkau @ 2026-07-24 12:48 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
The scan state bit is cleared by mt76_scan_complete(), but nothing ever
sets it: mt76_sw_scan() is only called for drivers without hw scan
support. As a result, all MT76_SCANNING checks are inert for drivers
using mt76_hw_scan(), including the DFS state handling in
mt76_phy_dfs_state() and the guard against manually triggered radar
detection while scanning.
Set the bit when the scan request is accepted. Since every channel
programmed while scanning now evaluates the DFS state as disabled and
stops the radar detector, re-program the operating channel at scan
completion regardless of the off-channel state, after the scanning bit
has been cleared. Otherwise a scan whose last visited channel was the
operating channel, or one that returned to it early because of
associated stations, would leave radar detection stopped until the
next channel switch.
Fixes: 31083e38548f ("wifi: mt76: add code for emulating hardware scanning")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/scan.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/scan.c b/drivers/net/wireless/mediatek/mt76/scan.c
index 325638d587c9..3cb11689d4bf 100644
--- a/drivers/net/wireless/mediatek/mt76/scan.c
+++ b/drivers/net/wireless/mediatek/mt76/scan.c
@@ -16,10 +16,17 @@ static void mt76_scan_complete(struct mt76_dev *dev, bool abort)
clear_bit(MT76_SCANNING, &phy->state);
- if (dev->scan.chan && phy->main_chandef.chan && phy->offchannel &&
+ /* Re-program the operating channel even when the scan never left it:
+ * any channel set during the scan ran with MT76_SCANNING held, which
+ * left DFS radar detection disabled
+ */
+ if (phy->main_chandef.chan &&
!test_bit(MT76_MCU_RESET, &dev->phy.state)) {
+ bool offchannel = phy->offchannel;
+
mt76_set_channel(phy, &phy->main_chandef, false);
- mt76_offchannel_notify(phy, false);
+ if (offchannel)
+ mt76_offchannel_notify(phy, false);
}
mt76_put_vif_phy_link(phy, dev->scan.vif, dev->scan.mlink);
memset(&dev->scan, 0, sizeof(dev->scan));
@@ -211,6 +218,7 @@ int mt76_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
dev->scan.vif = vif;
dev->scan.phy = phy;
dev->scan.mlink = mlink;
+ set_bit(MT76_SCANNING, &phy->state);
ieee80211_queue_delayed_work(dev->phy.hw, &dev->scan_work, 0);
out:
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 28/29] wifi: mt76: serialize scan-link teardown with dev->mutex
From: Felix Fietkau @ 2026-07-24 12:48 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
From: Chad Monroe <chad@monroe.io>
The offchannel scan link is allocated in mt76_hw_scan() under
dev->mutex, but torn down without it: mt76_scan_complete() runs from
mt76_scan_work() on the mac80211 workqueue, or from mt76_abort_scan(),
and calls mt76_put_vif_phy_link(), whose vif_link_remove clears the
per-phy omac_mask and the device-wide vif_mask/mld_idx_mask with plain
read-modify-write. A vif link add or remove for another interface,
running concurrently under dev->mutex, can interleave with these
unlocked writes and lose an update: a cleared bit belonging to a live
link gets handed out again (two links sharing an omac/bss/wcid index,
breaking own-MAC unicast RX for the first one), or a freed bit stays
set until reboot and eventually exhausts the index space.
Take dev->mutex around the scan completion, mirroring the ROC teardown
in mt76_roc_complete_work()/mt76_abort_roc(), and switch the channel
restore to __mt76_set_channel() since the caller now holds the lock.
mt76_abort_scan() keeps cancelling the scan work before taking the
mutex, so the work-vs-abort ordering is unchanged.
Signed-off-by: Chad Monroe <chad@monroe.io>
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/scan.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/scan.c b/drivers/net/wireless/mediatek/mt76/scan.c
index 3cb11689d4bf..3594b599662d 100644
--- a/drivers/net/wireless/mediatek/mt76/scan.c
+++ b/drivers/net/wireless/mediatek/mt76/scan.c
@@ -11,6 +11,8 @@ static void mt76_scan_complete(struct mt76_dev *dev, bool abort)
.aborted = abort,
};
+ lockdep_assert_held(&dev->mutex);
+
if (!phy)
return;
@@ -24,7 +26,7 @@ static void mt76_scan_complete(struct mt76_dev *dev, bool abort)
!test_bit(MT76_MCU_RESET, &dev->phy.state)) {
bool offchannel = phy->offchannel;
- mt76_set_channel(phy, &phy->main_chandef, false);
+ __mt76_set_channel(phy, &phy->main_chandef, false);
if (offchannel)
mt76_offchannel_notify(phy, false);
}
@@ -41,7 +43,10 @@ void mt76_abort_scan(struct mt76_dev *dev)
spin_unlock_bh(&dev->scan_lock);
cancel_delayed_work_sync(&dev->scan_work);
+
+ mutex_lock(&dev->mutex);
mt76_scan_complete(dev, true);
+ mutex_unlock(&dev->mutex);
}
EXPORT_SYMBOL_GPL(mt76_abort_scan);
@@ -139,7 +144,9 @@ void mt76_scan_work(struct work_struct *work)
goto probe;
if (dev->scan.chan_idx >= req->n_channels) {
+ mutex_lock(&dev->mutex);
mt76_scan_complete(dev, false);
+ mutex_unlock(&dev->mutex);
return;
}
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 24/29] mt76: mt7915: trigger L1 SER on PLE MDP RIOC hang
From: Felix Fietkau @ 2026-07-24 12:48 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
From: Chad Monroe <chad@monroe.io>
WM firmware can enter a partial failure state where RX is hung.
Detect this condition by monitoring SER_PLE_ERR_1 for MDP_RIOC_HANG_ERR
and trigger L1 SER to restore operation. Use transition detection on the
error bit to fire only once per new occurrence, preventing an infinite
SER loop when the bit remains set across checks.
Signed-off-by: Chad Monroe <chad@monroe.io>
Suggested-by: Ryder Lee <ryder.lee@mediatek.com>
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7915/mac.c | 13 ++++++++++++-
drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h | 1 +
drivers/net/wireless/mediatek/mt76/mt7915/regs.h | 1 +
3 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
index 350d4573ced2..df6359c88b18 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
@@ -1934,7 +1934,7 @@ void mt7915_mac_update_stats(struct mt7915_phy *phy)
static void mt7915_mac_severe_check(struct mt7915_phy *phy)
{
struct mt7915_dev *dev = phy->dev;
- u32 trb;
+ u32 trb, ple_err;
if (!phy->omac_mask)
return;
@@ -1954,6 +1954,17 @@ static void mt7915_mac_severe_check(struct mt7915_phy *phy)
phy->mt76->band_idx);
phy->trb_ts = trb;
+
+ ple_err = mt76_rr(dev, MT_SWDEF_PLE1_STATS);
+ if ((ple_err & MT_SWDEF_PLE1_MDP_RIOC_HANG_ERR) &&
+ !(dev->ple1_sts & MT_SWDEF_PLE1_MDP_RIOC_HANG_ERR)) {
+ dev_warn(dev->mt76.dev,
+ "band%d: PLE error 0x%x detected, triggering L1 SER\n",
+ phy->mt76->band_idx, ple_err);
+ mt7915_mcu_set_ser(dev, SER_RECOVER, SER_SET_RECOVER_L1,
+ phy->mt76->band_idx);
+ }
+ dev->ple1_sts = ple_err;
}
void mt7915_mac_sta_rc_work(struct work_struct *work)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h b/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h
index 92e0f9f0169c..0f7e871dc7a1 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h
@@ -296,6 +296,7 @@ struct mt7915_dev {
spinlock_t reg_lock;
u32 hw_pattern;
+ u32 ple1_sts;
bool dbdc_support;
bool flash_mode;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/regs.h b/drivers/net/wireless/mediatek/mt76/mt7915/regs.h
index 307bf6a75674..dc7bb9a1f223 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/regs.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/regs.h
@@ -1044,6 +1044,7 @@ enum offs_rev {
#define MT_SWDEF_SER_STATS MT_SWDEF(0x040)
#define MT_SWDEF_PLE_STATS MT_SWDEF(0x044)
#define MT_SWDEF_PLE1_STATS MT_SWDEF(0x048)
+#define MT_SWDEF_PLE1_MDP_RIOC_HANG_ERR BIT(3)
#define MT_SWDEF_PLE_AMSDU_STATS MT_SWDEF(0x04C)
#define MT_SWDEF_PSE_STATS MT_SWDEF(0x050)
#define MT_SWDEF_PSE1_STATS MT_SWDEF(0x054)
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 25/29] wifi: mt76: mt7996: free vif links after clearing wcid entries on full reset
From: Felix Fietkau @ 2026-07-24 12:48 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
mt7996_mac_reset_vif_iter() queues non-default vif links for kfree_rcu
while dev->wcid[] still holds pointers to the wcid embedded in each
freed link; mt76_reset_device() then dereferences those entries and
runs mt76_wcid_cleanup() on them. If a grace period elapses in between,
the cleanup operates on freed memory.
Run mt76_reset_device() first, so the wcid entries are cleaned up and
cleared while the links are still valid.
Fixes: ace5d3b6b49e ("wifi: mt76: mt7996: improve hardware restart reliability")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
index 544da01b0411..ce8c5a5ea8cc 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
@@ -2485,10 +2485,10 @@ mt7996_mac_full_reset(struct mt7996_dev *dev)
phy->omac_mask = 0;
ieee80211_iterate_stations_atomic(hw, mt7996_mac_reset_sta_iter, dev);
+ mt76_reset_device(&dev->mt76);
ieee80211_iterate_active_interfaces_atomic(hw,
IEEE80211_IFACE_SKIP_SDATA_NOT_IN_DRIVER,
mt7996_mac_reset_vif_iter, dev);
- mt76_reset_device(&dev->mt76);
INIT_LIST_HEAD(&dev->sta_rc_list);
INIT_LIST_HEAD(&dev->twt_list);
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 23/29] wifi: mt76: mt7615: fix NULL deref in mt7615_mac_set_rates
From: Felix Fietkau @ 2026-07-24 12:48 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
mt7615_tx() falls back to the vif BSS wcid when mac80211 hands a frame
over without a station, e.g. while a station is being torn down, and to
the global wcid when there is no vif either. Both tx_prepare_skb
implementations derive a mt7615_sta from that wcid unconditionally and,
if the rate control probe flag is set, pass it to mt7615_mac_set_rates(),
which dereferences the NULL vif backpointer of the per-vif embedded sta:
Unable to handle kernel read from unreadable memory at virtual
address 0000000000000002
...
pc : mt7615_mac_set_rates
lr : mt7615_tx_prepare_skb
Neither entry is rate controlled by mac80211: the embedded sta has an
empty rate set, and for the global wcid the container_of does not yield a
valid mt7615_sta at all. Only resolve the sta for wcids that belong to a
station.
Reported-by: Chad Monroe <chad@monroe.io>
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7615/pci_mac.c | 5 +++--
drivers/net/wireless/mediatek/mt76/mt7615/usb_sdio.c | 5 +++--
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/pci_mac.c b/drivers/net/wireless/mediatek/mt76/mt7615/pci_mac.c
index 53cb1eed1e4f..0aca590f2bae 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7615/pci_mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7615/pci_mac.c
@@ -68,10 +68,11 @@ int mt7615_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
int pid, id;
u8 *txwi = (u8 *)txwi_ptr;
struct mt76_txwi_cache *t;
- struct mt7615_sta *msta;
+ struct mt7615_sta *msta = NULL;
void *txp;
- msta = wcid ? container_of(wcid, struct mt7615_sta, wcid) : NULL;
+ if (wcid && wcid->sta)
+ msta = container_of(wcid, struct mt7615_sta, wcid);
if (!wcid)
wcid = &dev->mt76.global_wcid;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/usb_sdio.c b/drivers/net/wireless/mediatek/mt76/mt7615/usb_sdio.c
index f4169de939c4..9525d8062b44 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7615/usb_sdio.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7615/usb_sdio.c
@@ -187,10 +187,11 @@ int mt7663_usb_sdio_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
struct sk_buff *skb = tx_info->skb;
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
struct ieee80211_key_conf *key = info->control.hw_key;
- struct mt7615_sta *msta;
+ struct mt7615_sta *msta = NULL;
int pad, err, pktid;
- msta = wcid ? container_of(wcid, struct mt7615_sta, wcid) : NULL;
+ if (wcid && wcid->sta)
+ msta = container_of(wcid, struct mt7615_sta, wcid);
if (!wcid)
wcid = &dev->mt76.global_wcid;
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 22/29] wifi: mt76: fix stuck TX queues after SER with no active interfaces
From: Felix Fietkau @ 2026-07-24 12:48 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
From: Chad Monroe <chad@monroe.io>
When a firmware watchdog triggers full SER recovery before any VAPs
are up, mac80211 skips drv_reconfig_complete() because open_count is
zero. This leaves the DRIVER queue stop reason set permanently,
blocking all TX when interfaces eventually start.
Signed-off-by: Chad Monroe <chad@monroe.io>
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7915/mac.c | 4 ++++
drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 2 ++
2 files changed, 6 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
index ebe1376ffc2c..350d4573ced2 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
@@ -1469,6 +1469,10 @@ mt7915_mac_full_reset(struct mt7915_dev *dev)
mutex_unlock(&dev->mt76.mutex);
+ ieee80211_wake_queues(mt76_hw(dev));
+ if (ext_phy)
+ ieee80211_wake_queues(ext_phy->hw);
+
ieee80211_restart_hw(mt76_hw(dev));
if (ext_phy)
ieee80211_restart_hw(ext_phy->hw);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
index 96589aea1548..544da01b0411 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
@@ -2512,6 +2512,8 @@ mt7996_mac_full_reset(struct mt7996_dev *dev)
mutex_unlock(&dev->mt76.mutex);
+ ieee80211_wake_queues(mt76_hw(dev));
+
ieee80211_restart_hw(mt76_hw(dev));
}
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 20/29] wifi: mt76: fix queue reg access when building WED and NPU together
From: Felix Fietkau @ 2026-07-24 12:48 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
Move the offload specific parts of Q_READ/Q_WRITE into
mt76_dma_handle_read/write, which return false to fall back to
readl/writel. The WED and NPU #ifdefs now live inside those helpers
instead of selecting between mutually exclusive macro definitions, so a
kernel with both enabled supports both at runtime.
Also fixes the NPU path dereferencing a hardcoded q instead of the macro
argument.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/dma.h | 110 ++++++++++++-----------
1 file changed, 57 insertions(+), 53 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/dma.h b/drivers/net/wireless/mediatek/mt76/dma.h
index a2cf82cfdaaa..7694764febe7 100644
--- a/drivers/net/wireless/mediatek/mt76/dma.h
+++ b/drivers/net/wireless/mediatek/mt76/dma.h
@@ -5,6 +5,8 @@
#ifndef __MT76_DMA_H
#define __MT76_DMA_H
+#include <linux/regmap.h>
+
#define DMA_DUMMY_DATA ((void *)~0)
#define MT_RING_SIZE 0x10
@@ -46,73 +48,75 @@
#define MT_FCE_INFO_LEN 4
#define MT_RX_RXWI_LEN 32
+static inline bool
+mt76_dma_handle_read(struct mt76_queue *q, u32 offset, u32 *val)
+{
#if IS_ENABLED(CONFIG_NET_MEDIATEK_SOC_WED)
+ if (q->flags & MT_QFLAG_WED) {
+ *val = mtk_wed_device_reg_read(q->wed, q->wed_regs + offset);
+
+ return true;
+ }
+#endif
+#if IS_ENABLED(CONFIG_MT76_NPU)
+ if (q->flags & MT_QFLAG_NPU) {
+ struct airoha_npu *npu;
+
+ *val = 0;
+ rcu_read_lock();
+ npu = rcu_dereference(q->dev->mmio.npu);
+ if (npu)
+ regmap_read(npu->regmap, q->wed_regs + offset, val);
+ rcu_read_unlock();
+
+ return true;
+ }
+#endif
+
+ return false;
+}
+
+static inline bool
+mt76_dma_handle_write(struct mt76_queue *q, u32 offset, u32 val)
+{
+#if IS_ENABLED(CONFIG_NET_MEDIATEK_SOC_WED)
+ if (q->flags & MT_QFLAG_WED) {
+ mtk_wed_device_reg_write(q->wed, q->wed_regs + offset, val);
+
+ return true;
+ }
+#endif
+#if IS_ENABLED(CONFIG_MT76_NPU)
+ if (q->flags & MT_QFLAG_NPU) {
+ struct airoha_npu *npu;
+
+ rcu_read_lock();
+ npu = rcu_dereference(q->dev->mmio.npu);
+ if (npu)
+ regmap_write(npu->regmap, q->wed_regs + offset, val);
+ rcu_read_unlock();
+
+ return true;
+ }
+#endif
+
+ return false;
+}
#define Q_READ(_q, _field) ({ \
u32 _offset = offsetof(struct mt76_queue_regs, _field); \
u32 _val; \
- if ((_q)->flags & MT_QFLAG_WED) \
- _val = mtk_wed_device_reg_read((_q)->wed, \
- ((_q)->wed_regs + \
- _offset)); \
- else \
+ if (!mt76_dma_handle_read(_q, _offset, &_val)) \
_val = readl(&(_q)->regs->_field); \
_val; \
})
#define Q_WRITE(_q, _field, _val) do { \
u32 _offset = offsetof(struct mt76_queue_regs, _field); \
- if ((_q)->flags & MT_QFLAG_WED) \
- mtk_wed_device_reg_write((_q)->wed, \
- ((_q)->wed_regs + _offset), \
- _val); \
- else \
+ if (!mt76_dma_handle_write(_q, _offset, _val)) \
writel(_val, &(_q)->regs->_field); \
} while (0)
-#elif IS_ENABLED(CONFIG_MT76_NPU)
-
-#define Q_READ(_q, _field) ({ \
- u32 _offset = offsetof(struct mt76_queue_regs, _field); \
- u32 _val = 0; \
- if ((_q)->flags & MT_QFLAG_NPU) { \
- struct airoha_npu *npu; \
- \
- rcu_read_lock(); \
- npu = rcu_dereference(q->dev->mmio.npu); \
- if (npu) \
- regmap_read(npu->regmap, \
- ((_q)->wed_regs + _offset), &_val); \
- rcu_read_unlock(); \
- } else { \
- _val = readl(&(_q)->regs->_field); \
- } \
- _val; \
-})
-
-#define Q_WRITE(_q, _field, _val) do { \
- u32 _offset = offsetof(struct mt76_queue_regs, _field); \
- if ((_q)->flags & MT_QFLAG_NPU) { \
- struct airoha_npu *npu; \
- \
- rcu_read_lock(); \
- npu = rcu_dereference(q->dev->mmio.npu); \
- if (npu) \
- regmap_write(npu->regmap, \
- ((_q)->wed_regs + _offset), _val); \
- rcu_read_unlock(); \
- } else { \
- writel(_val, &(_q)->regs->_field); \
- } \
-} while (0)
-
-#else
-
-#define Q_READ(_q, _field) readl(&(_q)->regs->_field)
-#define Q_WRITE(_q, _field, _val) writel(_val, &(_q)->regs->_field)
-
-#endif
-
struct mt76_desc {
__le32 buf0;
__le32 ctrl;
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 21/29] wifi: mt76: mt7996: select net_setup_tc handler at runtime
From: Felix Fietkau @ 2026-07-24 12:48 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
From: Chad Monroe <chad@monroe.io>
The net_setup_tc callback is chosen at compile time and the WED
handler shadows the NPU one when CONFIG_NET_MEDIATEK_SOC_WED is
enabled. mt76_wed_net_setup_tc() returns -EOPNOTSUPP without an
active WED device, so on boards using the Airoha NPU with a WED
enabled kernel the tc offload block is never bound and PPE flow
offload for wireless traffic is silently disabled.
Dispatch on the active offload backend instead: use the WED handler
when a WED device is attached and fall back to the NPU handler
otherwise. Builds without MT76_NPU keep the old behavior through the
mt76_npu_net_setup_tc() stub.
Signed-off-by: Chad Monroe <chad@monroe.io>
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
.../net/wireless/mediatek/mt76/mt7996/main.c | 23 +++++++++++++++----
1 file changed, 19 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
index f436109ac0a8..ce7aede6201e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
@@ -2443,6 +2443,23 @@ mt7996_net_fill_forward_path(struct ieee80211_hw *hw,
return 0;
}
+#if defined(CONFIG_NET_MEDIATEK_SOC_WED) || defined(CONFIG_MT7996_NPU)
+static int
+mt7996_net_setup_tc(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ struct net_device *netdev, enum tc_setup_type type,
+ void *type_data)
+{
+#ifdef CONFIG_NET_MEDIATEK_SOC_WED
+ struct mt7996_dev *dev = mt7996_hw_dev(hw);
+
+ if (mtk_wed_device_active(&dev->mt76.mmio.wed))
+ return mt76_wed_net_setup_tc(hw, vif, netdev, type,
+ type_data);
+#endif
+ return mt76_npu_net_setup_tc(hw, vif, netdev, type, type_data);
+}
+#endif
+
static int
mt7996_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
u16 old_links, u16 new_links,
@@ -2570,10 +2587,8 @@ const struct ieee80211_ops mt7996_ops = {
#endif
.set_radar_background = mt7996_set_radar_background,
.net_fill_forward_path = mt7996_net_fill_forward_path,
-#ifdef CONFIG_NET_MEDIATEK_SOC_WED
- .net_setup_tc = mt76_wed_net_setup_tc,
-#elif defined(CONFIG_MT7996_NPU)
- .net_setup_tc = mt76_npu_net_setup_tc,
+#if defined(CONFIG_NET_MEDIATEK_SOC_WED) || defined(CONFIG_MT7996_NPU)
+ .net_setup_tc = mt7996_net_setup_tc,
#endif
.change_vif_links = mt7996_change_vif_links,
.change_sta_links = mt7996_mac_sta_change_links,
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 18/29] wifi: mt76: mt7996: skip key upload when adding an offchannel link
From: Felix Fietkau @ 2026-07-24 12:48 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
No hw keys are ever uploaded for scanning/roc links and the link remove
path already skips the key iteration for them. The add path still runs
it, and since mt7996_set_hw_key() resolves the target through
mvif->link[link_id] rather than the offchannel link, starting a scan on
another band re-uploads the group keys of the link sharing the same
link_id, re-sending its BSS cipher info and, for BIGTK with beacon
protection on an AP link, toggling its beacons off and on.
Skip the key iteration for offchannel links, mirroring the remove path.
Fixes: 69d54ce7491d ("wifi: mt76: mt7996: switch to single multi-radio wiphy")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7996/main.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
index b6717d4e4dda..f436109ac0a8 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
@@ -372,7 +372,8 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif,
CONN_STATE_PORT_SECURE, true);
rcu_assign_pointer(dev->mt76.wcid[idx], &msta_link->wcid);
- ieee80211_iter_keys(mphy->hw, vif, mt7996_key_iter, &it);
+ if (!mlink->wcid->offchannel)
+ ieee80211_iter_keys(mphy->hw, vif, mt7996_key_iter, &it);
if (!mlink->wcid->offchannel) {
if (vif->txq &&
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 17/29] wifi: mt76: mt7915: unlink TWT flow if the MCU rejects the agreement
From: Felix Fietkau @ 2026-07-24 12:48 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
The flow is added to dev->twt_list before sending the agreement to the
firmware, but the error path leaves it linked while flowid_mask is
never set. The flow slot can then be reused and memset while still on
the list, corrupting twt_list, and station removal leaves a dangling
entry behind that mt7915_mac_twt_sched_list_add() later walks.
Fixes: 3782b69d03e7 ("mt76: mt7915: introduce mt7915_mac_add_twt_setup routine")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7915/mac.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
index 1154d3c4b811..ebe1376ffc2c 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
@@ -2350,8 +2350,10 @@ void mt7915_mac_add_twt_setup(struct ieee80211_hw *hw,
}
flow->tsf = le64_to_cpu(twt_agrt->twt);
- if (mt7915_mcu_twt_agrt_update(dev, msta->vif, flow, MCU_TWT_AGRT_ADD))
+ if (mt7915_mcu_twt_agrt_update(dev, msta->vif, flow, MCU_TWT_AGRT_ADD)) {
+ list_del(&flow->list);
goto unlock;
+ }
setup_cmd = TWT_SETUP_CMD_ACCEPT;
dev->twt.table_mask |= BIT(table_id);
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 19/29] wifi: mt76: mt7996: wake MCU waiters before aborting scan in L1 SER
From: Felix Fietkau @ 2026-07-24 12:48 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
The L1 reset path calls mt76_abort_scan() between setting MT76_MCU_RESET
and waking mcu.wait. A scan work blocked on an in-flight MCU command
does not re-evaluate its wait condition until woken, so the
cancel_delayed_work_sync() inside the abort sleeps out the full MCU
timeout before recovery can proceed, adding several seconds of SER
latency. mt7996_mac_full_reset() and the mt7915 counterpart already
order the wake-up first.
Wake mcu.wait immediately after setting MT76_MCU_RESET so in-flight
commands bail out before the abort synchronises against them.
Fixes: b36d55610215 ("wifi: mt76: abort scan/roc on hw restart")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
index 4449dde33f4e..96589aea1548 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
@@ -2567,8 +2567,8 @@ void mt7996_mac_reset_work(struct work_struct *work)
set_bit(MT76_RESET, &dev->mphy.state);
set_bit(MT76_MCU_RESET, &dev->mphy.state);
- mt76_abort_scan(&dev->mt76);
wake_up(&dev->mt76.mcu.wait);
+ mt76_abort_scan(&dev->mt76);
cancel_work_sync(&dev->wed_rro.work);
mt7996_for_each_phy(dev, phy) {
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 16/29] wifi: mt76: mt7996: leave PS when a 4-address connection is established
From: Felix Fietkau @ 2026-07-24 12:48 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
From: Peter Chiu <chui-hao.chiu@mediatek.com>
Because 4 address non-AMSDU packets do not have a bssid field, the
hardware cannot get the bssid. Without the bssid, stations are not
able to leave PS mode due to HW design. Wake up non-setup links when
4-address mode is established to prevent this issue.
mt7992 and mt7990 handle this via the BSSID mapping band config
instead, so restrict the command to mt7996.
Signed-off-by: Peter Chiu <chui-hao.chiu@mediatek.com>
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
.../wireless/mediatek/mt76/mt76_connac_mcu.h | 1 +
.../net/wireless/mediatek/mt76/mt7996/main.c | 8 ++++++++
.../net/wireless/mediatek/mt76/mt7996/mcu.c | 18 ++++++++++++++++++
.../net/wireless/mediatek/mt76/mt7996/mcu.h | 6 ++++++
.../net/wireless/mediatek/mt76/mt7996/mt7996.h | 2 ++
5 files changed, 35 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
index 8198efc6c05d..51380849d24e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
@@ -867,6 +867,7 @@ enum {
STA_REC_HDRT = 0x28,
STA_REC_EML_OP = 0x29,
STA_REC_HDR_TRANS = 0x2B,
+ STA_REC_PS_LEAVE = 0x45,
STA_REC_MAX_NUM
};
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
index 57afe4e81666..b6717d4e4dda 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
@@ -2067,6 +2067,14 @@ static void mt7996_sta_set_decap_offload(struct ieee80211_hw *hw,
continue;
mt7996_mcu_wtbl_update_hdr_trans(dev, vif, link, msta_link);
+
+ /* HW cannot derive the BSSID from 4-address non-AMSDU frames,
+ * so PS exit is missed on links other than the setup link.
+ * Let the firmware wake those links instead.
+ */
+ if (enabled && msta->deflink_id != link_id &&
+ is_mt7996(&dev->mt76))
+ mt7996_mcu_ps_leave(dev, link, msta_link);
}
mutex_unlock(&dev->mt76.mutex);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
index 69676a605549..57117ce69cbb 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
@@ -5184,6 +5184,24 @@ int mt7996_mcu_wtbl_update_hdr_trans(struct mt7996_dev *dev,
MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true);
}
+int mt7996_mcu_ps_leave(struct mt7996_dev *dev, struct mt7996_vif_link *link,
+ struct mt7996_sta_link *msta_link)
+{
+ struct sk_buff *skb;
+
+ skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &link->mt76,
+ &msta_link->wcid,
+ MT7996_STA_UPDATE_MAX_SIZE);
+ if (IS_ERR(skb))
+ return PTR_ERR(skb);
+
+ mt76_connac_mcu_add_tlv(skb, STA_REC_PS_LEAVE,
+ sizeof(struct sta_rec_ps_leave));
+
+ return mt76_mcu_skb_send_msg(&dev->mt76, skb,
+ MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true);
+}
+
int mt7996_mcu_set_fixed_rate_table(struct mt7996_phy *phy, u8 table_idx,
u16 rate_idx, bool beacon)
{
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h
index b53a9e71c281..1487d33a8dbb 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h
@@ -680,6 +680,12 @@ struct sta_rec_hdr_trans {
u8 mesh;
} __packed;
+struct sta_rec_ps_leave {
+ __le16 tag;
+ __le16 len;
+ u8 __rsv[4];
+} __packed;
+
struct sta_rec_mld_setup {
__le16 tag;
__le16 len;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
index dcacc7d06e85..2a0cdb56f822 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
@@ -928,6 +928,8 @@ int mt7996_mcu_wtbl_update_hdr_trans(struct mt7996_dev *dev,
struct ieee80211_vif *vif,
struct mt7996_vif_link *link,
struct mt7996_sta_link *msta_link);
+int mt7996_mcu_ps_leave(struct mt7996_dev *dev, struct mt7996_vif_link *link,
+ struct mt7996_sta_link *msta_link);
int mt7996_mcu_cp_support(struct mt7996_dev *dev, u8 mode);
int mt7996_mcu_set_emlsr_mode(struct mt7996_dev *dev,
struct ieee80211_vif *vif,
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 14/29] wifi: mt76: only consume the WO drop bit on WED v2 devices
From: Felix Fietkau @ 2026-07-24 12:47 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
The RX path is handled by the WO MCU only on WED v2 hardware. On WED
v3 the same buf1 bit does not carry drop information, so evaluating it
there causes spurious RX drops.
Fixes: e4d2b8bcac11 ("wifi: mt76: drop the incorrect scatter and gather frame")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/dma.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c
index 1658c37835cf..a67880a9281c 100644
--- a/drivers/net/wireless/mediatek/mt76/dma.c
+++ b/drivers/net/wireless/mediatek/mt76/dma.c
@@ -547,8 +547,13 @@ mt76_dma_get_buf(struct mt76_dev *dev, struct mt76_queue *q, int idx,
t->ptr = NULL;
mt76_put_rxwi(dev, t);
- if (drop)
+#ifdef CONFIG_NET_MEDIATEK_SOC_WED
+ /* the WO MCU owns the RX path only on WED v2, on newer
+ * versions this buf1 bit carries no drop information
+ */
+ if (drop && dev->mmio.wed.version == 2)
*drop |= !!(buf1 & MT_DMA_CTL_WO_DROP);
+#endif
} else {
dma_sync_single_for_cpu(dev->dma_dev, e->dma_addr[0],
SKB_WITH_OVERHEAD(q->buf_size),
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 15/29] wifi: mt76: mt7996: add mcu command to set bssid mapping address
From: Felix Fietkau @ 2026-07-24 12:47 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
From: Peter Chiu <chui-hao.chiu@mediatek.com>
When receiving a 4 address non-AMSDU packet, there is no bssid in the
address fields, which breaks powersave handling for 4-address peers.
Set the mcu command to use A1 as bssid when receiving 4 address
non-AMSDU packets on mt7992 and mt7990.
Also skip mt7996_mac_init_band() for invalid bands, so the command is
only sent for bands that actually exist on the device.
Signed-off-by: Peter Chiu <chui-hao.chiu@mediatek.com>
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
.../net/wireless/mediatek/mt76/mt7996/init.c | 6 +++++
.../net/wireless/mediatek/mt76/mt7996/mcu.c | 26 +++++++++++++++++++
.../net/wireless/mediatek/mt76/mt7996/mcu.h | 1 +
.../wireless/mediatek/mt76/mt7996/mt7996.h | 1 +
4 files changed, 34 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c
index a344829f5a01..ee1f7a9f4851 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c
@@ -562,6 +562,9 @@ mt7996_mac_init_band(struct mt7996_dev *dev, u8 band)
{
u32 mask, set;
+ if (!mt7996_band_valid(dev, band))
+ return;
+
/* clear estimated value of EIFS for Rx duration & OBSS time */
mt76_wr(dev, MT_WF_RMAC_RSVD0(band), MT_WF_RMAC_RSVD0_EIFS_CLR);
@@ -593,6 +596,9 @@ mt7996_mac_init_band(struct mt7996_dev *dev, u8 band)
* MT_AGG_ACR_PPDU_TXS2H (PPDU format) even though ACR bit is set.
*/
mt76_set(dev, MT_AGG_ACR4(band), MT_AGG_ACR_PPDU_TXS2H);
+
+ if (!is_mt7996(&dev->mt76))
+ mt7996_mcu_set_bssid_mapping_addr(&dev->mt76, band);
}
static void mt7996_mac_init_basic_rates(struct mt7996_dev *dev)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
index 09fcddb8e21d..69676a605549 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
@@ -5042,6 +5042,32 @@ int mt7996_mcu_twt_agrt_update(struct mt7996_dev *dev,
&req, sizeof(req), true);
}
+int mt7996_mcu_set_bssid_mapping_addr(struct mt76_dev *dev, u8 band_idx)
+{
+ enum {
+ BSSID_MAPPING_ADDR1,
+ BSSID_MAPPING_ADDR2,
+ BSSID_MAPPING_ADDR3,
+ };
+ struct {
+ u8 band_idx;
+ u8 _rsv1[3];
+
+ __le16 tag;
+ __le16 len;
+ u8 addr;
+ u8 _rsv2[3];
+ } __packed req = {
+ .band_idx = band_idx,
+ .tag = cpu_to_le16(UNI_BAND_CONFIG_BSSID_MAPPING_ADDR),
+ .len = cpu_to_le16(sizeof(req) - 4),
+ .addr = BSSID_MAPPING_ADDR1,
+ };
+
+ return mt76_mcu_send_msg(dev, MCU_WM_UNI_CMD(BAND_CONFIG),
+ &req, sizeof(req), true);
+}
+
int mt7996_mcu_set_rts_thresh(struct mt7996_phy *phy, u32 val)
{
struct {
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h
index c673e986ecb5..b53a9e71c281 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h
@@ -927,6 +927,7 @@ enum {
UNI_BAND_CONFIG_RADIO_ENABLE,
UNI_BAND_CONFIG_RTS_THRESHOLD = 0x08,
UNI_BAND_CONFIG_MAC_ENABLE_CTRL = 0x0c,
+ UNI_BAND_CONFIG_BSSID_MAPPING_ADDR = 0x12,
};
enum {
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
index 60397a1b1a2b..dcacc7d06e85 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
@@ -772,6 +772,7 @@ int mt7996_mcu_set_protection(struct mt7996_phy *phy, struct mt7996_vif_link *li
u8 ht_mode, bool use_cts_prot);
int mt7996_mcu_set_timing(struct mt7996_phy *phy, struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link_conf);
+int mt7996_mcu_set_bssid_mapping_addr(struct mt76_dev *dev, u8 band_idx);
int mt7996_mcu_get_chan_mib_info(struct mt7996_phy *phy, bool chan_switch);
int mt7996_mcu_get_temperature(struct mt7996_phy *phy);
int mt7996_mcu_set_thermal_throttling(struct mt7996_phy *phy, u8 state);
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 13/29] wifi: mt76: mt7996: add missing rdd_idx check when enabling background radar
From: Felix Fietkau @ 2026-07-24 12:47 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
Add the missing rdd idx check (< 0) in
mt7996_mcu_rdd_background_enable(). mt7996_get_rdd_idx() returns -1
for phys without 5 GHz support, and the negative index was passed to
the RDD MCU command unchecked.
Fixes: 1529e335f93d ("wifi: mt76: mt7996: rework radar HWRDD idx")
Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
index 1b334cbf6d72..09fcddb8e21d 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
@@ -4016,6 +4016,9 @@ int mt7996_mcu_rdd_background_enable(struct mt7996_phy *phy,
struct mt7996_dev *dev = phy->dev;
int err, region, rdd_idx = mt7996_get_rdd_idx(phy, true);
+ if (rdd_idx < 0)
+ return -EINVAL;
+
if (!chandef) { /* disable offchain */
err = mt7996_mcu_rdd_cmd(dev, RDD_STOP, rdd_idx, 0);
if (err)
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 11/29] wifi: mt76: report data NSS for STBC frames in RX rate decode
From: Felix Fietkau @ 2026-07-24 12:47 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
The RX rate decoder set status->nss straight from the PRXV NSTS field,
which for STBC frames is twice the data spatial-stream count. cfg80211
then reported a doubled RX bitrate in station dumps and radiotap. Halve
nss for STBC, matching the TX status path.
Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices")
Fixes: d832f5e73815 ("mt76: connac: move mt76_connac2_mac_fill_rx_rate in connac module")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c | 4 ++++
drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 4 ++++
2 files changed, 8 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
index cbf6a987cf90..8a8d608dd56e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
@@ -1069,6 +1069,10 @@ int mt76_connac2_mac_fill_rx_rate(struct mt76_dev *dev,
bw = FIELD_GET(MT_CRXV_FRAME_MODE, v2);
}
+ /* the hardware reports NSTS; report the data NSS for STBC frames */
+ if (stbc && nss > 1)
+ nss >>= 1;
+
switch (*mode) {
case MT_PHY_TYPE_CCK:
cck = true;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
index 866752f2fa24..4449dde33f4e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
@@ -294,6 +294,10 @@ mt7996_mac_fill_rx_rate(struct mt7996_dev *dev,
dcm = FIELD_GET(MT_PRXV_DCM, v2);
bw = FIELD_GET(MT_PRXV_FRAME_MODE, v2);
+ /* the hardware reports NSTS; report the data NSS for STBC frames */
+ if (stbc && nss > 1)
+ nss >>= 1;
+
switch (*mode) {
case MT_PHY_TYPE_CCK:
cck = true;
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 10/29] wifi: mt76: cancel reset and rc work on device unregister
From: Felix Fietkau @ 2026-07-24 12:47 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
Both drivers cancelled dump_work on unregister but left reset_work and
rc_work to be flushed only by destroy_workqueue() in mt76_free_device(),
which runs after the hw is unregistered and the hardware stopped. A
reset_work that fires in that window calls ieee80211_restart_hw() and
re-arms mac_work on an unregistered hw, and rc_work touches station state
being torn down. Cancel both up front, alongside dump_work.
Fixes: e57b7901469f ("mt76: add mac80211 driver for MT7915 PCIe-based chipsets")
Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7915/init.c | 2 ++
drivers/net/wireless/mediatek/mt76/mt7996/init.c | 2 ++
2 files changed, 4 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/init.c b/drivers/net/wireless/mediatek/mt76/mt7915/init.c
index 6568d7b6bc0a..0f6346731308 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/init.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/init.c
@@ -1325,6 +1325,8 @@ int mt7915_register_device(struct mt7915_dev *dev)
void mt7915_unregister_device(struct mt7915_dev *dev)
{
cancel_work_sync(&dev->dump_work);
+ cancel_work_sync(&dev->reset_work);
+ cancel_work_sync(&dev->rc_work);
mt7915_unregister_ext_phy(dev);
mt7915_coredump_unregister(dev);
mt7915_unregister_thermal(&dev->phy);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c
index 2ff93bd8b6dc..a344829f5a01 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c
@@ -1806,6 +1806,8 @@ void mt7996_unregister_device(struct mt7996_dev *dev)
{
cancel_work_sync(&dev->dump_work);
cancel_work_sync(&dev->wed_rro.work);
+ cancel_work_sync(&dev->reset_work);
+ cancel_work_sync(&dev->rc_work);
mt7996_unregister_phy(mt7996_phy3(dev));
mt7996_unregister_phy(mt7996_phy2(dev));
mt7996_unregister_thermal(&dev->phy);
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 12/29] wifi: mt76: mt7915: use little-endian for bss_info_ra wire fields
From: Felix Fietkau @ 2026-07-24 12:47 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
train_up_high_thres, train_up_rule_rssi and low_traffic_thres were
declared as host-native short in a firmware-facing TLV and assigned
host-order constants, so on a big-endian host the firmware received
byte-swapped rate-adaptation thresholds. Declare them __le16 and convert
with cpu_to_le16().
Fixes: e57b7901469f ("mt76: add mac80211 driver for MT7915 PCIe-based chipsets")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7915/mcu.c | 6 +++---
drivers/net/wireless/mediatek/mt76/mt7915/mcu.h | 6 +++---
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c
index 459dbcc9d90e..c7c1b37e5bbf 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c
@@ -576,9 +576,9 @@ mt7915_mcu_bss_ra_tlv(struct sk_buff *skb, struct ieee80211_vif *vif,
ra->rx_streams = max_nss;
ra->algo = 4;
ra->train_up_rule = 2;
- ra->train_up_high_thres = 110;
- ra->train_up_rule_rssi = -70;
- ra->low_traffic_thres = 2;
+ ra->train_up_high_thres = cpu_to_le16(110);
+ ra->train_up_rule_rssi = cpu_to_le16(-70);
+ ra->low_traffic_thres = cpu_to_le16(2);
ra->phy_cap = cpu_to_le32(0xfdf);
ra->interval = cpu_to_le32(500);
ra->fast_interval = cpu_to_le32(100);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h
index 22f73a5ed425..7c472062a90e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h
@@ -318,9 +318,9 @@ struct bss_info_ra {
u8 antenna_idx;
u8 train_up_rule;
u8 rsv[3];
- unsigned short train_up_high_thres;
- short train_up_rule_rssi;
- unsigned short low_traffic_thres;
+ __le16 train_up_high_thres;
+ __le16 train_up_rule_rssi;
+ __le16 low_traffic_thres;
__le16 max_phyrate;
__le32 phy_cap;
__le32 interval;
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 09/29] wifi: mt76: mt7996: don't leak MLD group index on remap alloc failure
From: Felix Fietkau @ 2026-07-24 12:47 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
mt7996_change_vif_links() sets the mld_idx_mask group bit before
allocating the remap index. If the remap allocation fails it jumped to
the exit without clearing that bit, permanently consuming one of the 16
MLD group slots. Release the group bit on the error path.
Fixes: 4fb3b4e7d1ca ("wifi: mt76: mt7996: fix MLD group index assignment")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7996/main.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
index 2487b00c3790..57afe4e81666 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
@@ -2458,6 +2458,7 @@ mt7996_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
idx = get_free_idx(dev->mld_remap_idx_mask, 0, 15) - 1;
if (idx < 0) {
+ dev->mld_idx_mask &= ~BIT_ULL(mvif->mld_group_idx);
ret = -ENOSPC;
goto out;
}
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 08/29] wifi: mt76: mt7996: reserve space for the CSA-abort countdown TLV
From: Felix Fietkau @ 2026-07-24 12:47 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
When a CSA countdown is active, mt7996_mcu_beacon_cntdwn() emits two
bss_bcn_cntdwn_tlv entries (the CSA countdown and the CCA-abort BCC), but
MT7996_BEACON_UPDATE_SIZE only reserved one. With MBSSID enabled and a
near-maximum beacon template the extra 8 bytes could push the offload
command past MT7996_MAX_BSS_OFFLOAD_SIZE and trigger skb_over_panic().
Reserve room for both countdown TLVs.
Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7996/mcu.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h
index 8902e16508b7..c673e986ecb5 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h
@@ -917,7 +917,7 @@ enum {
#define MT7996_BEACON_UPDATE_SIZE (sizeof(struct bss_req_hdr) + \
sizeof(struct bss_bcn_content_tlv) + \
4 + MT_TXD_SIZE + \
- sizeof(struct bss_bcn_cntdwn_tlv) + \
+ sizeof(struct bss_bcn_cntdwn_tlv) * 2 + \
sizeof(struct bss_bcn_mbss_tlv))
#define MT7996_MAX_BSS_OFFLOAD_SIZE 2048
#define MT7996_MAX_BEACON_SIZE (MT7996_MAX_BSS_OFFLOAD_SIZE - \
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox