From 58de1f91e033b1fface8d8948984583125f93736 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Thu, 24 Jul 2025 08:48:15 +0800 Subject: [PATCH 001/167] wifi: rtw88: sdio: use indirect IO for device registers before power-on The register REG_SYS_CFG1 is used to determine chip basic information as arguments of following flows, such as download firmware and load PHY parameters, so driver read the value early (before power-on). However, the direct IO is disallowed before power-on, or it causes wrong values, which driver recognizes a chip as a wrong type RF_1T1R, but actually RF_2T2R, causing driver warns: rtw88_8822cs mmc1:0001:1: unsupported rf path (1) Fix it by using indirect IO before power-on. Reported-by: Piotr Oniszczuk Closes: https://lore.kernel.org/linux-wireless/699C22B4-A3E3-4206-97D0-22AB3348EBF6@gmail.com/T/#t Suggested-by: Bitterblue Smith Tested-by: Piotr Oniszczuk Reviewed-by: Martin Blumenstingl Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250724004815.7043-1-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw88/sdio.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c index cc2d4fef3587..99d7c629eac6 100644 --- a/drivers/net/wireless/realtek/rtw88/sdio.c +++ b/drivers/net/wireless/realtek/rtw88/sdio.c @@ -144,6 +144,10 @@ static u32 rtw_sdio_to_io_address(struct rtw_dev *rtwdev, u32 addr, static bool rtw_sdio_use_direct_io(struct rtw_dev *rtwdev, u32 addr) { + if (!test_bit(RTW_FLAG_POWERON, rtwdev->flags) && + !rtw_sdio_is_bus_addr(addr)) + return false; + return !rtw_sdio_is_sdio30_supported(rtwdev) || rtw_sdio_is_bus_addr(addr); } From 26a8bf978ae9cd7688af1d08bc8760674d372e22 Mon Sep 17 00:00:00 2001 From: Bitterblue Smith Date: Fri, 1 Aug 2025 23:08:24 +0300 Subject: [PATCH 002/167] wifi: rtw88: Lock rtwdev->mutex before setting the LED Some users report that the LED blinking breaks AP mode somehow. Most likely the LED code and the dynamic mechanism are trying to access the hardware registers at the same time. Fix it by locking rtwdev->mutex before setting the LED and unlocking it after. Fixes: 4b6652bc6d8d ("wifi: rtw88: Add support for LED blinking") Closes: https://github.com/lwfinger/rtw88/issues/305 Signed-off-by: Bitterblue Smith Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/ed69fa07-8678-4a40-af44-65e7b1862197@gmail.com --- drivers/net/wireless/realtek/rtw88/led.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw88/led.c b/drivers/net/wireless/realtek/rtw88/led.c index 25aa6cbaa728..7f9ace351a5b 100644 --- a/drivers/net/wireless/realtek/rtw88/led.c +++ b/drivers/net/wireless/realtek/rtw88/led.c @@ -6,13 +6,23 @@ #include "debug.h" #include "led.h" -static int rtw_led_set_blocking(struct led_classdev *led, - enum led_brightness brightness) +static void rtw_led_set(struct led_classdev *led, + enum led_brightness brightness) { struct rtw_dev *rtwdev = container_of(led, struct rtw_dev, led_cdev); + mutex_lock(&rtwdev->mutex); + rtwdev->chip->ops->led_set(led, brightness); + mutex_unlock(&rtwdev->mutex); +} + +static int rtw_led_set_blocking(struct led_classdev *led, + enum led_brightness brightness) +{ + rtw_led_set(led, brightness); + return 0; } @@ -37,7 +47,7 @@ void rtw_led_init(struct rtw_dev *rtwdev) return; if (rtw_hci_type(rtwdev) == RTW_HCI_TYPE_PCIE) - led->brightness_set = rtwdev->chip->ops->led_set; + led->brightness_set = rtw_led_set; else led->brightness_set_blocking = rtw_led_set_blocking; From 7e1c44fe4c2e1e01fa47d9490893d95309a99687 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Mon, 4 Aug 2025 09:22:33 +0800 Subject: [PATCH 003/167] wifi: rtw89: print just once for unknown C2H events When driver receives new or unknown C2H events, it print out messages repeatedly once events are received, like rtw89_8922ae 0000:81:00.0: PHY c2h class 2 not support To avoid the thousands of messages, use rtw89_info_once() instead. Also, print out class/func for unknown (undefined) class. Reported-by: Sean Anderson Closes: https://lore.kernel.org/linux-wireless/20250729204437.164320-1-sean.anderson@linux.dev/ Reviewed-by: Sean Anderson Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250804012234.8913-2-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/debug.h | 1 + drivers/net/wireless/realtek/rtw89/mac.c | 7 +++---- drivers/net/wireless/realtek/rtw89/phy.c | 7 +++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/debug.h b/drivers/net/wireless/realtek/rtw89/debug.h index fc690f7c55dc..a364e7adb079 100644 --- a/drivers/net/wireless/realtek/rtw89/debug.h +++ b/drivers/net/wireless/realtek/rtw89/debug.h @@ -56,6 +56,7 @@ static inline void rtw89_debugfs_deinit(struct rtw89_dev *rtwdev) {} #endif #define rtw89_info(rtwdev, a...) dev_info((rtwdev)->dev, ##a) +#define rtw89_info_once(rtwdev, a...) dev_info_once((rtwdev)->dev, ##a) #define rtw89_warn(rtwdev, a...) dev_warn((rtwdev)->dev, ##a) #define rtw89_err(rtwdev, a...) dev_err((rtwdev)->dev, ##a) diff --git a/drivers/net/wireless/realtek/rtw89/mac.c b/drivers/net/wireless/realtek/rtw89/mac.c index 5a5da9d9c0c5..ef17a307b770 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.c +++ b/drivers/net/wireless/realtek/rtw89/mac.c @@ -5813,12 +5813,11 @@ void rtw89_mac_c2h_handle(struct rtw89_dev *rtwdev, struct sk_buff *skb, case RTW89_MAC_C2H_CLASS_ROLE: return; default: - rtw89_info(rtwdev, "MAC c2h class %d not support\n", class); - return; + break; } if (!handler) { - rtw89_info(rtwdev, "MAC c2h class %d func %d not support\n", class, - func); + rtw89_info_once(rtwdev, "MAC c2h class %d func %d not support\n", + class, func); return; } handler(rtwdev, skb, len); diff --git a/drivers/net/wireless/realtek/rtw89/phy.c b/drivers/net/wireless/realtek/rtw89/phy.c index d607577b353c..01a03d2de3ff 100644 --- a/drivers/net/wireless/realtek/rtw89/phy.c +++ b/drivers/net/wireless/realtek/rtw89/phy.c @@ -3626,12 +3626,11 @@ void rtw89_phy_c2h_handle(struct rtw89_dev *rtwdev, struct sk_buff *skb, handler = rtw89_phy_c2h_dm_handler[func]; break; default: - rtw89_info(rtwdev, "PHY c2h class %d not support\n", class); - return; + break; } if (!handler) { - rtw89_info(rtwdev, "PHY c2h class %d func %d not support\n", class, - func); + rtw89_info_once(rtwdev, "PHY c2h class %d func %d not support\n", + class, func); return; } handler(rtwdev, skb, len); From 04a2de8cfc95076d6c65d4d6d06d0f9d964a2105 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Mon, 4 Aug 2025 09:22:34 +0800 Subject: [PATCH 004/167] wifi: rtw89: add dummy C2H handlers for BCN resend and update done Two C2H events are not listed, and driver throws MAC c2h class 0 func 6 not support MAC c2h class 1 func 3 not support Since the implementation in vendor driver does nothing, add two dummy functions for them. Reported-by: Bitterblue Smith Closes: https://lore.kernel.org/linux-wireless/d2d62793-046c-4b55-93ed-1d1f43cff7f2@gmail.com/ Reviewed-by: Sean Anderson Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250804012234.8913-3-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/mac.c | 13 ++++++++++++- drivers/net/wireless/realtek/rtw89/mac.h | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/mac.c b/drivers/net/wireless/realtek/rtw89/mac.c index ef17a307b770..33a7dd9d6f0e 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.c +++ b/drivers/net/wireless/realtek/rtw89/mac.c @@ -5235,6 +5235,11 @@ rtw89_mac_c2h_bcn_cnt(struct rtw89_dev *rtwdev, struct sk_buff *c2h, u32 len) { } +static void +rtw89_mac_c2h_bcn_upd_done(struct rtw89_dev *rtwdev, struct sk_buff *c2h, u32 len) +{ +} + static void rtw89_mac_c2h_pkt_ofld_rsp(struct rtw89_dev *rtwdev, struct sk_buff *skb_c2h, u32 len) @@ -5257,6 +5262,11 @@ rtw89_mac_c2h_pkt_ofld_rsp(struct rtw89_dev *rtwdev, struct sk_buff *skb_c2h, rtw89_complete_cond(wait, cond, &data); } +static void +rtw89_mac_c2h_bcn_resend(struct rtw89_dev *rtwdev, struct sk_buff *c2h, u32 len) +{ +} + static void rtw89_mac_c2h_tx_duty_rpt(struct rtw89_dev *rtwdev, struct sk_buff *skb_c2h, u32 len) { @@ -5646,7 +5656,7 @@ void (* const rtw89_mac_c2h_ofld_handler[])(struct rtw89_dev *rtwdev, [RTW89_MAC_C2H_FUNC_EFUSE_DUMP] = NULL, [RTW89_MAC_C2H_FUNC_READ_RSP] = NULL, [RTW89_MAC_C2H_FUNC_PKT_OFLD_RSP] = rtw89_mac_c2h_pkt_ofld_rsp, - [RTW89_MAC_C2H_FUNC_BCN_RESEND] = NULL, + [RTW89_MAC_C2H_FUNC_BCN_RESEND] = rtw89_mac_c2h_bcn_resend, [RTW89_MAC_C2H_FUNC_MACID_PAUSE] = rtw89_mac_c2h_macid_pause, [RTW89_MAC_C2H_FUNC_SCANOFLD_RSP] = rtw89_mac_c2h_scanofld_rsp, [RTW89_MAC_C2H_FUNC_TX_DUTY_RPT] = rtw89_mac_c2h_tx_duty_rpt, @@ -5661,6 +5671,7 @@ void (* const rtw89_mac_c2h_info_handler[])(struct rtw89_dev *rtwdev, [RTW89_MAC_C2H_FUNC_DONE_ACK] = rtw89_mac_c2h_done_ack, [RTW89_MAC_C2H_FUNC_C2H_LOG] = rtw89_mac_c2h_log, [RTW89_MAC_C2H_FUNC_BCN_CNT] = rtw89_mac_c2h_bcn_cnt, + [RTW89_MAC_C2H_FUNC_BCN_UPD_DONE] = rtw89_mac_c2h_bcn_upd_done, }; static diff --git a/drivers/net/wireless/realtek/rtw89/mac.h b/drivers/net/wireless/realtek/rtw89/mac.h index 241e89983c4a..25fe5e5c8a97 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.h +++ b/drivers/net/wireless/realtek/rtw89/mac.h @@ -419,6 +419,7 @@ enum rtw89_mac_c2h_info_func { RTW89_MAC_C2H_FUNC_DONE_ACK, RTW89_MAC_C2H_FUNC_C2H_LOG, RTW89_MAC_C2H_FUNC_BCN_CNT, + RTW89_MAC_C2H_FUNC_BCN_UPD_DONE = 0x06, RTW89_MAC_C2H_FUNC_INFO_MAX, }; From 5846154126541b27281fbc9aba0c8ee97e22597b Mon Sep 17 00:00:00 2001 From: Akhilesh Patil Date: Fri, 8 Aug 2025 11:09:18 +0530 Subject: [PATCH 005/167] wifi: rtw89: 8852bt: Use standard helper for string choice Use standard helper str_on_off() defined at string_choices.h in _dpk_information() to improve code reusability. Reduce hardcoding of repeated use of the same strings to save code space. Reported-by: kernel test robot Closes: https://lore.kernel.org/r/202507282341.drTGfLWA-lkp@intel.com/ Signed-off-by: Akhilesh Patil Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/aJWNhu9bAkcjEyb4@bhairav-test.ee.iitb.ac.in --- drivers/net/wireless/realtek/rtw89/rtw8852bt_rfk.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bt_rfk.c b/drivers/net/wireless/realtek/rtw89/rtw8852bt_rfk.c index d0e299803225..b01f921b4224 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852bt_rfk.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852bt_rfk.c @@ -1883,8 +1883,8 @@ static void _dpk_information(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy, rtw89_debug(rtwdev, RTW89_DBG_RFK, "[DPK] S%d[%d] (PHY%d): TSSI %s/ DBCC %s/ %s/ CH%d/ %s\n", path, dpk->cur_idx[path], phy, - rtwdev->is_tssi_mode[path] ? "on" : "off", - rtwdev->dbcc_en ? "on" : "off", + str_on_off(rtwdev->is_tssi_mode[path]), + str_on_off(rtwdev->dbcc_en), dpk->bp[path][kidx].band == 0 ? "2G" : dpk->bp[path][kidx].band == 1 ? "5G" : "6G", dpk->bp[path][kidx].ch, From d360551f265e3c942ce06cd6f4d2f7f67741bcbd Mon Sep 17 00:00:00 2001 From: Kuan-Chung Chen Date: Mon, 11 Aug 2025 20:37:37 +0800 Subject: [PATCH 006/167] wifi: rtw89: introduce beacon tracking to improve connection stability In ideal scenario, AP's beacon should transmit at the Target Beacon Transmission Time (TBTT). However, in practice, beacon may be slightly off-schedule. This beacon "drift" prevents the firmware from receiving beacon at the expected TBTT, leading to connection disruptions. To address this, we introduce beacon tracking mechanism to enhance overall connection stability. This mechanism executes the following steps in each cycle (2 seconds): 1) Based on the last 32 received beacons, compute the minimum TBTT offset to use for the next cycle 2) Using the same 32 beacons, calculate the drift of each. A histogram is plotted, and outliers are identified using a boxplot. 3) According to the statistical results from the second step, a maximum receive window size (beacon timeout) is selected to cover approximately 80% of the beacons and applied to the next cycle. Signed-off-by: Kuan-Chung Chen Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250811123744.15361-2-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.c | 457 +++++++++++++++++++++- drivers/net/wireless/realtek/rtw89/core.h | 50 ++- drivers/net/wireless/realtek/rtw89/fw.c | 90 +++++ drivers/net/wireless/realtek/rtw89/fw.h | 27 ++ drivers/net/wireless/realtek/rtw89/mac.c | 22 -- drivers/net/wireless/realtek/rtw89/phy.c | 2 +- drivers/net/wireless/realtek/rtw89/ps.c | 3 + 7 files changed, 619 insertions(+), 32 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index 57590f5577a3..7d522031dfa1 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -2,6 +2,7 @@ /* Copyright(c) 2019-2020 Realtek Corporation */ #include +#include #include #include "cam.h" @@ -272,17 +273,18 @@ rtw89_get_6ghz_span(struct rtw89_dev *rtwdev, u32 center_freq) return NULL; } -bool rtw89_ra_report_to_bitrate(struct rtw89_dev *rtwdev, u8 rpt_rate, u16 *bitrate) +bool rtw89_legacy_rate_to_bitrate(struct rtw89_dev *rtwdev, u8 legacy_rate, u16 *bitrate) { - struct ieee80211_rate rate; + const struct ieee80211_rate *rate; - if (unlikely(rpt_rate >= ARRAY_SIZE(rtw89_bitrates))) { - rtw89_debug(rtwdev, RTW89_DBG_UNEXP, "invalid rpt rate %d\n", rpt_rate); + if (unlikely(legacy_rate >= ARRAY_SIZE(rtw89_bitrates))) { + rtw89_debug(rtwdev, RTW89_DBG_UNEXP, + "invalid legacy rate %d\n", legacy_rate); return false; } - rate = rtw89_bitrates[rpt_rate]; - *bitrate = rate.bitrate; + rate = &rtw89_bitrates[legacy_rate]; + *bitrate = rate->bitrate; return true; } @@ -2221,6 +2223,435 @@ static void rtw89_vif_sync_bcn_tsf(struct rtw89_vif_link *rtwvif_link, WRITE_ONCE(rtwvif_link->sync_bcn_tsf, le64_to_cpu(mgmt->u.beacon.timestamp)); } +static u32 rtw89_bcn_calc_min_tbtt(struct rtw89_dev *rtwdev, u32 tbtt1, u32 tbtt2) +{ + struct rtw89_beacon_track_info *bcn_track = &rtwdev->bcn_track; + u32 close_bcn_intvl_th = bcn_track->close_bcn_intvl_th; + u32 tbtt_diff_th = bcn_track->tbtt_diff_th; + + if (tbtt2 > tbtt1) + swap(tbtt1, tbtt2); + + if (tbtt1 - tbtt2 > tbtt_diff_th) + return tbtt1; + else if (tbtt2 > close_bcn_intvl_th) + return tbtt2; + else if (tbtt1 > close_bcn_intvl_th) + return tbtt1; + else + return tbtt2; +} + +static void rtw89_bcn_cfg_tbtt_offset(struct rtw89_dev *rtwdev, + struct rtw89_vif_link *rtwvif_link) +{ + struct rtw89_beacon_track_info *bcn_track = &rtwdev->bcn_track; + enum rtw89_core_chip_id chip_id = rtwdev->chip->chip_id; + u32 offset = bcn_track->tbtt_offset; + + if (chip_id == RTL8852A || rtw89_is_rtl885xb(rtwdev)) { + const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; + const struct rtw89_port_reg *p = mac->port_base; + u32 bcnspc, val; + + bcnspc = rtw89_read32_port_mask(rtwdev, rtwvif_link, + p->bcn_space, B_AX_BCN_SPACE_MASK); + val = bcnspc - (offset / 1024); + val = u32_encode_bits(val, B_AX_TBTT_SHIFT_OFST_MAG) | + B_AX_TBTT_SHIFT_OFST_SIGN; + + rtw89_write16_port_mask(rtwdev, rtwvif_link, p->tbtt_shift, + B_AX_TBTT_SHIFT_OFST_MASK, val); + + return; + } + + rtw89_fw_h2c_tbtt_tuning(rtwdev, rtwvif_link, offset); +} + +static void rtw89_bcn_update_tbtt_offset(struct rtw89_dev *rtwdev, + struct rtw89_vif_link *rtwvif_link) +{ + struct rtw89_beacon_stat *bcn_stat = &rtwdev->phystat.bcn_stat; + struct rtw89_beacon_track_info *bcn_track = &rtwdev->bcn_track; + u32 *tbtt_us = bcn_stat->tbtt_us; + u32 offset = tbtt_us[0]; + u8 i; + + for (i = 1; i < RTW89_BCN_TRACK_STAT_NR; i++) + offset = rtw89_bcn_calc_min_tbtt(rtwdev, tbtt_us[i], offset); + + if (bcn_track->tbtt_offset == offset) + return; + + bcn_track->tbtt_offset = offset; + rtw89_bcn_cfg_tbtt_offset(rtwdev, rtwvif_link); +} + +static int cmp_u16(const void *a, const void *b) +{ + return *(const u16 *)a - *(const u16 *)b; +} + +static u16 _rtw89_bcn_calc_drift(u16 tbtt, u16 offset, u16 beacon_int) +{ + if (tbtt < offset) + return beacon_int - offset + tbtt; + + return tbtt - offset; +} + +static void rtw89_bcn_calc_drift(struct rtw89_dev *rtwdev) +{ + struct rtw89_beacon_stat *bcn_stat = &rtwdev->phystat.bcn_stat; + struct rtw89_beacon_track_info *bcn_track = &rtwdev->bcn_track; + u16 offset_tu = bcn_track->tbtt_offset / 1024; + u16 *tbtt_tu = bcn_stat->tbtt_tu; + u16 *drift = bcn_stat->drift; + u8 i; + + bcn_stat->tbtt_tu_min = U16_MAX; + bcn_stat->tbtt_tu_max = 0; + for (i = 0; i < RTW89_BCN_TRACK_STAT_NR; i++) { + drift[i] = _rtw89_bcn_calc_drift(tbtt_tu[i], offset_tu, + bcn_track->beacon_int); + + bcn_stat->tbtt_tu_min = min(bcn_stat->tbtt_tu_min, tbtt_tu[i]); + bcn_stat->tbtt_tu_max = max(bcn_stat->tbtt_tu_max, tbtt_tu[i]); + } + + sort(drift, RTW89_BCN_TRACK_STAT_NR, sizeof(*drift), cmp_u16, NULL); +} + +static void rtw89_bcn_calc_distribution(struct rtw89_dev *rtwdev) +{ + struct rtw89_beacon_stat *bcn_stat = &rtwdev->phystat.bcn_stat; + struct rtw89_beacon_dist *bcn_dist = &bcn_stat->bcn_dist; + u16 lower_bound, upper_bound, outlier_count = 0; + u16 *drift = bcn_stat->drift; + u16 *bins = bcn_dist->bins; + u16 q1, q3, iqr, tmp; + u8 i; + + BUILD_BUG_ON(RTW89_BCN_TRACK_STAT_NR % 4 != 0); + + memset(bcn_dist, 0, sizeof(*bcn_dist)); + + bcn_dist->min = drift[0]; + bcn_dist->max = drift[RTW89_BCN_TRACK_STAT_NR - 1]; + + tmp = RTW89_BCN_TRACK_STAT_NR / 4; + q1 = ((drift[tmp] + drift[tmp - 1]) * RTW89_BCN_TRACK_SCALE_FACTOR) / 2; + + tmp = (RTW89_BCN_TRACK_STAT_NR * 3) / 4; + q3 = ((drift[tmp] + drift[tmp - 1]) * RTW89_BCN_TRACK_SCALE_FACTOR) / 2; + + iqr = q3 - q1; + tmp = (3 * iqr) / 2; + + if (bcn_dist->min <= 5) + lower_bound = bcn_dist->min; + else if (q1 > tmp) + lower_bound = (q1 - tmp) / RTW89_BCN_TRACK_SCALE_FACTOR; + else + lower_bound = 0; + + upper_bound = (q3 + tmp) / RTW89_BCN_TRACK_SCALE_FACTOR; + + for (i = 0; i < RTW89_BCN_TRACK_STAT_NR; i++) { + u16 tbtt = bcn_stat->tbtt_tu[i]; + u16 min = bcn_stat->tbtt_tu_min; + u8 bin_idx; + + /* histogram */ + bin_idx = min((tbtt - min) / RTW89_BCN_TRACK_BIN_WIDTH, + RTW89_BCN_TRACK_MAX_BIN_NUM - 1); + bins[bin_idx]++; + + /* boxplot outlier */ + if (drift[i] < lower_bound || drift[i] > upper_bound) + outlier_count++; + } + + bcn_dist->outlier_count = outlier_count; + bcn_dist->lower_bound = lower_bound; + bcn_dist->upper_bound = upper_bound; +} + +static u8 rtw89_bcn_get_coverage(struct rtw89_dev *rtwdev, u16 threshold) +{ + struct rtw89_beacon_stat *bcn_stat = &rtwdev->phystat.bcn_stat; + int l = 0, r = RTW89_BCN_TRACK_STAT_NR - 1, m; + u16 *drift = bcn_stat->drift; + int index = -1; + u8 count = 0; + + while (l <= r) { + m = l + (r - l) / 2; + + if (drift[m] <= threshold) { + index = m; + l = m + 1; + } else { + r = m - 1; + } + } + + count = (index == -1) ? 0 : (index + 1); + + return (count * PERCENT) / RTW89_BCN_TRACK_STAT_NR; +} + +static u16 rtw89_bcn_get_histogram_bound(struct rtw89_dev *rtwdev, u8 target) +{ + struct rtw89_beacon_stat *bcn_stat = &rtwdev->phystat.bcn_stat; + struct rtw89_beacon_dist *bcn_dist = &bcn_stat->bcn_dist; + u16 tbtt_tu_max = bcn_stat->tbtt_tu_max; + u16 upper, lower = bcn_stat->tbtt_tu_min; + u8 i, count = 0; + + for (i = 0; i < RTW89_BCN_TRACK_MAX_BIN_NUM; i++) { + upper = lower + RTW89_BCN_TRACK_BIN_WIDTH - 1; + if (i == RTW89_BCN_TRACK_MAX_BIN_NUM - 1) + upper = max(upper, tbtt_tu_max); + + count += bcn_dist->bins[i]; + if (count > target) + break; + + lower = upper + 1; + } + + return upper; +} + +static u16 rtw89_bcn_get_rx_time(struct rtw89_dev *rtwdev, + const struct rtw89_chan *chan) +{ +#define RTW89_SYMBOL_TIME_2GHZ 192 +#define RTW89_SYMBOL_TIME_5GHZ 20 +#define RTW89_SYMBOL_TIME_6GHZ 20 + struct rtw89_pkt_stat *pkt_stat = &rtwdev->phystat.cur_pkt_stat; + u16 bitrate, val; + + if (!rtw89_legacy_rate_to_bitrate(rtwdev, pkt_stat->beacon_rate, &bitrate)) + return 0; + + val = (pkt_stat->beacon_len * 8 * RTW89_BCN_TRACK_SCALE_FACTOR) / bitrate; + + switch (chan->band_type) { + default: + case RTW89_BAND_2G: + val += RTW89_SYMBOL_TIME_2GHZ; + break; + case RTW89_BAND_5G: + val += RTW89_SYMBOL_TIME_5GHZ; + break; + case RTW89_BAND_6G: + val += RTW89_SYMBOL_TIME_6GHZ; + break; + } + + /* convert to millisecond */ + return DIV_ROUND_UP(val, 1000); +} + +static void rtw89_bcn_calc_timeout(struct rtw89_dev *rtwdev, + struct rtw89_vif_link *rtwvif_link) +{ +#define RTW89_BCN_TRACK_EXTEND_TIMEOUT 5 +#define RTW89_BCN_TRACK_COVERAGE_TH 0 /* unit: TU */ +#define RTW89_BCN_TRACK_STRONG_RSSI 80 + const struct rtw89_chan *chan = rtw89_chan_get(rtwdev, rtwvif_link->chanctx_idx); + struct rtw89_pkt_stat *pkt_stat = &rtwdev->phystat.cur_pkt_stat; + struct rtw89_beacon_stat *bcn_stat = &rtwdev->phystat.bcn_stat; + struct rtw89_beacon_track_info *bcn_track = &rtwdev->bcn_track; + struct rtw89_beacon_dist *bcn_dist = &bcn_stat->bcn_dist; + u16 outlier_high_bcn_th = bcn_track->outlier_high_bcn_th; + u16 outlier_low_bcn_th = bcn_track->outlier_low_bcn_th; + u8 rssi = ewma_rssi_read(&rtwdev->phystat.bcn_rssi); + u16 target_bcn_th = bcn_track->target_bcn_th; + u16 low_bcn_th = bcn_track->low_bcn_th; + u16 med_bcn_th = bcn_track->med_bcn_th; + u16 beacon_int = bcn_track->beacon_int; + u16 bcn_timeout; + + if (pkt_stat->beacon_nr < low_bcn_th) { + bcn_timeout = (RTW89_BCN_TRACK_TARGET_BCN * beacon_int) / PERCENT; + goto out; + } + + if (bcn_dist->outlier_count >= outlier_high_bcn_th) { + bcn_timeout = bcn_dist->max; + goto out; + } + + if (pkt_stat->beacon_nr < med_bcn_th) { + if (bcn_dist->outlier_count > outlier_low_bcn_th) + bcn_timeout = (bcn_dist->max + bcn_dist->upper_bound) / 2; + else + bcn_timeout = bcn_dist->upper_bound + + RTW89_BCN_TRACK_EXTEND_TIMEOUT; + + goto out; + } + + if (rssi >= RTW89_BCN_TRACK_STRONG_RSSI) { + if (rtw89_bcn_get_coverage(rtwdev, RTW89_BCN_TRACK_COVERAGE_TH) >= 90) { + /* ideal case */ + bcn_timeout = 0; + } else { + u16 offset_tu = bcn_track->tbtt_offset / 1024; + u16 upper_bound; + + upper_bound = + rtw89_bcn_get_histogram_bound(rtwdev, target_bcn_th); + bcn_timeout = + _rtw89_bcn_calc_drift(upper_bound, offset_tu, beacon_int); + } + + goto out; + } + + bcn_timeout = bcn_stat->drift[target_bcn_th]; + +out: + bcn_track->bcn_timeout = bcn_timeout + rtw89_bcn_get_rx_time(rtwdev, chan); +} + +static void rtw89_bcn_update_timeout(struct rtw89_dev *rtwdev, + struct rtw89_vif_link *rtwvif_link) +{ + rtw89_bcn_calc_drift(rtwdev); + rtw89_bcn_calc_distribution(rtwdev); + rtw89_bcn_calc_timeout(rtwdev, rtwvif_link); +} + +static void rtw89_core_bcn_track(struct rtw89_dev *rtwdev) +{ + struct rtw89_beacon_track_info *bcn_track = &rtwdev->bcn_track; + struct rtw89_vif_link *rtwvif_link; + struct rtw89_vif *rtwvif; + unsigned int link_id; + + if (!RTW89_CHK_FW_FEATURE(BEACON_TRACKING, &rtwdev->fw)) + return; + + if (!rtwdev->lps_enabled) + return; + + if (!bcn_track->is_data_ready) + return; + + rtw89_for_each_rtwvif(rtwdev, rtwvif) { + rtw89_vif_for_each_link(rtwvif, rtwvif_link, link_id) { + if (!(rtwvif_link->wifi_role == RTW89_WIFI_ROLE_STATION || + rtwvif_link->wifi_role == RTW89_WIFI_ROLE_P2P_CLIENT)) + continue; + + rtw89_bcn_update_tbtt_offset(rtwdev, rtwvif_link); + rtw89_bcn_update_timeout(rtwdev, rtwvif_link); + } + } +} + +static bool rtw89_core_bcn_track_can_lps(struct rtw89_dev *rtwdev) +{ + struct rtw89_beacon_track_info *bcn_track = &rtwdev->bcn_track; + + if (!RTW89_CHK_FW_FEATURE(BEACON_TRACKING, &rtwdev->fw)) + return true; + + return bcn_track->is_data_ready; +} + +static void rtw89_core_bcn_track_assoc(struct rtw89_dev *rtwdev, + struct rtw89_vif_link *rtwvif_link) +{ +#define RTW89_BCN_TRACK_MED_BCN 70 +#define RTW89_BCN_TRACK_LOW_BCN 30 +#define RTW89_BCN_TRACK_OUTLIER_HIGH_BCN 30 +#define RTW89_BCN_TRACK_OUTLIER_LOW_BCN 20 + struct rtw89_beacon_track_info *bcn_track = &rtwdev->bcn_track; + u32 period = jiffies_to_msecs(RTW89_TRACK_WORK_PERIOD); + struct ieee80211_bss_conf *bss_conf; + u32 beacons_in_period; + u32 bcn_intvl_us; + u16 beacon_int; + u8 dtim; + + rcu_read_lock(); + bss_conf = rtw89_vif_rcu_dereference_link(rtwvif_link, true); + beacon_int = bss_conf->beacon_int; + dtim = bss_conf->dtim_period; + rcu_read_unlock(); + + beacons_in_period = period / beacon_int / dtim; + bcn_intvl_us = ieee80211_tu_to_usec(beacon_int); + + bcn_track->low_bcn_th = + (beacons_in_period * RTW89_BCN_TRACK_LOW_BCN) / PERCENT; + bcn_track->med_bcn_th = + (beacons_in_period * RTW89_BCN_TRACK_MED_BCN) / PERCENT; + bcn_track->outlier_low_bcn_th = + (RTW89_BCN_TRACK_STAT_NR * RTW89_BCN_TRACK_OUTLIER_LOW_BCN) / PERCENT; + bcn_track->outlier_high_bcn_th = + (RTW89_BCN_TRACK_STAT_NR * RTW89_BCN_TRACK_OUTLIER_HIGH_BCN) / PERCENT; + bcn_track->target_bcn_th = + (RTW89_BCN_TRACK_STAT_NR * RTW89_BCN_TRACK_TARGET_BCN) / PERCENT; + + bcn_track->close_bcn_intvl_th = ieee80211_tu_to_usec(beacon_int - 3); + bcn_track->tbtt_diff_th = (bcn_intvl_us * 85) / PERCENT; + bcn_track->beacon_int = beacon_int; + bcn_track->dtim = dtim; +} + +static void rtw89_core_bcn_track_reset(struct rtw89_dev *rtwdev) +{ + memset(&rtwdev->phystat.bcn_stat, 0, sizeof(rtwdev->phystat.bcn_stat)); + memset(&rtwdev->bcn_track, 0, sizeof(rtwdev->bcn_track)); +} + +static void rtw89_vif_rx_bcn_stat(struct rtw89_dev *rtwdev, + struct ieee80211_bss_conf *bss_conf, + struct sk_buff *skb) +{ +#define RTW89_APPEND_TSF_2GHZ 384 +#define RTW89_APPEND_TSF_5GHZ 52 +#define RTW89_APPEND_TSF_6GHZ 52 + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data; + struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb); + struct rtw89_beacon_stat *bcn_stat = &rtwdev->phystat.bcn_stat; + struct rtw89_beacon_track_info *bcn_track = &rtwdev->bcn_track; + u32 bcn_intvl_us = ieee80211_tu_to_usec(bss_conf->beacon_int); + u64 tsf = le64_to_cpu(mgmt->u.beacon.timestamp); + u8 wp, num = bcn_stat->num; + u16 append; + + if (!RTW89_CHK_FW_FEATURE(BEACON_TRACKING, &rtwdev->fw)) + return; + + switch (rx_status->band) { + default: + case NL80211_BAND_2GHZ: + append = RTW89_APPEND_TSF_2GHZ; + break; + case NL80211_BAND_5GHZ: + append = RTW89_APPEND_TSF_5GHZ; + break; + case NL80211_BAND_6GHZ: + append = RTW89_APPEND_TSF_6GHZ; + break; + } + + wp = bcn_stat->wp; + div_u64_rem(tsf - append, bcn_intvl_us, &bcn_stat->tbtt_us[wp]); + bcn_stat->tbtt_tu[wp] = bcn_stat->tbtt_us[wp] / 1024; + bcn_stat->wp = (wp + 1) % RTW89_BCN_TRACK_STAT_NR; + bcn_stat->num = umin(num + 1, RTW89_BCN_TRACK_STAT_NR); + bcn_track->is_data_ready = bcn_stat->num == RTW89_BCN_TRACK_STAT_NR; +} + static void rtw89_vif_rx_stats_iter(void *data, u8 *mac, struct ieee80211_vif *vif) { @@ -2272,7 +2703,6 @@ static void rtw89_vif_rx_stats_iter(void *data, u8 *mac, rtw89_vif_sync_bcn_tsf(rtwvif_link, hdr, skb->len); rtw89_fw_h2c_rssi_offload(rtwdev, phy_ppdu); } - pkt_stat->beacon_nr++; if (phy_ppdu) { ewma_rssi_add(&rtwdev->phystat.bcn_rssi, phy_ppdu->rssi_avg); @@ -2280,7 +2710,11 @@ static void rtw89_vif_rx_stats_iter(void *data, u8 *mac, rtwvif_link->bcn_bw_idx = phy_ppdu->bw_idx; } + pkt_stat->beacon_nr++; pkt_stat->beacon_rate = desc_info->data_rate; + pkt_stat->beacon_len = skb->len; + + rtw89_vif_rx_bcn_stat(rtwdev, bss_conf, skb); } if (!ether_addr_equal(bss_conf->addr, hdr->addr1)) @@ -3697,6 +4131,9 @@ static void rtw89_enter_lps_track(struct rtw89_dev *rtwdev) vif->type == NL80211_IFTYPE_P2P_CLIENT)) continue; + if (!rtw89_core_bcn_track_can_lps(rtwdev)) + continue; + rtw89_enter_lps(rtwdev, rtwvif, true); } } @@ -3883,6 +4320,7 @@ static void rtw89_track_work(struct wiphy *wiphy, struct wiphy_work *work) rtw89_btc_ntfy_wl_sta(rtwdev); } rtw89_mac_bf_monitor_track(rtwdev); + rtw89_core_bcn_track(rtwdev); rtw89_phy_stat_track(rtwdev); rtw89_phy_env_monitor_track(rtwdev); rtw89_phy_dig(rtwdev); @@ -4129,8 +4567,10 @@ int rtw89_core_sta_link_disassoc(struct rtw89_dev *rtwdev, rtw89_assoc_link_clr(rtwsta_link); - if (vif->type == NL80211_IFTYPE_STATION) + if (vif->type == NL80211_IFTYPE_STATION) { rtw89_fw_h2c_set_bcn_fltr_cfg(rtwdev, rtwvif_link, false); + rtw89_core_bcn_track_reset(rtwdev); + } if (rtwvif_link->wifi_role == RTW89_WIFI_ROLE_P2P_CLIENT) rtw89_p2p_noa_once_deinit(rtwvif_link); @@ -4271,6 +4711,7 @@ int rtw89_core_sta_link_assoc(struct rtw89_dev *rtwdev, BTC_ROLE_MSTS_STA_CONN_END); rtw89_core_get_no_ul_ofdma_htc(rtwdev, &rtwsta_link->htc_template, chan); rtw89_phy_ul_tb_assoc(rtwdev, rtwvif_link); + rtw89_core_bcn_track_assoc(rtwdev, rtwvif_link); ret = rtw89_fw_h2c_general_pkt(rtwdev, rtwvif_link, rtwsta_link->mac_id); if (ret) { diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 43e10278e14d..ca48426c577f 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -4622,6 +4622,7 @@ enum rtw89_fw_feature { RTW89_FW_FEATURE_SCAN_OFFLOAD_EXTRA_OP, RTW89_FW_FEATURE_RFK_NTFY_MCC_V0, RTW89_FW_FEATURE_LPS_DACK_BY_C2H_REG, + RTW89_FW_FEATURE_BEACON_TRACKING, }; struct rtw89_fw_suit { @@ -5074,9 +5075,36 @@ struct rtw89_pkt_drop_params { struct rtw89_pkt_stat { u16 beacon_nr; u8 beacon_rate; + u32 beacon_len; u32 rx_rate_cnt[RTW89_HW_RATE_NR]; }; +#define RTW89_BCN_TRACK_STAT_NR 32 +#define RTW89_BCN_TRACK_SCALE_FACTOR 10 +#define RTW89_BCN_TRACK_MAX_BIN_NUM 6 +#define RTW89_BCN_TRACK_BIN_WIDTH 5 +#define RTW89_BCN_TRACK_TARGET_BCN 80 + +struct rtw89_beacon_dist { + u16 min; + u16 max; + u16 outlier_count; + u16 lower_bound; + u16 upper_bound; + u16 bins[RTW89_BCN_TRACK_MAX_BIN_NUM]; +}; + +struct rtw89_beacon_stat { + u8 num; + u8 wp; + u16 tbtt_tu_min; + u16 tbtt_tu_max; + u16 drift[RTW89_BCN_TRACK_STAT_NR]; + u32 tbtt_us[RTW89_BCN_TRACK_STAT_NR]; + u16 tbtt_tu[RTW89_BCN_TRACK_STAT_NR]; + struct rtw89_beacon_dist bcn_dist; +}; + DECLARE_EWMA(thermal, 4, 4); struct rtw89_phy_stat { @@ -5085,6 +5113,7 @@ struct rtw89_phy_stat { struct ewma_rssi bcn_rssi; struct rtw89_pkt_stat cur_pkt_stat; struct rtw89_pkt_stat last_pkt_stat; + struct rtw89_beacon_stat bcn_stat; }; enum rtw89_rfk_report_state { @@ -5882,6 +5911,24 @@ struct rtw89_mlo_info { struct rtw89_wait_info wait; }; +struct rtw89_beacon_track_info { + bool is_data_ready; + u32 tbtt_offset; /* in unit of microsecond */ + u16 bcn_timeout; /* in unit of millisecond */ + + /* The following are constant and set at association. */ + u8 dtim; + u16 beacon_int; + u16 low_bcn_th; + u16 med_bcn_th; + u16 high_bcn_th; + u16 target_bcn_th; + u16 outlier_low_bcn_th; + u16 outlier_high_bcn_th; + u32 close_bcn_intvl_th; + u32 tbtt_diff_th; +}; + struct rtw89_dev { struct ieee80211_hw *hw; struct device *dev; @@ -5896,6 +5943,7 @@ struct rtw89_dev { const struct rtw89_pci_info *pci_info; const struct rtw89_rfe_parms *rfe_parms; struct rtw89_hal hal; + struct rtw89_beacon_track_info bcn_track; struct rtw89_mcc_info mcc; struct rtw89_mlo_info mlo; struct rtw89_mac_info mac; @@ -7454,7 +7502,7 @@ void rtw89_vif_type_mapping(struct rtw89_vif_link *rtwvif_link, bool assoc); int rtw89_chip_info_setup(struct rtw89_dev *rtwdev); void rtw89_chip_cfg_txpwr_ul_tb_offset(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link); -bool rtw89_ra_report_to_bitrate(struct rtw89_dev *rtwdev, u8 rpt_rate, u16 *bitrate); +bool rtw89_legacy_rate_to_bitrate(struct rtw89_dev *rtwdev, u8 legacy_rate, u16 *bitrate); int rtw89_regd_setup(struct rtw89_dev *rtwdev); int rtw89_regd_init_hint(struct rtw89_dev *rtwdev); const char *rtw89_regd_get_string(enum rtw89_regulation_type regd); diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 16e59a4a486e..cc1956245f9b 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -835,6 +835,7 @@ static const struct __fw_feat_cfg fw_feat_tbl[] = { __CFG_FW_FEAT(RTL8852BT, ge, 0, 29, 90, 0, CRASH_TRIGGER_TYPE_0), __CFG_FW_FEAT(RTL8852BT, ge, 0, 29, 91, 0, SCAN_OFFLOAD), __CFG_FW_FEAT(RTL8852BT, ge, 0, 29, 110, 0, BEACON_FILTER), + __CFG_FW_FEAT(RTL8852BT, ge, 0, 29, 122, 0, BEACON_TRACKING), __CFG_FW_FEAT(RTL8852BT, ge, 0, 29, 127, 0, SCAN_OFFLOAD_EXTRA_OP), __CFG_FW_FEAT(RTL8852BT, ge, 0, 29, 127, 0, LPS_DACK_BY_C2H_REG), __CFG_FW_FEAT(RTL8852BT, ge, 0, 29, 127, 0, CRASH_TRIGGER_TYPE_1), @@ -846,6 +847,7 @@ static const struct __fw_feat_cfg fw_feat_tbl[] = { __CFG_FW_FEAT(RTL8852C, ge, 0, 27, 56, 10, BEACON_FILTER), __CFG_FW_FEAT(RTL8852C, ge, 0, 27, 80, 0, WOW_REASON_V1), __CFG_FW_FEAT(RTL8852C, ge, 0, 27, 128, 0, BEACON_LOSS_COUNT_V1), + __CFG_FW_FEAT(RTL8852C, ge, 0, 27, 129, 1, BEACON_TRACKING), __CFG_FW_FEAT(RTL8922A, ge, 0, 34, 30, 0, CRASH_TRIGGER_TYPE_0), __CFG_FW_FEAT(RTL8922A, ge, 0, 34, 11, 0, MACID_PAUSE_SLEEP), __CFG_FW_FEAT(RTL8922A, ge, 0, 34, 35, 0, SCAN_OFFLOAD), @@ -864,6 +866,7 @@ static const struct __fw_feat_cfg fw_feat_tbl[] = { __CFG_FW_FEAT(RTL8922A, ge, 0, 35, 71, 0, BEACON_LOSS_COUNT_V1), __CFG_FW_FEAT(RTL8922A, ge, 0, 35, 76, 0, LPS_DACK_BY_C2H_REG), __CFG_FW_FEAT(RTL8922A, ge, 0, 35, 79, 0, CRASH_TRIGGER_TYPE_1), + __CFG_FW_FEAT(RTL8922A, ge, 0, 35, 80, 0, BEACON_TRACKING), }; static void rtw89_fw_iterate_feature_cfg(struct rtw89_fw_info *fw, @@ -3990,6 +3993,93 @@ fail: } EXPORT_SYMBOL(rtw89_fw_h2c_update_beacon_be); +int rtw89_fw_h2c_tbtt_tuning(struct rtw89_dev *rtwdev, + struct rtw89_vif_link *rtwvif_link, u32 offset) +{ + struct rtw89_h2c_tbtt_tuning *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 tbtt tuning\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_tbtt_tuning *)skb->data; + + h2c->w0 = le32_encode_bits(rtwvif_link->phy_idx, RTW89_H2C_TBTT_TUNING_W0_BAND) | + le32_encode_bits(rtwvif_link->port, RTW89_H2C_TBTT_TUNING_W0_PORT); + h2c->w1 = le32_encode_bits(offset, RTW89_H2C_TBTT_TUNING_W1_SHIFT); + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_MAC, H2C_CL_MAC_PS, + H2C_FUNC_TBTT_TUNING, 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; +} + +int rtw89_fw_h2c_pwr_lvl(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link) +{ +#define RTW89_BCN_TO_VAL_MIN 4 +#define RTW89_BCN_TO_VAL_MAX 64 +#define RTW89_DTIM_TO_VAL_MIN 7 +#define RTW89_DTIM_TO_VAL_MAX 15 + struct rtw89_beacon_track_info *bcn_track = &rtwdev->bcn_track; + struct rtw89_h2c_pwr_lvl *h2c; + u32 len = sizeof(*h2c); + struct sk_buff *skb; + u8 bcn_to_val; + int ret; + + skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); + if (!skb) { + rtw89_err(rtwdev, "failed to alloc skb for h2c pwr lvl\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_pwr_lvl *)skb->data; + + bcn_to_val = clamp_t(u8, bcn_track->bcn_timeout, + RTW89_BCN_TO_VAL_MIN, RTW89_BCN_TO_VAL_MAX); + + h2c->w0 = le32_encode_bits(rtwvif_link->mac_id, RTW89_H2C_PWR_LVL_W0_MACID) | + le32_encode_bits(bcn_to_val, RTW89_H2C_PWR_LVL_W0_BCN_TO_VAL) | + le32_encode_bits(0, RTW89_H2C_PWR_LVL_W0_PS_LVL) | + le32_encode_bits(0, RTW89_H2C_PWR_LVL_W0_TRX_LVL) | + le32_encode_bits(RTW89_DTIM_TO_VAL_MIN, + RTW89_H2C_PWR_LVL_W0_DTIM_TO_VAL); + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_MAC, H2C_CL_MAC_PS, + H2C_FUNC_PS_POWER_LEVEL, 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; +} + int rtw89_fw_h2c_role_maintain(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link, struct rtw89_sta_link *rtwsta_link, diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index 3de29137b113..fad9d87d3e56 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -1602,6 +1602,28 @@ struct rtw89_h2c_bcn_upd_be { #define RTW89_H2C_BCN_UPD_BE_W7_ECSA_OFST GENMASK(30, 16) #define RTW89_H2C_BCN_UPD_BE_W7_PROTECTION_KEY_ID BIT(31) +struct rtw89_h2c_tbtt_tuning { + __le32 w0; + __le32 w1; +} __packed; + +#define RTW89_H2C_TBTT_TUNING_W0_BAND GENMASK(3, 0) +#define RTW89_H2C_TBTT_TUNING_W0_PORT GENMASK(7, 4) +#define RTW89_H2C_TBTT_TUNING_W1_SHIFT GENMASK(31, 0) + +struct rtw89_h2c_pwr_lvl { + __le32 w0; + __le32 w1; +} __packed; + +#define RTW89_H2C_PWR_LVL_W0_MACID GENMASK(7, 0) +#define RTW89_H2C_PWR_LVL_W0_BCN_TO_VAL GENMASK(15, 8) +#define RTW89_H2C_PWR_LVL_W0_PS_LVL GENMASK(19, 16) +#define RTW89_H2C_PWR_LVL_W0_TRX_LVL GENMASK(23, 20) +#define RTW89_H2C_PWR_LVL_W0_BCN_TO_LVL GENMASK(27, 24) +#define RTW89_H2C_PWR_LVL_W0_DTIM_TO_VAL GENMASK(31, 28) +#define RTW89_H2C_PWR_LVL_W1_MACID_EXT GENMASK(7, 0) + struct rtw89_h2c_role_maintain { __le32 w0; }; @@ -4201,6 +4223,8 @@ enum rtw89_ps_h2c_func { H2C_FUNC_MAC_LPS_PARM = 0x0, H2C_FUNC_P2P_ACT = 0x1, H2C_FUNC_IPS_CFG = 0x3, + H2C_FUNC_PS_POWER_LEVEL = 0x7, + H2C_FUNC_TBTT_TUNING = 0xA, NUM_OF_RTW89_PS_H2C_FUNC, }; @@ -4750,6 +4774,9 @@ int rtw89_fw_h2c_update_beacon(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link); int rtw89_fw_h2c_update_beacon_be(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link); +int rtw89_fw_h2c_tbtt_tuning(struct rtw89_dev *rtwdev, + struct rtw89_vif_link *rtwvif_link, u32 offset); +int rtw89_fw_h2c_pwr_lvl(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link); int rtw89_fw_h2c_cam(struct rtw89_dev *rtwdev, struct rtw89_vif_link *vif, struct rtw89_sta_link *rtwsta_link, const u8 *scan_mac_addr); int rtw89_fw_h2c_dctl_sec_cam_v1(struct rtw89_dev *rtwdev, diff --git a/drivers/net/wireless/realtek/rtw89/mac.c b/drivers/net/wireless/realtek/rtw89/mac.c index 33a7dd9d6f0e..e4f9d251d5ef 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.c +++ b/drivers/net/wireless/realtek/rtw89/mac.c @@ -4649,27 +4649,6 @@ static void rtw89_mac_port_cfg_bcn_early(struct rtw89_dev *rtwdev, BCN_ERLY_DEF); } -static void rtw89_mac_port_cfg_tbtt_shift(struct rtw89_dev *rtwdev, - struct rtw89_vif_link *rtwvif_link) -{ - const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; - const struct rtw89_port_reg *p = mac->port_base; - u16 val; - - if (rtwdev->chip->chip_id != RTL8852C) - return; - - if (rtwvif_link->wifi_role != RTW89_WIFI_ROLE_P2P_CLIENT && - rtwvif_link->wifi_role != RTW89_WIFI_ROLE_STATION) - return; - - val = FIELD_PREP(B_AX_TBTT_SHIFT_OFST_MAG, 1) | - B_AX_TBTT_SHIFT_OFST_SIGN; - - rtw89_write16_port_mask(rtwdev, rtwvif_link, p->tbtt_shift, - B_AX_TBTT_SHIFT_OFST_MASK, val); -} - void rtw89_mac_port_tsf_sync(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link, struct rtw89_vif_link *rtwvif_src, @@ -4820,7 +4799,6 @@ int rtw89_mac_port_update(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvi rtw89_mac_port_cfg_bcn_hold_time(rtwdev, rtwvif_link); rtw89_mac_port_cfg_bcn_mask_area(rtwdev, rtwvif_link); rtw89_mac_port_cfg_tbtt_early(rtwdev, rtwvif_link); - rtw89_mac_port_cfg_tbtt_shift(rtwdev, rtwvif_link); rtw89_mac_port_cfg_bss_color(rtwdev, rtwvif_link); rtw89_mac_port_cfg_mbssid(rtwdev, rtwvif_link); rtw89_mac_port_cfg_func_en(rtwdev, rtwvif_link, true); diff --git a/drivers/net/wireless/realtek/rtw89/phy.c b/drivers/net/wireless/realtek/rtw89/phy.c index 01a03d2de3ff..06598723074e 100644 --- a/drivers/net/wireless/realtek/rtw89/phy.c +++ b/drivers/net/wireless/realtek/rtw89/phy.c @@ -2943,7 +2943,7 @@ static void __rtw89_phy_c2h_ra_rpt_iter(struct rtw89_sta_link *rtwsta_link, } if (mode == RTW89_RA_RPT_MODE_LEGACY) { - valid = rtw89_ra_report_to_bitrate(rtwdev, rate, &legacy_bitrate); + valid = rtw89_legacy_rate_to_bitrate(rtwdev, rate, &legacy_bitrate); if (!valid) return; } diff --git a/drivers/net/wireless/realtek/rtw89/ps.c b/drivers/net/wireless/realtek/rtw89/ps.c index 652f8fc81b79..cf58121eb541 100644 --- a/drivers/net/wireless/realtek/rtw89/ps.c +++ b/drivers/net/wireless/realtek/rtw89/ps.c @@ -119,6 +119,9 @@ static void __rtw89_enter_lps_link(struct rtw89_dev *rtwdev, rtw89_btc_ntfy_radio_state(rtwdev, BTC_RFCTRL_FW_CTRL); rtw89_fw_h2c_lps_parm(rtwdev, &lps_param); + + if (RTW89_CHK_FW_FEATURE(BEACON_TRACKING, &rtwdev->fw)) + rtw89_fw_h2c_pwr_lvl(rtwdev, rtwvif_link); } static void __rtw89_leave_lps(struct rtw89_dev *rtwdev, From 194b7ce982471468569299991b110049c4617163 Mon Sep 17 00:00:00 2001 From: Kuan-Chung Chen Date: Mon, 11 Aug 2025 20:37:38 +0800 Subject: [PATCH 007/167] wifi: rtw89: debug: add beacon_info debugfs Add debugfs beacon_info to display the information of beacon tracking. The screenshot as below: [Beacon info] count: 15 interval: 100 dtim: 1 raw rssi: 146 hw rate: 0 length: 407 [Distribution] tbtt 00 - 04: 26 05 - 09: 5 10 - 14: 1 15 - 19: 0 20 - 24: 0 25 - 29: 0 drift 0: 9 1: 7 2: 3 3: 3 4: 4 5: 1 6: 1 7: 2 8: 1 11: 1 lower bound: 0 upper bound: 10 outlier count: 1 [Tracking] tbtt offset: 20 bcn timeout: 19 Signed-off-by: Kuan-Chung Chen Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250811123744.15361-3-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/debug.c | 61 ++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/debug.c b/drivers/net/wireless/realtek/rtw89/debug.c index afdfb9647fc1..a37cdd73ddc3 100644 --- a/drivers/net/wireless/realtek/rtw89/debug.c +++ b/drivers/net/wireless/realtek/rtw89/debug.c @@ -86,6 +86,7 @@ struct rtw89_debugfs { struct rtw89_debugfs_priv stations; struct rtw89_debugfs_priv disable_dm; struct rtw89_debugfs_priv mlo_mode; + struct rtw89_debugfs_priv beacon_info; }; struct rtw89_debugfs_iter_data { @@ -4298,6 +4299,64 @@ rtw89_debug_priv_mlo_mode_set(struct rtw89_dev *rtwdev, return count; } +static ssize_t +rtw89_debug_priv_beacon_info_get(struct rtw89_dev *rtwdev, + struct rtw89_debugfs_priv *debugfs_priv, + char *buf, size_t bufsz) +{ + struct rtw89_pkt_stat *pkt_stat = &rtwdev->phystat.last_pkt_stat; + struct rtw89_beacon_track_info *bcn_track = &rtwdev->bcn_track; + struct rtw89_beacon_stat *bcn_stat = &rtwdev->phystat.bcn_stat; + struct rtw89_beacon_dist *bcn_dist = &bcn_stat->bcn_dist; + u16 upper, lower = bcn_stat->tbtt_tu_min; + char *p = buf, *end = buf + bufsz; + u16 *drift = bcn_stat->drift; + u8 bcn_num = bcn_stat->num; + u8 count; + u8 i; + + p += scnprintf(p, end - p, "[Beacon info]\n"); + p += scnprintf(p, end - p, "count: %u\n", pkt_stat->beacon_nr); + p += scnprintf(p, end - p, "interval: %u\n", bcn_track->beacon_int); + p += scnprintf(p, end - p, "dtim: %u\n", bcn_track->dtim); + p += scnprintf(p, end - p, "raw rssi: %lu\n", + ewma_rssi_read(&rtwdev->phystat.bcn_rssi)); + p += scnprintf(p, end - p, "hw rate: %u\n", pkt_stat->beacon_rate); + p += scnprintf(p, end - p, "length: %u\n", pkt_stat->beacon_len); + + p += scnprintf(p, end - p, "\n[Distribution]\n"); + p += scnprintf(p, end - p, "tbtt\n"); + for (i = 0; i < RTW89_BCN_TRACK_MAX_BIN_NUM; i++) { + upper = lower + RTW89_BCN_TRACK_BIN_WIDTH - 1; + if (i == RTW89_BCN_TRACK_MAX_BIN_NUM - 1) + upper = max(upper, bcn_stat->tbtt_tu_max); + + p += scnprintf(p, end - p, "%02u - %02u: %u\n", + lower, upper, bcn_dist->bins[i]); + + lower = upper + 1; + } + + p += scnprintf(p, end - p, "\ndrift\n"); + + for (i = 0; i < bcn_num; i += count) { + count = 1; + while (i + count < bcn_num && drift[i] == drift[i + count]) + count++; + + p += scnprintf(p, end - p, "%u: %u\n", drift[i], count); + } + p += scnprintf(p, end - p, "\nlower bound: %u\n", bcn_dist->lower_bound); + p += scnprintf(p, end - p, "upper bound: %u\n", bcn_dist->upper_bound); + p += scnprintf(p, end - p, "outlier count: %u\n", bcn_dist->outlier_count); + + p += scnprintf(p, end - p, "\n[Tracking]\n"); + p += scnprintf(p, end - p, "tbtt offset: %u\n", bcn_track->tbtt_offset); + p += scnprintf(p, end - p, "bcn timeout: %u\n", bcn_track->bcn_timeout); + + return p - buf; +} + #define rtw89_debug_priv_get(name, opts...) \ { \ .cb_read = rtw89_debug_priv_ ##name## _get, \ @@ -4356,6 +4415,7 @@ static const struct rtw89_debugfs rtw89_debugfs_templ = { .stations = rtw89_debug_priv_get(stations, RLOCK), .disable_dm = rtw89_debug_priv_set_and_get(disable_dm, RWLOCK), .mlo_mode = rtw89_debug_priv_set_and_get(mlo_mode, RWLOCK), + .beacon_info = rtw89_debug_priv_get(beacon_info), }; #define rtw89_debugfs_add(name, mode, fopname, parent) \ @@ -4401,6 +4461,7 @@ void rtw89_debugfs_add_sec1(struct rtw89_dev *rtwdev, struct dentry *debugfs_top rtw89_debugfs_add_r(stations); rtw89_debugfs_add_rw(disable_dm); rtw89_debugfs_add_rw(mlo_mode); + rtw89_debugfs_add_r(beacon_info); } void rtw89_debugfs_init(struct rtw89_dev *rtwdev) From 38846585f9df9af1f7261d85134a5510fc079458 Mon Sep 17 00:00:00 2001 From: Kuan-Chung Chen Date: Mon, 11 Aug 2025 20:37:39 +0800 Subject: [PATCH 008/167] wifi: rtw89: wow: remove notify during WoWLAN net-detect In WoWLAN net-detect mode, the firmware periodically performs scans and sends scan reports via C2H, which driver does not need. These unnecessary C2H events cause firmware watchdog timeout, leading to unexpected wakeups and SER 0x2599 on 8922AE. Signed-off-by: Kuan-Chung Chen Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250811123744.15361-4-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/fw.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index cc1956245f9b..398d8ab98f63 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -7213,7 +7213,6 @@ static void rtw89_pno_scan_add_chan_ax(struct rtw89_dev *rtwdev, struct rtw89_pktofld_info *info; u8 probe_count = 0; - ch_info->notify_action = RTW89_SCANOFLD_DEBUG_MASK; ch_info->dfs_ch = chan_type == RTW89_CHAN_DFS; ch_info->bw = RTW89_SCAN_WIDTH; ch_info->tx_pkt = true; @@ -7354,7 +7353,6 @@ static void rtw89_pno_scan_add_chan_be(struct rtw89_dev *rtwdev, int chan_type, struct rtw89_pktofld_info *info; u8 probe_count = 0, i; - ch_info->notify_action = RTW89_SCANOFLD_DEBUG_MASK; ch_info->dfs_ch = chan_type == RTW89_CHAN_DFS; ch_info->bw = RTW89_SCAN_WIDTH; ch_info->tx_null = false; From b521685da35ebf091e51f9ea9ad2896a4ddb6e98 Mon Sep 17 00:00:00 2001 From: Kuan-Chung Chen Date: Mon, 11 Aug 2025 20:37:40 +0800 Subject: [PATCH 009/167] wifi: rtw89: 8851b: rfk: update IQK TIA setting With the new TIA setting of RX IQK, unstable RX throughput can be avoided, especially in medium-high attenuation environments. Signed-off-by: Kuan-Chung Chen Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250811123744.15361-5-pkshih@realtek.com --- .../net/wireless/realtek/rtw89/rtw8851b_rfk.c | 85 ++++++++++++------- 1 file changed, 54 insertions(+), 31 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b_rfk.c b/drivers/net/wireless/realtek/rtw89/rtw8851b_rfk.c index 7a319a6c838a..a7867b0e083a 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851b_rfk.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851b_rfk.c @@ -17,7 +17,7 @@ #define DPK_RF_REG_NUM_8851B 4 #define DPK_KSET_NUM 4 #define RTW8851B_RXK_GROUP_NR 4 -#define RTW8851B_RXK_GROUP_IDX_NR 2 +#define RTW8851B_RXK_GROUP_IDX_NR 4 #define RTW8851B_TXK_GROUP_NR 1 #define RTW8851B_IQK_VER 0x14 #define RTW8851B_IQK_SS 1 @@ -114,9 +114,9 @@ static const u32 _tssi_de_mcs_10m[RF_PATH_NUM_8851B] = {0x5830}; static const u32 g_idxrxgain[RTW8851B_RXK_GROUP_NR] = {0x10e, 0x116, 0x28e, 0x296}; static const u32 g_idxattc2[RTW8851B_RXK_GROUP_NR] = {0x0, 0xf, 0x0, 0xf}; static const u32 g_idxrxagc[RTW8851B_RXK_GROUP_NR] = {0x0, 0x1, 0x2, 0x3}; -static const u32 a_idxrxgain[RTW8851B_RXK_GROUP_IDX_NR] = {0x10C, 0x28c}; -static const u32 a_idxattc2[RTW8851B_RXK_GROUP_IDX_NR] = {0xf, 0xf}; -static const u32 a_idxrxagc[RTW8851B_RXK_GROUP_IDX_NR] = {0x4, 0x6}; +static const u32 a_idxrxgain[RTW8851B_RXK_GROUP_IDX_NR] = {0x10C, 0x112, 0x28c, 0x292}; +static const u32 a_idxattc2[RTW8851B_RXK_GROUP_IDX_NR] = {0xf, 0xf, 0xf, 0xf}; +static const u32 a_idxrxagc[RTW8851B_RXK_GROUP_IDX_NR] = {0x4, 0x5, 0x6, 0x7}; static const u32 a_power_range[RTW8851B_TXK_GROUP_NR] = {0x0}; static const u32 a_track_range[RTW8851B_TXK_GROUP_NR] = {0x6}; static const u32 a_gain_bb[RTW8851B_TXK_GROUP_NR] = {0x0a}; @@ -139,17 +139,6 @@ static const u32 dpk_rf_reg[DPK_RF_REG_NUM_8851B] = {0xde, 0x8f, 0x5, 0x10005}; static void _set_ch(struct rtw89_dev *rtwdev, u32 val); -static u8 _rxk_5ghz_group_from_idx(u8 idx) -{ - /* There are four RXK groups (RTW8851B_RXK_GROUP_NR), but only group 0 - * and 2 are used in 5 GHz band, so reduce elements to 2. - */ - if (idx < RTW8851B_RXK_GROUP_IDX_NR) - return idx * 2; - - return 0; -} - static u8 _kpath(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx) { return RF_A; @@ -196,7 +185,7 @@ static void _txck_force(struct rtw89_dev *rtwdev, enum rtw89_rf_path path, static void _rxck_force(struct rtw89_dev *rtwdev, enum rtw89_rf_path path, bool force, enum adc_ck ck) { - static const u32 ck960_8851b[] = {0x8, 0x2, 0x2, 0x4, 0xf, 0xa, 0x93}; + static const u32 ck960_8851b[] = {0x8, 0x2, 0x2, 0x4, 0xf, 0xa, 0x92}; static const u32 ck1920_8851b[] = {0x9, 0x0, 0x0, 0x3, 0xf, 0xa, 0x49}; const u32 *data; @@ -905,18 +894,27 @@ static bool _rxk_5g_group_sel(struct rtw89_dev *rtwdev, bool kfail = false; bool notready; u32 rf_0; - u8 idx; + u32 val; u8 gp; rtw89_debug(rtwdev, RTW89_DBG_RFK, "[IQK]===>%s\n", __func__); - for (idx = 0; idx < RTW8851B_RXK_GROUP_IDX_NR; idx++) { - gp = _rxk_5ghz_group_from_idx(idx); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWE, RFREG_MASK, 0x1000); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWA, RFREG_MASK, 0x4); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWD0, RFREG_MASK, 0x17); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWA, RFREG_MASK, 0x5); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWD0, RFREG_MASK, 0x27); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWE, RFREG_MASK, 0x0); + val = rtw89_read_rf(rtwdev, RF_PATH_A, RR_RXA2, 0x20); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_MOD, RR_MOD_MASK, 0xc); + + for (gp = 0; gp < RTW8851B_RXK_GROUP_IDX_NR; gp++) { rtw89_debug(rtwdev, RTW89_DBG_RFK, "[IQK]S%x, gp = %x\n", path, gp); - rtw89_write_rf(rtwdev, RF_PATH_A, RR_MOD, RR_MOD_RGM, a_idxrxgain[idx]); - rtw89_write_rf(rtwdev, RF_PATH_A, RR_RXA2, RR_RXA2_ATT, a_idxattc2[idx]); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_MOD, RR_MOD_RGM, a_idxrxgain[gp]); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_RXA2, RR_RXA2_ATT, a_idxattc2[gp]); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_RXA2, 0x20, 0x1); rtw89_phy_write32_mask(rtwdev, R_CFIR_LUT, B_CFIR_LUT_SEL, 0x1); rtw89_phy_write32_mask(rtwdev, R_CFIR_LUT, B_CFIR_LUT_G3, 0x0); @@ -926,7 +924,7 @@ static bool _rxk_5g_group_sel(struct rtw89_dev *rtwdev, fsleep(100); rf_0 = rtw89_read_rf(rtwdev, path, RR_MOD, RFREG_MASK); rtw89_phy_write32_mask(rtwdev, R_IQK_DIF2, B_IQK_DIF2_RXPI, rf_0); - rtw89_phy_write32_mask(rtwdev, R_IQK_RXA, B_IQK_RXAGC, a_idxrxagc[idx]); + rtw89_phy_write32_mask(rtwdev, R_IQK_RXA, B_IQK_RXAGC, a_idxrxagc[gp]); rtw89_phy_write32_mask(rtwdev, R_IQK_DIF4, B_IQK_DIF4_RXT, 0x11); notready = _iqk_one_shot(rtwdev, phy_idx, path, ID_RXAGC); @@ -959,6 +957,7 @@ static bool _rxk_5g_group_sel(struct rtw89_dev *rtwdev, _iqk_sram(rtwdev, path); if (kfail) { + rtw89_phy_write32_mask(rtwdev, R_IQK_RES, B_IQK_RES_RXCFIR, 0x0); rtw89_phy_write32_mask(rtwdev, R_RXIQC + (path << 8), MASKDWORD, iqk_info->nb_rxcfir[path] | 0x2); iqk_info->is_wb_txiqk[path] = false; @@ -968,6 +967,14 @@ static bool _rxk_5g_group_sel(struct rtw89_dev *rtwdev, iqk_info->is_wb_txiqk[path] = true; } + rtw89_write_rf(rtwdev, RF_PATH_A, RR_RXA2, 0x20, val); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWE, RFREG_MASK, 0x1000); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWA, RFREG_MASK, 0x4); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWD0, RFREG_MASK, 0x37); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWA, RFREG_MASK, 0x5); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWD0, RFREG_MASK, 0x27); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWE, RFREG_MASK, 0x0); + rtw89_debug(rtwdev, RTW89_DBG_RFK, "[IQK]S%x, kfail = 0x%x, 0x8%x3c = 0x%x\n", path, kfail, 1 << path, iqk_info->nb_rxcfir[path]); @@ -980,17 +987,26 @@ static bool _iqk_5g_nbrxk(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx, struct rtw89_iqk_info *iqk_info = &rtwdev->iqk; bool kfail = false; bool notready; - u8 idx = 0x1; + u8 gp = 2; u32 rf_0; - u8 gp; - - gp = _rxk_5ghz_group_from_idx(idx); + u32 val; rtw89_debug(rtwdev, RTW89_DBG_RFK, "[IQK]===>%s\n", __func__); rtw89_debug(rtwdev, RTW89_DBG_RFK, "[IQK]S%x, gp = %x\n", path, gp); - rtw89_write_rf(rtwdev, RF_PATH_A, RR_MOD, RR_MOD_RGM, a_idxrxgain[idx]); - rtw89_write_rf(rtwdev, RF_PATH_A, RR_RXA2, RR_RXA2_ATT, a_idxattc2[idx]); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWE, RFREG_MASK, 0x1000); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWA, RFREG_MASK, 0x4); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWD0, RFREG_MASK, 0x17); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWA, RFREG_MASK, 0x5); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWD0, RFREG_MASK, 0x27); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWE, RFREG_MASK, 0x0); + + val = rtw89_read_rf(rtwdev, RF_PATH_A, RR_RXA2, 0x20); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_MOD, RR_MOD_MASK, 0xc); + + rtw89_write_rf(rtwdev, RF_PATH_A, RR_MOD, RR_MOD_RGM, a_idxrxgain[gp]); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_RXA2, RR_RXA2_ATT, a_idxattc2[gp]); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_RXA2, 0x20, 0x1); rtw89_phy_write32_mask(rtwdev, R_CFIR_LUT, B_CFIR_LUT_SEL, 0x1); rtw89_phy_write32_mask(rtwdev, R_CFIR_LUT, B_CFIR_LUT_G3, 0x0); @@ -1000,7 +1016,7 @@ static bool _iqk_5g_nbrxk(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx, fsleep(100); rf_0 = rtw89_read_rf(rtwdev, path, RR_MOD, RFREG_MASK); rtw89_phy_write32_mask(rtwdev, R_IQK_DIF2, B_IQK_DIF2_RXPI, rf_0); - rtw89_phy_write32_mask(rtwdev, R_IQK_RXA, B_IQK_RXAGC, a_idxrxagc[idx]); + rtw89_phy_write32_mask(rtwdev, R_IQK_RXA, B_IQK_RXAGC, a_idxrxagc[gp]); rtw89_phy_write32_mask(rtwdev, R_IQK_DIF4, B_IQK_DIF4_RXT, 0x11); notready = _iqk_one_shot(rtwdev, phy_idx, path, ID_RXAGC); @@ -1026,6 +1042,7 @@ static bool _iqk_5g_nbrxk(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx, kfail = !!rtw89_phy_read32_mask(rtwdev, R_NCTL_RPT, B_NCTL_RPT_FLG); if (kfail) { + rtw89_phy_write32_mask(rtwdev, R_IQK_RES + (path << 8), 0xf, 0x0); rtw89_phy_write32_mask(rtwdev, R_RXIQC + (path << 8), MASKDWORD, 0x40000002); iqk_info->is_wb_rxiqk[path] = false; @@ -1033,6 +1050,14 @@ static bool _iqk_5g_nbrxk(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx, iqk_info->is_wb_rxiqk[path] = false; } + rtw89_write_rf(rtwdev, RF_PATH_A, RR_RXA2, 0x20, val); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWE, RFREG_MASK, 0x1000); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWA, RFREG_MASK, 0x4); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWD0, RFREG_MASK, 0x37); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWA, RFREG_MASK, 0x5); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWD0, RFREG_MASK, 0x27); + rtw89_write_rf(rtwdev, RF_PATH_A, RR_LUTWE, RFREG_MASK, 0x0); + rtw89_debug(rtwdev, RTW89_DBG_RFK, "[IQK]S%x, kfail = 0x%x, 0x8%x3c = 0x%x\n", path, kfail, 1 << path, iqk_info->nb_rxcfir[path]); @@ -1664,8 +1689,6 @@ static void _iqk_init(struct rtw89_dev *rtwdev) struct rtw89_iqk_info *iqk_info = &rtwdev->iqk; u8 idx, path; - rtw89_phy_write32_mask(rtwdev, R_IQKINF, MASKDWORD, 0x0); - if (iqk_info->is_iqk_init) return; From 5b2341efbb7af974925985f7798321fe92b4f8af Mon Sep 17 00:00:00 2001 From: Kuan-Chung Chen Date: Mon, 11 Aug 2025 20:39:13 +0800 Subject: [PATCH 010/167] wifi: rtw89: 8851b: rfk: update TX wideband IQK Adjust TX wideband calibration from 1 to 2 loop gain settings. This can reflect in better performance in 5 GHz medium-high attenuation environments. Signed-off-by: Kuan-Chung Chen Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250811123913.15524-1-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/reg.h | 1 + .../net/wireless/realtek/rtw89/rtw8851b_rfk.c | 72 +++++++++++-------- 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h index de81103a072f..0aad9dc91736 100644 --- a/drivers/net/wireless/realtek/rtw89/reg.h +++ b/drivers/net/wireless/realtek/rtw89/reg.h @@ -9126,6 +9126,7 @@ #define B_COEF_SEL_MDPD BIT(8) #define B_COEF_SEL_MDPD_V1 GENMASK(9, 8) #define B_COEF_SEL_EN BIT(31) +#define R_CFIR_COEF 0x810c #define R_CFIR_SYS 0x8120 #define R_IQK_RES 0x8124 #define B_IQK_RES_K BIT(28) diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b_rfk.c b/drivers/net/wireless/realtek/rtw89/rtw8851b_rfk.c index a7867b0e083a..84c46d2f4d85 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851b_rfk.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851b_rfk.c @@ -18,7 +18,8 @@ #define DPK_KSET_NUM 4 #define RTW8851B_RXK_GROUP_NR 4 #define RTW8851B_RXK_GROUP_IDX_NR 4 -#define RTW8851B_TXK_GROUP_NR 1 +#define RTW8851B_A_TXK_GROUP_NR 2 +#define RTW8851B_G_TXK_GROUP_NR 1 #define RTW8851B_IQK_VER 0x14 #define RTW8851B_IQK_SS 1 #define RTW8851B_LOK_GRAM 10 @@ -117,16 +118,18 @@ static const u32 g_idxrxagc[RTW8851B_RXK_GROUP_NR] = {0x0, 0x1, 0x2, 0x3}; static const u32 a_idxrxgain[RTW8851B_RXK_GROUP_IDX_NR] = {0x10C, 0x112, 0x28c, 0x292}; static const u32 a_idxattc2[RTW8851B_RXK_GROUP_IDX_NR] = {0xf, 0xf, 0xf, 0xf}; static const u32 a_idxrxagc[RTW8851B_RXK_GROUP_IDX_NR] = {0x4, 0x5, 0x6, 0x7}; -static const u32 a_power_range[RTW8851B_TXK_GROUP_NR] = {0x0}; -static const u32 a_track_range[RTW8851B_TXK_GROUP_NR] = {0x6}; -static const u32 a_gain_bb[RTW8851B_TXK_GROUP_NR] = {0x0a}; -static const u32 a_itqt[RTW8851B_TXK_GROUP_NR] = {0x12}; -static const u32 g_power_range[RTW8851B_TXK_GROUP_NR] = {0x0}; -static const u32 g_track_range[RTW8851B_TXK_GROUP_NR] = {0x6}; -static const u32 g_gain_bb[RTW8851B_TXK_GROUP_NR] = {0x10}; -static const u32 g_itqt[RTW8851B_TXK_GROUP_NR] = {0x12}; +static const u32 a_power_range[RTW8851B_A_TXK_GROUP_NR] = {0x0, 0x0}; +static const u32 a_track_range[RTW8851B_A_TXK_GROUP_NR] = {0x7, 0x7}; +static const u32 a_gain_bb[RTW8851B_A_TXK_GROUP_NR] = {0x08, 0x0d}; +static const u32 a_itqt[RTW8851B_A_TXK_GROUP_NR] = {0x12, 0x12}; +static const u32 a_att_smxr[RTW8851B_A_TXK_GROUP_NR] = {0x0, 0x2}; +static const u32 g_power_range[RTW8851B_G_TXK_GROUP_NR] = {0x0}; +static const u32 g_track_range[RTW8851B_G_TXK_GROUP_NR] = {0x6}; +static const u32 g_gain_bb[RTW8851B_G_TXK_GROUP_NR] = {0x10}; +static const u32 g_itqt[RTW8851B_G_TXK_GROUP_NR] = {0x12}; -static const u32 rtw8851b_backup_bb_regs[] = {0xc0d4, 0xc0d8, 0xc0c4, 0xc0ec, 0xc0e8}; +static const u32 rtw8851b_backup_bb_regs[] = { + 0xc0d4, 0xc0d8, 0xc0c4, 0xc0ec, 0xc0e8, 0x12a0, 0xc0f0}; static const u32 rtw8851b_backup_rf_regs[] = { 0xef, 0xde, 0x0, 0x1e, 0x2, 0x85, 0x90, 0x5}; @@ -789,7 +792,7 @@ static bool _iqk_one_shot(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx, "[IQK]============ S%d ID_NBTXK ============\n", path); rtw89_phy_write32_mask(rtwdev, R_UPD_CLK, B_IQK_RFC_ON, 0x0); rtw89_phy_write32_mask(rtwdev, R_IQK_DIF4, B_IQK_DIF4_TXT, - 0x00b); + 0x11); iqk_cmd = 0x408 | (1 << (4 + path)); break; case ID_NBRXK: @@ -807,7 +810,7 @@ static bool _iqk_one_shot(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx, rtw89_phy_write32_mask(rtwdev, R_NCTL_CFG, MASKDWORD, iqk_cmd + 1); notready = _iqk_check_cal(rtwdev, path); if (iqk_info->iqk_sram_en && - (ktype == ID_NBRXK || ktype == ID_RXK)) + (ktype == ID_NBRXK || ktype == ID_RXK || ktype == ID_NBTXK)) _iqk_sram(rtwdev, path); rtw89_phy_write32_mask(rtwdev, R_UPD_CLK, B_IQK_RFC_ON, 0x0); @@ -1174,6 +1177,7 @@ static void _iqk_rxclk_setting(struct rtw89_dev *rtwdev, u8 path) static bool _txk_5g_group_sel(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx, u8 path) { + static const u8 a_idx[RTW8851B_A_TXK_GROUP_NR] = {2, 3}; struct rtw89_iqk_info *iqk_info = &rtwdev->iqk; bool kfail = false; bool notready; @@ -1181,16 +1185,20 @@ static bool _txk_5g_group_sel(struct rtw89_dev *rtwdev, rtw89_debug(rtwdev, RTW89_DBG_RFK, "[IQK]===>%s\n", __func__); - for (gp = 0x0; gp < RTW8851B_TXK_GROUP_NR; gp++) { + rtw89_phy_write32_mask(rtwdev, R_CFIR_COEF, MASKDWORD, 0x33332222); + + for (gp = 0x0; gp < RTW8851B_A_TXK_GROUP_NR; gp++) { rtw89_write_rf(rtwdev, path, RR_TXIG, RR_TXIG_GR0, a_power_range[gp]); rtw89_write_rf(rtwdev, path, RR_TXIG, RR_TXIG_GR1, a_track_range[gp]); rtw89_write_rf(rtwdev, path, RR_TXIG, RR_TXIG_TG, a_gain_bb[gp]); + rtw89_write_rf(rtwdev, path, RR_BIASA, RR_BIASA_A, a_att_smxr[gp]); rtw89_phy_write32_mask(rtwdev, R_CFIR_LUT, B_CFIR_LUT_SEL, 0x1); rtw89_phy_write32_mask(rtwdev, R_CFIR_LUT, B_CFIR_LUT_G3, 0x1); rtw89_phy_write32_mask(rtwdev, R_CFIR_LUT, B_CFIR_LUT_G2, 0x0); - rtw89_phy_write32_mask(rtwdev, R_CFIR_LUT, B_CFIR_LUT_GP, gp); + rtw89_phy_write32_mask(rtwdev, R_CFIR_LUT, B_CFIR_LUT_GP, a_idx[gp]); rtw89_phy_write32_mask(rtwdev, R_NCTL_N1, B_NCTL_N1_CIP, 0x00); + rtw89_phy_write32_mask(rtwdev, R_IQK_DIF4, B_IQK_DIF4_TXT, 0x11); rtw89_phy_write32_mask(rtwdev, R_KIP_IQP, MASKDWORD, a_itqt[gp]); notready = _iqk_one_shot(rtwdev, phy_idx, path, ID_NBTXK); @@ -1231,7 +1239,9 @@ static bool _txk_2g_group_sel(struct rtw89_dev *rtwdev, rtw89_debug(rtwdev, RTW89_DBG_RFK, "[IQK]===>%s\n", __func__); - for (gp = 0x0; gp < RTW8851B_TXK_GROUP_NR; gp++) { + rtw89_phy_write32_mask(rtwdev, R_CFIR_COEF, MASKDWORD, 0x0); + + for (gp = 0x0; gp < RTW8851B_G_TXK_GROUP_NR; gp++) { rtw89_write_rf(rtwdev, path, RR_TXIG, RR_TXIG_GR0, g_power_range[gp]); rtw89_write_rf(rtwdev, path, RR_TXIG, RR_TXIG_GR1, g_track_range[gp]); rtw89_write_rf(rtwdev, path, RR_TXIG, RR_TXIG_TG, g_gain_bb[gp]); @@ -1274,29 +1284,29 @@ static bool _txk_2g_group_sel(struct rtw89_dev *rtwdev, static bool _iqk_5g_nbtxk(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx, u8 path) { + static const u8 a_idx[RTW8851B_A_TXK_GROUP_NR] = {2, 3}; struct rtw89_iqk_info *iqk_info = &rtwdev->iqk; bool kfail = false; bool notready; - u8 gp; + u8 gp = 0; rtw89_debug(rtwdev, RTW89_DBG_RFK, "[IQK]===>%s\n", __func__); - for (gp = 0x0; gp < RTW8851B_TXK_GROUP_NR; gp++) { - rtw89_write_rf(rtwdev, path, RR_TXIG, RR_TXIG_GR0, a_power_range[gp]); - rtw89_write_rf(rtwdev, path, RR_TXIG, RR_TXIG_GR1, a_track_range[gp]); - rtw89_write_rf(rtwdev, path, RR_TXIG, RR_TXIG_TG, a_gain_bb[gp]); + rtw89_write_rf(rtwdev, path, RR_TXIG, RR_TXIG_GR0, a_power_range[gp]); + rtw89_write_rf(rtwdev, path, RR_TXIG, RR_TXIG_GR1, a_track_range[gp]); + rtw89_write_rf(rtwdev, path, RR_TXIG, RR_TXIG_TG, a_gain_bb[gp]); + rtw89_write_rf(rtwdev, path, RR_BIASA, RR_BIASA_A, a_att_smxr[gp]); - rtw89_phy_write32_mask(rtwdev, R_CFIR_LUT, B_CFIR_LUT_SEL, 0x1); - rtw89_phy_write32_mask(rtwdev, R_CFIR_LUT, B_CFIR_LUT_G3, 0x1); - rtw89_phy_write32_mask(rtwdev, R_CFIR_LUT, B_CFIR_LUT_G2, 0x0); - rtw89_phy_write32_mask(rtwdev, R_CFIR_LUT, B_CFIR_LUT_GP, gp); - rtw89_phy_write32_mask(rtwdev, R_NCTL_N1, B_NCTL_N1_CIP, 0x00); - rtw89_phy_write32_mask(rtwdev, R_KIP_IQP, MASKDWORD, a_itqt[gp]); + rtw89_phy_write32_mask(rtwdev, R_CFIR_LUT, B_CFIR_LUT_SEL, 0x1); + rtw89_phy_write32_mask(rtwdev, R_CFIR_LUT, B_CFIR_LUT_G3, 0x1); + rtw89_phy_write32_mask(rtwdev, R_CFIR_LUT, B_CFIR_LUT_G2, 0x0); + rtw89_phy_write32_mask(rtwdev, R_CFIR_LUT, B_CFIR_LUT_GP, a_idx[gp]); + rtw89_phy_write32_mask(rtwdev, R_NCTL_N1, B_NCTL_N1_CIP, 0x00); + rtw89_phy_write32_mask(rtwdev, R_KIP_IQP, MASKDWORD, a_itqt[gp]); - notready = _iqk_one_shot(rtwdev, phy_idx, path, ID_NBTXK); - iqk_info->nb_txcfir[path] = - rtw89_phy_read32_mask(rtwdev, R_TXIQC, MASKDWORD) | 0x2; - } + notready = _iqk_one_shot(rtwdev, phy_idx, path, ID_NBTXK); + iqk_info->nb_txcfir[path] = + rtw89_phy_read32_mask(rtwdev, R_TXIQC, MASKDWORD) | 0x2; if (!notready) kfail = !!rtw89_phy_read32_mask(rtwdev, R_NCTL_RPT, B_NCTL_RPT_FLG); @@ -1325,7 +1335,7 @@ static bool _iqk_2g_nbtxk(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx, rtw89_debug(rtwdev, RTW89_DBG_RFK, "[IQK]===>%s\n", __func__); - for (gp = 0x0; gp < RTW8851B_TXK_GROUP_NR; gp++) { + for (gp = 0x0; gp < RTW8851B_G_TXK_GROUP_NR; gp++) { rtw89_write_rf(rtwdev, path, RR_TXIG, RR_TXIG_GR0, g_power_range[gp]); rtw89_write_rf(rtwdev, path, RR_TXIG, RR_TXIG_GR1, g_track_range[gp]); rtw89_write_rf(rtwdev, path, RR_TXIG, RR_TXIG_TG, g_gain_bb[gp]); From 46ac5412e406f63149f199e38279f21f87f3701a Mon Sep 17 00:00:00 2001 From: Chih-Kang Chang Date: Mon, 11 Aug 2025 20:39:34 +0800 Subject: [PATCH 011/167] wifi: rtw89: 8852c: check LPS H2C command complete by C2H reg instead of done ack 8852C after FW 0.27.128.0 driver check LPS H2C command received by FW using C2H reg instead of done ack. Signed-off-by: Chih-Kang Chang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250811123934.15614-1-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/fw.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 398d8ab98f63..ede4c15314a8 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -847,6 +847,7 @@ static const struct __fw_feat_cfg fw_feat_tbl[] = { __CFG_FW_FEAT(RTL8852C, ge, 0, 27, 56, 10, BEACON_FILTER), __CFG_FW_FEAT(RTL8852C, ge, 0, 27, 80, 0, WOW_REASON_V1), __CFG_FW_FEAT(RTL8852C, ge, 0, 27, 128, 0, BEACON_LOSS_COUNT_V1), + __CFG_FW_FEAT(RTL8852C, ge, 0, 27, 128, 0, LPS_DACK_BY_C2H_REG), __CFG_FW_FEAT(RTL8852C, ge, 0, 27, 129, 1, BEACON_TRACKING), __CFG_FW_FEAT(RTL8922A, ge, 0, 34, 30, 0, CRASH_TRIGGER_TYPE_0), __CFG_FW_FEAT(RTL8922A, ge, 0, 34, 11, 0, MACID_PAUSE_SLEEP), From c4c16c88e78417424b4e3f33177e84baf0bc9a99 Mon Sep 17 00:00:00 2001 From: Kuan-Chung Chen Date: Mon, 11 Aug 2025 20:39:50 +0800 Subject: [PATCH 012/167] wifi: rtw89: fix BSSID comparison for non-transmitted BSSID For non-transmitted connections, beacons are received from the transmitted BSSID. Fix this to avoid missing beacon statistics. Signed-off-by: Kuan-Chung Chen Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250811123950.15697-1-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index 7d522031dfa1..0ad7562632a5 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -2668,6 +2668,7 @@ static void rtw89_vif_rx_stats_iter(void *data, u8 *mac, struct ieee80211_bss_conf *bss_conf; struct rtw89_vif_link *rtwvif_link; const u8 *bssid = iter_data->bssid; + const u8 *target_bssid; if (rtwdev->scanning && (ieee80211_is_beacon(hdr->frame_control) || @@ -2689,7 +2690,10 @@ static void rtw89_vif_rx_stats_iter(void *data, u8 *mac, goto out; } - if (!ether_addr_equal(bss_conf->bssid, bssid)) + target_bssid = ieee80211_is_beacon(hdr->frame_control) && + bss_conf->nontransmitted ? + bss_conf->transmitter_bssid : bss_conf->bssid; + if (!ether_addr_equal(target_bssid, bssid)) goto out; if (is_mld) { From bf02a01d1dd5bfe76ad8db3b588a39ecac96ebee Mon Sep 17 00:00:00 2001 From: Kuan-Chung Chen Date: Mon, 11 Aug 2025 20:40:01 +0800 Subject: [PATCH 013/167] wifi: rtw89: fix group frames loss when connected to non-transmitted BSSID When STA connects to AP with dot11MultiBSSIDImplemented set to true, the layout of the TIM element's Partial Virtual Bitmap changes. Bits 1 to (2^n - 1) are used to indicate buffered group addressed frames (e.g., broadcast/multicast) for non-transmitted BSSIDs. Fix the interpretation of this field to ensure group addressed frames are correctly received. Signed-off-by: Kuan-Chung Chen Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250811124001.15774-1-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.h | 1 + drivers/net/wireless/realtek/rtw89/mac.c | 26 +++++++++++++++++++++ drivers/net/wireless/realtek/rtw89/mac_be.c | 1 + drivers/net/wireless/realtek/rtw89/reg.h | 8 +++++++ 4 files changed, 36 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index ca48426c577f..d8c40ce3ec61 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -1011,6 +1011,7 @@ struct rtw89_port_reg { u32 ptcl_dbg; u32 ptcl_dbg_info; u32 bcn_drop_all; + u32 bcn_psr_rpt; u32 hiq_win[RTW89_PORT_NUM]; }; diff --git a/drivers/net/wireless/realtek/rtw89/mac.c b/drivers/net/wireless/realtek/rtw89/mac.c index e4f9d251d5ef..48712a2994b6 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.c +++ b/drivers/net/wireless/realtek/rtw89/mac.c @@ -4197,6 +4197,7 @@ static const struct rtw89_port_reg rtw89_port_base_ax = { .ptcl_dbg = R_AX_PTCL_DBG, .ptcl_dbg_info = R_AX_PTCL_DBG_INFO, .bcn_drop_all = R_AX_BCN_DROP_ALL0, + .bcn_psr_rpt = R_AX_BCN_PSR_RPT_P0, .hiq_win = {R_AX_P0MB_HGQ_WINDOW_CFG_0, R_AX_PORT_HGQ_WINDOW_CFG, R_AX_PORT_HGQ_WINDOW_CFG + 1, R_AX_PORT_HGQ_WINDOW_CFG + 2, R_AX_PORT_HGQ_WINDOW_CFG + 3}, @@ -4649,6 +4650,30 @@ static void rtw89_mac_port_cfg_bcn_early(struct rtw89_dev *rtwdev, BCN_ERLY_DEF); } +static void rtw89_mac_port_cfg_bcn_psr_rpt(struct rtw89_dev *rtwdev, + struct rtw89_vif_link *rtwvif_link) +{ + const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; + const struct rtw89_port_reg *p = mac->port_base; + struct ieee80211_bss_conf *bss_conf; + u8 bssid_index; + u32 reg; + + rcu_read_lock(); + + bss_conf = rtw89_vif_rcu_dereference_link(rtwvif_link, true); + if (bss_conf->nontransmitted) + bssid_index = bss_conf->bssid_index; + else + bssid_index = 0; + + rcu_read_unlock(); + + reg = rtw89_mac_reg_by_idx(rtwdev, p->bcn_psr_rpt + rtwvif_link->port * 4, + rtwvif_link->mac_idx); + rtw89_write32_mask(rtwdev, reg, B_AX_BCAID_P0_MASK, bssid_index); +} + void rtw89_mac_port_tsf_sync(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link, struct rtw89_vif_link *rtwvif_src, @@ -4805,6 +4830,7 @@ int rtw89_mac_port_update(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvi rtw89_mac_port_tsf_resync_all(rtwdev); fsleep(BCN_ERLY_SET_DLY); rtw89_mac_port_cfg_bcn_early(rtwdev, rtwvif_link); + rtw89_mac_port_cfg_bcn_psr_rpt(rtwdev, rtwvif_link); return 0; } diff --git a/drivers/net/wireless/realtek/rtw89/mac_be.c b/drivers/net/wireless/realtek/rtw89/mac_be.c index 0078080b3999..ef69672b6862 100644 --- a/drivers/net/wireless/realtek/rtw89/mac_be.c +++ b/drivers/net/wireless/realtek/rtw89/mac_be.c @@ -56,6 +56,7 @@ static const struct rtw89_port_reg rtw89_port_base_be = { .ptcl_dbg = R_BE_PTCL_DBG, .ptcl_dbg_info = R_BE_PTCL_DBG_INFO, .bcn_drop_all = R_BE_BCN_DROP_ALL0, + .bcn_psr_rpt = R_BE_BCN_PSR_RPT_P0, .hiq_win = {R_BE_P0MB_HGQ_WINDOW_CFG_0, R_BE_PORT_HGQ_WINDOW_CFG, R_BE_PORT_HGQ_WINDOW_CFG + 1, R_BE_PORT_HGQ_WINDOW_CFG + 2, R_BE_PORT_HGQ_WINDOW_CFG + 3}, diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h index 0aad9dc91736..bfed0bbcfb7e 100644 --- a/drivers/net/wireless/realtek/rtw89/reg.h +++ b/drivers/net/wireless/realtek/rtw89/reg.h @@ -3370,6 +3370,10 @@ #define B_AX_CSIPRT_HESU_AID_EN BIT(25) #define B_AX_CSIPRT_VHTSU_AID_EN BIT(24) +#define R_AX_BCN_PSR_RPT_P0 0xCE84 +#define R_AX_BCN_PSR_RPT_P0_C1 0xEE84 +#define B_AX_BCAID_P0_MASK GENMASK(10, 0) + #define R_AX_RX_STATE_MONITOR 0xCEF0 #define R_AX_RX_STATE_MONITOR_C1 0xEEF0 #define B_AX_RX_STATE_MONITOR_MASK GENMASK(31, 0) @@ -7494,6 +7498,10 @@ #define R_BE_DRV_INFO_OPTION_C1 0x15470 #define B_BE_DRV_INFO_PHYRPT_EN BIT(0) +#define R_BE_BCN_PSR_RPT_P0 0x11484 +#define R_BE_BCN_PSR_RPT_P0_C1 0x15484 +#define B_BE_BCAID_P0_MASK GENMASK(10, 0) + #define R_BE_RX_ERR_ISR 0x114F4 #define R_BE_RX_ERR_ISR_C1 0x154F4 #define B_BE_RX_ERR_TRIG_ACT_TO BIT(9) From e798f2ac6040f46a04795d7de977341fa9aeabae Mon Sep 17 00:00:00 2001 From: Bitterblue Smith Date: Mon, 11 Aug 2025 18:32:55 +0300 Subject: [PATCH 014/167] wifi: rtlwifi: rtl8192cu: Don't claim USB ID 07b8:8188 This ID appears to be RTL8188SU, not RTL8188CU. This is the wrong driver for RTL8188SU. The r8712u driver from staging used to handle this ID. Closes: https://lore.kernel.org/linux-wireless/ee0acfef-a753-4f90-87df-15f8eaa9c3a8@gmx.de/ Cc: stable@vger.kernel.org Signed-off-by: Bitterblue Smith Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/2e5e2348-bdb3-44b2-92b2-0231dbf464b0@gmail.com --- drivers/net/wireless/realtek/rtlwifi/rtl8192cu/sw.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtlwifi/rtl8192cu/sw.c b/drivers/net/wireless/realtek/rtlwifi/rtl8192cu/sw.c index 00a6778df704..9480823af838 100644 --- a/drivers/net/wireless/realtek/rtlwifi/rtl8192cu/sw.c +++ b/drivers/net/wireless/realtek/rtlwifi/rtl8192cu/sw.c @@ -291,7 +291,6 @@ static const struct usb_device_id rtl8192c_usb_ids[] = { {RTL_USB_DEVICE(0x050d, 0x1102, rtl92cu_hal_cfg)}, /*Belkin - Edimax*/ {RTL_USB_DEVICE(0x050d, 0x11f2, rtl92cu_hal_cfg)}, /*Belkin - ISY*/ {RTL_USB_DEVICE(0x06f8, 0xe033, rtl92cu_hal_cfg)}, /*Hercules - Edimax*/ - {RTL_USB_DEVICE(0x07b8, 0x8188, rtl92cu_hal_cfg)}, /*Abocom - Abocom*/ {RTL_USB_DEVICE(0x07b8, 0x8189, rtl92cu_hal_cfg)}, /*Funai - Abocom*/ {RTL_USB_DEVICE(0x0846, 0x9041, rtl92cu_hal_cfg)}, /*NetGear WNA1000M*/ {RTL_USB_DEVICE(0x0846, 0x9043, rtl92cu_hal_cfg)}, /*NG WNA1000Mv2*/ From ec0b44736b1d22b763ee94f1aee856f9e793f3fe Mon Sep 17 00:00:00 2001 From: Bitterblue Smith Date: Mon, 11 Aug 2025 18:33:28 +0300 Subject: [PATCH 015/167] wifi: rtl8xxxu: Don't claim USB ID 07b8:8188 This ID appears to be RTL8188SU, not RTL8188CU. This is the wrong driver for RTL8188SU. The r8712u driver from staging used to handle this ID. Closes: https://lore.kernel.org/linux-wireless/ee0acfef-a753-4f90-87df-15f8eaa9c3a8@gmx.de/ Cc: stable@vger.kernel.org Signed-off-by: Bitterblue Smith Reviewed-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/f147b2ab-4505-435a-aa32-62964e4f1f1e@gmail.com --- drivers/net/wireless/realtek/rtl8xxxu/core.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtl8xxxu/core.c b/drivers/net/wireless/realtek/rtl8xxxu/core.c index 831b5025c634..018f5afcd50d 100644 --- a/drivers/net/wireless/realtek/rtl8xxxu/core.c +++ b/drivers/net/wireless/realtek/rtl8xxxu/core.c @@ -8172,8 +8172,6 @@ static const struct usb_device_id dev_table[] = { .driver_info = (unsigned long)&rtl8192cu_fops}, {USB_DEVICE_AND_INTERFACE_INFO(0x06f8, 0xe033, 0xff, 0xff, 0xff), .driver_info = (unsigned long)&rtl8192cu_fops}, -{USB_DEVICE_AND_INTERFACE_INFO(0x07b8, 0x8188, 0xff, 0xff, 0xff), - .driver_info = (unsigned long)&rtl8192cu_fops}, {USB_DEVICE_AND_INTERFACE_INFO(0x07b8, 0x8189, 0xff, 0xff, 0xff), .driver_info = (unsigned long)&rtl8192cu_fops}, {USB_DEVICE_AND_INTERFACE_INFO(0x0846, 0x9041, 0xff, 0xff, 0xff), From 33319e8fd7ac2631eef89c770940fa5f96a93063 Mon Sep 17 00:00:00 2001 From: Liao Yuanhong Date: Mon, 18 Aug 2025 14:42:19 +0800 Subject: [PATCH 016/167] wifi: rtw89: 8852bt: Simplify unnecessary if-else conditions in _dpk_onoff() Some simple if-else logic can be simplified using the ! operator to improve code readability. Signed-off-by: Liao Yuanhong Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250818064219.448066-1-liaoyuanhong@vivo.com --- drivers/net/wireless/realtek/rtw89/rtw8852bt_rfk.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bt_rfk.c b/drivers/net/wireless/realtek/rtw89/rtw8852bt_rfk.c index b01f921b4224..99cb6a973de2 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852bt_rfk.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852bt_rfk.c @@ -1803,10 +1803,7 @@ static void _dpk_onoff(struct rtw89_dev *rtwdev, enum rtw89_rf_path path, bool o val = dpk->is_dpk_enable && !off && dpk->bp[path][kidx].path_ok; - if (off) - off_reverse = false; - else - off_reverse = true; + off_reverse = !off; val = dpk->is_dpk_enable & off_reverse & dpk->bp[path][kidx].path_ok; From 4367000c0e33e0bf24e2b872ccb49527a20f87e5 Mon Sep 17 00:00:00 2001 From: Po-Hao Huang Date: Tue, 19 Aug 2025 11:44:25 +0800 Subject: [PATCH 017/167] wifi: rtw89: 8852a: report per-channel noise level by get_survey ops To optimize roaming decisions, report per-channel noise levels during scans so supplicant can have a better view of the current channel condition. This allows it to prefer the APs operating on channels with lower noise levels. Signed-off-by: Po-Hao Huang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250819034428.26307-2-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.c | 4 + drivers/net/wireless/realtek/rtw89/core.h | 22 +- drivers/net/wireless/realtek/rtw89/mac.c | 4 + drivers/net/wireless/realtek/rtw89/mac80211.c | 35 ++ drivers/net/wireless/realtek/rtw89/phy.c | 306 +++++++++++++++++- drivers/net/wireless/realtek/rtw89/phy.h | 20 ++ drivers/net/wireless/realtek/rtw89/phy_be.c | 9 + drivers/net/wireless/realtek/rtw89/reg.h | 42 +++ drivers/net/wireless/realtek/rtw89/rtw8851b.c | 3 + drivers/net/wireless/realtek/rtw89/rtw8852a.c | 32 ++ drivers/net/wireless/realtek/rtw89/rtw8852b.c | 3 + drivers/net/wireless/realtek/rtw89/rtw8852c.c | 3 + drivers/net/wireless/realtek/rtw89/rtw8922a.c | 3 + 13 files changed, 482 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index 0ad7562632a5..2b658ee89bb6 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -6258,6 +6258,7 @@ int rtw89_core_register(struct rtw89_dev *rtwdev) return ret; } + rtw89_phy_dm_init_data(rtwdev); rtw89_debugfs_init(rtwdev); return 0; @@ -6308,6 +6309,9 @@ struct rtw89_dev *rtw89_alloc_ieee80211_hw(struct device *device, ops->cancel_remain_on_channel = NULL; } + if (!chip->support_noise) + ops->get_survey = NULL; + driver_data_size = sizeof(struct rtw89_dev) + bus_data_size; hw = ieee80211_alloc_hw(driver_data_size, ops); if (!hw) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index d8c40ce3ec61..a5fef3c84b20 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -4364,6 +4364,9 @@ struct rtw89_chanctx_listener { (struct rtw89_dev *rtwdev, enum rtw89_chanctx_state state); }; +#define RTW89_NHM_TH_NUM 11 +#define RTW89_NHM_RPT_NUM 12 + struct rtw89_chip_info { enum rtw89_core_chip_id chip_id; enum rtw89_chip_gen chip_gen; @@ -4398,6 +4401,7 @@ struct rtw89_chip_info { bool support_ant_gain; bool support_tas; bool support_sar_by_ant; + bool support_noise; bool ul_tb_waveform_ctrl; bool ul_tb_pwr_diff; bool rx_freq_frome_ie; @@ -4482,6 +4486,8 @@ struct rtw89_chip_info { bool cfo_hw_comp; const struct rtw89_reg_def *dcfo_comp; u8 dcfo_comp_sft; + const struct rtw89_reg_def (*nhm_report)[RTW89_NHM_RPT_NUM]; + const struct rtw89_reg_def (*nhm_th)[RTW89_NHM_TH_NUM]; const struct rtw89_imr_info *imr_info; const struct rtw89_imr_table *imr_dmac_table; const struct rtw89_imr_table *imr_cmac_table; @@ -5464,6 +5470,7 @@ enum rtw89_env_racing_lv { struct rtw89_ccx_para_info { enum rtw89_env_racing_lv rac_lv; u16 mntr_time; + bool nhm_incld_cca; u8 nhm_manual_th_ofst; u8 nhm_manual_th0; enum rtw89_ifs_clm_application ifs_clm_app; @@ -5497,9 +5504,13 @@ enum rtw89_ccx_edcca_opt_bw_idx { RTW89_CCX_EDCCA_BW20_7 = 7 }; -#define RTW89_NHM_TH_NUM 11 +struct rtw89_nhm_report { + struct list_head list; + struct ieee80211_channel *channel; + u8 noise; +}; + #define RTW89_FAHM_TH_NUM 11 -#define RTW89_NHM_RPT_NUM 12 #define RTW89_FAHM_RPT_NUM 12 #define RTW89_IFS_CLM_NUM 4 struct rtw89_env_monitor_info { @@ -5533,6 +5544,13 @@ struct rtw89_env_monitor_info { u16 ifs_clm_ofdm_fa_permil; u32 ifs_clm_ifs_avg[RTW89_IFS_CLM_NUM]; u32 ifs_clm_cca_avg[RTW89_IFS_CLM_NUM]; + bool nhm_include_cca; + u32 nhm_sum; + u32 nhm_mntr_time; + u16 nhm_result[RTW89_NHM_RPT_NUM]; + u8 nhm_th[RTW89_NHM_RPT_NUM]; + struct rtw89_nhm_report *nhm_his[RTW89_BAND_NUM]; + struct list_head nhm_rpt_list; }; enum rtw89_ser_rcvy_step { diff --git a/drivers/net/wireless/realtek/rtw89/mac.c b/drivers/net/wireless/realtek/rtw89/mac.c index 48712a2994b6..06fc113ffaf0 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.c +++ b/drivers/net/wireless/realtek/rtw89/mac.c @@ -9,6 +9,7 @@ #include "fw.h" #include "mac.h" #include "pci.h" +#include "phy.h" #include "ps.h" #include "reg.h" #include "util.h" @@ -5045,6 +5046,8 @@ rtw89_mac_c2h_scanofld_rsp(struct rtw89_dev *rtwdev, struct sk_buff *skb, if (op_chan) { rtw89_mac_enable_aps_bcn_by_chan(rtwdev, op_chan, false); ieee80211_stop_queues(rtwdev->hw); + } else { + rtw89_phy_nhm_get_result(rtwdev, band, chan); } return; case RTW89_SCAN_END_SCAN_NOTIFY: @@ -5075,6 +5078,7 @@ rtw89_mac_c2h_scanofld_rsp(struct rtw89_dev *rtwdev, struct sk_buff *skb, RTW89_CHANNEL_WIDTH_20); rtw89_assign_entity_chan(rtwdev, rtwvif_link->chanctx_idx, &new); + rtw89_phy_nhm_trigger(rtwdev); } break; default: diff --git a/drivers/net/wireless/realtek/rtw89/mac80211.c b/drivers/net/wireless/realtek/rtw89/mac80211.c index c1ca6d741b32..7b04183a3a5d 100644 --- a/drivers/net/wireless/realtek/rtw89/mac80211.c +++ b/drivers/net/wireless/realtek/rtw89/mac80211.c @@ -1837,6 +1837,40 @@ static void rtw89_set_rekey_data(struct ieee80211_hw *hw, } #endif +static int rtw89_ops_get_survey(struct ieee80211_hw *hw, int idx, + struct survey_info *survey) +{ + struct ieee80211_conf *conf = &hw->conf; + struct rtw89_dev *rtwdev = hw->priv; + struct rtw89_bb_ctx *bb; + + if (idx == 0) { + survey->channel = conf->chandef.chan; + survey->filled = SURVEY_INFO_NOISE_DBM; + survey->noise = RTW89_NOISE_DEFAULT; + + return 0; + } + + rtw89_for_each_active_bb(rtwdev, bb) { + struct rtw89_env_monitor_info *env = &bb->env_monitor; + struct rtw89_nhm_report *rpt; + + rpt = list_first_entry_or_null(&env->nhm_rpt_list, typeof(*rpt), list); + if (!rpt) + continue; + + survey->filled = SURVEY_INFO_NOISE_DBM; + survey->noise = rpt->noise - MAX_RSSI; + survey->channel = rpt->channel; + list_del_init(&rpt->list); + + return 0; + } + + return -EINVAL; +} + static void rtw89_ops_rfkill_poll(struct ieee80211_hw *hw) { struct rtw89_dev *rtwdev = hw->priv; @@ -1869,6 +1903,7 @@ const struct ieee80211_ops rtw89_ops = { .sta_state = rtw89_ops_sta_state, .set_key = rtw89_ops_set_key, .ampdu_action = rtw89_ops_ampdu_action, + .get_survey = rtw89_ops_get_survey, .set_rts_threshold = rtw89_ops_set_rts_threshold, .sta_statistics = rtw89_ops_sta_statistics, .flush = rtw89_ops_flush, diff --git a/drivers/net/wireless/realtek/rtw89/phy.c b/drivers/net/wireless/realtek/rtw89/phy.c index 06598723074e..c3181a301f7c 100644 --- a/drivers/net/wireless/realtek/rtw89/phy.c +++ b/drivers/net/wireless/realtek/rtw89/phy.c @@ -5496,6 +5496,34 @@ static void rtw89_phy_ifs_clm_set_th_reg(struct rtw89_dev *rtwdev, i + 1, env->ifs_clm_th_l[i], env->ifs_clm_th_h[i]); } +static void __rtw89_phy_nhm_setting_init(struct rtw89_dev *rtwdev, + struct rtw89_bb_ctx *bb) +{ + const struct rtw89_phy_gen_def *phy = rtwdev->chip->phy_def; + struct rtw89_env_monitor_info *env = &bb->env_monitor; + const struct rtw89_ccx_regs *ccx = phy->ccx; + + env->nhm_include_cca = false; + env->nhm_mntr_time = 0; + env->nhm_sum = 0; + + rtw89_phy_write32_idx_set(rtwdev, ccx->nhm_config, ccx->nhm_en_mask, bb->phy_idx); + rtw89_phy_write32_idx_set(rtwdev, ccx->nhm_method, ccx->nhm_pwr_method_msk, + bb->phy_idx); +} + +void rtw89_phy_nhm_setting_init(struct rtw89_dev *rtwdev) +{ + const struct rtw89_chip_info *chip = rtwdev->chip; + struct rtw89_bb_ctx *bb; + + if (!chip->support_noise) + return; + + rtw89_for_each_active_bb(rtwdev, bb) + __rtw89_phy_nhm_setting_init(rtwdev, bb); +} + static void rtw89_phy_ifs_clm_setting_init(struct rtw89_dev *rtwdev, struct rtw89_bb_ctx *bb) { @@ -5557,7 +5585,7 @@ static int rtw89_phy_ccx_racing_ctrl(struct rtw89_dev *rtwdev, } static void rtw89_phy_ccx_trigger(struct rtw89_dev *rtwdev, - struct rtw89_bb_ctx *bb) + struct rtw89_bb_ctx *bb, u8 sel) { const struct rtw89_phy_gen_def *phy = rtwdev->chip->phy_def; struct rtw89_env_monitor_info *env = &bb->env_monitor; @@ -5567,10 +5595,17 @@ static void rtw89_phy_ccx_trigger(struct rtw89_dev *rtwdev, bb->phy_idx); rtw89_phy_write32_idx(rtwdev, ccx->setting_addr, ccx->measurement_trig_mask, 0, bb->phy_idx); + if (sel & RTW89_PHY_ENV_MON_NHM) + rtw89_phy_write32_idx_clr(rtwdev, ccx->nhm_config, + ccx->nhm_en_mask, bb->phy_idx); + rtw89_phy_write32_idx(rtwdev, ccx->ifs_cnt_addr, ccx->ifs_clm_cnt_clear_mask, 1, bb->phy_idx); rtw89_phy_write32_idx(rtwdev, ccx->setting_addr, ccx->measurement_trig_mask, 1, bb->phy_idx); + if (sel & RTW89_PHY_ENV_MON_NHM) + rtw89_phy_write32_idx_set(rtwdev, ccx->nhm_config, + ccx->nhm_en_mask, bb->phy_idx); env->ccx_ongoing = true; } @@ -5641,6 +5676,125 @@ static void rtw89_phy_ifs_clm_get_utility(struct rtw89_dev *rtwdev, env->ifs_clm_cca_avg[i]); } +static u8 rtw89_nhm_weighted_avg(struct rtw89_dev *rtwdev, struct rtw89_bb_ctx *bb) +{ + struct rtw89_env_monitor_info *env = &bb->env_monitor; + u8 nhm_weight[RTW89_NHM_RPT_NUM]; + u32 nhm_weighted_sum = 0; + u8 weight_zero; + u8 i; + + if (env->nhm_sum == 0) + return 0; + + weight_zero = clamp_t(u16, env->nhm_th[0] - RTW89_NHM_WEIGHT_OFFSET, 0, U8_MAX); + + for (i = 0; i < RTW89_NHM_RPT_NUM; i++) { + if (i == 0) + nhm_weight[i] = weight_zero; + else if (i == (RTW89_NHM_RPT_NUM - 1)) + nhm_weight[i] = env->nhm_th[i - 1] + RTW89_NHM_WEIGHT_OFFSET; + else + nhm_weight[i] = (env->nhm_th[i - 1] + env->nhm_th[i]) / 2; + } + + if (rtwdev->chip->chip_id == RTL8852A || rtwdev->chip->chip_id == RTL8852B || + rtwdev->chip->chip_id == RTL8852C) { + if (env->nhm_th[RTW89_NHM_TH_NUM - 1] == RTW89_NHM_WA_TH) { + nhm_weight[RTW89_NHM_RPT_NUM - 1] = + env->nhm_th[RTW89_NHM_TH_NUM - 2] + + RTW89_NHM_WEIGHT_OFFSET; + nhm_weight[RTW89_NHM_RPT_NUM - 2] = + nhm_weight[RTW89_NHM_RPT_NUM - 1]; + } + + env->nhm_result[0] += env->nhm_result[RTW89_NHM_RPT_NUM - 1]; + env->nhm_result[RTW89_NHM_RPT_NUM - 1] = 0; + } + + for (i = 0; i < RTW89_NHM_RPT_NUM; i++) + nhm_weighted_sum += env->nhm_result[i] * nhm_weight[i]; + + return (nhm_weighted_sum / env->nhm_sum) >> RTW89_NHM_TH_FACTOR; +} + +static void __rtw89_phy_nhm_get_result(struct rtw89_dev *rtwdev, + struct rtw89_bb_ctx *bb, enum rtw89_band hw_band, + u16 ch_hw_value) +{ + const struct rtw89_phy_gen_def *phy = rtwdev->chip->phy_def; + struct rtw89_env_monitor_info *env = &bb->env_monitor; + const struct rtw89_chip_info *chip = rtwdev->chip; + const struct rtw89_ccx_regs *ccx = phy->ccx; + struct ieee80211_supported_band *sband; + const struct rtw89_reg_def *nhm_rpt; + enum nl80211_band band; + u32 sum = 0; + u8 chan_idx; + u8 nhm_pwr; + u8 i; + + if (!rtw89_phy_read32_idx(rtwdev, ccx->nhm, ccx->nhm_ready, bb->phy_idx)) { + rtw89_debug(rtwdev, RTW89_DBG_PHY_TRACK, "[NHM] Get NHM report Fail\n"); + return; + } + + for (i = 0; i < RTW89_NHM_RPT_NUM; i++) { + nhm_rpt = &(*chip->nhm_report)[i]; + + env->nhm_result[i] = + rtw89_phy_read32_idx(rtwdev, nhm_rpt->addr, + nhm_rpt->mask, bb->phy_idx); + sum += env->nhm_result[i]; + } + env->nhm_sum = sum; + nhm_pwr = rtw89_nhm_weighted_avg(rtwdev, bb); + + if (!ch_hw_value) + return; + + band = rtw89_hw_to_nl80211_band(hw_band); + sband = rtwdev->hw->wiphy->bands[band]; + if (!sband) + return; + + for (chan_idx = 0; chan_idx < sband->n_channels; chan_idx++) { + struct ieee80211_channel *channel; + struct rtw89_nhm_report *rpt; + struct list_head *nhm_list; + + channel = &sband->channels[chan_idx]; + if (channel->hw_value != ch_hw_value) + continue; + + rpt = &env->nhm_his[hw_band][chan_idx]; + nhm_list = &env->nhm_rpt_list; + + rpt->channel = channel; + rpt->noise = nhm_pwr; + + if (list_empty(&rpt->list)) + list_add_tail(&rpt->list, nhm_list); + + return; + } + + rtw89_debug(rtwdev, RTW89_DBG_PHY_TRACK, "[NHM] channel not found\n"); +} + +void rtw89_phy_nhm_get_result(struct rtw89_dev *rtwdev, enum rtw89_band hw_band, + u16 ch_hw_value) +{ + const struct rtw89_chip_info *chip = rtwdev->chip; + struct rtw89_bb_ctx *bb; + + if (!chip->support_noise) + return; + + rtw89_for_each_active_bb(rtwdev, bb) + __rtw89_phy_nhm_get_result(rtwdev, bb, hw_band, ch_hw_value); +} + static bool rtw89_phy_ifs_clm_get_result(struct rtw89_dev *rtwdev, struct rtw89_bb_ctx *bb) { @@ -5741,6 +5895,107 @@ static bool rtw89_phy_ifs_clm_get_result(struct rtw89_dev *rtwdev, return true; } +static void rtw89_phy_nhm_th_update(struct rtw89_dev *rtwdev, + struct rtw89_bb_ctx *bb) +{ + struct rtw89_env_monitor_info *env = &bb->env_monitor; + static const u8 nhm_th_11k[RTW89_NHM_RPT_NUM] = { + 18, 21, 24, 27, 30, 35, 40, 45, 50, 55, 60, 0 + }; + const struct rtw89_chip_info *chip = rtwdev->chip; + const struct rtw89_reg_def *nhm_th; + u8 i; + + for (i = 0; i < RTW89_NHM_RPT_NUM; i++) + env->nhm_th[i] = nhm_th_11k[i] << RTW89_NHM_TH_FACTOR; + + if (chip->chip_id == RTL8852A || chip->chip_id == RTL8852B || + chip->chip_id == RTL8852C) + env->nhm_th[RTW89_NHM_TH_NUM - 1] = RTW89_NHM_WA_TH; + + for (i = 0; i < RTW89_NHM_TH_NUM; i++) { + nhm_th = &(*chip->nhm_th)[i]; + + rtw89_phy_write32_idx(rtwdev, nhm_th->addr, nhm_th->mask, + env->nhm_th[i], bb->phy_idx); + } +} + +static int rtw89_phy_nhm_set(struct rtw89_dev *rtwdev, + struct rtw89_bb_ctx *bb, + struct rtw89_ccx_para_info *para) +{ + const struct rtw89_phy_gen_def *phy = rtwdev->chip->phy_def; + struct rtw89_env_monitor_info *env = &bb->env_monitor; + const struct rtw89_ccx_regs *ccx = phy->ccx; + u32 unit_idx = 0; + u32 period = 0; + + if (para->mntr_time == 0) { + rtw89_debug(rtwdev, RTW89_DBG_PHY_TRACK, + "[NHM] MNTR_TIME is 0\n"); + return -EINVAL; + } + + if (rtw89_phy_ccx_racing_ctrl(rtwdev, bb, para->rac_lv)) + return -EINVAL; + + rtw89_debug(rtwdev, RTW89_DBG_PHY_TRACK, + "[NHM]nhm_incld_cca=%d, mntr_time=%d ms\n", + para->nhm_incld_cca, para->mntr_time); + + if (para->mntr_time != env->nhm_mntr_time) { + rtw89_phy_ccx_ms_to_period_unit(rtwdev, para->mntr_time, + &period, &unit_idx); + rtw89_phy_write32_idx(rtwdev, ccx->nhm_config, + ccx->nhm_period_mask, period, bb->phy_idx); + rtw89_phy_write32_idx(rtwdev, ccx->nhm_config, + ccx->nhm_unit_mask, period, bb->phy_idx); + + env->nhm_mntr_time = para->mntr_time; + env->ccx_period = period; + env->ccx_unit_idx = unit_idx; + } + + if (para->nhm_incld_cca != env->nhm_include_cca) { + rtw89_phy_write32_idx(rtwdev, ccx->nhm_config, + ccx->nhm_include_cca_mask, para->nhm_incld_cca, + bb->phy_idx); + + env->nhm_include_cca = para->nhm_incld_cca; + } + + rtw89_phy_nhm_th_update(rtwdev, bb); + + return 0; +} + +static void __rtw89_phy_nhm_trigger(struct rtw89_dev *rtwdev, struct rtw89_bb_ctx *bb) +{ + struct rtw89_ccx_para_info para = { + .mntr_time = RTW89_NHM_MNTR_TIME, + .rac_lv = RTW89_RAC_LV_1, + .nhm_incld_cca = true, + }; + + rtw89_phy_ccx_racing_release(rtwdev, bb); + + rtw89_phy_nhm_set(rtwdev, bb, ¶); + rtw89_phy_ccx_trigger(rtwdev, bb, RTW89_PHY_ENV_MON_NHM); +} + +void rtw89_phy_nhm_trigger(struct rtw89_dev *rtwdev) +{ + const struct rtw89_chip_info *chip = rtwdev->chip; + struct rtw89_bb_ctx *bb; + + if (!chip->support_noise) + return; + + rtw89_for_each_active_bb(rtwdev, bb) + __rtw89_phy_nhm_trigger(rtwdev, bb); +} + static int rtw89_phy_ifs_clm_set(struct rtw89_dev *rtwdev, struct rtw89_bb_ctx *bb, struct rtw89_ccx_para_info *para) @@ -5815,7 +6070,7 @@ static void __rtw89_phy_env_monitor_track(struct rtw89_dev *rtwdev, if (rtw89_phy_ifs_clm_set(rtwdev, bb, ¶) == 0) chk_result |= RTW89_PHY_ENV_MON_IFS_CLM; if (chk_result) - rtw89_phy_ccx_trigger(rtwdev, bb); + rtw89_phy_ccx_trigger(rtwdev, bb, chk_result); rtw89_debug(rtwdev, RTW89_DBG_PHY_TRACK, "get_result=0x%x, chk_result:0x%x\n", @@ -6909,6 +7164,7 @@ void rtw89_phy_dm_init(struct rtw89_dev *rtwdev) rtw89_chip_bb_sethw(rtwdev); rtw89_phy_env_monitor_init(rtwdev); + rtw89_phy_nhm_setting_init(rtwdev); rtw89_physts_parsing_init(rtwdev); rtw89_phy_dig_init(rtwdev); rtw89_phy_cfo_init(rtwdev); @@ -6934,6 +7190,43 @@ void rtw89_phy_dm_reinit(struct rtw89_dev *rtwdev) rtw89_physts_parsing_init(rtwdev); } +static void __rtw89_phy_dm_init_data(struct rtw89_dev *rtwdev, struct rtw89_bb_ctx *bb) +{ + struct rtw89_env_monitor_info *env = &bb->env_monitor; + const struct rtw89_chip_info *chip = rtwdev->chip; + struct ieee80211_supported_band *sband; + enum rtw89_band hw_band; + enum nl80211_band band; + u8 idx; + + if (!chip->support_noise) + return; + + for (band = 0; band < NUM_NL80211_BANDS; band++) { + sband = rtwdev->hw->wiphy->bands[band]; + if (!sband) + continue; + + hw_band = rtw89_nl80211_to_hw_band(band); + env->nhm_his[hw_band] = + devm_kcalloc(rtwdev->dev, sband->n_channels, + sizeof(*env->nhm_his[0]), GFP_KERNEL); + + for (idx = 0; idx < sband->n_channels; idx++) + INIT_LIST_HEAD(&env->nhm_his[hw_band][idx].list); + + INIT_LIST_HEAD(&env->nhm_rpt_list); + } +} + +void rtw89_phy_dm_init_data(struct rtw89_dev *rtwdev) +{ + struct rtw89_bb_ctx *bb; + + rtw89_for_each_capab_bb(rtwdev, bb) + __rtw89_phy_dm_init_data(rtwdev, bb); +} + void rtw89_phy_set_bss_color(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link) { @@ -7589,6 +7882,15 @@ static const struct rtw89_ccx_regs rtw89_ccx_regs_ax = { .ifs_total_addr = R_IFSCNT, .ifs_cnt_done_mask = B_IFSCNT_DONE_MSK, .ifs_total_mask = B_IFSCNT_TOTAL_CNT_MSK, + .nhm = R_NHM_AX, + .nhm_ready = B_NHM_READY_MSK, + .nhm_config = R_NHM_CFG, + .nhm_period_mask = B_NHM_PERIOD_MSK, + .nhm_unit_mask = B_NHM_COUNTER_MSK, + .nhm_include_cca_mask = B_NHM_INCLUDE_CCA_MSK, + .nhm_en_mask = B_NHM_EN_MSK, + .nhm_method = R_NHM_TH9, + .nhm_pwr_method_msk = B_NHM_PWDB_METHOD_MSK, }; static const struct rtw89_physts_regs rtw89_physts_regs_ax = { diff --git a/drivers/net/wireless/realtek/rtw89/phy.h b/drivers/net/wireless/realtek/rtw89/phy.h index dc156376d951..1184b3c16d6f 100644 --- a/drivers/net/wireless/realtek/rtw89/phy.h +++ b/drivers/net/wireless/realtek/rtw89/phy.h @@ -188,6 +188,12 @@ enum rtw89_env_monitor_result_level { RTW89_PHY_ENV_MON_EDCCA_CLM = BIT(4), }; +#define RTW89_NHM_WEIGHT_OFFSET 2 +#define RTW89_NHM_WA_TH (109 << 1) +#define RTW89_NOISE_DEFAULT -96 +#define RTW89_NHM_MNTR_TIME 40 +#define RTW89_NHM_TH_FACTOR 1 + #define CCX_US_BASE_RATIO 4 enum rtw89_ccx_unit { RTW89_CCX_4_US = 0, @@ -428,6 +434,15 @@ struct rtw89_ccx_regs { u32 ifs_total_addr; u32 ifs_cnt_done_mask; u32 ifs_total_mask; + u32 nhm; + u32 nhm_ready; + u32 nhm_config; + u32 nhm_period_mask; + u32 nhm_unit_mask; + u32 nhm_include_cca_mask; + u32 nhm_en_mask; + u32 nhm_method; + u32 nhm_pwr_method_msk; }; struct rtw89_physts_regs { @@ -821,6 +836,7 @@ void rtw89_phy_config_rf_reg_v1(struct rtw89_dev *rtwdev, void *extra_data); void rtw89_phy_dm_init(struct rtw89_dev *rtwdev); void rtw89_phy_dm_reinit(struct rtw89_dev *rtwdev); +void rtw89_phy_dm_init_data(struct rtw89_dev *rtwdev); void rtw89_phy_write32_idx(struct rtw89_dev *rtwdev, u32 addr, u32 mask, u32 data, enum rtw89_phy_idx phy_idx); void rtw89_phy_write32_idx_set(struct rtw89_dev *rtwdev, u32 addr, u32 bits, @@ -1038,5 +1054,9 @@ enum rtw89_rf_path rtw89_phy_get_syn_sel(struct rtw89_dev *rtwdev, u8 rtw89_rfk_chan_lookup(struct rtw89_dev *rtwdev, const struct rtw89_rfk_chan_desc *desc, u8 desc_nr, const struct rtw89_chan *target_chan); +void rtw89_phy_nhm_setting_init(struct rtw89_dev *rtwdev); +void rtw89_phy_nhm_get_result(struct rtw89_dev *rtwdev, enum rtw89_band hw_band, + u16 ch_hw_value); +void rtw89_phy_nhm_trigger(struct rtw89_dev *rtwdev); #endif diff --git a/drivers/net/wireless/realtek/rtw89/phy_be.c b/drivers/net/wireless/realtek/rtw89/phy_be.c index d321cf1fc485..3316a38a62d0 100644 --- a/drivers/net/wireless/realtek/rtw89/phy_be.c +++ b/drivers/net/wireless/realtek/rtw89/phy_be.c @@ -63,6 +63,15 @@ static const struct rtw89_ccx_regs rtw89_ccx_regs_be = { .ifs_total_addr = R_IFSCNT_V1, .ifs_cnt_done_mask = B_IFSCNT_DONE_MSK, .ifs_total_mask = B_IFSCNT_TOTAL_CNT_MSK, + .nhm = R_NHM_BE, + .nhm_ready = B_NHM_READY_BE_MSK, + .nhm_config = R_NHM_CFG, + .nhm_period_mask = B_NHM_PERIOD_MSK, + .nhm_unit_mask = B_NHM_COUNTER_MSK, + .nhm_include_cca_mask = B_NHM_INCLUDE_CCA_MSK, + .nhm_en_mask = B_NHM_EN_MSK, + .nhm_method = R_NHM_TH9, + .nhm_pwr_method_msk = B_NHM_PWDB_METHOD_MSK, }; static const struct rtw89_physts_regs rtw89_physts_regs_be = { diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h index bfed0bbcfb7e..d94d73e50e93 100644 --- a/drivers/net/wireless/realtek/rtw89/reg.h +++ b/drivers/net/wireless/realtek/rtw89/reg.h @@ -8100,6 +8100,26 @@ #define B_MEASUREMENT_TRIG_MSK BIT(2) #define B_CCX_TRIG_OPT_MSK BIT(1) #define B_CCX_EN_MSK BIT(0) +#define R_NHM_CFG 0x0C08 +#define B_NHM_PERIOD_MSK GENMASK(15, 0) +#define B_NHM_COUNTER_MSK GENMASK(17, 16) +#define B_NHM_EN_MSK BIT(18) +#define B_NHM_INCLUDE_CCA_MSK BIT(19) +#define B_NHM_TH0_MSK GENMASK(31, 24) +#define R_NHM_TH1 0x0C0C +#define B_NHM_TH1_MSK GENMASK(7, 0) +#define B_NHM_TH2_MSK GENMASK(15, 8) +#define B_NHM_TH3_MSK GENMASK(23, 16) +#define B_NHM_TH4_MSK GENMASK(31, 24) +#define R_NHM_TH5 0x0C10 +#define B_NHM_TH5_MSK GENMASK(7, 0) +#define B_NHM_TH6_MSK GENMASK(15, 8) +#define B_NHM_TH7_MSK GENMASK(23, 16) +#define B_NHM_TH8_MSK GENMASK(31, 24) +#define R_NHM_TH9 0x0C14 +#define B_NHM_TH9_MSK GENMASK(7, 0) +#define B_NHM_TH10_MSK GENMASK(15, 8) +#define B_NHM_PWDB_METHOD_MSK GENMASK(17, 16) #define R_FAHM 0x0C1C #define B_RXTD_CKEN BIT(2) #define R_IFS_COUNTER 0x0C28 @@ -8169,6 +8189,8 @@ #define R_BRK_ASYNC_RST_EN_1 0x0DC0 #define R_BRK_ASYNC_RST_EN_2 0x0DC4 #define R_BRK_ASYNC_RST_EN_3 0x0DC8 +#define R_NHM_BE 0x0EA4 +#define B_NHM_READY_BE_MSK BIT(16) #define R_CTLTOP 0x1008 #define B_CTLTOP_ON BIT(23) #define B_CTLTOP_VAL GENMASK(15, 12) @@ -8224,6 +8246,26 @@ #define B_SWSI_R_BUSY_V1 BIT(25) #define B_SWSI_R_DATA_DONE_V1 BIT(26) #define R_TX_COUNTER 0x1A40 +#define R_NHM_CNT0 0x1A88 +#define B_NHM_CNT0_MSK GENMASK(15, 0) +#define B_NHM_CNT1_MSK GENMASK(31, 16) +#define R_NHM_CNT2 0x1A8C +#define B_NHM_CNT2_MSK GENMASK(15, 0) +#define B_NHM_CNT3_MSK GENMASK(31, 16) +#define R_NHM_CNT4 0x1A90 +#define B_NHM_CNT4_MSK GENMASK(15, 0) +#define B_NHM_CNT5_MSK GENMASK(31, 16) +#define R_NHM_CNT6 0x1A94 +#define B_NHM_CNT6_MSK GENMASK(15, 0) +#define B_NHM_CNT7_MSK GENMASK(31, 16) +#define R_NHM_CNT8 0x1A98 +#define B_NHM_CNT8_MSK GENMASK(15, 0) +#define B_NHM_CNT9_MSK GENMASK(31, 16) +#define R_NHM_CNT10 0x1A9C +#define B_NHM_CNT10_MSK GENMASK(15, 0) +#define B_NHM_CNT11_MSK GENMASK(31, 16) +#define R_NHM_AX 0x1AA4 +#define B_NHM_READY_MSK BIT(16) #define R_IFS_CLM_TX_CNT 0x1ACC #define R_IFS_CLM_TX_CNT_V1 0x0ECC #define B_IFS_CLM_EDCCA_EXCLUDE_CCA_FA_MSK GENMASK(31, 16) diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b.c b/drivers/net/wireless/realtek/rtw89/rtw8851b.c index 393df2b0dcae..084bbf9ecf0b 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851b.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851b.c @@ -2628,6 +2628,7 @@ const struct rtw89_chip_info rtw8851b_chip_info = { .support_ant_gain = false, .support_tas = false, .support_sar_by_ant = false, + .support_noise = false, .ul_tb_waveform_ctrl = true, .ul_tb_pwr_diff = false, .rx_freq_frome_ie = true, @@ -2689,6 +2690,8 @@ const struct rtw89_chip_info rtw8851b_chip_info = { .cfo_hw_comp = true, .dcfo_comp = &rtw8851b_dcfo_comp, .dcfo_comp_sft = 12, + .nhm_report = NULL, + .nhm_th = NULL, .imr_info = &rtw8851b_imr_info, .imr_dmac_table = NULL, .imr_cmac_table = NULL, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852a.c b/drivers/net/wireless/realtek/rtw89/rtw8852a.c index 3bbe2a808844..1d85607e9424 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852a.c @@ -426,6 +426,35 @@ static const struct rtw89_reg_def rtw8852a_dcfo_comp = { R_DCFO_COMP_S0, B_DCFO_COMP_S0_MSK }; +static const struct rtw89_reg_def rtw8852a_nhm_th[RTW89_NHM_TH_NUM] = { + {R_NHM_CFG, B_NHM_TH0_MSK}, + {R_NHM_TH1, B_NHM_TH1_MSK}, + {R_NHM_TH1, B_NHM_TH2_MSK}, + {R_NHM_TH1, B_NHM_TH3_MSK}, + {R_NHM_TH1, B_NHM_TH4_MSK}, + {R_NHM_TH5, B_NHM_TH5_MSK}, + {R_NHM_TH5, B_NHM_TH6_MSK}, + {R_NHM_TH5, B_NHM_TH7_MSK}, + {R_NHM_TH5, B_NHM_TH8_MSK}, + {R_NHM_TH9, B_NHM_TH9_MSK}, + {R_NHM_TH9, B_NHM_TH10_MSK}, +}; + +static const struct rtw89_reg_def rtw8852a_nhm_rpt[RTW89_NHM_RPT_NUM] = { + {R_NHM_CNT0, B_NHM_CNT0_MSK}, + {R_NHM_CNT0, B_NHM_CNT1_MSK}, + {R_NHM_CNT2, B_NHM_CNT2_MSK}, + {R_NHM_CNT2, B_NHM_CNT3_MSK}, + {R_NHM_CNT4, B_NHM_CNT4_MSK}, + {R_NHM_CNT4, B_NHM_CNT5_MSK}, + {R_NHM_CNT6, B_NHM_CNT6_MSK}, + {R_NHM_CNT6, B_NHM_CNT7_MSK}, + {R_NHM_CNT8, B_NHM_CNT8_MSK}, + {R_NHM_CNT8, B_NHM_CNT9_MSK}, + {R_NHM_CNT10, B_NHM_CNT10_MSK}, + {R_NHM_CNT10, B_NHM_CNT11_MSK}, +}; + static const struct rtw89_imr_info rtw8852a_imr_info = { .wdrls_imr_set = B_AX_WDRLS_IMR_SET, .wsec_imr_reg = R_AX_SEC_DEBUG, @@ -2220,6 +2249,7 @@ const struct rtw89_chip_info rtw8852a_chip_info = { .support_ant_gain = false, .support_tas = false, .support_sar_by_ant = false, + .support_noise = true, .ul_tb_waveform_ctrl = false, .ul_tb_pwr_diff = false, .rx_freq_frome_ie = true, @@ -2282,6 +2312,8 @@ const struct rtw89_chip_info rtw8852a_chip_info = { .cfo_hw_comp = false, .dcfo_comp = &rtw8852a_dcfo_comp, .dcfo_comp_sft = 10, + .nhm_report = &rtw8852a_nhm_rpt, + .nhm_th = &rtw8852a_nhm_th, .imr_info = &rtw8852a_imr_info, .imr_dmac_table = NULL, .imr_cmac_table = NULL, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852b.c b/drivers/net/wireless/realtek/rtw89/rtw8852b.c index 7ede07f7b1eb..6f33f6db2763 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852b.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852b.c @@ -939,6 +939,7 @@ const struct rtw89_chip_info rtw8852b_chip_info = { .support_ant_gain = true, .support_tas = false, .support_sar_by_ant = true, + .support_noise = false, .ul_tb_waveform_ctrl = true, .ul_tb_pwr_diff = false, .rx_freq_frome_ie = true, @@ -1001,6 +1002,8 @@ const struct rtw89_chip_info rtw8852b_chip_info = { .cfo_hw_comp = true, .dcfo_comp = &rtw8852b_dcfo_comp, .dcfo_comp_sft = 10, + .nhm_report = NULL, + .nhm_th = NULL, .imr_info = &rtw8852b_imr_info, .imr_dmac_table = NULL, .imr_cmac_table = NULL, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852c.c b/drivers/net/wireless/realtek/rtw89/rtw8852c.c index 88cf8ea13e7c..b0418e89802f 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852c.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852c.c @@ -3043,6 +3043,7 @@ const struct rtw89_chip_info rtw8852c_chip_info = { .support_ant_gain = true, .support_tas = true, .support_sar_by_ant = true, + .support_noise = false, .ul_tb_waveform_ctrl = false, .ul_tb_pwr_diff = true, .rx_freq_frome_ie = false, @@ -3106,6 +3107,8 @@ const struct rtw89_chip_info rtw8852c_chip_info = { .cfo_hw_comp = false, .dcfo_comp = &rtw8852c_dcfo_comp, .dcfo_comp_sft = 12, + .nhm_report = NULL, + .nhm_th = NULL, .imr_info = &rtw8852c_imr_info, .imr_dmac_table = NULL, .imr_cmac_table = NULL, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922a.c b/drivers/net/wireless/realtek/rtw89/rtw8922a.c index 36c641e3bc13..d7d09d832252 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922a.c @@ -2896,6 +2896,7 @@ const struct rtw89_chip_info rtw8922a_chip_info = { .support_ant_gain = true, .support_tas = false, .support_sar_by_ant = true, + .support_noise = false, .ul_tb_waveform_ctrl = false, .ul_tb_pwr_diff = false, .rx_freq_frome_ie = false, @@ -2958,6 +2959,8 @@ const struct rtw89_chip_info rtw8922a_chip_info = { .cfo_hw_comp = true, .dcfo_comp = NULL, .dcfo_comp_sft = 0, + .nhm_report = NULL, + .nhm_th = NULL, .imr_info = NULL, .imr_dmac_table = &rtw8922a_imr_dmac_table, .imr_cmac_table = &rtw8922a_imr_cmac_table, From f0f3bf4b370cbd72a3dc63cb3a359677c4c27263 Mon Sep 17 00:00:00 2001 From: Po-Hao Huang Date: Tue, 19 Aug 2025 11:44:26 +0800 Subject: [PATCH 018/167] wifi: rtw89: 8852a: report average RSSI to avoid unnecessary scanning 8852A uses single antenna during power save, when the loss between two antennas is too large, previous logic induces greater RSSI variation. Report the average beacon RSSI for connected AP to get more stable RSSI and less unnecessary scanning. Signed-off-by: Po-Hao Huang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250819034428.26307-3-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/rtw8852a.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852a.c b/drivers/net/wireless/realtek/rtw89/rtw8852a.c index 1d85607e9424..d4200246eecc 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852a.c @@ -2109,10 +2109,17 @@ static void rtw8852a_query_ppdu(struct rtw89_dev *rtwdev, { u8 path; u8 *rx_power = phy_ppdu->rssi; + u8 raw; + + if (!status->signal) { + if (phy_ppdu->to_self) + raw = ewma_rssi_read(&rtwdev->phystat.bcn_rssi); + else + raw = max(rx_power[RF_PATH_A], rx_power[RF_PATH_B]); + + status->signal = RTW89_RSSI_RAW_TO_DBM(raw); + } - if (!status->signal) - status->signal = RTW89_RSSI_RAW_TO_DBM(max(rx_power[RF_PATH_A], - rx_power[RF_PATH_B])); for (path = 0; path < rtwdev->chip->rf_path_num; path++) { status->chains |= BIT(path); status->chain_signal[path] = RTW89_RSSI_RAW_TO_DBM(rx_power[path]); From d47c1c6f321c115e9adc0f3e8c347b36060b440b Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 19 Aug 2025 11:44:27 +0800 Subject: [PATCH 019/167] wifi: rtw89: 8852c: update firmware crash trigger type for newer firmware The newer firmware (after 0.27.128.0) uses trigger type 1. Add an entry accordingly. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250819034428.26307-4-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/fw.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index ede4c15314a8..334c33e6251d 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -848,6 +848,7 @@ static const struct __fw_feat_cfg fw_feat_tbl[] = { __CFG_FW_FEAT(RTL8852C, ge, 0, 27, 80, 0, WOW_REASON_V1), __CFG_FW_FEAT(RTL8852C, ge, 0, 27, 128, 0, BEACON_LOSS_COUNT_V1), __CFG_FW_FEAT(RTL8852C, ge, 0, 27, 128, 0, LPS_DACK_BY_C2H_REG), + __CFG_FW_FEAT(RTL8852C, ge, 0, 27, 128, 0, CRASH_TRIGGER_TYPE_1), __CFG_FW_FEAT(RTL8852C, ge, 0, 27, 129, 1, BEACON_TRACKING), __CFG_FW_FEAT(RTL8922A, ge, 0, 34, 30, 0, CRASH_TRIGGER_TYPE_0), __CFG_FW_FEAT(RTL8922A, ge, 0, 34, 11, 0, MACID_PAUSE_SLEEP), From ebea22c7f1b2f06f4ff0719d76bd19830cf25c9f Mon Sep 17 00:00:00 2001 From: Ching-Te Ku Date: Tue, 19 Aug 2025 11:44:28 +0800 Subject: [PATCH 020/167] wifi: rtw89: coex: Limit Wi-Fi scan slot cost to avoid A2DP glitch When Wi-Fi is scanning at 2.4GHz, PTA will abort almost all the BT request. Once the Wi-Fi slot stay too long, BT audio device can not get enough data, audio glitch will happened. This patch limit 2.4Ghz Wi-Fi slot to 80ms while Wi-Fi is scanning to avoid audio glitch. Signed-off-by: Ching-Te Ku Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250819034428.26307-5-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/coex.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/coex.c b/drivers/net/wireless/realtek/rtw89/coex.c index e4e6daf51a1b..0f7ae572ef91 100644 --- a/drivers/net/wireless/realtek/rtw89/coex.c +++ b/drivers/net/wireless/realtek/rtw89/coex.c @@ -93,7 +93,7 @@ static const struct rtw89_btc_fbtc_slot s_def[] = { [CXST_E2G] = __DEF_FBTC_SLOT(5, 0xea5a5a5a, SLOT_MIX), [CXST_E5G] = __DEF_FBTC_SLOT(5, 0xffffffff, SLOT_ISO), [CXST_EBT] = __DEF_FBTC_SLOT(5, 0xe5555555, SLOT_MIX), - [CXST_ENULL] = __DEF_FBTC_SLOT(5, 0xaaaaaaaa, SLOT_ISO), + [CXST_ENULL] = __DEF_FBTC_SLOT(5, 0x55555555, SLOT_MIX), [CXST_WLK] = __DEF_FBTC_SLOT(250, 0xea5a5a5a, SLOT_MIX), [CXST_W1FDD] = __DEF_FBTC_SLOT(50, 0xffffffff, SLOT_ISO), [CXST_B1FDD] = __DEF_FBTC_SLOT(50, 0xffffdfff, SLOT_ISO), @@ -4153,6 +4153,7 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) s_def[CXST_EBT].cxtbl, s_def[CXST_EBT].cxtype); _slot_set_le(btc, CXST_ENULL, s_def[CXST_ENULL].dur, s_def[CXST_ENULL].cxtbl, s_def[CXST_ENULL].cxtype); + _slot_set_dur(btc, CXST_EBT, dur_2); break; case BTC_CXP_OFFE_DEF2: _slot_set(btc, CXST_E2G, 20, cxtbl[1], SLOT_ISO); @@ -4162,6 +4163,7 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) s_def[CXST_EBT].cxtbl, s_def[CXST_EBT].cxtype); _slot_set_le(btc, CXST_ENULL, s_def[CXST_ENULL].dur, s_def[CXST_ENULL].cxtbl, s_def[CXST_ENULL].cxtype); + _slot_set_dur(btc, CXST_EBT, dur_2); break; case BTC_CXP_OFFE_2GBWMIXB: if (a2dp->exist) @@ -4170,6 +4172,7 @@ void rtw89_btc_set_policy_v1(struct rtw89_dev *rtwdev, u16 policy_type) _slot_set(btc, CXST_E2G, 5, tbl_w1, SLOT_MIX); _slot_set_le(btc, CXST_EBT, cpu_to_le16(40), s_def[CXST_EBT].cxtbl, s_def[CXST_EBT].cxtype); + _slot_set_dur(btc, CXST_EBT, dur_2); break; case BTC_CXP_OFFE_WL: /* for 4-way */ _slot_set(btc, CXST_E2G, 5, cxtbl[1], SLOT_MIX); From fce6fee0817b8899e0ee38ab6b98f0d7e939ceed Mon Sep 17 00:00:00 2001 From: Bitterblue Smith Date: Tue, 19 Aug 2025 21:46:02 +0300 Subject: [PATCH 021/167] wifi: rtw88: Use led->brightness_set_blocking for PCI too Commit 26a8bf978ae9 ("wifi: rtw88: Lock rtwdev->mutex before setting the LED") made rtw_led_set() sleep, but that's not allowed. Fix it by using the brightness_set_blocking member of struct led_classdev for PCI devices too. This one is allowed to sleep. bad: scheduling from the idle thread! nix kernel: CPU: 7 UID: 0 PID: 0 Comm: swapper/7 Tainted: G W O 6.16.0 #1-NixOS PREEMPT(voluntary) nix kernel: Tainted: [W]=WARN, [O]=OOT_MODULE nix kernel: Hardware name: [REDACTED] nix kernel: Call Trace: nix kernel: nix kernel: dump_stack_lvl+0x63/0x90 nix kernel: dequeue_task_idle+0x2d/0x50 nix kernel: __schedule+0x191/0x1310 nix kernel: ? xas_load+0x11/0xd0 nix kernel: schedule+0x2b/0xe0 nix kernel: schedule_preempt_disabled+0x19/0x30 nix kernel: __mutex_lock.constprop.0+0x3fd/0x7d0 nix kernel: rtw_led_set+0x27/0x60 [rtw_core] nix kernel: led_blink_set_nosleep+0x56/0xb0 nix kernel: led_trigger_blink+0x49/0x80 nix kernel: ? __pfx_tpt_trig_timer+0x10/0x10 [mac80211] nix kernel: call_timer_fn+0x2f/0x140 nix kernel: ? __pfx_tpt_trig_timer+0x10/0x10 [mac80211] nix kernel: __run_timers+0x21a/0x2b0 nix kernel: run_timer_softirq+0x8e/0x100 nix kernel: handle_softirqs+0xea/0x2c0 nix kernel: ? srso_alias_return_thunk+0x5/0xfbef5 nix kernel: __irq_exit_rcu+0xdc/0x100 nix kernel: sysvec_apic_timer_interrupt+0x7c/0x90 nix kernel: nix kernel: nix kernel: asm_sysvec_apic_timer_interrupt+0x1a/0x20 nix kernel: RIP: 0010:cpuidle_enter_state+0xcc/0x450 nix kernel: Code: 00 e8 08 7c 2e ff e8 d3 ee ff ff 49 89 c6 0f 1f 44 00 00 31 ff e8 c4 d1 2c ff 80 7d d7 00 0f 85 5d 02 00 00 fb 0f 1f 44 00 00 <45> 85 ff 0f 88 a0 01 00 00 49 63 f7 4c 89 f2 48 8d 0> nix kernel: RSP: 0018:ffffd579801c7e68 EFLAGS: 00000246 nix kernel: RAX: 0000000000000000 RBX: 0000000000000003 RCX: 0000000000000000 nix kernel: RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000 nix kernel: RBP: ffffd579801c7ea0 R08: 0000000000000000 R09: 0000000000000000 nix kernel: R10: 0000000000000000 R11: 0000000000000000 R12: ffff8eab0462a400 nix kernel: R13: ffffffff9a7d7a20 R14: 00000003aebe751d R15: 0000000000000003 nix kernel: ? cpuidle_enter_state+0xbc/0x450 nix kernel: cpuidle_enter+0x32/0x50 nix kernel: do_idle+0x1b1/0x210 nix kernel: cpu_startup_entry+0x2d/0x30 nix kernel: start_secondary+0x118/0x140 nix kernel: common_startup_64+0x13e/0x141 nix kernel: Fixes: 26a8bf978ae9 ("wifi: rtw88: Lock rtwdev->mutex before setting the LED") Signed-off-by: Bitterblue Smith Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/ad8a49ef-4f2d-4a61-8292-952db9c4eb65@gmail.com --- drivers/net/wireless/realtek/rtw88/led.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw88/led.c b/drivers/net/wireless/realtek/rtw88/led.c index 7f9ace351a5b..4cc62e49d167 100644 --- a/drivers/net/wireless/realtek/rtw88/led.c +++ b/drivers/net/wireless/realtek/rtw88/led.c @@ -6,8 +6,8 @@ #include "debug.h" #include "led.h" -static void rtw_led_set(struct led_classdev *led, - enum led_brightness brightness) +static int rtw_led_set(struct led_classdev *led, + enum led_brightness brightness) { struct rtw_dev *rtwdev = container_of(led, struct rtw_dev, led_cdev); @@ -16,12 +16,6 @@ static void rtw_led_set(struct led_classdev *led, rtwdev->chip->ops->led_set(led, brightness); mutex_unlock(&rtwdev->mutex); -} - -static int rtw_led_set_blocking(struct led_classdev *led, - enum led_brightness brightness) -{ - rtw_led_set(led, brightness); return 0; } @@ -46,10 +40,7 @@ void rtw_led_init(struct rtw_dev *rtwdev) if (!rtwdev->chip->ops->led_set) return; - if (rtw_hci_type(rtwdev) == RTW_HCI_TYPE_PCIE) - led->brightness_set = rtw_led_set; - else - led->brightness_set_blocking = rtw_led_set_blocking; + led->brightness_set_blocking = rtw_led_set; snprintf(rtwdev->led_name, sizeof(rtwdev->led_name), "rtw88-%s", dev_name(rtwdev->dev)); From f46edd92040f2687aa80fe986f1dd214be2b4950 Mon Sep 17 00:00:00 2001 From: Aleksej Smirnov Date: Fri, 22 Aug 2025 12:46:24 +0300 Subject: [PATCH 022/167] wifi: rtl8xxxu: Remove TL-WN722N V2 (0x2357: 0x010c) from untested devices This Wi-Fi dongle has been used for many years now and have had no problems with it. The device is quite old and known, dumping its efuse to the log and asking the user to send the results to Jes.Sorensen@gmail.com on every boot makes little sense. Signed-off-by: Aleksej Smirnov Reviewed-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/CAAN7eZ7QKEeQgNHEBuZKy4Gqg3oqpGi6BUdOVBOxPN7dedhVJQ@mail.gmail.com --- drivers/net/wireless/realtek/rtl8xxxu/core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtl8xxxu/core.c b/drivers/net/wireless/realtek/rtl8xxxu/core.c index 018f5afcd50d..9e00dc020e30 100644 --- a/drivers/net/wireless/realtek/rtl8xxxu/core.c +++ b/drivers/net/wireless/realtek/rtl8xxxu/core.c @@ -7815,7 +7815,8 @@ static int rtl8xxxu_probe(struct usb_interface *interface, untested = 0; break; case 0x2357: - if (id->idProduct == 0x0109 || id->idProduct == 0x0135) + if (id->idProduct == 0x0109 || id->idProduct == 0x010c || + id->idProduct == 0x0135) untested = 0; break; case 0x0b05: From 5cc73513f9b2cb9d0c0273ca4256656c8c479318 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 26 Aug 2025 16:51:43 +0800 Subject: [PATCH 023/167] wifi: rtw89: pci: move chip ISR definition out from chip generation The existing WiFi 6 chips can share the same ISR (interrupt status registers), but the coming WiFi 7 chip 8922DE can't share the same definition with existing WiFi 7 chip, so move the definition to an individual struct. Don't change logic at all. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250826085152.28164-2-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/pci.c | 19 +++++++++++-------- drivers/net/wireless/realtek/rtw89/pci.h | 7 ++++++- drivers/net/wireless/realtek/rtw89/pci_be.c | 5 ++++- .../net/wireless/realtek/rtw89/rtw8851be.c | 1 + .../net/wireless/realtek/rtw89/rtw8852ae.c | 1 + .../net/wireless/realtek/rtw89/rtw8852be.c | 1 + .../net/wireless/realtek/rtw89/rtw8852bte.c | 1 + .../net/wireless/realtek/rtw89/rtw8852ce.c | 1 + .../net/wireless/realtek/rtw89/rtw8922ae.c | 1 + 9 files changed, 27 insertions(+), 10 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/pci.c b/drivers/net/wireless/realtek/rtw89/pci.c index a669f2f843aa..162075882fa4 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.c +++ b/drivers/net/wireless/realtek/rtw89/pci.c @@ -885,7 +885,7 @@ static irqreturn_t rtw89_pci_interrupt_threadfn(int irq, void *dev) struct rtw89_dev *rtwdev = dev; struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; const struct rtw89_pci_info *info = rtwdev->pci_info; - const struct rtw89_pci_gen_def *gen_def = info->gen_def; + const struct rtw89_pci_isr_def *isr_def = info->isr_def; struct rtw89_pci_isrs isrs; unsigned long flags; @@ -893,13 +893,13 @@ static irqreturn_t rtw89_pci_interrupt_threadfn(int irq, void *dev) rtw89_chip_recognize_intrs(rtwdev, rtwpci, &isrs); spin_unlock_irqrestore(&rtwpci->irq_lock, flags); - if (unlikely(isrs.isrs[0] & gen_def->isr_rdu)) + if (unlikely(isrs.isrs[0] & isr_def->isr_rdu)) rtw89_pci_isr_rxd_unavail(rtwdev, rtwpci); - if (unlikely(isrs.halt_c2h_isrs & gen_def->isr_halt_c2h)) + if (unlikely(isrs.halt_c2h_isrs & isr_def->isr_halt_c2h)) rtw89_ser_notify(rtwdev, rtw89_mac_get_err_status(rtwdev)); - if (unlikely(isrs.halt_c2h_isrs & gen_def->isr_wdt_timeout)) + if (unlikely(isrs.halt_c2h_isrs & isr_def->isr_wdt_timeout)) rtw89_ser_notify(rtwdev, MAC_AX_ERR_L2_ERR_WDT_TIMEOUT_INT); if (unlikely(rtwpci->under_recovery)) @@ -4228,18 +4228,18 @@ static int rtw89_pci_napi_poll(struct napi_struct *napi, int budget) struct rtw89_dev *rtwdev = container_of(napi, struct rtw89_dev, napi); struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; const struct rtw89_pci_info *info = rtwdev->pci_info; - const struct rtw89_pci_gen_def *gen_def = info->gen_def; + const struct rtw89_pci_isr_def *isr_def = info->isr_def; unsigned long flags; int work_done; rtwdev->napi_budget_countdown = budget; - rtw89_write32(rtwdev, gen_def->isr_clear_rpq.addr, gen_def->isr_clear_rpq.data); + rtw89_write32(rtwdev, isr_def->isr_clear_rpq.addr, isr_def->isr_clear_rpq.data); work_done = rtw89_pci_poll_rpq_dma(rtwdev, rtwpci, rtwdev->napi_budget_countdown); if (work_done == budget) return budget; - rtw89_write32(rtwdev, gen_def->isr_clear_rxq.addr, gen_def->isr_clear_rxq.data); + rtw89_write32(rtwdev, isr_def->isr_clear_rxq.addr, isr_def->isr_clear_rxq.data); work_done += rtw89_pci_poll_rxq_dma(rtwdev, rtwpci, rtwdev->napi_budget_countdown); if (work_done < budget && napi_complete_done(napi, work_done)) { spin_lock_irqsave(&rtwpci->irq_lock, flags); @@ -4394,14 +4394,17 @@ const struct pci_error_handlers rtw89_pci_err_handler = { }; EXPORT_SYMBOL(rtw89_pci_err_handler); -const struct rtw89_pci_gen_def rtw89_pci_gen_ax = { +const struct rtw89_pci_isr_def rtw89_pci_isr_ax = { .isr_rdu = B_AX_RDU_INT, .isr_halt_c2h = B_AX_HALT_C2H_INT_EN, .isr_wdt_timeout = B_AX_WDT_TIMEOUT_INT_EN, .isr_clear_rpq = {R_AX_PCIE_HISR00, B_AX_RPQDMA_INT | B_AX_RPQBD_FULL_INT}, .isr_clear_rxq = {R_AX_PCIE_HISR00, B_AX_RXP1DMA_INT | B_AX_RXDMA_INT | B_AX_RDU_INT}, +}; +EXPORT_SYMBOL(rtw89_pci_isr_ax); +const struct rtw89_pci_gen_def rtw89_pci_gen_ax = { .mac_pre_init = rtw89_pci_ops_mac_pre_init_ax, .mac_pre_deinit = rtw89_pci_ops_mac_pre_deinit_ax, .mac_post_init = rtw89_pci_ops_mac_post_init_ax, diff --git a/drivers/net/wireless/realtek/rtw89/pci.h b/drivers/net/wireless/realtek/rtw89/pci.h index 52f527069da6..14b1d388d46b 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.h +++ b/drivers/net/wireless/realtek/rtw89/pci.h @@ -1267,13 +1267,15 @@ struct rtw89_pci_bd_ram { u8 min_num; }; -struct rtw89_pci_gen_def { +struct rtw89_pci_isr_def { u32 isr_rdu; u32 isr_halt_c2h; u32 isr_wdt_timeout; struct rtw89_reg2_def isr_clear_rpq; struct rtw89_reg2_def isr_clear_rxq; +}; +struct rtw89_pci_gen_def { int (*mac_pre_init)(struct rtw89_dev *rtwdev); int (*mac_pre_deinit)(struct rtw89_dev *rtwdev); int (*mac_post_init)(struct rtw89_dev *rtwdev); @@ -1311,6 +1313,7 @@ struct rtw89_pci_ssid_quirk { struct rtw89_pci_info { const struct rtw89_pci_gen_def *gen_def; + const struct rtw89_pci_isr_def *isr_def; enum mac_ax_bd_trunc_mode txbd_trunc_mode; enum mac_ax_bd_trunc_mode rxbd_trunc_mode; enum mac_ax_rxbd_mode rxbd_mode; @@ -1628,6 +1631,8 @@ extern const struct rtw89_pci_ch_dma_addr_set rtw89_pci_ch_dma_addr_set_v1; extern const struct rtw89_pci_ch_dma_addr_set rtw89_pci_ch_dma_addr_set_be; extern const struct rtw89_pci_bd_ram rtw89_bd_ram_table_dual[RTW89_TXCH_NUM]; extern const struct rtw89_pci_bd_ram rtw89_bd_ram_table_single[RTW89_TXCH_NUM]; +extern const struct rtw89_pci_isr_def rtw89_pci_isr_ax; +extern const struct rtw89_pci_isr_def rtw89_pci_isr_be; extern const struct rtw89_pci_gen_def rtw89_pci_gen_ax; extern const struct rtw89_pci_gen_def rtw89_pci_gen_be; diff --git a/drivers/net/wireless/realtek/rtw89/pci_be.c b/drivers/net/wireless/realtek/rtw89/pci_be.c index 12e6a0cbb889..29ca58b86085 100644 --- a/drivers/net/wireless/realtek/rtw89/pci_be.c +++ b/drivers/net/wireless/realtek/rtw89/pci_be.c @@ -665,13 +665,16 @@ static int __maybe_unused rtw89_pci_resume_be(struct device *dev) SIMPLE_DEV_PM_OPS(rtw89_pm_ops_be, rtw89_pci_suspend_be, rtw89_pci_resume_be); EXPORT_SYMBOL(rtw89_pm_ops_be); -const struct rtw89_pci_gen_def rtw89_pci_gen_be = { +const struct rtw89_pci_isr_def rtw89_pci_isr_be = { .isr_rdu = B_BE_RDU_CH1_INT_V1 | B_BE_RDU_CH0_INT_V1, .isr_halt_c2h = B_BE_HALT_C2H_INT, .isr_wdt_timeout = B_BE_WDT_TIMEOUT_INT, .isr_clear_rpq = {R_BE_PCIE_DMA_ISR, B_BE_PCIE_RX_RPQ0_ISR_V1}, .isr_clear_rxq = {R_BE_PCIE_DMA_ISR, B_BE_PCIE_RX_RX0P2_ISR_V1}, +}; +EXPORT_SYMBOL(rtw89_pci_isr_be); +const struct rtw89_pci_gen_def rtw89_pci_gen_be = { .mac_pre_init = rtw89_pci_ops_mac_pre_init_be, .mac_pre_deinit = rtw89_pci_ops_mac_pre_deinit_be, .mac_post_init = rtw89_pci_ops_mac_post_init_be, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851be.c b/drivers/net/wireless/realtek/rtw89/rtw8851be.c index 598730831707..c9d60870ed9e 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851be.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851be.c @@ -11,6 +11,7 @@ static const struct rtw89_pci_info rtw8851b_pci_info = { .gen_def = &rtw89_pci_gen_ax, + .isr_def = &rtw89_pci_isr_ax, .txbd_trunc_mode = MAC_AX_BD_TRUNC, .rxbd_trunc_mode = MAC_AX_BD_TRUNC, .rxbd_mode = MAC_AX_RXBD_PKT, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852ae.c b/drivers/net/wireless/realtek/rtw89/rtw8852ae.c index 90ffaf9f4f6a..1bfade7e7e1b 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852ae.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852ae.c @@ -11,6 +11,7 @@ static const struct rtw89_pci_info rtw8852a_pci_info = { .gen_def = &rtw89_pci_gen_ax, + .isr_def = &rtw89_pci_isr_ax, .txbd_trunc_mode = MAC_AX_BD_TRUNC, .rxbd_trunc_mode = MAC_AX_BD_TRUNC, .rxbd_mode = MAC_AX_RXBD_PKT, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852be.c b/drivers/net/wireless/realtek/rtw89/rtw8852be.c index b0726f590ca2..8f7676a0a89e 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852be.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852be.c @@ -11,6 +11,7 @@ static const struct rtw89_pci_info rtw8852b_pci_info = { .gen_def = &rtw89_pci_gen_ax, + .isr_def = &rtw89_pci_isr_ax, .txbd_trunc_mode = MAC_AX_BD_TRUNC, .rxbd_trunc_mode = MAC_AX_BD_TRUNC, .rxbd_mode = MAC_AX_RXBD_PKT, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bte.c b/drivers/net/wireless/realtek/rtw89/rtw8852bte.c index a584c75b801d..642ab20e9d06 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852bte.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852bte.c @@ -17,6 +17,7 @@ static const struct rtw89_pci_ssid_quirk rtw8852bt_pci_ssid_quirks[] = { static const struct rtw89_pci_info rtw8852bt_pci_info = { .gen_def = &rtw89_pci_gen_ax, + .isr_def = &rtw89_pci_isr_ax, .txbd_trunc_mode = MAC_AX_BD_TRUNC, .rxbd_trunc_mode = MAC_AX_BD_TRUNC, .rxbd_mode = MAC_AX_RXBD_PKT, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852ce.c b/drivers/net/wireless/realtek/rtw89/rtw8852ce.c index db01d3966c27..4c7682f1d00c 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852ce.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852ce.c @@ -20,6 +20,7 @@ static const struct rtw89_pci_bd_idx_addr rtw8852c_bd_idx_addr_low_power = { static const struct rtw89_pci_info rtw8852c_pci_info = { .gen_def = &rtw89_pci_gen_ax, + .isr_def = &rtw89_pci_isr_ax, .txbd_trunc_mode = MAC_AX_BD_TRUNC, .rxbd_trunc_mode = MAC_AX_BD_TRUNC, .rxbd_mode = MAC_AX_RXBD_PKT, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922ae.c b/drivers/net/wireless/realtek/rtw89/rtw8922ae.c index b730d79edd10..a0fc6b2832e1 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922ae.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922ae.c @@ -17,6 +17,7 @@ static const struct rtw89_pci_ssid_quirk rtw8922a_pci_ssid_quirks[] = { static const struct rtw89_pci_info rtw8922a_pci_info = { .gen_def = &rtw89_pci_gen_be, + .isr_def = &rtw89_pci_isr_be, .txbd_trunc_mode = MAC_AX_BD_TRUNC, .rxbd_trunc_mode = MAC_AX_BD_TRUNC, .rxbd_mode = MAC_AX_RXBD_PKT, From 862132fbfc898ee720b0c431846442961f6b3f64 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 26 Aug 2025 16:51:44 +0800 Subject: [PATCH 024/167] wifi: rtw89: pci: prepare interrupt related registers and functions for 8922DE The 8922DE is very similar to 8922AE except to RDU (RX desc unavailable) registers. The following patch will adjust to read this status from another register to reduce one reading IO, so create a set of functions ahead. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250826085152.28164-3-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/pci.c | 85 +++++++++++++++++++++ drivers/net/wireless/realtek/rtw89/pci.h | 15 ++++ drivers/net/wireless/realtek/rtw89/pci_be.c | 9 +++ 3 files changed, 109 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/pci.c b/drivers/net/wireless/realtek/rtw89/pci.c index 162075882fa4..0153977d4cf2 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.c +++ b/drivers/net/wireless/realtek/rtw89/pci.c @@ -797,6 +797,27 @@ void rtw89_pci_recognize_intrs_v2(struct rtw89_dev *rtwdev, } EXPORT_SYMBOL(rtw89_pci_recognize_intrs_v2); +void rtw89_pci_recognize_intrs_v3(struct rtw89_dev *rtwdev, + struct rtw89_pci *rtwpci, + struct rtw89_pci_isrs *isrs) +{ + isrs->ind_isrs = rtw89_read32(rtwdev, R_BE_PCIE_HISR) & rtwpci->ind_intrs; + isrs->halt_c2h_isrs = isrs->ind_isrs & B_BE_HS0ISR_IND_INT ? + rtw89_read32(rtwdev, R_BE_HISR0) & rtwpci->halt_c2h_intrs : 0; + isrs->isrs[0] = isrs->ind_isrs & B_BE_HCI_AXIDMA_INT ? + rtw89_read32(rtwdev, R_BE_HAXI_HISR00) & rtwpci->intrs[0] : 0; + isrs->isrs[1] = rtw89_read32(rtwdev, R_BE_PCIE_DMA_ISR) & rtwpci->intrs[1]; + + if (isrs->halt_c2h_isrs) + rtw89_write32(rtwdev, R_BE_HISR0, isrs->halt_c2h_isrs); + if (isrs->isrs[0]) + rtw89_write32(rtwdev, R_BE_HAXI_HISR00, isrs->isrs[0]); + if (isrs->isrs[1]) + rtw89_write32(rtwdev, R_BE_PCIE_DMA_ISR, isrs->isrs[1]); + rtw89_write32(rtwdev, R_BE_PCIE_HISR, isrs->ind_isrs); +} +EXPORT_SYMBOL(rtw89_pci_recognize_intrs_v3); + void rtw89_pci_enable_intr(struct rtw89_dev *rtwdev, struct rtw89_pci *rtwpci) { rtw89_write32(rtwdev, R_AX_HIMR0, rtwpci->halt_c2h_intrs); @@ -844,6 +865,22 @@ void rtw89_pci_disable_intr_v2(struct rtw89_dev *rtwdev, struct rtw89_pci *rtwpc } EXPORT_SYMBOL(rtw89_pci_disable_intr_v2); +void rtw89_pci_enable_intr_v3(struct rtw89_dev *rtwdev, struct rtw89_pci *rtwpci) +{ + rtw89_write32(rtwdev, R_BE_HIMR0, rtwpci->halt_c2h_intrs); + rtw89_write32(rtwdev, R_BE_HAXI_HIMR00, rtwpci->intrs[0]); + rtw89_write32(rtwdev, R_BE_PCIE_DMA_IMR_0_V1, rtwpci->intrs[1]); + rtw89_write32(rtwdev, R_BE_PCIE_HIMR0, rtwpci->ind_intrs); +} +EXPORT_SYMBOL(rtw89_pci_enable_intr_v3); + +void rtw89_pci_disable_intr_v3(struct rtw89_dev *rtwdev, struct rtw89_pci *rtwpci) +{ + rtw89_write32(rtwdev, R_BE_PCIE_HIMR0, 0); + rtw89_write32(rtwdev, R_BE_PCIE_DMA_IMR_0_V1, 0); +} +EXPORT_SYMBOL(rtw89_pci_disable_intr_v3); + static void rtw89_pci_ops_recovery_start(struct rtw89_dev *rtwdev) { struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; @@ -3776,6 +3813,54 @@ void rtw89_pci_config_intr_mask_v2(struct rtw89_dev *rtwdev) } EXPORT_SYMBOL(rtw89_pci_config_intr_mask_v2); +static void rtw89_pci_recovery_intr_mask_v3(struct rtw89_dev *rtwdev) +{ + struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; + + rtwpci->ind_intrs = B_BE_HS0_IND_INT_EN0; + rtwpci->halt_c2h_intrs = B_BE_HALT_C2H_INT_EN | B_BE_WDT_TIMEOUT_INT_EN; + rtwpci->intrs[0] = 0; + rtwpci->intrs[1] = 0; +} + +static void rtw89_pci_default_intr_mask_v3(struct rtw89_dev *rtwdev) +{ + struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; + + rtwpci->ind_intrs = B_BE_HCI_AXIDMA_INT_EN0 | + B_BE_HS0_IND_INT_EN0; + rtwpci->halt_c2h_intrs = B_BE_HALT_C2H_INT_EN | B_BE_WDT_TIMEOUT_INT_EN; + rtwpci->intrs[0] = B_BE_RDU_CH1_INT_EN_V2 | + B_BE_RDU_CH0_INT_EN_V2; + rtwpci->intrs[1] = B_BE_PCIE_RX_RX0P2_IMR0_V1 | + B_BE_PCIE_RX_RPQ0_IMR0_V1; +} + +static void rtw89_pci_low_power_intr_mask_v3(struct rtw89_dev *rtwdev) +{ + struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; + + rtwpci->ind_intrs = B_BE_HS0_IND_INT_EN0 | + B_BE_HS1_IND_INT_EN0; + rtwpci->halt_c2h_intrs = B_BE_HALT_C2H_INT_EN | B_BE_WDT_TIMEOUT_INT_EN; + rtwpci->intrs[0] = 0; + rtwpci->intrs[1] = B_BE_PCIE_RX_RX0P2_IMR0_V1 | + B_BE_PCIE_RX_RPQ0_IMR0_V1; +} + +void rtw89_pci_config_intr_mask_v3(struct rtw89_dev *rtwdev) +{ + struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; + + if (rtwpci->under_recovery) + rtw89_pci_recovery_intr_mask_v3(rtwdev); + else if (rtwpci->low_power) + rtw89_pci_low_power_intr_mask_v3(rtwdev); + else + rtw89_pci_default_intr_mask_v3(rtwdev); +} +EXPORT_SYMBOL(rtw89_pci_config_intr_mask_v3); + static int rtw89_pci_request_irq(struct rtw89_dev *rtwdev, struct pci_dev *pdev) { diff --git a/drivers/net/wireless/realtek/rtw89/pci.h b/drivers/net/wireless/realtek/rtw89/pci.h index 14b1d388d46b..95da43627608 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.h +++ b/drivers/net/wireless/realtek/rtw89/pci.h @@ -426,9 +426,13 @@ #define B_BE_RDU_CH4_INT_IMR_V1 BIT(29) #define B_BE_RDU_CH3_INT_IMR_V1 BIT(28) #define B_BE_RDU_CH2_INT_IMR_V1 BIT(27) +#define B_BE_RDU_CH1_INT_EN_V2 BIT(27) #define B_BE_RDU_CH1_INT_IMR_V1 BIT(26) +#define B_BE_RDU_CH0_INT_EN_V2 BIT(26) #define B_BE_RDU_CH0_INT_IMR_V1 BIT(25) +#define B_BE_RXDMA_STUCK_INT_EN_V2 BIT(25) #define B_BE_RXDMA_STUCK_INT_EN_V1 BIT(24) +#define B_BE_TXDMA_STUCK_INT_EN_V2 BIT(24) #define B_BE_TXDMA_STUCK_INT_EN_V1 BIT(23) #define B_BE_TXDMA_CH14_INT_EN_V1 BIT(22) #define B_BE_TXDMA_CH13_INT_EN_V1 BIT(21) @@ -459,9 +463,13 @@ #define B_BE_RDU_CH4_INT_V1 BIT(29) #define B_BE_RDU_CH3_INT_V1 BIT(28) #define B_BE_RDU_CH2_INT_V1 BIT(27) +#define B_BE_RDU_CH1_INT_V2 BIT(27) #define B_BE_RDU_CH1_INT_V1 BIT(26) +#define B_BE_RDU_CH0_INT_V2 BIT(26) #define B_BE_RDU_CH0_INT_V1 BIT(25) +#define B_BE_RXDMA_STUCK_INT_V2 BIT(25) #define B_BE_RXDMA_STUCK_INT_V1 BIT(24) +#define B_BE_TXDMA_STUCK_INT_V2 BIT(24) #define B_BE_TXDMA_STUCK_INT_V1 BIT(23) #define B_BE_TXDMA_CH14_INT_V1 BIT(22) #define B_BE_TXDMA_CH13_INT_V1 BIT(21) @@ -1633,6 +1641,7 @@ extern const struct rtw89_pci_bd_ram rtw89_bd_ram_table_dual[RTW89_TXCH_NUM]; extern const struct rtw89_pci_bd_ram rtw89_bd_ram_table_single[RTW89_TXCH_NUM]; extern const struct rtw89_pci_isr_def rtw89_pci_isr_ax; extern const struct rtw89_pci_isr_def rtw89_pci_isr_be; +extern const struct rtw89_pci_isr_def rtw89_pci_isr_be_v1; extern const struct rtw89_pci_gen_def rtw89_pci_gen_ax; extern const struct rtw89_pci_gen_def rtw89_pci_gen_be; @@ -1655,12 +1664,15 @@ void rtw89_pci_ctrl_dma_all(struct rtw89_dev *rtwdev, bool enable); void rtw89_pci_config_intr_mask(struct rtw89_dev *rtwdev); void rtw89_pci_config_intr_mask_v1(struct rtw89_dev *rtwdev); void rtw89_pci_config_intr_mask_v2(struct rtw89_dev *rtwdev); +void rtw89_pci_config_intr_mask_v3(struct rtw89_dev *rtwdev); void rtw89_pci_enable_intr(struct rtw89_dev *rtwdev, struct rtw89_pci *rtwpci); void rtw89_pci_disable_intr(struct rtw89_dev *rtwdev, struct rtw89_pci *rtwpci); void rtw89_pci_enable_intr_v1(struct rtw89_dev *rtwdev, struct rtw89_pci *rtwpci); void rtw89_pci_disable_intr_v1(struct rtw89_dev *rtwdev, struct rtw89_pci *rtwpci); void rtw89_pci_enable_intr_v2(struct rtw89_dev *rtwdev, struct rtw89_pci *rtwpci); void rtw89_pci_disable_intr_v2(struct rtw89_dev *rtwdev, struct rtw89_pci *rtwpci); +void rtw89_pci_enable_intr_v3(struct rtw89_dev *rtwdev, struct rtw89_pci *rtwpci); +void rtw89_pci_disable_intr_v3(struct rtw89_dev *rtwdev, struct rtw89_pci *rtwpci); void rtw89_pci_recognize_intrs(struct rtw89_dev *rtwdev, struct rtw89_pci *rtwpci, struct rtw89_pci_isrs *isrs); @@ -1670,6 +1682,9 @@ void rtw89_pci_recognize_intrs_v1(struct rtw89_dev *rtwdev, void rtw89_pci_recognize_intrs_v2(struct rtw89_dev *rtwdev, struct rtw89_pci *rtwpci, struct rtw89_pci_isrs *isrs); +void rtw89_pci_recognize_intrs_v3(struct rtw89_dev *rtwdev, + struct rtw89_pci *rtwpci, + struct rtw89_pci_isrs *isrs); static inline u32 rtw89_chip_fill_txaddr_info(struct rtw89_dev *rtwdev, diff --git a/drivers/net/wireless/realtek/rtw89/pci_be.c b/drivers/net/wireless/realtek/rtw89/pci_be.c index 29ca58b86085..f4b9c98e4eb4 100644 --- a/drivers/net/wireless/realtek/rtw89/pci_be.c +++ b/drivers/net/wireless/realtek/rtw89/pci_be.c @@ -674,6 +674,15 @@ const struct rtw89_pci_isr_def rtw89_pci_isr_be = { }; EXPORT_SYMBOL(rtw89_pci_isr_be); +const struct rtw89_pci_isr_def rtw89_pci_isr_be_v1 = { + .isr_rdu = B_BE_RDU_CH1_INT_V2 | B_BE_RDU_CH0_INT_V2, + .isr_halt_c2h = B_BE_HALT_C2H_INT, + .isr_wdt_timeout = B_BE_WDT_TIMEOUT_INT, + .isr_clear_rpq = {R_BE_PCIE_DMA_ISR, B_BE_PCIE_RX_RPQ0_ISR_V1}, + .isr_clear_rxq = {R_BE_PCIE_DMA_ISR, B_BE_PCIE_RX_RX0P2_ISR_V1}, +}; +EXPORT_SYMBOL(rtw89_pci_isr_be_v1); + const struct rtw89_pci_gen_def rtw89_pci_gen_be = { .mac_pre_init = rtw89_pci_ops_mac_pre_init_be, .mac_pre_deinit = rtw89_pci_ops_mac_pre_deinit_be, From d6303028ae55b116c33227a595034b6d55df96c4 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 26 Aug 2025 16:51:45 +0800 Subject: [PATCH 025/167] wifi: rtw89: pci: use RDU status of R_BE_PCIE_DMA_IMR_0_V1 instead for 8922DE The original RDU status of R_BE_HAXI_HIMR00 needs additional IO to get the status. The new WiFi 7 8922DE add the status to R_BE_PCIE_DMA_IMR_0_V1 which is read already, so we can reduce one reading IO. After the changes, interrupt behavior of RTL8922DE in low power mode is the same as normal mode, so remove the configuration function for low power mode. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250826085152.28164-4-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/pci.c | 33 +++++++-------------- drivers/net/wireless/realtek/rtw89/pci.h | 16 ++++++++++ drivers/net/wireless/realtek/rtw89/pci_be.c | 2 +- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/pci.c b/drivers/net/wireless/realtek/rtw89/pci.c index 0153977d4cf2..e3fbe43a95ea 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.c +++ b/drivers/net/wireless/realtek/rtw89/pci.c @@ -804,14 +804,16 @@ void rtw89_pci_recognize_intrs_v3(struct rtw89_dev *rtwdev, isrs->ind_isrs = rtw89_read32(rtwdev, R_BE_PCIE_HISR) & rtwpci->ind_intrs; isrs->halt_c2h_isrs = isrs->ind_isrs & B_BE_HS0ISR_IND_INT ? rtw89_read32(rtwdev, R_BE_HISR0) & rtwpci->halt_c2h_intrs : 0; - isrs->isrs[0] = isrs->ind_isrs & B_BE_HCI_AXIDMA_INT ? - rtw89_read32(rtwdev, R_BE_HAXI_HISR00) & rtwpci->intrs[0] : 0; isrs->isrs[1] = rtw89_read32(rtwdev, R_BE_PCIE_DMA_ISR) & rtwpci->intrs[1]; + /* isrs[0] is not used, so borrow to store RDU status to share common + * flow in rtw89_pci_interrupt_threadfn(). + */ + isrs->isrs[0] = isrs->isrs[1] & (B_BE_PCIE_RDU_CH1_INT | + B_BE_PCIE_RDU_CH0_INT); + if (isrs->halt_c2h_isrs) rtw89_write32(rtwdev, R_BE_HISR0, isrs->halt_c2h_isrs); - if (isrs->isrs[0]) - rtw89_write32(rtwdev, R_BE_HAXI_HISR00, isrs->isrs[0]); if (isrs->isrs[1]) rtw89_write32(rtwdev, R_BE_PCIE_DMA_ISR, isrs->isrs[1]); rtw89_write32(rtwdev, R_BE_PCIE_HISR, isrs->ind_isrs); @@ -868,7 +870,6 @@ EXPORT_SYMBOL(rtw89_pci_disable_intr_v2); void rtw89_pci_enable_intr_v3(struct rtw89_dev *rtwdev, struct rtw89_pci *rtwpci) { rtw89_write32(rtwdev, R_BE_HIMR0, rtwpci->halt_c2h_intrs); - rtw89_write32(rtwdev, R_BE_HAXI_HIMR00, rtwpci->intrs[0]); rtw89_write32(rtwdev, R_BE_PCIE_DMA_IMR_0_V1, rtwpci->intrs[1]); rtw89_write32(rtwdev, R_BE_PCIE_HIMR0, rtwpci->ind_intrs); } @@ -3827,24 +3828,12 @@ static void rtw89_pci_default_intr_mask_v3(struct rtw89_dev *rtwdev) { struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; - rtwpci->ind_intrs = B_BE_HCI_AXIDMA_INT_EN0 | - B_BE_HS0_IND_INT_EN0; - rtwpci->halt_c2h_intrs = B_BE_HALT_C2H_INT_EN | B_BE_WDT_TIMEOUT_INT_EN; - rtwpci->intrs[0] = B_BE_RDU_CH1_INT_EN_V2 | - B_BE_RDU_CH0_INT_EN_V2; - rtwpci->intrs[1] = B_BE_PCIE_RX_RX0P2_IMR0_V1 | - B_BE_PCIE_RX_RPQ0_IMR0_V1; -} - -static void rtw89_pci_low_power_intr_mask_v3(struct rtw89_dev *rtwdev) -{ - struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; - - rtwpci->ind_intrs = B_BE_HS0_IND_INT_EN0 | - B_BE_HS1_IND_INT_EN0; + rtwpci->ind_intrs = B_BE_HS0_IND_INT_EN0; rtwpci->halt_c2h_intrs = B_BE_HALT_C2H_INT_EN | B_BE_WDT_TIMEOUT_INT_EN; rtwpci->intrs[0] = 0; - rtwpci->intrs[1] = B_BE_PCIE_RX_RX0P2_IMR0_V1 | + rtwpci->intrs[1] = B_BE_PCIE_RDU_CH1_IMR | + B_BE_PCIE_RDU_CH0_IMR | + B_BE_PCIE_RX_RX0P2_IMR0_V1 | B_BE_PCIE_RX_RPQ0_IMR0_V1; } @@ -3854,8 +3843,6 @@ void rtw89_pci_config_intr_mask_v3(struct rtw89_dev *rtwdev) if (rtwpci->under_recovery) rtw89_pci_recovery_intr_mask_v3(rtwdev); - else if (rtwpci->low_power) - rtw89_pci_low_power_intr_mask_v3(rtwdev); else rtw89_pci_default_intr_mask_v3(rtwdev); } diff --git a/drivers/net/wireless/realtek/rtw89/pci.h b/drivers/net/wireless/realtek/rtw89/pci.h index 95da43627608..6558f60ec914 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.h +++ b/drivers/net/wireless/realtek/rtw89/pci.h @@ -372,6 +372,14 @@ #define B_BE_HS0ISR_IND_INT BIT(0) #define R_BE_PCIE_DMA_IMR_0_V1 0x30B8 +#define B_BE_PCIE_RDU_CH7_IMR BIT(31) +#define B_BE_PCIE_RDU_CH6_IMR BIT(30) +#define B_BE_PCIE_RDU_CH5_IMR BIT(29) +#define B_BE_PCIE_RDU_CH4_IMR BIT(28) +#define B_BE_PCIE_RDU_CH3_IMR BIT(27) +#define B_BE_PCIE_RDU_CH2_IMR BIT(26) +#define B_BE_PCIE_RDU_CH1_IMR BIT(25) +#define B_BE_PCIE_RDU_CH0_IMR BIT(24) #define B_BE_PCIE_RX_RX1P1_IMR0_V1 BIT(23) #define B_BE_PCIE_RX_RX0P1_IMR0_V1 BIT(22) #define B_BE_PCIE_RX_ROQ1_IMR0_V1 BIT(21) @@ -397,6 +405,14 @@ #define B_BE_PCIE_TX_CH0_IMR0 BIT(0) #define R_BE_PCIE_DMA_ISR 0x30BC +#define B_BE_PCIE_RDU_CH7_INT BIT(31) +#define B_BE_PCIE_RDU_CH6_INT BIT(30) +#define B_BE_PCIE_RDU_CH5_INT BIT(29) +#define B_BE_PCIE_RDU_CH4_INT BIT(28) +#define B_BE_PCIE_RDU_CH3_INT BIT(27) +#define B_BE_PCIE_RDU_CH2_INT BIT(26) +#define B_BE_PCIE_RDU_CH1_INT BIT(25) +#define B_BE_PCIE_RDU_CH0_INT BIT(24) #define B_BE_PCIE_RX_RX1P1_ISR_V1 BIT(23) #define B_BE_PCIE_RX_RX0P1_ISR_V1 BIT(22) #define B_BE_PCIE_RX_ROQ1_ISR_V1 BIT(21) diff --git a/drivers/net/wireless/realtek/rtw89/pci_be.c b/drivers/net/wireless/realtek/rtw89/pci_be.c index f4b9c98e4eb4..ecfe1b60e847 100644 --- a/drivers/net/wireless/realtek/rtw89/pci_be.c +++ b/drivers/net/wireless/realtek/rtw89/pci_be.c @@ -675,7 +675,7 @@ const struct rtw89_pci_isr_def rtw89_pci_isr_be = { EXPORT_SYMBOL(rtw89_pci_isr_be); const struct rtw89_pci_isr_def rtw89_pci_isr_be_v1 = { - .isr_rdu = B_BE_RDU_CH1_INT_V2 | B_BE_RDU_CH0_INT_V2, + .isr_rdu = B_BE_PCIE_RDU_CH1_INT | B_BE_PCIE_RDU_CH0_INT, .isr_halt_c2h = B_BE_HALT_C2H_INT, .isr_wdt_timeout = B_BE_WDT_TIMEOUT_INT, .isr_clear_rpq = {R_BE_PCIE_DMA_ISR, B_BE_PCIE_RX_RPQ0_ISR_V1}, From 7bd90ec75e76c60a00fa6f2e6949a2ac4da14e1f Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 26 Aug 2025 16:51:46 +0800 Subject: [PATCH 026/167] wifi: rtw89: pci: add struct rtw89_{tx,rx}_rings to put related fields The new hardware design will expect a continual memory region across all rings, so a new field will be added to describe the region. To help the changes, add struct and make changes ahead. Don't change logic at all. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250826085152.28164-5-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/pci.c | 48 ++++++++++----------- drivers/net/wireless/realtek/rtw89/pci.h | 12 +++++- drivers/net/wireless/realtek/rtw89/pci_be.c | 4 +- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/pci.c b/drivers/net/wireless/realtek/rtw89/pci.c index e3fbe43a95ea..d4677df35861 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.c +++ b/drivers/net/wireless/realtek/rtw89/pci.c @@ -134,7 +134,7 @@ static void rtw89_pci_release_fwcmd(struct rtw89_dev *rtwdev, static void rtw89_pci_reclaim_tx_fwcmd(struct rtw89_dev *rtwdev, struct rtw89_pci *rtwpci) { - struct rtw89_pci_tx_ring *tx_ring = &rtwpci->tx_rings[RTW89_TXCH_CH12]; + struct rtw89_pci_tx_ring *tx_ring = &rtwpci->tx.rings[RTW89_TXCH_CH12]; u32 cnt; cnt = rtw89_pci_txbd_recalc(rtwdev, tx_ring); @@ -440,7 +440,7 @@ static int rtw89_pci_poll_rxq_dma(struct rtw89_dev *rtwdev, int countdown = rtwdev->napi_budget_countdown; u32 cnt; - rx_ring = &rtwpci->rx_rings[RTW89_RXCH_RXQ]; + rx_ring = &rtwpci->rx.rings[RTW89_RXCH_RXQ]; cnt = rtw89_pci_rxbd_recalc(rtwdev, rx_ring); if (!cnt) @@ -588,7 +588,7 @@ static void rtw89_pci_release_rpp(struct rtw89_dev *rtwdev, return; } - tx_ring = &rtwpci->tx_rings[txch]; + tx_ring = &rtwpci->tx.rings[txch]; wd_ring = &tx_ring->wd_ring; txwd = &wd_ring->pages[seq]; @@ -694,7 +694,7 @@ static int rtw89_pci_poll_rpq_dma(struct rtw89_dev *rtwdev, u32 cnt; int work_done; - rx_ring = &rtwpci->rx_rings[RTW89_RXCH_RPQ]; + rx_ring = &rtwpci->rx.rings[RTW89_RXCH_RPQ]; spin_lock_bh(&rtwpci->trx_lock); @@ -724,7 +724,7 @@ static void rtw89_pci_isr_rxd_unavail(struct rtw89_dev *rtwdev, int i; for (i = 0; i < RTW89_RXCH_NUM; i++) { - rx_ring = &rtwpci->rx_rings[i]; + rx_ring = &rtwpci->rx.rings[i]; bd_ring = &rx_ring->bd_ring; reg_idx = rtw89_read32(rtwdev, bd_ring->addr.idx); @@ -1139,7 +1139,7 @@ static u32 __rtw89_pci_check_and_reclaim_tx_fwcmd_resource(struct rtw89_dev *rtwdev) { struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; - struct rtw89_pci_tx_ring *tx_ring = &rtwpci->tx_rings[RTW89_TXCH_CH12]; + struct rtw89_pci_tx_ring *tx_ring = &rtwpci->tx.rings[RTW89_TXCH_CH12]; u32 cnt; spin_lock_bh(&rtwpci->trx_lock); @@ -1155,7 +1155,7 @@ u32 __rtw89_pci_check_and_reclaim_tx_resource_noio(struct rtw89_dev *rtwdev, u8 txch) { struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; - struct rtw89_pci_tx_ring *tx_ring = &rtwpci->tx_rings[txch]; + struct rtw89_pci_tx_ring *tx_ring = &rtwpci->tx.rings[txch]; struct rtw89_pci_tx_wd_ring *wd_ring = &tx_ring->wd_ring; u32 cnt; @@ -1172,7 +1172,7 @@ static u32 __rtw89_pci_check_and_reclaim_tx_resource(struct rtw89_dev *rtwdev, u8 txch) { struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; - struct rtw89_pci_tx_ring *tx_ring = &rtwpci->tx_rings[txch]; + struct rtw89_pci_tx_ring *tx_ring = &rtwpci->tx.rings[txch]; struct rtw89_pci_tx_wd_ring *wd_ring = &tx_ring->wd_ring; const struct rtw89_chip_info *chip = rtwdev->chip; u32 bd_cnt, wd_cnt, min_cnt = 0; @@ -1180,7 +1180,7 @@ static u32 __rtw89_pci_check_and_reclaim_tx_resource(struct rtw89_dev *rtwdev, enum rtw89_debug_mask debug_mask; u32 cnt; - rx_ring = &rtwpci->rx_rings[RTW89_RXCH_RPQ]; + rx_ring = &rtwpci->rx.rings[RTW89_RXCH_RPQ]; spin_lock_bh(&rtwpci->trx_lock); bd_cnt = rtw89_pci_get_avail_txbd_num(tx_ring); @@ -1265,7 +1265,7 @@ static void rtw89_pci_tx_bd_ring_update(struct rtw89_dev *rtwdev, struct rtw89_p static void rtw89_pci_ops_tx_kick_off(struct rtw89_dev *rtwdev, u8 txch) { struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; - struct rtw89_pci_tx_ring *tx_ring = &rtwpci->tx_rings[txch]; + struct rtw89_pci_tx_ring *tx_ring = &rtwpci->tx.rings[txch]; if (rtwdev->hci.paused) { set_bit(txch, rtwpci->kick_map); @@ -1285,7 +1285,7 @@ static void rtw89_pci_tx_kick_off_pending(struct rtw89_dev *rtwdev) if (!test_and_clear_bit(txch, rtwpci->kick_map)) continue; - tx_ring = &rtwpci->tx_rings[txch]; + tx_ring = &rtwpci->tx.rings[txch]; __rtw89_pci_tx_kick_off(rtwdev, tx_ring); } } @@ -1293,7 +1293,7 @@ static void rtw89_pci_tx_kick_off_pending(struct rtw89_dev *rtwdev) static void __pci_flush_txch(struct rtw89_dev *rtwdev, u8 txch, bool drop) { struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; - struct rtw89_pci_tx_ring *tx_ring = &rtwpci->tx_rings[txch]; + struct rtw89_pci_tx_ring *tx_ring = &rtwpci->tx.rings[txch]; struct rtw89_pci_dma_ring *bd_ring = &tx_ring->bd_ring; u32 cur_idx, cur_rp; u8 i; @@ -1559,7 +1559,7 @@ static int rtw89_pci_tx_write(struct rtw89_dev *rtwdev, struct rtw89_core_tx_req return -EINVAL; } - tx_ring = &rtwpci->tx_rings[txch]; + tx_ring = &rtwpci->tx.rings[txch]; spin_lock_bh(&rtwpci->trx_lock); n_avail_txbd = rtw89_pci_get_avail_txbd_num(tx_ring); @@ -1665,7 +1665,7 @@ static void rtw89_pci_reset_trx_rings(struct rtw89_dev *rtwdev) if (info->tx_dma_ch_mask & BIT(i)) continue; - tx_ring = &rtwpci->tx_rings[i]; + tx_ring = &rtwpci->tx.rings[i]; bd_ring = &tx_ring->bd_ring; bd_ram = bd_ram_table ? &bd_ram_table[i] : NULL; addr_num = bd_ring->addr.num; @@ -1687,7 +1687,7 @@ static void rtw89_pci_reset_trx_rings(struct rtw89_dev *rtwdev) } for (i = 0; i < RTW89_RXCH_NUM; i++) { - rx_ring = &rtwpci->rx_rings[i]; + rx_ring = &rtwpci->rx.rings[i]; bd_ring = &rx_ring->bd_ring; addr_num = bd_ring->addr.num; addr_idx = bd_ring->addr.idx; @@ -1736,7 +1736,7 @@ void rtw89_pci_ops_reset(struct rtw89_dev *rtwdev) skb_queue_len(&rtwpci->h2c_queue), true); continue; } - rtw89_pci_release_tx_ring(rtwdev, &rtwpci->tx_rings[txch]); + rtw89_pci_release_tx_ring(rtwdev, &rtwpci->tx.rings[txch]); } spin_unlock_bh(&rtwpci->trx_lock); } @@ -1812,14 +1812,14 @@ void rtw89_pci_switch_bd_idx_addr(struct rtw89_dev *rtwdev, bool low_power) return; for (i = 0; i < RTW89_TXCH_NUM; i++) { - tx_ring = &rtwpci->tx_rings[i]; + tx_ring = &rtwpci->tx.rings[i]; tx_ring->bd_ring.addr.idx = low_power ? bd_idx_addr->tx_bd_addrs[i] : dma_addr_set->tx[i].idx; } for (i = 0; i < RTW89_RXCH_NUM; i++) { - rx_ring = &rtwpci->rx_rings[i]; + rx_ring = &rtwpci->rx.rings[i]; rx_ring->bd_ring.addr.idx = low_power ? bd_idx_addr->rx_bd_addrs[i] : dma_addr_set->rx[i].idx; @@ -3272,7 +3272,7 @@ static void rtw89_pci_free_tx_rings(struct rtw89_dev *rtwdev, for (i = 0; i < RTW89_TXCH_NUM; i++) { if (info->tx_dma_ch_mask & BIT(i)) continue; - tx_ring = &rtwpci->tx_rings[i]; + tx_ring = &rtwpci->tx.rings[i]; rtw89_pci_free_tx_wd_ring(rtwdev, pdev, tx_ring); rtw89_pci_free_tx_ring(rtwdev, pdev, tx_ring); } @@ -3318,7 +3318,7 @@ static void rtw89_pci_free_rx_rings(struct rtw89_dev *rtwdev, int i; for (i = 0; i < RTW89_RXCH_NUM; i++) { - rx_ring = &rtwpci->rx_rings[i]; + rx_ring = &rtwpci->rx.rings[i]; rtw89_pci_free_rx_ring(rtwdev, pdev, rx_ring); } } @@ -3470,7 +3470,7 @@ static int rtw89_pci_alloc_tx_rings(struct rtw89_dev *rtwdev, for (i = 0; i < RTW89_TXCH_NUM; i++) { if (info->tx_dma_ch_mask & BIT(i)) continue; - tx_ring = &rtwpci->tx_rings[i]; + tx_ring = &rtwpci->tx.rings[i]; desc_size = sizeof(struct rtw89_pci_tx_bd_32); len = RTW89_PCI_TXBD_NUM_MAX; ret = rtw89_pci_alloc_tx_ring(rtwdev, pdev, tx_ring, @@ -3486,7 +3486,7 @@ static int rtw89_pci_alloc_tx_rings(struct rtw89_dev *rtwdev, err_free: tx_allocated = i; for (i = 0; i < tx_allocated; i++) { - tx_ring = &rtwpci->tx_rings[i]; + tx_ring = &rtwpci->tx.rings[i]; rtw89_pci_free_tx_ring(rtwdev, pdev, tx_ring); } @@ -3588,7 +3588,7 @@ static int rtw89_pci_alloc_rx_rings(struct rtw89_dev *rtwdev, int ret; for (i = 0; i < RTW89_RXCH_NUM; i++) { - rx_ring = &rtwpci->rx_rings[i]; + rx_ring = &rtwpci->rx.rings[i]; desc_size = sizeof(struct rtw89_pci_rx_bd_32); len = RTW89_PCI_RXBD_NUM_MAX; ret = rtw89_pci_alloc_rx_ring(rtwdev, pdev, rx_ring, @@ -3604,7 +3604,7 @@ static int rtw89_pci_alloc_rx_rings(struct rtw89_dev *rtwdev, err_free: rx_allocated = i; for (i = 0; i < rx_allocated; i++) { - rx_ring = &rtwpci->rx_rings[i]; + rx_ring = &rtwpci->rx.rings[i]; rtw89_pci_free_rx_ring(rtwdev, pdev, rx_ring); } diff --git a/drivers/net/wireless/realtek/rtw89/pci.h b/drivers/net/wireless/realtek/rtw89/pci.h index 6558f60ec914..3156b4f9ebfc 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.h +++ b/drivers/net/wireless/realtek/rtw89/pci.h @@ -1524,6 +1524,10 @@ struct rtw89_pci_tx_ring { u64 tx_mac_id_drop; }; +struct rtw89_pci_tx_rings { + struct rtw89_pci_tx_ring rings[RTW89_TXCH_NUM]; +}; + struct rtw89_pci_rx_ring { struct rtw89_pci_dma_ring bd_ring; struct sk_buff *buf[RTW89_PCI_RXBD_NUM_MAX]; @@ -1533,6 +1537,10 @@ struct rtw89_pci_rx_ring { u32 target_rx_tag:13; }; +struct rtw89_pci_rx_rings { + struct rtw89_pci_rx_ring rings[RTW89_RXCH_NUM]; +}; + struct rtw89_pci_isrs { u32 ind_isrs; u32 halt_c2h_isrs; @@ -1550,8 +1558,8 @@ struct rtw89_pci { bool low_power; bool under_recovery; bool enable_dac; - struct rtw89_pci_tx_ring tx_rings[RTW89_TXCH_NUM]; - struct rtw89_pci_rx_ring rx_rings[RTW89_RXCH_NUM]; + struct rtw89_pci_tx_rings tx; + struct rtw89_pci_rx_rings rx; struct sk_buff_head h2c_queue; struct sk_buff_head h2c_release_queue; DECLARE_BITMAP(kick_map, RTW89_TXCH_NUM); diff --git a/drivers/net/wireless/realtek/rtw89/pci_be.c b/drivers/net/wireless/realtek/rtw89/pci_be.c index ecfe1b60e847..e4590879b800 100644 --- a/drivers/net/wireless/realtek/rtw89/pci_be.c +++ b/drivers/net/wireless/realtek/rtw89/pci_be.c @@ -175,10 +175,10 @@ static void rtw89_pci_clr_idx_all_be(struct rtw89_dev *rtwdev) rtw89_write32(rtwdev, R_BE_RXBD_RWPTR_CLR1_V1, B_BE_CLR_RXQ0_IDX | B_BE_CLR_RPQ0_IDX); - rx_ring = &rtwpci->rx_rings[RTW89_RXCH_RXQ]; + rx_ring = &rtwpci->rx.rings[RTW89_RXCH_RXQ]; rtw89_write16(rtwdev, R_BE_RXQ0_RXBD_IDX_V1, rx_ring->bd_ring.len - 1); - rx_ring = &rtwpci->rx_rings[RTW89_RXCH_RPQ]; + rx_ring = &rtwpci->rx.rings[RTW89_RXCH_RPQ]; rtw89_write16(rtwdev, R_BE_RPQ0_RXBD_IDX_V1, rx_ring->bd_ring.len - 1); } From a86a0fea192cf735c1693cef566d5bb8dc2b49fc Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 26 Aug 2025 16:52:58 +0800 Subject: [PATCH 027/167] wifi: rtw89: pci: define TX/RX buffer descriptor pool Buffer descriptor (BD) is a helper of DMA for each ring. The new hardware design expects a continual memory across all rings, so allocate a pool and assign to each ring rather than allocate a buffer for a ring individually. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250826085258.28308-1-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/pci.c | 119 +++++++++++++---------- drivers/net/wireless/realtek/rtw89/pci.h | 8 ++ 2 files changed, 77 insertions(+), 50 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/pci.c b/drivers/net/wireless/realtek/rtw89/pci.c index d4677df35861..d4f78396dfde 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.c +++ b/drivers/net/wireless/realtek/rtw89/pci.c @@ -3249,15 +3249,6 @@ static void rtw89_pci_free_tx_ring(struct rtw89_dev *rtwdev, struct pci_dev *pdev, struct rtw89_pci_tx_ring *tx_ring) { - int ring_sz; - u8 *head; - dma_addr_t dma; - - head = tx_ring->bd_ring.head; - dma = tx_ring->bd_ring.dma; - ring_sz = tx_ring->bd_ring.desc_size * tx_ring->bd_ring.len; - dma_free_coherent(&pdev->dev, ring_sz, head, dma); - tx_ring->bd_ring.head = NULL; } @@ -3265,6 +3256,7 @@ static void rtw89_pci_free_tx_rings(struct rtw89_dev *rtwdev, struct pci_dev *pdev) { struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; + struct rtw89_pci_dma_pool *bd_pool = &rtwpci->tx.bd_pool; const struct rtw89_pci_info *info = rtwdev->pci_info; struct rtw89_pci_tx_ring *tx_ring; int i; @@ -3276,6 +3268,8 @@ static void rtw89_pci_free_tx_rings(struct rtw89_dev *rtwdev, rtw89_pci_free_tx_wd_ring(rtwdev, pdev, tx_ring); rtw89_pci_free_tx_ring(rtwdev, pdev, tx_ring); } + + dma_free_coherent(&pdev->dev, bd_pool->size, bd_pool->head, bd_pool->dma); } static void rtw89_pci_free_rx_ring(struct rtw89_dev *rtwdev, @@ -3286,8 +3280,6 @@ static void rtw89_pci_free_rx_ring(struct rtw89_dev *rtwdev, struct sk_buff *skb; dma_addr_t dma; u32 buf_sz; - u8 *head; - int ring_sz = rx_ring->bd_ring.desc_size * rx_ring->bd_ring.len; int i; buf_sz = rx_ring->buf_sz; @@ -3303,10 +3295,6 @@ static void rtw89_pci_free_rx_ring(struct rtw89_dev *rtwdev, rx_ring->buf[i] = NULL; } - head = rx_ring->bd_ring.head; - dma = rx_ring->bd_ring.dma; - dma_free_coherent(&pdev->dev, ring_sz, head, dma); - rx_ring->bd_ring.head = NULL; } @@ -3314,6 +3302,7 @@ static void rtw89_pci_free_rx_rings(struct rtw89_dev *rtwdev, struct pci_dev *pdev) { struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; + struct rtw89_pci_dma_pool *bd_pool = &rtwpci->rx.bd_pool; struct rtw89_pci_rx_ring *rx_ring; int i; @@ -3321,6 +3310,8 @@ static void rtw89_pci_free_rx_rings(struct rtw89_dev *rtwdev, rx_ring = &rtwpci->rx.rings[i]; rtw89_pci_free_rx_ring(rtwdev, pdev, rx_ring); } + + dma_free_coherent(&pdev->dev, bd_pool->size, bd_pool->head, bd_pool->dma); } static void rtw89_pci_free_trx_rings(struct rtw89_dev *rtwdev, @@ -3412,12 +3403,10 @@ static int rtw89_pci_alloc_tx_ring(struct rtw89_dev *rtwdev, struct pci_dev *pdev, struct rtw89_pci_tx_ring *tx_ring, u32 desc_size, u32 len, - enum rtw89_tx_channel txch) + enum rtw89_tx_channel txch, + void *head, dma_addr_t dma) { const struct rtw89_pci_ch_dma_addr *txch_addr; - int ring_sz = desc_size * len; - u8 *head; - dma_addr_t dma; int ret; ret = rtw89_pci_alloc_tx_wd_ring(rtwdev, pdev, tx_ring, txch); @@ -3432,12 +3421,6 @@ static int rtw89_pci_alloc_tx_ring(struct rtw89_dev *rtwdev, goto err_free_wd_ring; } - head = dma_alloc_coherent(&pdev->dev, ring_sz, &dma, GFP_KERNEL); - if (!head) { - ret = -ENOMEM; - goto err_free_wd_ring; - } - INIT_LIST_HEAD(&tx_ring->busy_pages); tx_ring->bd_ring.head = head; tx_ring->bd_ring.dma = dma; @@ -3460,25 +3443,48 @@ static int rtw89_pci_alloc_tx_rings(struct rtw89_dev *rtwdev, struct pci_dev *pdev) { struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; + struct rtw89_pci_dma_pool *bd_pool = &rtwpci->tx.bd_pool; const struct rtw89_pci_info *info = rtwdev->pci_info; struct rtw89_pci_tx_ring *tx_ring; - u32 desc_size; - u32 len; u32 i, tx_allocated; + dma_addr_t dma; + u32 desc_size; + u32 ring_sz; + u32 pool_sz; + u32 ch_num; + void *head; + u32 len; int ret; + BUILD_BUG_ON(RTW89_PCI_TXBD_NUM_MAX % 16); + + desc_size = sizeof(struct rtw89_pci_tx_bd_32); + len = RTW89_PCI_TXBD_NUM_MAX; + ch_num = RTW89_TXCH_NUM - hweight32(info->tx_dma_ch_mask); + ring_sz = desc_size * len; + pool_sz = ring_sz * ch_num; + + head = dma_alloc_coherent(&pdev->dev, pool_sz, &dma, GFP_KERNEL); + if (!head) + return -ENOMEM; + + bd_pool->head = head; + bd_pool->dma = dma; + bd_pool->size = pool_sz; + for (i = 0; i < RTW89_TXCH_NUM; i++) { if (info->tx_dma_ch_mask & BIT(i)) continue; tx_ring = &rtwpci->tx.rings[i]; - desc_size = sizeof(struct rtw89_pci_tx_bd_32); - len = RTW89_PCI_TXBD_NUM_MAX; ret = rtw89_pci_alloc_tx_ring(rtwdev, pdev, tx_ring, - desc_size, len, i); + desc_size, len, i, head, dma); if (ret) { rtw89_err(rtwdev, "failed to alloc tx ring %d\n", i); goto err_free; } + + head += ring_sz; + dma += ring_sz; } return 0; @@ -3490,20 +3496,20 @@ err_free: rtw89_pci_free_tx_ring(rtwdev, pdev, tx_ring); } + dma_free_coherent(&pdev->dev, bd_pool->size, bd_pool->head, bd_pool->dma); + return ret; } static int rtw89_pci_alloc_rx_ring(struct rtw89_dev *rtwdev, struct pci_dev *pdev, struct rtw89_pci_rx_ring *rx_ring, - u32 desc_size, u32 len, u32 rxch) + u32 desc_size, u32 len, u32 rxch, + void *head, dma_addr_t dma) { const struct rtw89_pci_info *info = rtwdev->pci_info; const struct rtw89_pci_ch_dma_addr *rxch_addr; struct sk_buff *skb; - u8 *head; - dma_addr_t dma; - int ring_sz = desc_size * len; int buf_sz = RTW89_PCI_RX_BUF_SIZE; int i, allocated; int ret; @@ -3514,12 +3520,6 @@ static int rtw89_pci_alloc_rx_ring(struct rtw89_dev *rtwdev, return ret; } - head = dma_alloc_coherent(&pdev->dev, ring_sz, &dma, GFP_KERNEL); - if (!head) { - ret = -ENOMEM; - goto err; - } - rx_ring->bd_ring.head = head; rx_ring->bd_ring.dma = dma; rx_ring->bd_ring.len = len; @@ -3568,12 +3568,8 @@ err_free: rx_ring->buf[i] = NULL; } - head = rx_ring->bd_ring.head; - dma = rx_ring->bd_ring.dma; - dma_free_coherent(&pdev->dev, ring_sz, head, dma); - rx_ring->bd_ring.head = NULL; -err: + return ret; } @@ -3581,22 +3577,43 @@ static int rtw89_pci_alloc_rx_rings(struct rtw89_dev *rtwdev, struct pci_dev *pdev) { struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; + struct rtw89_pci_dma_pool *bd_pool = &rtwpci->rx.bd_pool; struct rtw89_pci_rx_ring *rx_ring; - u32 desc_size; - u32 len; int i, rx_allocated; + dma_addr_t dma; + u32 desc_size; + u32 ring_sz; + u32 pool_sz; + void *head; + u32 len; int ret; + desc_size = sizeof(struct rtw89_pci_rx_bd_32); + len = RTW89_PCI_RXBD_NUM_MAX; + ring_sz = desc_size * len; + pool_sz = ring_sz * RTW89_RXCH_NUM; + + head = dma_alloc_coherent(&pdev->dev, pool_sz, &dma, GFP_KERNEL); + if (!head) + return -ENOMEM; + + bd_pool->head = head; + bd_pool->dma = dma; + bd_pool->size = pool_sz; + for (i = 0; i < RTW89_RXCH_NUM; i++) { rx_ring = &rtwpci->rx.rings[i]; - desc_size = sizeof(struct rtw89_pci_rx_bd_32); - len = RTW89_PCI_RXBD_NUM_MAX; + ret = rtw89_pci_alloc_rx_ring(rtwdev, pdev, rx_ring, - desc_size, len, i); + desc_size, len, i, + head, dma); if (ret) { rtw89_err(rtwdev, "failed to alloc rx ring %d\n", i); goto err_free; } + + head += ring_sz; + dma += ring_sz; } return 0; @@ -3608,6 +3625,8 @@ err_free: rtw89_pci_free_rx_ring(rtwdev, pdev, rx_ring); } + dma_free_coherent(&pdev->dev, bd_pool->size, bd_pool->head, bd_pool->dma); + return ret; } diff --git a/drivers/net/wireless/realtek/rtw89/pci.h b/drivers/net/wireless/realtek/rtw89/pci.h index 3156b4f9ebfc..f00d717b668b 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.h +++ b/drivers/net/wireless/realtek/rtw89/pci.h @@ -1495,6 +1495,12 @@ struct rtw89_pci_dma_ring { u32 rp; /* hw idx */ }; +struct rtw89_pci_dma_pool { + void *head; + dma_addr_t dma; + u32 size; +}; + struct rtw89_pci_tx_wd_ring { void *head; dma_addr_t dma; @@ -1526,6 +1532,7 @@ struct rtw89_pci_tx_ring { struct rtw89_pci_tx_rings { struct rtw89_pci_tx_ring rings[RTW89_TXCH_NUM]; + struct rtw89_pci_dma_pool bd_pool; }; struct rtw89_pci_rx_ring { @@ -1539,6 +1546,7 @@ struct rtw89_pci_rx_ring { struct rtw89_pci_rx_rings { struct rtw89_pci_rx_ring rings[RTW89_RXCH_NUM]; + struct rtw89_pci_dma_pool bd_pool; }; struct rtw89_pci_isrs { From 2d7514829950d6e4ab2fe7b855f21546b3420450 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 26 Aug 2025 16:53:18 +0800 Subject: [PATCH 028/167] wifi: rtw89: pci: add group BD address design The newly designed group BD address is to use the same starting DMA address with offset as below picture: Original DMA memory Group BD address +--------+ (*1) +---------+ (*2) +--------+ | CH 0 -|-------> | | <-------|- CH 0 | <-+--+--+--+ +--------+ | | | | | | | | | CH 1 -|-------> | | | CH 1 -|---+ | | | +--------+ | | | | | | | | CH 2 -|-------> | | | CH 2 -|------+ | | +--------+ | | | | | | | CH 3 -|-------> | | | CH 3 -|---------+ | +--------+ | | | | | | : -|-------> | | | : -|------------+ +--------+ | | +--------+ (*3; offset from CH 0) | CH 8 -|-------> | | <-------|- CH 8 | <-+ +--------+ | | | | | (offset from CH 8) | : -|-------> | | | : -|---+ +--------+ +---------+ +--------+ Compare (*1) and (*2), for original design, each DMA channel has individual DMA address, so it is not necessary to allocate continual DMA address across channels. For group BD address, only two DMA address are set, and each channel set the offset (*3) from base address. The element number and offset of a channel are encoded rather than a raw number, so add a function to translate numbers into hardware format. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250826085318.28361-1-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/pci.c | 141 +++++++++++++++++- drivers/net/wireless/realtek/rtw89/pci.h | 34 ++++- .../net/wireless/realtek/rtw89/rtw8851be.c | 1 + .../net/wireless/realtek/rtw89/rtw8852ae.c | 1 + .../net/wireless/realtek/rtw89/rtw8852be.c | 1 + .../net/wireless/realtek/rtw89/rtw8852bte.c | 1 + .../net/wireless/realtek/rtw89/rtw8852ce.c | 1 + .../net/wireless/realtek/rtw89/rtw8922ae.c | 1 + 8 files changed, 172 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/pci.c b/drivers/net/wireless/realtek/rtw89/pci.c index d4f78396dfde..55d82af732c0 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.c +++ b/drivers/net/wireless/realtek/rtw89/pci.c @@ -988,6 +988,24 @@ exit: return irqret; } +#define DEF_TXCHADDRS_TYPE3(gen, ch_idx, txch, v...) \ + [RTW89_TXCH_##ch_idx] = { \ + .num = R_##gen##_##txch##_TXBD_CFG, \ + .idx = R_##gen##_##txch##_TXBD_IDX ##v, \ + .bdram = 0, \ + .desa_l = 0, \ + .desa_h = 0, \ + } + +#define DEF_TXCHADDRS_TYPE3_GRP_BASE(gen, ch_idx, txch, grp, v...) \ + [RTW89_TXCH_##ch_idx] = { \ + .num = R_##gen##_##txch##_TXBD_CFG, \ + .idx = R_##gen##_##txch##_TXBD_IDX ##v, \ + .bdram = 0, \ + .desa_l = R_##gen##_##grp##_TXBD_DESA_L, \ + .desa_h = R_##gen##_##grp##_TXBD_DESA_H, \ + } + #define DEF_TXCHADDRS_TYPE2(gen, ch_idx, txch, v...) \ [RTW89_TXCH_##ch_idx] = { \ .num = R_##gen##_##txch##_TXBD_NUM ##v, \ @@ -1015,6 +1033,22 @@ exit: .desa_h = R_AX_##txch##_TXBD_DESA_H ##v, \ } +#define DEF_RXCHADDRS_TYPE3(gen, ch_idx, rxch, v...) \ + [RTW89_RXCH_##ch_idx] = { \ + .num = R_##gen##_RX_##rxch##_RXBD_CONFIG, \ + .idx = R_##gen##_##ch_idx##0_RXBD_IDX ##v, \ + .desa_l = 0, \ + .desa_h = 0, \ + } + +#define DEF_RXCHADDRS_TYPE3_GRP_BASE(gen, ch_idx, rxch, grp, v...) \ + [RTW89_RXCH_##ch_idx] = { \ + .num = R_##gen##_RX_##rxch##_RXBD_CONFIG, \ + .idx = R_##gen##_##ch_idx##0_RXBD_IDX ##v, \ + .desa_l = R_##gen##_##grp##_RXBD_DESA_L, \ + .desa_h = R_##gen##_##grp##_RXBD_DESA_H, \ + } + #define DEF_RXCHADDRS(gen, ch_idx, rxch, v...) \ [RTW89_RXCH_##ch_idx] = { \ .num = R_##gen##_##rxch##_RXBD_NUM ##v, \ @@ -1092,8 +1126,36 @@ const struct rtw89_pci_ch_dma_addr_set rtw89_pci_ch_dma_addr_set_be = { }; EXPORT_SYMBOL(rtw89_pci_ch_dma_addr_set_be); +const struct rtw89_pci_ch_dma_addr_set rtw89_pci_ch_dma_addr_set_be_v1 = { + .tx = { + DEF_TXCHADDRS_TYPE3_GRP_BASE(BE, ACH0, CH0, ACQ, _V1), + /* no CH1 */ + DEF_TXCHADDRS_TYPE3(BE, ACH2, CH2, _V1), + /* no CH3 */ + DEF_TXCHADDRS_TYPE3(BE, ACH4, CH4, _V1), + /* no CH5 */ + DEF_TXCHADDRS_TYPE3(BE, ACH6, CH6, _V1), + /* no CH7 */ + DEF_TXCHADDRS_TYPE3_GRP_BASE(BE, CH8, CH8, NACQ, _V1), + /* no CH9 */ + DEF_TXCHADDRS_TYPE3(BE, CH10, CH10, _V1), + /* no CH11 */ + DEF_TXCHADDRS_TYPE3(BE, CH12, CH12, _V1), + }, + .rx = { + DEF_RXCHADDRS_TYPE3_GRP_BASE(BE, RXQ, CH0, HOST0, _V1), + DEF_RXCHADDRS_TYPE3(BE, RPQ, CH1, _V1), + }, +}; +EXPORT_SYMBOL(rtw89_pci_ch_dma_addr_set_be_v1); + +#undef DEF_TXCHADDRS_TYPE3 +#undef DEF_TXCHADDRS_TYPE3_GRP_BASE +#undef DEF_TXCHADDRS_TYPE2 #undef DEF_TXCHADDRS_TYPE1 #undef DEF_TXCHADDRS +#undef DEF_RXCHADDRS_TYPE3 +#undef DEF_RXCHADDRS_TYPE3_GRP_BASE #undef DEF_RXCHADDRS static int rtw89_pci_get_txch_addrs(struct rtw89_dev *rtwdev, @@ -1645,6 +1707,41 @@ static void rtw89_pci_init_wp_16sel(struct rtw89_dev *rtwdev) } } +static u16 rtw89_pci_enc_bd_cfg(struct rtw89_dev *rtwdev, u16 bd_num, + u32 dma_offset) +{ + u16 dma_offset_sel; + u16 num_sel; + + /* B_BE_TX_NUM_SEL_MASK, B_BE_RX_NUM_SEL_MASK: + * 0 -> 0 + * 1 -> 64 = 2^6 + * 2 -> 128 = 2^7 + * ... + * 7 -> 4096 = 2^12 + */ + num_sel = ilog2(bd_num) - 5; + + if (hweight16(bd_num) != 1) + rtw89_warn(rtwdev, "bd_num %u is not power of 2\n", bd_num); + + /* B_BE_TX_START_OFFSET_MASK, B_BE_RX_START_OFFSET_MASK: + * 0 -> 0 = 0 * 2^9 + * 1 -> 512 = 1 * 2^9 + * 2 -> 1024 = 2 * 2^9 + * 3 -> 1536 = 3 * 2^9 + * ... + * 255 -> 130560 = 255 * 2^9 + */ + dma_offset_sel = dma_offset >> 9; + + if (dma_offset % 512) + rtw89_warn(rtwdev, "offset %u is not multiple of 512\n", dma_offset); + + return u16_encode_bits(num_sel, B_BE_TX_NUM_SEL_MASK) | + u16_encode_bits(dma_offset_sel, B_BE_TX_START_OFFSET_MASK); +} + static void rtw89_pci_reset_trx_rings(struct rtw89_dev *rtwdev) { struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; @@ -1654,10 +1751,12 @@ static void rtw89_pci_reset_trx_rings(struct rtw89_dev *rtwdev) struct rtw89_pci_rx_ring *rx_ring; struct rtw89_pci_dma_ring *bd_ring; const struct rtw89_pci_bd_ram *bd_ram; + dma_addr_t group_dma_base = 0; + u16 num_or_offset; + u32 addr_desa_l; + u32 addr_bdram; u32 addr_num; u32 addr_idx; - u32 addr_bdram; - u32 addr_desa_l; u32 val32; int i; @@ -1674,7 +1773,18 @@ static void rtw89_pci_reset_trx_rings(struct rtw89_dev *rtwdev) bd_ring->wp = 0; bd_ring->rp = 0; - rtw89_write16(rtwdev, addr_num, bd_ring->len); + if (info->group_bd_addr) { + if (addr_desa_l) + group_dma_base = bd_ring->dma; + + num_or_offset = + rtw89_pci_enc_bd_cfg(rtwdev, bd_ring->len, + bd_ring->dma - group_dma_base); + } else { + num_or_offset = bd_ring->len; + } + rtw89_write16(rtwdev, addr_num, num_or_offset); + if (addr_bdram && bd_ram) { val32 = FIELD_PREP(BDRAM_SIDX_MASK, bd_ram->start_idx) | FIELD_PREP(BDRAM_MAX_MASK, bd_ram->max_num) | @@ -1682,8 +1792,10 @@ static void rtw89_pci_reset_trx_rings(struct rtw89_dev *rtwdev) rtw89_write32(rtwdev, addr_bdram, val32); } - rtw89_write32(rtwdev, addr_desa_l, bd_ring->dma); - rtw89_write32(rtwdev, addr_desa_l + 4, upper_32_bits(bd_ring->dma)); + if (addr_desa_l) { + rtw89_write32(rtwdev, addr_desa_l, bd_ring->dma); + rtw89_write32(rtwdev, addr_desa_l + 4, upper_32_bits(bd_ring->dma)); + } } for (i = 0; i < RTW89_RXCH_NUM; i++) { @@ -1701,9 +1813,22 @@ static void rtw89_pci_reset_trx_rings(struct rtw89_dev *rtwdev) rx_ring->diliver_desc.ready = false; rx_ring->target_rx_tag = 0; - rtw89_write16(rtwdev, addr_num, bd_ring->len); - rtw89_write32(rtwdev, addr_desa_l, bd_ring->dma); - rtw89_write32(rtwdev, addr_desa_l + 4, upper_32_bits(bd_ring->dma)); + if (info->group_bd_addr) { + if (addr_desa_l) + group_dma_base = bd_ring->dma; + + num_or_offset = + rtw89_pci_enc_bd_cfg(rtwdev, bd_ring->len, + bd_ring->dma - group_dma_base); + } else { + num_or_offset = bd_ring->len; + } + rtw89_write16(rtwdev, addr_num, num_or_offset); + + if (addr_desa_l) { + rtw89_write32(rtwdev, addr_desa_l, bd_ring->dma); + rtw89_write32(rtwdev, addr_desa_l + 4, upper_32_bits(bd_ring->dma)); + } if (info->rx_ring_eq_is_full) rtw89_write16(rtwdev, addr_idx, bd_ring->wp); diff --git a/drivers/net/wireless/realtek/rtw89/pci.h b/drivers/net/wireless/realtek/rtw89/pci.h index f00d717b668b..e8a856537b7c 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.h +++ b/drivers/net/wireless/realtek/rtw89/pci.h @@ -808,9 +808,25 @@ #define R_BE_CH13_TXBD_NUM_V1 0xB04C #define R_BE_CH14_TXBD_NUM_V1 0xB04E +#define R_BE_CH0_TXBD_CFG 0xB030 +#define R_BE_CH2_TXBD_CFG 0xB034 +#define R_BE_CH4_TXBD_CFG 0xB038 +#define R_BE_CH6_TXBD_CFG 0xB03C +#define R_BE_CH8_TXBD_CFG 0xB040 +#define R_BE_CH10_TXBD_CFG 0xB044 +#define R_BE_CH12_TXBD_CFG 0xB048 +#define B_BE_TX_FLAG BIT(14) +#define B_BE_TX_START_OFFSET_MASK GENMASK(12, 4) +#define B_BE_TX_NUM_SEL_MASK GENMASK(2, 0) + #define R_BE_RXQ0_RXBD_NUM_V1 0xB050 #define R_BE_RPQ0_RXBD_NUM_V1 0xB052 +#define R_BE_RX_CH0_RXBD_CONFIG 0xB050 +#define R_BE_RX_CH1_RXBD_CONFIG 0xB052 +#define B_BE_RX_START_OFFSET_MASK GENMASK(11, 4) +#define B_BE_RX_NUM_SEL_MASK GENMASK(2, 0) + #define R_BE_CH0_TXBD_IDX_V1 0xB100 #define R_BE_CH1_TXBD_IDX_V1 0xB104 #define R_BE_CH2_TXBD_IDX_V1 0xB108 @@ -861,11 +877,25 @@ #define R_BE_CH14_TXBD_DESA_L_V1 0xB270 #define R_BE_CH14_TXBD_DESA_H_V1 0xB274 +#define R_BE_ACQ_TXBD_DESA_L 0xB200 +#define B_BE_TX_ACQ_DESA_L_MASK GENMASK(31, 3) +#define R_BE_ACQ_TXBD_DESA_H 0xB204 +#define B_BE_TX_ACQ_DESA_H_MASK GENMASK(7, 0) +#define R_BE_NACQ_TXBD_DESA_L 0xB240 +#define B_BE_TX_NACQ_DESA_L_MASK GENMASK(31, 3) +#define R_BE_NACQ_TXBD_DESA_H 0xB244 +#define B_BE_TX_NACQ_DESA_H_MASK GENMASK(7, 0) + #define R_BE_RXQ0_RXBD_DESA_L_V1 0xB300 #define R_BE_RXQ0_RXBD_DESA_H_V1 0xB304 #define R_BE_RPQ0_RXBD_DESA_L_V1 0xB308 #define R_BE_RPQ0_RXBD_DESA_H_V1 0xB30C +#define R_BE_HOST0_RXBD_DESA_L 0xB300 +#define B_BE_RX_HOST0_DESA_L_MASK GENMASK(31, 3) +#define R_BE_HOST0_RXBD_DESA_H 0xB304 +#define B_BE_RX_HOST0_DESA_H_MASK GENMASK(7, 0) + #define R_BE_WP_ADDR_H_SEL0_3_V1 0xB420 #define R_BE_WP_ADDR_H_SEL4_7_V1 0xB424 #define R_BE_WP_ADDR_H_SEL8_11_V1 0xB428 @@ -1273,7 +1303,7 @@ struct rtw89_pci_bd_idx_addr { }; struct rtw89_pci_ch_dma_addr { - u32 num; + u32 num; /* also `offset` addr for group_bd_addr design */ u32 idx; u32 bdram; u32 desa_l; @@ -1355,6 +1385,7 @@ struct rtw89_pci_info { bool rx_ring_eq_is_full; bool check_rx_tag; bool no_rxbd_fs; + bool group_bd_addr; u32 init_cfg_reg; u32 txhci_en_bit; @@ -1669,6 +1700,7 @@ extern const struct pci_error_handlers rtw89_pci_err_handler; extern const struct rtw89_pci_ch_dma_addr_set rtw89_pci_ch_dma_addr_set; extern const struct rtw89_pci_ch_dma_addr_set rtw89_pci_ch_dma_addr_set_v1; extern const struct rtw89_pci_ch_dma_addr_set rtw89_pci_ch_dma_addr_set_be; +extern const struct rtw89_pci_ch_dma_addr_set rtw89_pci_ch_dma_addr_set_be_v1; extern const struct rtw89_pci_bd_ram rtw89_bd_ram_table_dual[RTW89_TXCH_NUM]; extern const struct rtw89_pci_bd_ram rtw89_bd_ram_table_single[RTW89_TXCH_NUM]; extern const struct rtw89_pci_isr_def rtw89_pci_isr_ax; diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851be.c b/drivers/net/wireless/realtek/rtw89/rtw8851be.c index c9d60870ed9e..d2ea1e237a1f 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851be.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851be.c @@ -29,6 +29,7 @@ static const struct rtw89_pci_info rtw8851b_pci_info = { .rx_ring_eq_is_full = false, .check_rx_tag = false, .no_rxbd_fs = false, + .group_bd_addr = false, .init_cfg_reg = R_AX_PCIE_INIT_CFG1, .txhci_en_bit = B_AX_TXHCI_EN, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852ae.c b/drivers/net/wireless/realtek/rtw89/rtw8852ae.c index 1bfade7e7e1b..d733264b5dd1 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852ae.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852ae.c @@ -29,6 +29,7 @@ static const struct rtw89_pci_info rtw8852a_pci_info = { .rx_ring_eq_is_full = false, .check_rx_tag = false, .no_rxbd_fs = false, + .group_bd_addr = false, .init_cfg_reg = R_AX_PCIE_INIT_CFG1, .txhci_en_bit = B_AX_TXHCI_EN, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852be.c b/drivers/net/wireless/realtek/rtw89/rtw8852be.c index 8f7676a0a89e..146a1f2292d7 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852be.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852be.c @@ -29,6 +29,7 @@ static const struct rtw89_pci_info rtw8852b_pci_info = { .rx_ring_eq_is_full = false, .check_rx_tag = false, .no_rxbd_fs = false, + .group_bd_addr = false, .init_cfg_reg = R_AX_PCIE_INIT_CFG1, .txhci_en_bit = B_AX_TXHCI_EN, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bte.c b/drivers/net/wireless/realtek/rtw89/rtw8852bte.c index 642ab20e9d06..5373b13e3470 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852bte.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852bte.c @@ -35,6 +35,7 @@ static const struct rtw89_pci_info rtw8852bt_pci_info = { .rx_ring_eq_is_full = false, .check_rx_tag = false, .no_rxbd_fs = false, + .group_bd_addr = false, .init_cfg_reg = R_AX_PCIE_INIT_CFG1, .txhci_en_bit = B_AX_TXHCI_EN, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852ce.c b/drivers/net/wireless/realtek/rtw89/rtw8852ce.c index 4c7682f1d00c..b2bf4ba6ddd8 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852ce.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852ce.c @@ -38,6 +38,7 @@ static const struct rtw89_pci_info rtw8852c_pci_info = { .rx_ring_eq_is_full = false, .check_rx_tag = false, .no_rxbd_fs = false, + .group_bd_addr = false, .init_cfg_reg = R_AX_HAXI_INIT_CFG1, .txhci_en_bit = B_AX_TXHCI_EN_V1, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922ae.c b/drivers/net/wireless/realtek/rtw89/rtw8922ae.c index a0fc6b2832e1..32144fc66e4a 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922ae.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922ae.c @@ -35,6 +35,7 @@ static const struct rtw89_pci_info rtw8922a_pci_info = { .rx_ring_eq_is_full = true, .check_rx_tag = true, .no_rxbd_fs = true, + .group_bd_addr = false, .init_cfg_reg = R_BE_HAXI_INIT_CFG1, .txhci_en_bit = B_BE_TXDMA_EN, From 83d823ab27da7030d25001b7e293059112ee7734 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 26 Aug 2025 16:53:24 +0800 Subject: [PATCH 029/167] wifi: rtw89: pci: abstract RPP parser RPP is short for release report of payload, which carries information assisting TX completion. Since coming chips change the format, abstract the parser ahead. Don't change logic at all. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250826085324.28414-1-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/pci.c | 46 +++++++++++-------- drivers/net/wireless/realtek/rtw89/pci.h | 12 +++++ .../net/wireless/realtek/rtw89/rtw8851be.c | 2 + .../net/wireless/realtek/rtw89/rtw8852ae.c | 2 + .../net/wireless/realtek/rtw89/rtw8852be.c | 2 + .../net/wireless/realtek/rtw89/rtw8852bte.c | 2 + .../net/wireless/realtek/rtw89/rtw8852ce.c | 2 + .../net/wireless/realtek/rtw89/rtw8922ae.c | 2 + 8 files changed, 52 insertions(+), 18 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/pci.c b/drivers/net/wireless/realtek/rtw89/pci.c index 55d82af732c0..cc54694859a6 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.c +++ b/drivers/net/wireless/realtek/rtw89/pci.c @@ -568,31 +568,40 @@ static void rtw89_pci_release_txwd_skb(struct rtw89_dev *rtwdev, rtw89_pci_enqueue_txwd(tx_ring, txwd); } -static void rtw89_pci_release_rpp(struct rtw89_dev *rtwdev, - struct rtw89_pci_rpp_fmt *rpp) +void rtw89_pci_parse_rpp(struct rtw89_dev *rtwdev, void *_rpp, + struct rtw89_pci_rpp_info *rpp_info) +{ + const struct rtw89_pci_rpp_fmt *rpp = _rpp; + + rpp_info->seq = le32_get_bits(rpp->dword, RTW89_PCI_RPP_SEQ); + rpp_info->qsel = le32_get_bits(rpp->dword, RTW89_PCI_RPP_QSEL); + rpp_info->tx_status = le32_get_bits(rpp->dword, RTW89_PCI_RPP_TX_STATUS); + rpp_info->txch = rtw89_core_get_ch_dma(rtwdev, rpp_info->qsel); +} +EXPORT_SYMBOL(rtw89_pci_parse_rpp); + +static void rtw89_pci_release_rpp(struct rtw89_dev *rtwdev, void *rpp) { struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; - struct rtw89_pci_tx_ring *tx_ring; + const struct rtw89_pci_info *info = rtwdev->pci_info; + struct rtw89_pci_rpp_info rpp_info = {}; struct rtw89_pci_tx_wd_ring *wd_ring; + struct rtw89_pci_tx_ring *tx_ring; struct rtw89_pci_tx_wd *txwd; - u16 seq; - u8 qsel, tx_status, txch; - seq = le32_get_bits(rpp->dword, RTW89_PCI_RPP_SEQ); - qsel = le32_get_bits(rpp->dword, RTW89_PCI_RPP_QSEL); - tx_status = le32_get_bits(rpp->dword, RTW89_PCI_RPP_TX_STATUS); - txch = rtw89_core_get_ch_dma(rtwdev, qsel); + info->parse_rpp(rtwdev, rpp, &rpp_info); - if (txch == RTW89_TXCH_CH12) { + if (rpp_info.txch == RTW89_TXCH_CH12) { rtw89_warn(rtwdev, "should no fwcmd release report\n"); return; } - tx_ring = &rtwpci->tx.rings[txch]; + tx_ring = &rtwpci->tx.rings[rpp_info.txch]; wd_ring = &tx_ring->wd_ring; - txwd = &wd_ring->pages[seq]; + txwd = &wd_ring->pages[rpp_info.seq]; - rtw89_pci_release_txwd_skb(rtwdev, tx_ring, txwd, seq, tx_status); + rtw89_pci_release_txwd_skb(rtwdev, tx_ring, txwd, rpp_info.seq, + rpp_info.tx_status); } static void rtw89_pci_release_pending_txwd_skb(struct rtw89_dev *rtwdev, @@ -617,13 +626,14 @@ static u32 rtw89_pci_release_tx_skbs(struct rtw89_dev *rtwdev, u32 max_cnt) { struct rtw89_pci_dma_ring *bd_ring = &rx_ring->bd_ring; - struct rtw89_pci_rx_info *rx_info; - struct rtw89_pci_rpp_fmt *rpp; + const struct rtw89_pci_info *info = rtwdev->pci_info; struct rtw89_rx_desc_info desc_info = {}; + struct rtw89_pci_rx_info *rx_info; struct sk_buff *skb; - u32 cnt = 0; - u32 rpp_size = sizeof(struct rtw89_pci_rpp_fmt); + void *rpp; u32 rxinfo_size = sizeof(struct rtw89_pci_rxbd_info); + u32 rpp_size = info->rpp_fmt_size; + u32 cnt = 0; u32 skb_idx; u32 offset; int ret; @@ -649,7 +659,7 @@ static u32 rtw89_pci_release_tx_skbs(struct rtw89_dev *rtwdev, /* first segment has RX desc */ offset = desc_info.offset + desc_info.rxd_len; for (; offset + rpp_size <= rx_info->len; offset += rpp_size) { - rpp = (struct rtw89_pci_rpp_fmt *)(skb->data + offset); + rpp = skb->data + offset; rtw89_pci_release_rpp(rtwdev, rpp); } diff --git a/drivers/net/wireless/realtek/rtw89/pci.h b/drivers/net/wireless/realtek/rtw89/pci.h index e8a856537b7c..b0c6f4d38bf5 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.h +++ b/drivers/net/wireless/realtek/rtw89/pci.h @@ -1365,6 +1365,13 @@ struct rtw89_pci_ssid_quirk { unsigned long bitmap; /* bitmap of rtw89_quirks */ }; +struct rtw89_pci_rpp_info { + u16 seq; + u8 qsel; + u8 tx_status; + u8 txch; +}; + struct rtw89_pci_info { const struct rtw89_pci_gen_def *gen_def; const struct rtw89_pci_isr_def *isr_def; @@ -1386,6 +1393,7 @@ struct rtw89_pci_info { bool check_rx_tag; bool no_rxbd_fs; bool group_bd_addr; + u32 rpp_fmt_size; u32 init_cfg_reg; u32 txhci_en_bit; @@ -1415,6 +1423,8 @@ struct rtw89_pci_info { u32 (*fill_txaddr_info)(struct rtw89_dev *rtwdev, void *txaddr_info_addr, u32 total_len, dma_addr_t dma, u8 *add_info_nr); + void (*parse_rpp)(struct rtw89_dev *rtwdev, void *rpp, + struct rtw89_pci_rpp_info *rpp_info); void (*config_intr_mask)(struct rtw89_dev *rtwdev); void (*enable_intr)(struct rtw89_dev *rtwdev, struct rtw89_pci *rtwpci); void (*disable_intr)(struct rtw89_dev *rtwdev, struct rtw89_pci *rtwpci); @@ -1724,6 +1734,8 @@ u32 rtw89_pci_fill_txaddr_info(struct rtw89_dev *rtwdev, u32 rtw89_pci_fill_txaddr_info_v1(struct rtw89_dev *rtwdev, void *txaddr_info_addr, u32 total_len, dma_addr_t dma, u8 *add_info_nr); +void rtw89_pci_parse_rpp(struct rtw89_dev *rtwdev, void *_rpp, + struct rtw89_pci_rpp_info *rpp_info); void rtw89_pci_ctrl_dma_all(struct rtw89_dev *rtwdev, bool enable); void rtw89_pci_config_intr_mask(struct rtw89_dev *rtwdev); void rtw89_pci_config_intr_mask_v1(struct rtw89_dev *rtwdev); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851be.c b/drivers/net/wireless/realtek/rtw89/rtw8851be.c index d2ea1e237a1f..ce59ac9f56ba 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851be.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851be.c @@ -30,6 +30,7 @@ static const struct rtw89_pci_info rtw8851b_pci_info = { .check_rx_tag = false, .no_rxbd_fs = false, .group_bd_addr = false, + .rpp_fmt_size = sizeof(struct rtw89_pci_rpp_fmt), .init_cfg_reg = R_AX_PCIE_INIT_CFG1, .txhci_en_bit = B_AX_TXHCI_EN, @@ -59,6 +60,7 @@ static const struct rtw89_pci_info rtw8851b_pci_info = { .ltr_set = rtw89_pci_ltr_set, .fill_txaddr_info = rtw89_pci_fill_txaddr_info, + .parse_rpp = rtw89_pci_parse_rpp, .config_intr_mask = rtw89_pci_config_intr_mask, .enable_intr = rtw89_pci_enable_intr, .disable_intr = rtw89_pci_disable_intr, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852ae.c b/drivers/net/wireless/realtek/rtw89/rtw8852ae.c index d733264b5dd1..9e05e831569d 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852ae.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852ae.c @@ -30,6 +30,7 @@ static const struct rtw89_pci_info rtw8852a_pci_info = { .check_rx_tag = false, .no_rxbd_fs = false, .group_bd_addr = false, + .rpp_fmt_size = sizeof(struct rtw89_pci_rpp_fmt), .init_cfg_reg = R_AX_PCIE_INIT_CFG1, .txhci_en_bit = B_AX_TXHCI_EN, @@ -57,6 +58,7 @@ static const struct rtw89_pci_info rtw8852a_pci_info = { .ltr_set = rtw89_pci_ltr_set, .fill_txaddr_info = rtw89_pci_fill_txaddr_info, + .parse_rpp = rtw89_pci_parse_rpp, .config_intr_mask = rtw89_pci_config_intr_mask, .enable_intr = rtw89_pci_enable_intr, .disable_intr = rtw89_pci_disable_intr, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852be.c b/drivers/net/wireless/realtek/rtw89/rtw8852be.c index 146a1f2292d7..12db0d0be547 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852be.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852be.c @@ -30,6 +30,7 @@ static const struct rtw89_pci_info rtw8852b_pci_info = { .check_rx_tag = false, .no_rxbd_fs = false, .group_bd_addr = false, + .rpp_fmt_size = sizeof(struct rtw89_pci_rpp_fmt), .init_cfg_reg = R_AX_PCIE_INIT_CFG1, .txhci_en_bit = B_AX_TXHCI_EN, @@ -59,6 +60,7 @@ static const struct rtw89_pci_info rtw8852b_pci_info = { .ltr_set = rtw89_pci_ltr_set, .fill_txaddr_info = rtw89_pci_fill_txaddr_info, + .parse_rpp = rtw89_pci_parse_rpp, .config_intr_mask = rtw89_pci_config_intr_mask, .enable_intr = rtw89_pci_enable_intr, .disable_intr = rtw89_pci_disable_intr, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bte.c b/drivers/net/wireless/realtek/rtw89/rtw8852bte.c index 5373b13e3470..8c995aa95325 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852bte.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852bte.c @@ -36,6 +36,7 @@ static const struct rtw89_pci_info rtw8852bt_pci_info = { .check_rx_tag = false, .no_rxbd_fs = false, .group_bd_addr = false, + .rpp_fmt_size = sizeof(struct rtw89_pci_rpp_fmt), .init_cfg_reg = R_AX_PCIE_INIT_CFG1, .txhci_en_bit = B_AX_TXHCI_EN, @@ -65,6 +66,7 @@ static const struct rtw89_pci_info rtw8852bt_pci_info = { .ltr_set = rtw89_pci_ltr_set, .fill_txaddr_info = rtw89_pci_fill_txaddr_info, + .parse_rpp = rtw89_pci_parse_rpp, .config_intr_mask = rtw89_pci_config_intr_mask, .enable_intr = rtw89_pci_enable_intr, .disable_intr = rtw89_pci_disable_intr, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852ce.c b/drivers/net/wireless/realtek/rtw89/rtw8852ce.c index b2bf4ba6ddd8..150fed189414 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852ce.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852ce.c @@ -39,6 +39,7 @@ static const struct rtw89_pci_info rtw8852c_pci_info = { .check_rx_tag = false, .no_rxbd_fs = false, .group_bd_addr = false, + .rpp_fmt_size = sizeof(struct rtw89_pci_rpp_fmt), .init_cfg_reg = R_AX_HAXI_INIT_CFG1, .txhci_en_bit = B_AX_TXHCI_EN_V1, @@ -66,6 +67,7 @@ static const struct rtw89_pci_info rtw8852c_pci_info = { .ltr_set = rtw89_pci_ltr_set_v1, .fill_txaddr_info = rtw89_pci_fill_txaddr_info_v1, + .parse_rpp = rtw89_pci_parse_rpp, .config_intr_mask = rtw89_pci_config_intr_mask_v1, .enable_intr = rtw89_pci_enable_intr_v1, .disable_intr = rtw89_pci_disable_intr_v1, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922ae.c b/drivers/net/wireless/realtek/rtw89/rtw8922ae.c index 32144fc66e4a..90c62b757c57 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922ae.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922ae.c @@ -36,6 +36,7 @@ static const struct rtw89_pci_info rtw8922a_pci_info = { .check_rx_tag = true, .no_rxbd_fs = true, .group_bd_addr = false, + .rpp_fmt_size = sizeof(struct rtw89_pci_rpp_fmt), .init_cfg_reg = R_BE_HAXI_INIT_CFG1, .txhci_en_bit = B_BE_TXDMA_EN, @@ -63,6 +64,7 @@ static const struct rtw89_pci_info rtw8922a_pci_info = { .ltr_set = rtw89_pci_ltr_set_v2, .fill_txaddr_info = rtw89_pci_fill_txaddr_info_v1, + .parse_rpp = rtw89_pci_parse_rpp, .config_intr_mask = rtw89_pci_config_intr_mask_v2, .enable_intr = rtw89_pci_enable_intr_v2, .disable_intr = rtw89_pci_disable_intr_v2, From 110f3c11f440d78ef8a181f75456e24e428f69e4 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 26 Aug 2025 16:53:32 +0800 Subject: [PATCH 030/167] wifi: rtw89: pci: add RPP parser v1 The new format contains more information including TX DMA channel, actual TX link and etc. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250826085332.28463-1-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/pci.c | 12 ++++++++++++ drivers/net/wireless/realtek/rtw89/pci.h | 15 +++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/pci.c b/drivers/net/wireless/realtek/rtw89/pci.c index cc54694859a6..1316671cb31b 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.c +++ b/drivers/net/wireless/realtek/rtw89/pci.c @@ -580,6 +580,18 @@ void rtw89_pci_parse_rpp(struct rtw89_dev *rtwdev, void *_rpp, } EXPORT_SYMBOL(rtw89_pci_parse_rpp); +void rtw89_pci_parse_rpp_v1(struct rtw89_dev *rtwdev, void *_rpp, + struct rtw89_pci_rpp_info *rpp_info) +{ + const struct rtw89_pci_rpp_fmt_v1 *rpp = _rpp; + + rpp_info->seq = le32_get_bits(rpp->w0, RTW89_PCI_RPP_W0_PCIE_SEQ_V1_MASK); + rpp_info->qsel = le32_get_bits(rpp->w1, RTW89_PCI_RPP_W1_QSEL_V1_MASK); + rpp_info->tx_status = le32_get_bits(rpp->w0, RTW89_PCI_RPP_W0_TX_STATUS_V1_MASK); + rpp_info->txch = le32_get_bits(rpp->w0, RTW89_PCI_RPP_W0_DMA_CH_MASK); +} +EXPORT_SYMBOL(rtw89_pci_parse_rpp_v1); + static void rtw89_pci_release_rpp(struct rtw89_dev *rtwdev, void *rpp) { struct rtw89_pci *rtwpci = (struct rtw89_pci *)rtwdev->priv; diff --git a/drivers/net/wireless/realtek/rtw89/pci.h b/drivers/net/wireless/realtek/rtw89/pci.h index b0c6f4d38bf5..fc8268eb44db 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.h +++ b/drivers/net/wireless/realtek/rtw89/pci.h @@ -1498,6 +1498,19 @@ struct rtw89_pci_rpp_fmt { __le32 dword; } __packed; +#define RTW89_PCI_RPP_W0_MACID_V1_MASK GENMASK(9, 0) +#define RTW89_PCI_RPP_W0_DMA_CH_MASK GENMASK(13, 10) +#define RTW89_PCI_RPP_W0_TX_STATUS_V1_MASK GENMASK(16, 14) +#define RTW89_PCI_RPP_W0_PCIE_SEQ_V1_MASK GENMASK(31, 17) +#define RTW89_PCI_RPP_W1_QSEL_V1_MASK GENMASK(5, 0) +#define RTW89_PCI_RPP_W1_TID_IND BIT(6) +#define RTW89_PCI_RPP_W1_CHANGE_LINK BIT(7) + +struct rtw89_pci_rpp_fmt_v1 { + __le32 w0; + __le32 w1; +} __packed; + struct rtw89_pci_rx_bd_32 { __le16 buf_size; __le16 opt; @@ -1736,6 +1749,8 @@ u32 rtw89_pci_fill_txaddr_info_v1(struct rtw89_dev *rtwdev, dma_addr_t dma, u8 *add_info_nr); void rtw89_pci_parse_rpp(struct rtw89_dev *rtwdev, void *_rpp, struct rtw89_pci_rpp_info *rpp_info); +void rtw89_pci_parse_rpp_v1(struct rtw89_dev *rtwdev, void *_rpp, + struct rtw89_pci_rpp_info *rpp_info); void rtw89_pci_ctrl_dma_all(struct rtw89_dev *rtwdev, bool enable); void rtw89_pci_config_intr_mask(struct rtw89_dev *rtwdev); void rtw89_pci_config_intr_mask_v1(struct rtw89_dev *rtwdev); From 571ce803c28280921cfdbd7a6657d9404ff9ec25 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 26 Aug 2025 16:53:39 +0800 Subject: [PATCH 031/167] wifi: rtw89: abstract getting function of DMA channel The mapping function from QSEL (almost equivalent EDCA queue) to PCI DMA channel. Since coming chips change the definition, abstract this function as a chip_ops. Don't change logic at all. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250826085339.28512-1-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.c | 46 +++++++++++++++++-- drivers/net/wireless/realtek/rtw89/core.h | 10 ++++ drivers/net/wireless/realtek/rtw89/pci.c | 2 +- drivers/net/wireless/realtek/rtw89/rtw8851b.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8852a.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8852b.c | 1 + .../net/wireless/realtek/rtw89/rtw8852bt.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8852c.c | 1 + drivers/net/wireless/realtek/rtw89/rtw8922a.c | 1 + drivers/net/wireless/realtek/rtw89/txrx.h | 37 --------------- 10 files changed, 59 insertions(+), 42 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index 2b658ee89bb6..625baa0cb63c 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -699,6 +699,44 @@ static void rtw89_core_tx_update_llc_hdr(struct rtw89_dev *rtwdev, desc_info->hdr_llc_len >>= 1; /* in unit of 2 bytes */ } +u8 rtw89_core_get_ch_dma(struct rtw89_dev *rtwdev, u8 qsel) +{ + switch (qsel) { + default: + rtw89_warn(rtwdev, "Cannot map qsel to dma: %d\n", qsel); + fallthrough; + case RTW89_TX_QSEL_BE_0: + case RTW89_TX_QSEL_BE_1: + case RTW89_TX_QSEL_BE_2: + case RTW89_TX_QSEL_BE_3: + return RTW89_TXCH_ACH0; + case RTW89_TX_QSEL_BK_0: + case RTW89_TX_QSEL_BK_1: + case RTW89_TX_QSEL_BK_2: + case RTW89_TX_QSEL_BK_3: + return RTW89_TXCH_ACH1; + case RTW89_TX_QSEL_VI_0: + case RTW89_TX_QSEL_VI_1: + case RTW89_TX_QSEL_VI_2: + case RTW89_TX_QSEL_VI_3: + return RTW89_TXCH_ACH2; + case RTW89_TX_QSEL_VO_0: + case RTW89_TX_QSEL_VO_1: + case RTW89_TX_QSEL_VO_2: + case RTW89_TX_QSEL_VO_3: + return RTW89_TXCH_ACH3; + case RTW89_TX_QSEL_B0_MGMT: + return RTW89_TXCH_CH8; + case RTW89_TX_QSEL_B0_HI: + return RTW89_TXCH_CH9; + case RTW89_TX_QSEL_B1_MGMT: + return RTW89_TXCH_CH10; + case RTW89_TX_QSEL_B1_HI: + return RTW89_TXCH_CH11; + } +} +EXPORT_SYMBOL(rtw89_core_get_ch_dma); + static void rtw89_core_tx_update_mgmt_info(struct rtw89_dev *rtwdev, struct rtw89_core_tx_request *tx_req) @@ -712,7 +750,7 @@ rtw89_core_tx_update_mgmt_info(struct rtw89_dev *rtwdev, u8 qsel, ch_dma; qsel = rtw89_core_get_qsel_mgmt(rtwdev, tx_req); - ch_dma = rtw89_core_get_ch_dma(rtwdev, qsel); + ch_dma = rtw89_chip_get_ch_dma(rtwdev, qsel); desc_info->qsel = qsel; desc_info->ch_dma = ch_dma; @@ -929,7 +967,7 @@ rtw89_core_tx_update_data_info(struct rtw89_dev *rtwdev, tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK; tid_indicate = rtw89_core_get_tid_indicate(rtwdev, tid); qsel = desc_info->hiq ? RTW89_TX_QSEL_B0_HI : rtw89_core_get_qsel(rtwdev, tid); - ch_dma = rtw89_core_get_ch_dma(rtwdev, qsel); + ch_dma = rtw89_chip_get_ch_dma(rtwdev, qsel); desc_info->ch_dma = ch_dma; desc_info->tid_indicate = tid_indicate; @@ -1079,7 +1117,7 @@ void rtw89_core_tx_kick_off(struct rtw89_dev *rtwdev, u8 qsel) { u8 ch_dma; - ch_dma = rtw89_core_get_ch_dma(rtwdev, qsel); + ch_dma = rtw89_chip_get_ch_dma(rtwdev, qsel); rtw89_hci_tx_kick_off(rtwdev, ch_dma); } @@ -3664,7 +3702,7 @@ static u32 rtw89_check_and_reclaim_tx_resource(struct rtw89_dev *rtwdev, u8 tid) u8 qsel, ch_dma; qsel = rtw89_core_get_qsel(rtwdev, tid); - ch_dma = rtw89_core_get_ch_dma(rtwdev, qsel); + ch_dma = rtw89_chip_get_ch_dma(rtwdev, qsel); return rtw89_hci_check_and_reclaim_tx_resource(rtwdev, ch_dma); } diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index a5fef3c84b20..22f7fb30ff96 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -3760,6 +3760,7 @@ struct rtw89_chip_ops { void (*fill_txdesc_fwcmd)(struct rtw89_dev *rtwdev, struct rtw89_tx_desc_info *desc_info, void *txdesc); + u8 (*get_ch_dma)(struct rtw89_dev *rtwdev, u8 qsel); int (*cfg_ctrl_path)(struct rtw89_dev *rtwdev, bool wl); int (*mac_cfg_gnt)(struct rtw89_dev *rtwdev, const struct rtw89_mac_ax_coex_gnt *gnt_cfg); @@ -7197,6 +7198,14 @@ void rtw89_chip_fill_txdesc_fwcmd(struct rtw89_dev *rtwdev, chip->ops->fill_txdesc_fwcmd(rtwdev, desc_info, txdesc); } +static inline +u8 rtw89_chip_get_ch_dma(struct rtw89_dev *rtwdev, u8 qsel) +{ + const struct rtw89_chip_info *chip = rtwdev->chip; + + return chip->ops->get_ch_dma(rtwdev, qsel); +} + static inline void rtw89_chip_mac_cfg_gnt(struct rtw89_dev *rtwdev, const struct rtw89_mac_ax_coex_gnt *gnt_cfg) @@ -7441,6 +7450,7 @@ void rtw89_core_fill_txdesc_fwcmd_v1(struct rtw89_dev *rtwdev, void rtw89_core_fill_txdesc_fwcmd_v2(struct rtw89_dev *rtwdev, struct rtw89_tx_desc_info *desc_info, void *txdesc); +u8 rtw89_core_get_ch_dma(struct rtw89_dev *rtwdev, u8 qsel); void rtw89_core_rx(struct rtw89_dev *rtwdev, struct rtw89_rx_desc_info *desc_info, struct sk_buff *skb); diff --git a/drivers/net/wireless/realtek/rtw89/pci.c b/drivers/net/wireless/realtek/rtw89/pci.c index 1316671cb31b..b0c2ad22cbb3 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.c +++ b/drivers/net/wireless/realtek/rtw89/pci.c @@ -576,7 +576,7 @@ void rtw89_pci_parse_rpp(struct rtw89_dev *rtwdev, void *_rpp, rpp_info->seq = le32_get_bits(rpp->dword, RTW89_PCI_RPP_SEQ); rpp_info->qsel = le32_get_bits(rpp->dword, RTW89_PCI_RPP_QSEL); rpp_info->tx_status = le32_get_bits(rpp->dword, RTW89_PCI_RPP_TX_STATUS); - rpp_info->txch = rtw89_core_get_ch_dma(rtwdev, rpp_info->qsel); + rpp_info->txch = rtw89_chip_get_ch_dma(rtwdev, rpp_info->qsel); } EXPORT_SYMBOL(rtw89_pci_parse_rpp); diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b.c b/drivers/net/wireless/realtek/rtw89/rtw8851b.c index 084bbf9ecf0b..edcbda124916 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851b.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851b.c @@ -2537,6 +2537,7 @@ static const struct rtw89_chip_ops rtw8851b_chip_ops = { .query_rxdesc = rtw89_core_query_rxdesc, .fill_txdesc = rtw89_core_fill_txdesc, .fill_txdesc_fwcmd = rtw89_core_fill_txdesc, + .get_ch_dma = rtw89_core_get_ch_dma, .cfg_ctrl_path = rtw89_mac_cfg_ctrl_path, .mac_cfg_gnt = rtw89_mac_cfg_gnt, .stop_sch_tx = rtw89_mac_stop_sch_tx, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852a.c b/drivers/net/wireless/realtek/rtw89/rtw8852a.c index d4200246eecc..232f4c1bee1b 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852a.c @@ -2178,6 +2178,7 @@ static const struct rtw89_chip_ops rtw8852a_chip_ops = { .query_rxdesc = rtw89_core_query_rxdesc, .fill_txdesc = rtw89_core_fill_txdesc, .fill_txdesc_fwcmd = rtw89_core_fill_txdesc, + .get_ch_dma = rtw89_core_get_ch_dma, .cfg_ctrl_path = rtw89_mac_cfg_ctrl_path, .mac_cfg_gnt = rtw89_mac_cfg_gnt, .stop_sch_tx = rtw89_mac_stop_sch_tx, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852b.c b/drivers/net/wireless/realtek/rtw89/rtw8852b.c index 6f33f6db2763..0777e336aaa1 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852b.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852b.c @@ -842,6 +842,7 @@ static const struct rtw89_chip_ops rtw8852b_chip_ops = { .query_rxdesc = rtw89_core_query_rxdesc, .fill_txdesc = rtw89_core_fill_txdesc, .fill_txdesc_fwcmd = rtw89_core_fill_txdesc, + .get_ch_dma = rtw89_core_get_ch_dma, .cfg_ctrl_path = rtw89_mac_cfg_ctrl_path, .mac_cfg_gnt = rtw89_mac_cfg_gnt, .stop_sch_tx = rtw89_mac_stop_sch_tx, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bt.c b/drivers/net/wireless/realtek/rtw89/rtw8852bt.c index 9427823aca2f..b3a79ebc7e75 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852bt.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852bt.c @@ -708,6 +708,7 @@ static const struct rtw89_chip_ops rtw8852bt_chip_ops = { .query_rxdesc = rtw89_core_query_rxdesc, .fill_txdesc = rtw89_core_fill_txdesc, .fill_txdesc_fwcmd = rtw89_core_fill_txdesc, + .get_ch_dma = rtw89_core_get_ch_dma, .cfg_ctrl_path = rtw89_mac_cfg_ctrl_path, .mac_cfg_gnt = rtw89_mac_cfg_gnt, .stop_sch_tx = rtw89_mac_stop_sch_tx, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852c.c b/drivers/net/wireless/realtek/rtw89/rtw8852c.c index b0418e89802f..440801d63343 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852c.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852c.c @@ -2962,6 +2962,7 @@ static const struct rtw89_chip_ops rtw8852c_chip_ops = { .query_rxdesc = rtw89_core_query_rxdesc, .fill_txdesc = rtw89_core_fill_txdesc_v1, .fill_txdesc_fwcmd = rtw89_core_fill_txdesc_fwcmd_v1, + .get_ch_dma = rtw89_core_get_ch_dma, .cfg_ctrl_path = rtw89_mac_cfg_ctrl_path_v1, .mac_cfg_gnt = rtw89_mac_cfg_gnt_v1, .stop_sch_tx = rtw89_mac_stop_sch_tx_v1, diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922a.c b/drivers/net/wireless/realtek/rtw89/rtw8922a.c index d7d09d832252..8eb0cb9bb23e 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922a.c @@ -2817,6 +2817,7 @@ static const struct rtw89_chip_ops rtw8922a_chip_ops = { .query_rxdesc = rtw89_core_query_rxdesc_v2, .fill_txdesc = rtw89_core_fill_txdesc_v2, .fill_txdesc_fwcmd = rtw89_core_fill_txdesc_fwcmd_v2, + .get_ch_dma = rtw89_core_get_ch_dma, .cfg_ctrl_path = rtw89_mac_cfg_ctrl_path_v2, .mac_cfg_gnt = rtw89_mac_cfg_gnt_v2, .stop_sch_tx = rtw89_mac_stop_sch_tx_v2, diff --git a/drivers/net/wireless/realtek/rtw89/txrx.h b/drivers/net/wireless/realtek/rtw89/txrx.h index ec01bfc363da..82d6874337b5 100644 --- a/drivers/net/wireless/realtek/rtw89/txrx.h +++ b/drivers/net/wireless/realtek/rtw89/txrx.h @@ -732,43 +732,6 @@ rtw89_core_get_qsel_mgmt(struct rtw89_dev *rtwdev, struct rtw89_core_tx_request return RTW89_TX_QSEL_B0_MGMT; } -static inline u8 rtw89_core_get_ch_dma(struct rtw89_dev *rtwdev, u8 qsel) -{ - switch (qsel) { - default: - rtw89_warn(rtwdev, "Cannot map qsel to dma: %d\n", qsel); - fallthrough; - case RTW89_TX_QSEL_BE_0: - case RTW89_TX_QSEL_BE_1: - case RTW89_TX_QSEL_BE_2: - case RTW89_TX_QSEL_BE_3: - return RTW89_TXCH_ACH0; - case RTW89_TX_QSEL_BK_0: - case RTW89_TX_QSEL_BK_1: - case RTW89_TX_QSEL_BK_2: - case RTW89_TX_QSEL_BK_3: - return RTW89_TXCH_ACH1; - case RTW89_TX_QSEL_VI_0: - case RTW89_TX_QSEL_VI_1: - case RTW89_TX_QSEL_VI_2: - case RTW89_TX_QSEL_VI_3: - return RTW89_TXCH_ACH2; - case RTW89_TX_QSEL_VO_0: - case RTW89_TX_QSEL_VO_1: - case RTW89_TX_QSEL_VO_2: - case RTW89_TX_QSEL_VO_3: - return RTW89_TXCH_ACH3; - case RTW89_TX_QSEL_B0_MGMT: - return RTW89_TXCH_CH8; - case RTW89_TX_QSEL_B0_HI: - return RTW89_TXCH_CH9; - case RTW89_TX_QSEL_B1_MGMT: - return RTW89_TXCH_CH10; - case RTW89_TX_QSEL_B1_HI: - return RTW89_TXCH_CH11; - } -} - static inline u8 rtw89_core_get_tid_indicate(struct rtw89_dev *rtwdev, u8 tid) { switch (tid) { From e83a2a996b5cbf5398375d9b0b19d2b6bd27a18d Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 26 Aug 2025 16:54:24 +0800 Subject: [PATCH 032/167] wifi: rtw89: add getting function of DMA channel v1 The coming RTL8922DE uses new mapping of DMA channel. Add it accordingly. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250826085424.28713-1-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.c | 22 ++++++++++++++++++++++ drivers/net/wireless/realtek/rtw89/core.h | 1 + 2 files changed, 23 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index 625baa0cb63c..20d8f2221f81 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -737,6 +737,28 @@ u8 rtw89_core_get_ch_dma(struct rtw89_dev *rtwdev, u8 qsel) } EXPORT_SYMBOL(rtw89_core_get_ch_dma); +u8 rtw89_core_get_ch_dma_v1(struct rtw89_dev *rtwdev, u8 qsel) +{ + switch (qsel) { + default: + rtw89_warn(rtwdev, "Cannot map qsel to dma v1: %d\n", qsel); + fallthrough; + case RTW89_TX_QSEL_BE_0: + case RTW89_TX_QSEL_BK_0: + return RTW89_TXCH_ACH0; + case RTW89_TX_QSEL_VI_0: + case RTW89_TX_QSEL_VO_0: + return RTW89_TXCH_ACH2; + case RTW89_TX_QSEL_B0_MGMT: + case RTW89_TX_QSEL_B0_HI: + return RTW89_TXCH_CH8; + case RTW89_TX_QSEL_B1_MGMT: + case RTW89_TX_QSEL_B1_HI: + return RTW89_TXCH_CH10; + } +} +EXPORT_SYMBOL(rtw89_core_get_ch_dma_v1); + static void rtw89_core_tx_update_mgmt_info(struct rtw89_dev *rtwdev, struct rtw89_core_tx_request *tx_req) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 22f7fb30ff96..ecfbcff85047 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -7451,6 +7451,7 @@ void rtw89_core_fill_txdesc_fwcmd_v2(struct rtw89_dev *rtwdev, struct rtw89_tx_desc_info *desc_info, void *txdesc); u8 rtw89_core_get_ch_dma(struct rtw89_dev *rtwdev, u8 qsel); +u8 rtw89_core_get_ch_dma_v1(struct rtw89_dev *rtwdev, u8 qsel); void rtw89_core_rx(struct rtw89_dev *rtwdev, struct rtw89_rx_desc_info *desc_info, struct sk_buff *skb); From a650d86bcaf555f45019f38d5c1d996556141bbe Mon Sep 17 00:00:00 2001 From: Qianfeng Rong Date: Wed, 27 Aug 2025 23:06:19 +0800 Subject: [PATCH 033/167] wifi: rtw89: use int type to store negative error codes The 'ret' variable stores returns from other functions, which return either zero on success or negative error codes on failure. Storing error codes in u32 (an unsigned type) causes no runtime issues but is stylistically inconsistent and very ugly. Change 'ret' from u32 to int - this has no runtime impact. Signed-off-by: Qianfeng Rong Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250827150620.550641-1-rongqianfeng@vivo.com --- drivers/net/wireless/realtek/rtw89/fw.c | 7 ++++--- drivers/net/wireless/realtek/rtw89/mac.c | 18 +++++++++--------- drivers/net/wireless/realtek/rtw89/pci.c | 6 +++--- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 334c33e6251d..5f330b625659 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -1542,7 +1542,7 @@ static int __rtw89_fw_download_hdr(struct rtw89_dev *rtwdev, struct rtw89_fw_hdr *fw_hdr; struct sk_buff *skb; u32 truncated; - u32 ret = 0; + int ret; skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len); if (!skb) { @@ -6918,8 +6918,9 @@ static int rtw89_fw_read_c2h_reg(struct rtw89_dev *rtwdev, const struct rtw89_chip_info *chip = rtwdev->chip; struct rtw89_fw_info *fw_info = &rtwdev->fw; const u32 *c2h_reg = chip->c2h_regs; - u32 ret, timeout; + u32 timeout; u8 i, val; + int ret; info->id = RTW89_FWCMD_C2HREG_FUNC_NULL; @@ -6957,7 +6958,7 @@ int rtw89_fw_msg_reg(struct rtw89_dev *rtwdev, struct rtw89_mac_h2c_info *h2c_info, struct rtw89_mac_c2h_info *c2h_info) { - u32 ret; + int ret; if (h2c_info && h2c_info->id != RTW89_FWCMD_H2CREG_FUNC_GET_FEATURE) lockdep_assert_wiphy(rtwdev->hw->wiphy); diff --git a/drivers/net/wireless/realtek/rtw89/mac.c b/drivers/net/wireless/realtek/rtw89/mac.c index 06fc113ffaf0..fd11b8fb3c89 100644 --- a/drivers/net/wireless/realtek/rtw89/mac.c +++ b/drivers/net/wireless/realtek/rtw89/mac.c @@ -178,7 +178,7 @@ int rtw89_mac_dle_dfi_qempty_cfg(struct rtw89_dev *rtwdev, struct rtw89_mac_dle_dfi_qempty *qempty) { struct rtw89_mac_dle_dfi_ctrl ctrl; - u32 ret; + int ret; ctrl.type = qempty->dle_type; ctrl.target = DLE_DFI_TYPE_QEMPTY; @@ -986,7 +986,7 @@ static int hfc_upd_ch_info(struct rtw89_dev *rtwdev, u8 ch) struct rtw89_hfc_ch_info *info = param->ch_info; const struct rtw89_hfc_ch_cfg *cfg = param->ch_cfg; u32 val; - u32 ret; + int ret; ret = rtw89_mac_check_mac_en(rtwdev, RTW89_MAC_0, RTW89_DMAC_SEL); if (ret) @@ -1177,8 +1177,8 @@ int rtw89_mac_hfc_init(struct rtw89_dev *rtwdev, bool reset, bool en, bool h2c_e const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; const struct rtw89_chip_info *chip = rtwdev->chip; u32 dma_ch_mask = chip->dma_ch_mask; + int ret = 0; u8 ch; - u32 ret = 0; if (reset) ret = hfc_reset_param(rtwdev); @@ -1194,7 +1194,7 @@ int rtw89_mac_hfc_init(struct rtw89_dev *rtwdev, bool reset, bool en, bool h2c_e if (!en && h2c_en) { mac->hfc_h2c_cfg(rtwdev); mac->hfc_func_en(rtwdev, en, h2c_en); - return ret; + return 0; } for (ch = RTW89_DMA_ACH0; ch < RTW89_DMA_H2C; ch++) { @@ -2414,7 +2414,7 @@ static int addr_cam_init_ax(struct rtw89_dev *rtwdev, u8 mac_idx) static int scheduler_init_ax(struct rtw89_dev *rtwdev, u8 mac_idx) { - u32 ret; + int ret; u32 reg; u32 val; @@ -2955,7 +2955,7 @@ static int rtw89_mac_read_phycap(struct rtw89_dev *rtwdev, struct rtw89_mac_h2c_info h2c_info = {}; enum rtw89_mac_c2h_type c2h_type; u8 content_len; - u32 ret; + int ret; if (chip->chip_gen == RTW89_CHIP_AX) content_len = 0; @@ -3106,10 +3106,10 @@ int rtw89_mac_setup_phycap(struct rtw89_dev *rtwdev) static int rtw89_hw_sch_tx_en_h2c(struct rtw89_dev *rtwdev, u8 band, u16 tx_en_u16, u16 mask_u16) { - u32 ret; struct rtw89_mac_c2h_info c2h_info = {0}; struct rtw89_mac_h2c_info h2c_info = {0}; struct rtw89_h2creg_sch_tx_en *sch_tx_en = &h2c_info.u.sch_tx_en; + int ret; h2c_info.id = RTW89_FWCMD_H2CREG_FUNC_SCH_TX_EN; h2c_info.content_len = sizeof(*sch_tx_en) - RTW89_H2CREG_HDR_LEN; @@ -6738,7 +6738,7 @@ int rtw89_mac_set_hw_muedca_ctrl(struct rtw89_dev *rtwdev, u8 mac_idx = rtwvif_link->mac_idx; u16 set = mac->muedca_ctrl.mask; u32 reg; - u32 ret; + int ret; ret = rtw89_mac_check_mac_en(rtwdev, mac_idx, RTW89_CMAC_SEL); if (ret) @@ -6880,7 +6880,7 @@ int rtw89_mac_cpu_io_rx(struct rtw89_dev *rtwdev, bool wow_enable) { struct rtw89_mac_h2c_info h2c_info = {}; struct rtw89_mac_c2h_info c2h_info = {}; - u32 ret; + int ret; if (RTW89_CHK_FW_FEATURE(NO_WOW_CPU_IO_RX, &rtwdev->fw)) return 0; diff --git a/drivers/net/wireless/realtek/rtw89/pci.c b/drivers/net/wireless/realtek/rtw89/pci.c index b0c2ad22cbb3..c8286eb84276 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.c +++ b/drivers/net/wireless/realtek/rtw89/pci.c @@ -2910,7 +2910,7 @@ static int rtw89_pci_poll_rxdma_ch_idle_ax(struct rtw89_dev *rtwdev) static int rtw89_pci_poll_dma_all_idle(struct rtw89_dev *rtwdev) { - u32 ret; + int ret; ret = rtw89_pci_poll_txdma_ch_idle_ax(rtwdev); if (ret) { @@ -4396,7 +4396,7 @@ static int rtw89_pci_lv1rst_stop_dma_ax(struct rtw89_dev *rtwdev) static int rtw89_pci_lv1rst_start_dma_ax(struct rtw89_dev *rtwdev) { - u32 ret; + int ret; if (rtwdev->chip->chip_id == RTL8852C) return 0; @@ -4410,7 +4410,7 @@ static int rtw89_pci_lv1rst_start_dma_ax(struct rtw89_dev *rtwdev) return ret; rtw89_pci_ctrl_dma_all(rtwdev, true); - return ret; + return 0; } static int rtw89_pci_ops_mac_lv1_recovery(struct rtw89_dev *rtwdev, From 35ded83be0d4a50719463e3fad7438e1339cce0c Mon Sep 17 00:00:00 2001 From: Liao Yuanhong Date: Thu, 28 Aug 2025 17:47:17 +0800 Subject: [PATCH 034/167] wifi: rtw89: 8852bt: Remove redundant off_reverse variables The variable off_reverse and its related code are completely redundant in the function. Remove them to clean the code. Signed-off-by: Liao Yuanhong Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250828094717.599527-1-liaoyuanhong@vivo.com --- drivers/net/wireless/realtek/rtw89/rtw8852bt_rfk.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bt_rfk.c b/drivers/net/wireless/realtek/rtw89/rtw8852bt_rfk.c index 99cb6a973de2..961b26ba2d3c 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852bt_rfk.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852bt_rfk.c @@ -1799,19 +1799,14 @@ static void _dpk_onoff(struct rtw89_dev *rtwdev, enum rtw89_rf_path path, bool o { struct rtw89_dpk_info *dpk = &rtwdev->dpk; u8 val, kidx = dpk->cur_idx[path]; - bool off_reverse; val = dpk->is_dpk_enable && !off && dpk->bp[path][kidx].path_ok; - off_reverse = !off; - - val = dpk->is_dpk_enable & off_reverse & dpk->bp[path][kidx].path_ok; - rtw89_phy_write32_mask(rtwdev, R_DPD_CH0A + (path << 8) + (kidx << 2), BIT(24), val); rtw89_debug(rtwdev, RTW89_DBG_RFK, "[DPK] S%d[%d] DPK %s !!!\n", path, - kidx, str_enable_disable(dpk->is_dpk_enable & off_reverse)); + kidx, str_enable_disable(dpk->is_dpk_enable && !off)); } static void _dpk_one_shot(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy, From 00afddfe4c995efc2fd46ad13a67a764cca597e4 Mon Sep 17 00:00:00 2001 From: Martin Kaistra Date: Mon, 1 Sep 2025 14:16:13 +0200 Subject: [PATCH 035/167] wifi: rtl8xxxu: expose efuse via debugfs The efuse contains the mac address and calibration data. During manufacturing and testing it can be necessary to read and check this data. Add a debugfs interface to make it available to userspace. Signed-off-by: Martin Kaistra Reviewed-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250901121613.1876109-1-martin.kaistra@linutronix.de --- drivers/net/wireless/realtek/rtl8xxxu/core.c | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/net/wireless/realtek/rtl8xxxu/core.c b/drivers/net/wireless/realtek/rtl8xxxu/core.c index 9e00dc020e30..3ded5952729f 100644 --- a/drivers/net/wireless/realtek/rtl8xxxu/core.c +++ b/drivers/net/wireless/realtek/rtl8xxxu/core.c @@ -1901,6 +1901,27 @@ static void rtl8xxxu_dump_efuse(struct rtl8xxxu_priv *priv) priv->efuse_wifi.raw, EFUSE_MAP_LEN, true); } +static ssize_t read_file_efuse(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8xxxu_priv *priv = file_inode(file)->i_private; + + return simple_read_from_buffer(user_buf, count, ppos, + priv->efuse_wifi.raw, EFUSE_MAP_LEN); +} + +static const struct debugfs_short_fops fops_efuse = { + .read = read_file_efuse, +}; + +static void rtl8xxxu_debugfs_init(struct rtl8xxxu_priv *priv) +{ + struct dentry *phydir; + + phydir = debugfs_create_dir("rtl8xxxu", priv->hw->wiphy->debugfsdir); + debugfs_create_file("efuse", 0400, phydir, priv, &fops_efuse); +} + void rtl8xxxu_reset_8051(struct rtl8xxxu_priv *priv) { u8 val8; @@ -7975,6 +7996,7 @@ static int rtl8xxxu_probe(struct usb_interface *interface, } rtl8xxxu_init_led(priv); + rtl8xxxu_debugfs_init(priv); return 0; From 2ffc73cdb8247dc09b6534c4018681a126c1d5f5 Mon Sep 17 00:00:00 2001 From: Zenm Chen Date: Tue, 2 Sep 2025 11:57:55 +0800 Subject: [PATCH 036/167] wifi: rtw89: Add USB ID 2001:332a for D-Link AX9U rev. A1 Add USB ID 2001:332a for D-Link AX9U rev. A1 which is a RTL8851BU-based Wi-Fi adapter. Only managed mode and AP mode are tested and it works in both. Signed-off-by: Zenm Chen Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250902035755.1969530-1-zenmchen@gmail.com --- drivers/net/wireless/realtek/rtw89/rtw8851bu.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851bu.c b/drivers/net/wireless/realtek/rtw89/rtw8851bu.c index c3722547c6b0..04e1ab13b753 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8851bu.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8851bu.c @@ -16,6 +16,9 @@ static const struct rtw89_driver_info rtw89_8851bu_info = { static const struct usb_device_id rtw_8851bu_id_table[] = { { USB_DEVICE_AND_INTERFACE_INFO(0x0bda, 0xb851, 0xff, 0xff, 0xff), .driver_info = (kernel_ulong_t)&rtw89_8851bu_info }, + /* D-Link AX9U rev. A1 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2001, 0x332a, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&rtw89_8851bu_info }, /* TP-Link Archer TX10UB Nano */ { USB_DEVICE_AND_INTERFACE_INFO(0x3625, 0x010b, 0xff, 0xff, 0xff), .driver_info = (kernel_ulong_t)&rtw89_8851bu_info }, From 17002412a82feb21be040bd5577789049dfeebe2 Mon Sep 17 00:00:00 2001 From: Zenm Chen Date: Thu, 4 Sep 2025 06:31:00 +0800 Subject: [PATCH 037/167] wifi: rtw89: Add USB ID 2001:3327 for D-Link AX18U rev. A1 Add USB ID 2001:3327 for D-Link AX18U rev. A1 which is a RTL8832BU-based Wi-Fi adapter. Link: https://github.com/morrownr/rtw89/pull/17 Signed-off-by: Zenm Chen Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250903223100.3031-1-zenmchen@gmail.com --- drivers/net/wireless/realtek/rtw89/rtw8852bu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bu.c b/drivers/net/wireless/realtek/rtw89/rtw8852bu.c index b315cb997758..0694272f7ffa 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8852bu.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8852bu.c @@ -30,6 +30,8 @@ static const struct usb_device_id rtw_8852bu_id_table[] = { .driver_info = (kernel_ulong_t)&rtw89_8852bu_info }, { USB_DEVICE_AND_INTERFACE_INFO(0x0db0, 0x6931, 0xff, 0xff, 0xff), .driver_info = (kernel_ulong_t)&rtw89_8852bu_info }, + { USB_DEVICE_AND_INTERFACE_INFO(0x2001, 0x3327, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&rtw89_8852bu_info }, { USB_DEVICE_AND_INTERFACE_INFO(0x3574, 0x6121, 0xff, 0xff, 0xff), .driver_info = (kernel_ulong_t)&rtw89_8852bu_info }, { USB_DEVICE_AND_INTERFACE_INFO(0x35bc, 0x0100, 0xff, 0xff, 0xff), From 1609b014aa29f9a2bf3aaa93f71de4a06725845e Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 8 Jul 2025 09:12:04 +0200 Subject: [PATCH 038/167] wifi: mt76: mt7996: Overwrite unspecified link_id in mt7996_tx() Set link_id to msta or mvif primary value if it is set to IEEE80211_LINK_UNSPECIFIED by mac80211 in mt7996_tx routine. Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250708-mt7996-mlo-fixes-v2-v1-1-f2682818a8a3@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 84f731b387d2..35fdd3104f21 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1221,21 +1221,30 @@ static void mt7996_tx(struct ieee80211_hw *hw, struct mt76_wcid *wcid = &dev->mt76.global_wcid; u8 link_id = u32_get_bits(info->control.flags, IEEE80211_TX_CTRL_MLO_LINK); + struct mt7996_sta *msta; + struct mt7996_vif *mvif; rcu_read_lock(); - if (vif) { - struct mt7996_vif *mvif = (void *)vif->drv_priv; + msta = control->sta ? (void *)control->sta->drv_priv : NULL; + mvif = vif ? (void *)vif->drv_priv : NULL; + + /* Use primary link_id if the value from mac80211 is set to + * IEEE80211_LINK_UNSPECIFIED. + */ + if (link_id == IEEE80211_LINK_UNSPECIFIED) { + if (msta) + link_id = msta->deflink_id; + else if (mvif) + link_id = mvif->mt76.deflink_id; + } + + if (mvif) { struct mt76_vif_link *mlink = &mvif->deflink.mt76; if (link_id < IEEE80211_LINK_UNSPECIFIED) mlink = rcu_dereference(mvif->mt76.link[link_id]); - if (!mlink) { - ieee80211_free_txskb(hw, skb); - goto unlock; - } - if (mlink->wcid) wcid = mlink->wcid; @@ -1254,8 +1263,7 @@ static void mt7996_tx(struct ieee80211_hw *hw, goto unlock; } - if (control->sta && link_id < IEEE80211_LINK_UNSPECIFIED) { - struct mt7996_sta *msta = (void *)control->sta->drv_priv; + if (msta && link_id < IEEE80211_LINK_UNSPECIFIED) { struct mt7996_sta_link *msta_link; msta_link = rcu_dereference(msta->link[link_id]); From fe219a41adaf5354c59e75ebb642b8cb8a851d38 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 8 Jul 2025 09:12:05 +0200 Subject: [PATCH 039/167] wifi: mt76: mt7996: Fix mt7996_mcu_sta_ba wcid configuration Fix the wcid pointer used in mt7996_mcu_sta_ba routine to properly support MLO scenario. Fixes: 98686cd21624c ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250708-mt7996-mlo-fixes-v2-v1-2-f2682818a8a3@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 6 ++++-- drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 12 +++++++----- drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h | 3 ++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 35fdd3104f21..e4a1b05ddfee 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1335,11 +1335,13 @@ mt7996_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, case IEEE80211_AMPDU_RX_START: mt76_rx_aggr_start(&dev->mt76, &msta_link->wcid, tid, ssn, params->buf_size); - ret = mt7996_mcu_add_rx_ba(dev, params, link, true); + ret = mt7996_mcu_add_rx_ba(dev, params, link, + msta_link, true); break; case IEEE80211_AMPDU_RX_STOP: mt76_rx_aggr_stop(&dev->mt76, &msta_link->wcid, tid); - ret = mt7996_mcu_add_rx_ba(dev, params, link, false); + ret = mt7996_mcu_add_rx_ba(dev, params, link, + msta_link, false); break; case IEEE80211_AMPDU_TX_OPERATIONAL: mtxq->aggr = true; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 0be03eb3cf46..e6db3e0b2ffd 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -1149,9 +1149,8 @@ int mt7996_mcu_set_timing(struct mt7996_phy *phy, struct ieee80211_vif *vif, static int mt7996_mcu_sta_ba(struct mt7996_dev *dev, struct mt76_vif_link *mvif, struct ieee80211_ampdu_params *params, - bool enable, bool tx) + struct mt76_wcid *wcid, bool enable, bool tx) { - struct mt76_wcid *wcid = (struct mt76_wcid *)params->sta->drv_priv; struct sta_rec_ba_uni *ba; struct sk_buff *skb; struct tlv *tlv; @@ -1185,14 +1184,17 @@ int mt7996_mcu_add_tx_ba(struct mt7996_dev *dev, if (enable && !params->amsdu) msta_link->wcid.amsdu = false; - return mt7996_mcu_sta_ba(dev, &link->mt76, params, enable, true); + return mt7996_mcu_sta_ba(dev, &link->mt76, params, &msta_link->wcid, + enable, true); } int mt7996_mcu_add_rx_ba(struct mt7996_dev *dev, struct ieee80211_ampdu_params *params, - struct mt7996_vif_link *link, bool enable) + struct mt7996_vif_link *link, + struct mt7996_sta_link *msta_link, bool enable) { - return mt7996_mcu_sta_ba(dev, &link->mt76, params, enable, false); + return mt7996_mcu_sta_ba(dev, &link->mt76, params, &msta_link->wcid, + enable, false); } static void diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 8509d508e1e1..bbd4679edc9d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -608,7 +608,8 @@ int mt7996_mcu_add_tx_ba(struct mt7996_dev *dev, struct mt7996_sta_link *msta_link, bool enable); int mt7996_mcu_add_rx_ba(struct mt7996_dev *dev, struct ieee80211_ampdu_params *params, - struct mt7996_vif_link *link, bool enable); + struct mt7996_vif_link *link, + struct mt7996_sta_link *msta_link, bool enable); int mt7996_mcu_update_bss_color(struct mt7996_dev *dev, struct mt76_vif_link *mlink, struct cfg80211_he_bss_color *he_bss_color); From ed01c310eca96453c11b59db46c855aa593cffdd Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 10 Jul 2025 10:26:19 +0200 Subject: [PATCH 040/167] wifi: mt76: mt7996: Fix mt7996_mcu_bss_mld_tlv routine Update mt7996_mcu_bss_mld_tlv routine to properly support MLO configuring the BSS. Fixes: 98686cd21624c ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250710-mt7996-mlo-fixes-v3-v1-1-e7595b089f2c@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 46 ++++++++++++++++++- .../net/wireless/mediatek/mt76/mt7996/mcu.c | 26 ++++++++--- .../net/wireless/mediatek/mt76/mt7996/mcu.h | 3 +- .../wireless/mediatek/mt76/mt7996/mt7996.h | 8 ++++ 4 files changed, 75 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index e4a1b05ddfee..9bf66e112fd1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -138,6 +138,28 @@ static int get_omac_idx(enum nl80211_iftype type, u64 mask) return -1; } +static int get_own_mld_idx(u64 mask, bool group_mld) +{ + u8 start = group_mld ? 0 : 16; + u8 end = group_mld ? 15 : 63; + int idx; + + idx = get_free_idx(mask, start, end); + if (idx) + return idx - 1; + + /* If the 16-63 range is not available, perform another lookup in the + * range 0-15 + */ + if (!group_mld) { + idx = get_free_idx(mask, 0, 15); + if (idx) + return idx - 1; + } + + return -EINVAL; +} + static void mt7996_init_bitrate_mask(struct ieee80211_vif *vif, struct mt7996_vif_link *mlink) { @@ -279,7 +301,7 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, struct mt7996_dev *dev = phy->dev; u8 band_idx = phy->mt76->band_idx; struct mt76_txq *mtxq; - int idx, ret; + int mld_idx, idx, ret; mlink->idx = __ffs64(~dev->mt76.vif_mask); if (mlink->idx >= mt7996_max_interface_num(dev)) @@ -289,6 +311,17 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, if (idx < 0) return -ENOSPC; + if (!dev->mld_idx_mask) { /* first link in the group */ + mvif->mld_group_idx = get_own_mld_idx(dev->mld_idx_mask, true); + mvif->mld_remap_idx = get_free_idx(dev->mld_remap_idx_mask, + 0, 15); + } + + mld_idx = get_own_mld_idx(dev->mld_idx_mask, false); + if (mld_idx < 0) + return -ENOSPC; + + link->mld_idx = mld_idx; link->phy = phy; mlink->omac_idx = idx; mlink->band_idx = band_idx; @@ -301,6 +334,11 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, return ret; dev->mt76.vif_mask |= BIT_ULL(mlink->idx); + if (!dev->mld_idx_mask) { + dev->mld_idx_mask |= BIT_ULL(mvif->mld_group_idx); + dev->mld_remap_idx_mask |= BIT_ULL(mvif->mld_remap_idx); + } + dev->mld_idx_mask |= BIT_ULL(link->mld_idx); phy->omac_mask |= BIT_ULL(mlink->omac_idx); idx = MT7996_WTBL_RESERVED - mlink->idx; @@ -380,7 +418,13 @@ void mt7996_vif_link_remove(struct mt76_phy *mphy, struct ieee80211_vif *vif, } dev->mt76.vif_mask &= ~BIT_ULL(mlink->idx); + dev->mld_idx_mask &= ~BIT_ULL(link->mld_idx); phy->omac_mask &= ~BIT_ULL(mlink->omac_idx); + if (!(dev->mld_idx_mask & ~BIT_ULL(mvif->mld_group_idx))) { + /* last link */ + dev->mld_idx_mask &= ~BIT_ULL(mvif->mld_group_idx); + dev->mld_remap_idx_mask &= ~BIT_ULL(mvif->mld_remap_idx); + } spin_lock_bh(&dev->mt76.sta_poll_lock); if (!list_empty(&msta_link->wcid.poll_list)) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index e6db3e0b2ffd..aad58f7831c7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -899,17 +899,28 @@ mt7996_mcu_bss_txcmd_tlv(struct sk_buff *skb, bool en) } static void -mt7996_mcu_bss_mld_tlv(struct sk_buff *skb, struct mt76_vif_link *mlink) +mt7996_mcu_bss_mld_tlv(struct sk_buff *skb, + struct ieee80211_bss_conf *link_conf, + struct mt7996_vif_link *link) { + struct ieee80211_vif *vif = link_conf->vif; + struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; struct bss_mld_tlv *mld; struct tlv *tlv; tlv = mt7996_mcu_add_uni_tlv(skb, UNI_BSS_INFO_MLD, sizeof(*mld)); - mld = (struct bss_mld_tlv *)tlv; - mld->group_mld_id = 0xff; - mld->own_mld_id = mlink->idx; - mld->remap_idx = 0xff; + mld->own_mld_id = link->mld_idx; + mld->link_id = link_conf->link_id; + + if (ieee80211_vif_is_mld(vif)) { + mld->group_mld_id = mvif->mld_group_idx; + mld->remap_idx = mvif->mld_remap_idx; + memcpy(mld->mac_addr, vif->addr, ETH_ALEN); + } else { + mld->group_mld_id = 0xff; + mld->remap_idx = 0xff; + } } static void @@ -1108,6 +1119,8 @@ int mt7996_mcu_add_bss_info(struct mt7996_phy *phy, struct ieee80211_vif *vif, goto out; if (enable) { + struct mt7996_vif_link *link; + mt7996_mcu_bss_rfch_tlv(skb, phy); mt7996_mcu_bss_bmc_tlv(skb, mlink, phy); mt7996_mcu_bss_ra_tlv(skb, phy); @@ -1118,7 +1131,8 @@ int mt7996_mcu_add_bss_info(struct mt7996_phy *phy, struct ieee80211_vif *vif, mt7996_mcu_bss_he_tlv(skb, vif, link_conf, phy); /* this tag is necessary no matter if the vif is MLD */ - mt7996_mcu_bss_mld_tlv(skb, mlink); + link = container_of(mlink, struct mt7996_vif_link, mt76); + mt7996_mcu_bss_mld_tlv(skb, link_conf, link); } mt7996_mcu_bss_mbssid_tlv(skb, link_conf, enable); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index 130ea95626d5..7b21d6ae7e43 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -481,7 +481,8 @@ struct bss_mld_tlv { u8 own_mld_id; u8 mac_addr[ETH_ALEN]; u8 remap_idx; - u8 __rsv[3]; + u8 link_id; + u8 __rsv[2]; } __packed; struct sta_rec_ht_uni { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index bbd4679edc9d..b98cfe6e5be8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -248,11 +248,16 @@ struct mt7996_vif_link { struct ieee80211_tx_queue_params queue_params[IEEE80211_NUM_ACS]; struct cfg80211_bitrate_mask bitrate_mask; + + u8 mld_idx; }; struct mt7996_vif { struct mt7996_vif_link deflink; /* must be first */ struct mt76_vif_data mt76; + + u8 mld_group_idx; + u8 mld_remap_idx; }; /* crash-dump */ @@ -337,6 +342,9 @@ struct mt7996_dev { u32 q_int_mask[MT7996_MAX_QUEUE]; u32 q_wfdma_mask; + u64 mld_idx_mask; + u64 mld_remap_idx_mask; + const struct mt76_bus_ops *bus_ops; struct mt7996_phy phy; From a70b5903c57308fff525cbd62654f6104aa7ecbf Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 31 Jul 2025 12:41:23 +0200 Subject: [PATCH 041/167] wifi: mt76: mt7996: Set def_wcid pointer in mt7996_mac_sta_init_link() In order to get the ieee80211_sta pointer from wcid struct for a MLO client, set def_wcid pointer in mt7996_mac_sta_init_link routine. Signed-off-by: Lorenzo Bianconi Tested-by: Jose Ignacio Tornos Martinez Link: https://patch.msgid.link/20250731-mt7996-mlo-devel-v1-1-7ff4094285d0@kernel.org Signed-off-by: Felix Fietkau --- 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 9bf66e112fd1..9aeebfcc23e0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -969,6 +969,7 @@ mt7996_mac_sta_init_link(struct mt7996_dev *dev, msta_link->wcid.sta = 1; msta_link->wcid.idx = idx; msta_link->wcid.link_id = link_id; + msta_link->wcid.def_wcid = &msta->deflink.wcid; ewma_avg_signal_init(&msta_link->avg_ack_signal); ewma_signal_init(&msta_link->wcid.rssi); From f940c9b7aef65b20456dc709bab3056a79550a01 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 31 Jul 2025 12:41:24 +0200 Subject: [PATCH 042/167] wifi: mt76: mt7996: Set proper link destination address in mt7996_tx() Overwrite 802.11 destination address with in-use link address for MLD capable VIFs in mt7996_tx routine. Signed-off-by: Lorenzo Bianconi Tested-by: Jose Ignacio Tornos Martinez Link: https://patch.msgid.link/20250731-mt7996-mlo-devel-v1-2-7ff4094285d0@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 9aeebfcc23e0..dd951e2ed403 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1259,21 +1259,20 @@ static void mt7996_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control, struct sk_buff *skb) { + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; struct mt7996_dev *dev = mt7996_hw_dev(hw); + struct ieee80211_sta *sta = control->sta; + struct mt7996_sta *msta = sta ? (void *)sta->drv_priv : NULL; struct mt76_phy *mphy = hw->priv; struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); struct ieee80211_vif *vif = info->control.vif; + struct mt7996_vif *mvif = vif ? (void *)vif->drv_priv : NULL; struct mt76_wcid *wcid = &dev->mt76.global_wcid; u8 link_id = u32_get_bits(info->control.flags, IEEE80211_TX_CTRL_MLO_LINK); - struct mt7996_sta *msta; - struct mt7996_vif *mvif; rcu_read_lock(); - msta = control->sta ? (void *)control->sta->drv_priv : NULL; - mvif = vif ? (void *)vif->drv_priv : NULL; - /* Use primary link_id if the value from mac80211 is set to * IEEE80211_LINK_UNSPECIFIED. */ @@ -1284,6 +1283,31 @@ static void mt7996_tx(struct ieee80211_hw *hw, link_id = mvif->mt76.deflink_id; } + if (vif && ieee80211_vif_is_mld(vif)) { + struct ieee80211_bss_conf *link_conf; + + if (msta) { + struct ieee80211_link_sta *link_sta; + + link_sta = rcu_dereference(sta->link[link_id]); + if (!link_sta) + link_sta = rcu_dereference(sta->link[msta->deflink_id]); + + if (link_sta) { + memcpy(hdr->addr1, link_sta->addr, ETH_ALEN); + if (ether_addr_equal(sta->addr, hdr->addr3)) + memcpy(hdr->addr3, link_sta->addr, ETH_ALEN); + } + } + + link_conf = rcu_dereference(vif->link_conf[link_id]); + if (link_conf) { + memcpy(hdr->addr2, link_conf->addr, ETH_ALEN); + if (ether_addr_equal(vif->addr, hdr->addr3)) + memcpy(hdr->addr3, link_conf->addr, ETH_ALEN); + } + } + if (mvif) { struct mt76_vif_link *mlink = &mvif->deflink.mt76; From f6159b2051e157550d7609e19d04471609c6050b Mon Sep 17 00:00:00 2001 From: Nick Morrow Date: Tue, 8 Jul 2025 16:40:42 -0500 Subject: [PATCH 043/167] wifi: mt76: mt7925u: Add VID/PID for Netgear A9000 Add VID/PID 0846/9072 for recently released Netgear A9000. Signed-off-by: Nick Morrow Cc: stable@vger.kernel.org Link: https://patch.msgid.link/7afd3c3c-e7cf-4bd9-801d-bdfc76def506@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/usb.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c index 4dfbc1b6cfdd..bf040f34e4b9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c @@ -12,6 +12,9 @@ static const struct usb_device_id mt7925u_device_table[] = { { USB_DEVICE_AND_INTERFACE_INFO(0x0e8d, 0x7925, 0xff, 0xff, 0xff), .driver_info = (kernel_ulong_t)MT7925_FIRMWARE_WM }, + /* Netgear, Inc. A9000 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x0846, 0x9072, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)MT7925_FIRMWARE_WM }, { }, }; From 74e756b9e28af3ee94a7ea480bb39694be5fbd96 Mon Sep 17 00:00:00 2001 From: Ming Yen Hsieh Date: Tue, 29 Jul 2025 16:49:32 +0800 Subject: [PATCH 044/167] wifi: mt76: mt7925: add MBSSID support Enable MBSSID support for MT7925 by setting the appropriate capability to the firmware. Signed-off-by: Ming Yen Hsieh Link: https://patch.msgid.link/20250729084932.264155-1-mingyen.hsieh@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/main.c | 1 + .../net/wireless/mediatek/mt76/mt7925/mcu.c | 23 ++++++++++++++++++- .../net/wireless/mediatek/mt76/mt792x_core.c | 7 +++++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index b0e053b15227..c7903972b1d5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -240,6 +240,7 @@ int mt7925_init_mlo_caps(struct mt792x_phy *phy) { struct wiphy *wiphy = phy->mt76->hw->wiphy; static const u8 ext_capa_sta[] = { + [2] = WLAN_EXT_CAPA3_MULTI_BSSID_SUPPORT, [7] = WLAN_EXT_CAPA8_OPMODE_NOTIF, }; static struct wiphy_iftype_ext_capab ext_capab[] = { diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index cd457be26523..10d68d241ba1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -2621,6 +2621,25 @@ mt7925_mcu_bss_qos_tlv(struct sk_buff *skb, struct ieee80211_bss_conf *link_conf qos->qos = link_conf->qos; } +static void +mt7925_mcu_bss_mbssid_tlv(struct sk_buff *skb, struct ieee80211_bss_conf *link_conf, + bool enable) +{ + struct bss_info_uni_mbssid *mbssid; + struct tlv *tlv; + + if (!enable && !link_conf->bssid_indicator) + return; + + tlv = mt76_connac_mcu_add_tlv(skb, UNI_BSS_INFO_11V_MBSSID, + sizeof(*mbssid)); + + mbssid = (struct bss_info_uni_mbssid *)tlv; + mbssid->max_indicator = link_conf->bssid_indicator; + mbssid->mbss_idx = link_conf->bssid_index; + mbssid->tx_bss_omac_idx = 0; +} + static void mt7925_mcu_bss_he_tlv(struct sk_buff *skb, struct ieee80211_bss_conf *link_conf, struct mt792x_phy *phy) @@ -2787,8 +2806,10 @@ int mt7925_mcu_add_bss_info(struct mt792x_phy *phy, mt7925_mcu_bss_color_tlv(skb, link_conf, enable); } - if (enable) + if (enable) { mt7925_mcu_bss_rlm_tlv(skb, phy->mt76, link_conf, ctx); + mt7925_mcu_bss_mbssid_tlv(skb, link_conf, enable); + } return mt76_mcu_skb_send_msg(&dev->mt76, skb, MCU_UNI_CMD(BSS_INFO_UPDATE), true); diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c index e3a703398b30..44378f7394e8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c @@ -689,8 +689,13 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw) ieee80211_hw_set(hw, SUPPORTS_VHT_EXT_NSS_BW); ieee80211_hw_set(hw, CONNECTION_MONITOR); ieee80211_hw_set(hw, NO_VIRTUAL_MONITOR); - if (is_mt7921(&dev->mt76)) + + if (is_mt7921(&dev->mt76)) { ieee80211_hw_set(hw, CHANCTX_STA_CSA); + } else { + ieee80211_hw_set(hw, SUPPORTS_MULTI_BSSID); + ieee80211_hw_set(hw, SUPPORTS_ONLY_HE_MULTI_BSSID); + } if (dev->pm.enable) ieee80211_hw_set(hw, CONNECTION_MONITOR); From 42754b7de2b1a2cf116c5e3f1e8e78392f4ed700 Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Wed, 9 Jul 2025 20:25:30 +0530 Subject: [PATCH 045/167] wifi: mt76: fix potential memory leak in mt76_wmac_probe() In mt76_wmac_probe(), when the mt76_alloc_device() call succeeds, memory is allocated for both struct ieee80211_hw and a workqueue. However, on the error path, the workqueue is not freed. Fix that by calling mt76_free_device() on the error path. Fixes: c8846e101502 ("mt76: add driver for MT7603E and MT7628/7688") Signed-off-by: Abdun Nihaal Reviewed-by: Jiri Slaby Link: https://patch.msgid.link/20250709145532.41246-1-abdun.nihaal@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7603/soc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/soc.c b/drivers/net/wireless/mediatek/mt76/mt7603/soc.c index 08590aa68356..1dd372372048 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/soc.c +++ b/drivers/net/wireless/mediatek/mt76/mt7603/soc.c @@ -48,7 +48,7 @@ mt76_wmac_probe(struct platform_device *pdev) return 0; error: - ieee80211_free_hw(mt76_hw(dev)); + mt76_free_device(mdev); return ret; } From 7ae99dd459ba1ea83a9b3d8de254f374182e602c Mon Sep 17 00:00:00 2001 From: Ming Yen Hsieh Date: Tue, 12 Aug 2025 19:16:42 +0800 Subject: [PATCH 046/167] wifi: mt76: mt7921: add MBSSID support Enable MBSSID support for MT7921 by setting the appropriate capability to the firmware. Signed-off-by: Ming Yen Hsieh Link: https://patch.msgid.link/20250812111642.3620845-1-mingyen.hsieh@mediatek.com Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt76_connac_mcu.c | 25 +++++++++++++++++++ .../net/wireless/mediatek/mt76/mt792x_core.c | 5 ++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c index 16db0f2082d1..fc3e6728fcfb 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c @@ -1662,6 +1662,31 @@ int mt76_connac_mcu_uni_add_bss(struct mt76_phy *phy, return err; } + if (enable && vif->bss_conf.bssid_indicator) { + struct { + struct { + u8 bss_idx; + u8 pad[3]; + } __packed hdr; + struct bss_info_uni_mbssid mbssid; + } mbssid_req = { + .hdr = { + .bss_idx = mvif->idx, + }, + .mbssid = { + .tag = cpu_to_le16(UNI_BSS_INFO_11V_MBSSID), + .len = cpu_to_le16(sizeof(struct bss_info_uni_mbssid)), + .max_indicator = vif->bss_conf.bssid_indicator, + .mbss_idx = vif->bss_conf.bssid_index, + }, + }; + + err = mt76_mcu_send_msg(mdev, MCU_UNI_CMD(BSS_INFO_UPDATE), + &mbssid_req, sizeof(mbssid_req), true); + if (err < 0) + return err; + } + return mt76_connac_mcu_uni_set_chctx(phy, mvif, ctx); } EXPORT_SYMBOL_GPL(mt76_connac_mcu_uni_add_bss); diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c index 44378f7394e8..c0e56541a954 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c @@ -689,12 +689,11 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw) ieee80211_hw_set(hw, SUPPORTS_VHT_EXT_NSS_BW); ieee80211_hw_set(hw, CONNECTION_MONITOR); ieee80211_hw_set(hw, NO_VIRTUAL_MONITOR); + ieee80211_hw_set(hw, SUPPORTS_MULTI_BSSID); + ieee80211_hw_set(hw, SUPPORTS_ONLY_HE_MULTI_BSSID); if (is_mt7921(&dev->mt76)) { ieee80211_hw_set(hw, CHANCTX_STA_CSA); - } else { - ieee80211_hw_set(hw, SUPPORTS_MULTI_BSSID); - ieee80211_hw_set(hw, SUPPORTS_ONLY_HE_MULTI_BSSID); } if (dev->pm.enable) From 6ccb6bb9bd7de15b148cb2b4e7229091aa17ce18 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sat, 23 Aug 2025 16:55:37 +0200 Subject: [PATCH 047/167] wifi: mt76: mt7996: Use deflink for AMPDU rx reordering Packets belonging to the same TID can be sent over multiple links. AMPDU rx reordering must be performed over all active links. Fix the problem always using default link for AMPDU rx reordering. Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250823-mt7996-mlo-aggr-fix-v1-1-f35cf0ea4c8a@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/agg-rx.c | 2 + .../net/wireless/mediatek/mt76/mt7996/mac.c | 10 +- .../net/wireless/mediatek/mt76/mt7996/main.c | 97 +++++++------------ .../net/wireless/mediatek/mt76/mt7996/mcu.c | 64 ++++++++++-- .../wireless/mediatek/mt76/mt7996/mt7996.h | 6 +- 5 files changed, 101 insertions(+), 78 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/agg-rx.c b/drivers/net/wireless/mediatek/mt76/agg-rx.c index 07c386c7b4d0..936ab1ca9246 100644 --- a/drivers/net/wireless/mediatek/mt76/agg-rx.c +++ b/drivers/net/wireless/mediatek/mt76/agg-rx.c @@ -173,6 +173,8 @@ void mt76_rx_aggr_reorder(struct sk_buff *skb, struct sk_buff_head *frames) if (ackp == IEEE80211_QOS_CTL_ACK_POLICY_NOACK) return; + if (wcid->def_wcid) + wcid = wcid->def_wcid; tid = rcu_dereference(wcid->aggr[tidno]); if (!tid) return; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index b3fcca9bbb95..78f4c48c3617 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -1183,8 +1183,14 @@ mt7996_txwi_free(struct mt7996_dev *dev, struct mt76_txwi_cache *t, txwi = (__le32 *)mt76_get_txwi_ptr(mdev, t); if (link_sta) { wcid_idx = wcid->idx; - if (likely(t->skb->protocol != cpu_to_be16(ETH_P_PAE))) - mt7996_tx_check_aggr(link_sta, wcid, t->skb); + if (likely(t->skb->protocol != cpu_to_be16(ETH_P_PAE))) { + struct mt7996_sta *msta; + + /* AMPDU state is stored in the primary link */ + msta = (void *)link_sta->sta->drv_priv; + mt7996_tx_check_aggr(link_sta, &msta->deflink.wcid, + t->skb); + } } else { wcid_idx = le32_get_bits(txwi[9], MT_TXD9_WLAN_IDX); } diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index dd951e2ed403..8a90fee6e8b3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -990,11 +990,6 @@ static void mt7996_mac_sta_deinit_link(struct mt7996_dev *dev, struct mt7996_sta_link *msta_link) { - int i; - - for (i = 0; i < ARRAY_SIZE(msta_link->wcid.aggr); i++) - mt76_rx_aggr_stop(&dev->mt76, &msta_link->wcid, i); - mt7996_mac_wtbl_update(dev, msta_link->wcid.idx, MT_WTBL_UPDATE_ADM_COUNT_CLEAR); @@ -1369,16 +1364,13 @@ static int mt7996_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_ampdu_params *params) { - enum ieee80211_ampdu_mlme_action action = params->action; struct mt7996_dev *dev = mt7996_hw_dev(hw); struct ieee80211_sta *sta = params->sta; struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; struct ieee80211_txq *txq = sta->txq[params->tid]; - struct ieee80211_link_sta *link_sta; u16 tid = params->tid; u16 ssn = params->ssn; struct mt76_txq *mtxq; - unsigned int link_id; int ret = 0; if (!txq) @@ -1388,61 +1380,42 @@ mt7996_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, mutex_lock(&dev->mt76.mutex); - for_each_sta_active_link(vif, sta, link_sta, link_id) { - struct mt7996_sta_link *msta_link; - struct mt7996_vif_link *link; - - msta_link = mt76_dereference(msta->link[link_id], &dev->mt76); - if (!msta_link) - continue; - - link = mt7996_vif_link(dev, vif, link_id); - if (!link) - continue; - - switch (action) { - case IEEE80211_AMPDU_RX_START: - mt76_rx_aggr_start(&dev->mt76, &msta_link->wcid, tid, - ssn, params->buf_size); - ret = mt7996_mcu_add_rx_ba(dev, params, link, - msta_link, true); - break; - case IEEE80211_AMPDU_RX_STOP: - mt76_rx_aggr_stop(&dev->mt76, &msta_link->wcid, tid); - ret = mt7996_mcu_add_rx_ba(dev, params, link, - msta_link, false); - break; - case IEEE80211_AMPDU_TX_OPERATIONAL: - mtxq->aggr = true; - mtxq->send_bar = false; - ret = mt7996_mcu_add_tx_ba(dev, params, link, - msta_link, true); - break; - case IEEE80211_AMPDU_TX_STOP_FLUSH: - case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: - mtxq->aggr = false; - clear_bit(tid, &msta_link->wcid.ampdu_state); - ret = mt7996_mcu_add_tx_ba(dev, params, link, - msta_link, false); - break; - case IEEE80211_AMPDU_TX_START: - set_bit(tid, &msta_link->wcid.ampdu_state); - ret = IEEE80211_AMPDU_TX_START_IMMEDIATE; - break; - case IEEE80211_AMPDU_TX_STOP_CONT: - mtxq->aggr = false; - clear_bit(tid, &msta_link->wcid.ampdu_state); - ret = mt7996_mcu_add_tx_ba(dev, params, link, - msta_link, false); - break; - } - - if (ret) - break; - } - - if (action == IEEE80211_AMPDU_TX_STOP_CONT) + switch (params->action) { + case IEEE80211_AMPDU_RX_START: + /* Since packets belonging to the same TID can be split over + * multiple links, store the AMPDU state for reordering in the + * primary link + */ + mt76_rx_aggr_start(&dev->mt76, &msta->deflink.wcid, tid, + ssn, params->buf_size); + ret = mt7996_mcu_add_rx_ba(dev, params, vif, true); + break; + case IEEE80211_AMPDU_RX_STOP: + mt76_rx_aggr_stop(&dev->mt76, &msta->deflink.wcid, tid); + ret = mt7996_mcu_add_rx_ba(dev, params, vif, false); + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + mtxq->aggr = true; + mtxq->send_bar = false; + ret = mt7996_mcu_add_tx_ba(dev, params, vif, true); + break; + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + mtxq->aggr = false; + clear_bit(tid, &msta->deflink.wcid.ampdu_state); + ret = mt7996_mcu_add_tx_ba(dev, params, vif, false); + break; + case IEEE80211_AMPDU_TX_START: + set_bit(tid, &msta->deflink.wcid.ampdu_state); + ret = IEEE80211_AMPDU_TX_START_IMMEDIATE; + break; + case IEEE80211_AMPDU_TX_STOP_CONT: + mtxq->aggr = false; + clear_bit(tid, &msta->deflink.wcid.ampdu_state); + ret = mt7996_mcu_add_tx_ba(dev, params, vif, false); ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + } 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 aad58f7831c7..d63978b4c3d2 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -1192,23 +1192,67 @@ mt7996_mcu_sta_ba(struct mt7996_dev *dev, struct mt76_vif_link *mvif, /** starec & wtbl **/ int mt7996_mcu_add_tx_ba(struct mt7996_dev *dev, struct ieee80211_ampdu_params *params, - struct mt7996_vif_link *link, - struct mt7996_sta_link *msta_link, bool enable) + struct ieee80211_vif *vif, bool enable) { - if (enable && !params->amsdu) - msta_link->wcid.amsdu = false; + struct ieee80211_sta *sta = params->sta; + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + struct ieee80211_link_sta *link_sta; + unsigned int link_id; + int ret = 0; - return mt7996_mcu_sta_ba(dev, &link->mt76, params, &msta_link->wcid, - enable, true); + for_each_sta_active_link(vif, sta, link_sta, link_id) { + struct mt7996_sta_link *msta_link; + struct mt7996_vif_link *link; + + msta_link = mt76_dereference(msta->link[link_id], &dev->mt76); + if (!msta_link) + continue; + + link = mt7996_vif_link(dev, vif, link_id); + if (!link) + continue; + + if (enable && !params->amsdu) + msta_link->wcid.amsdu = false; + + ret = mt7996_mcu_sta_ba(dev, &link->mt76, params, + &msta_link->wcid, enable, true); + if (ret) + break; + } + + return ret; } int mt7996_mcu_add_rx_ba(struct mt7996_dev *dev, struct ieee80211_ampdu_params *params, - struct mt7996_vif_link *link, - struct mt7996_sta_link *msta_link, bool enable) + struct ieee80211_vif *vif, bool enable) { - return mt7996_mcu_sta_ba(dev, &link->mt76, params, &msta_link->wcid, - enable, false); + struct ieee80211_sta *sta = params->sta; + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + struct ieee80211_link_sta *link_sta; + unsigned int link_id; + int ret = 0; + + for_each_sta_active_link(vif, sta, link_sta, link_id) { + struct mt7996_sta_link *msta_link; + struct mt7996_vif_link *link; + + msta_link = mt76_dereference(msta->link[link_id], &dev->mt76); + if (!msta_link) + continue; + + link = mt7996_vif_link(dev, vif, link_id); + if (!link) + continue; + + ret = mt7996_mcu_sta_ba(dev, &link->mt76, params, + &msta_link->wcid, enable, false); + if (ret) + break; + } + + return ret; } static void diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index b98cfe6e5be8..97b84710667f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -612,12 +612,10 @@ int mt7996_mcu_teardown_mld_sta(struct mt7996_dev *dev, struct mt7996_sta_link *msta_link); int mt7996_mcu_add_tx_ba(struct mt7996_dev *dev, struct ieee80211_ampdu_params *params, - struct mt7996_vif_link *link, - struct mt7996_sta_link *msta_link, bool enable); + struct ieee80211_vif *vif, bool enable); int mt7996_mcu_add_rx_ba(struct mt7996_dev *dev, struct ieee80211_ampdu_params *params, - struct mt7996_vif_link *link, - struct mt7996_sta_link *msta_link, bool enable); + struct ieee80211_vif *vif, bool enable); int mt7996_mcu_update_bss_color(struct mt7996_dev *dev, struct mt76_vif_link *mlink, struct cfg80211_he_bss_color *he_bss_color); From 1318d6822f6ceca5a6932655d140aea56f672f52 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 26 Aug 2025 10:13:04 +0200 Subject: [PATCH 048/167] wifi: mt76: Remove dead code in mt76_scan_work Duration can't be equal to 0 in mt76_scan_work routine so get rid of related if condition. Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250826-mt76_scan_work-dead-code-v1-1-ac5234c4d044@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/scan.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/scan.c b/drivers/net/wireless/mediatek/mt76/scan.c index 9b20ccbeb8cf..458f8cdebc10 100644 --- a/drivers/net/wireless/mediatek/mt76/scan.c +++ b/drivers/net/wireless/mediatek/mt76/scan.c @@ -112,9 +112,6 @@ void mt76_scan_work(struct work_struct *work) local_bh_enable(); out: - if (!duration) - return; - if (dev->scan.chan) duration = max_t(int, duration, msecs_to_jiffies(req->duration + From afff4325548f0cf872e404df2856bf8bd9581c7e Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Mon, 1 Sep 2025 00:14:37 +0200 Subject: [PATCH 049/167] wifi: mt76: mt7996: Use proper link_id in link_sta_rc_update callback Do not always use deflink_id in link_sta_rc_update mac80211 callback but use the proper link_id provided by mac80211. Fixes: 0762bdd30279f ("wifi: mt76: mt7996: rework mt7996_mac_sta_rc_work to support MLO") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250901-mt7996-fix-link_sta_rc_update-callback-v1-1-e24caf196222@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 8a90fee6e8b3..05d894182676 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1669,19 +1669,13 @@ static void mt7996_sta_statistics(struct ieee80211_hw *hw, } } -static void mt7996_link_rate_ctrl_update(void *data, struct ieee80211_sta *sta) +static void mt7996_link_rate_ctrl_update(void *data, + struct mt7996_sta_link *msta_link) { - struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + struct mt7996_sta *msta = msta_link->sta; struct mt7996_dev *dev = msta->vif->deflink.phy->dev; - struct mt7996_sta_link *msta_link; u32 *changed = data; - rcu_read_lock(); - - msta_link = rcu_dereference(msta->link[msta->deflink_id]); - if (!msta_link) - goto out; - spin_lock_bh(&dev->mt76.sta_poll_lock); msta_link->changed |= *changed; @@ -1689,8 +1683,6 @@ static void mt7996_link_rate_ctrl_update(void *data, struct ieee80211_sta *sta) list_add_tail(&msta_link->rc_list, &dev->sta_rc_list); spin_unlock_bh(&dev->mt76.sta_poll_lock); -out: - rcu_read_unlock(); } static void mt7996_link_sta_rc_update(struct ieee80211_hw *hw, @@ -1698,11 +1690,32 @@ static void mt7996_link_sta_rc_update(struct ieee80211_hw *hw, struct ieee80211_link_sta *link_sta, u32 changed) { - struct mt7996_dev *dev = mt7996_hw_dev(hw); struct ieee80211_sta *sta = link_sta->sta; + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + struct mt7996_sta_link *msta_link; - mt7996_link_rate_ctrl_update(&changed, sta); - ieee80211_queue_work(hw, &dev->rc_work); + rcu_read_lock(); + + msta_link = rcu_dereference(msta->link[link_sta->link_id]); + if (msta_link) { + struct mt7996_dev *dev = mt7996_hw_dev(hw); + + mt7996_link_rate_ctrl_update(&changed, msta_link); + ieee80211_queue_work(hw, &dev->rc_work); + } + + rcu_read_unlock(); +} + +static void mt7996_sta_rate_ctrl_update(void *data, struct ieee80211_sta *sta) +{ + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + struct mt7996_sta_link *msta_link; + u32 *changed = data; + + msta_link = rcu_dereference(msta->link[msta->deflink_id]); + if (msta_link) + mt7996_link_rate_ctrl_update(&changed, msta_link); } static int @@ -1723,7 +1736,7 @@ mt7996_set_bitrate_mask(struct ieee80211_hw *hw, struct ieee80211_vif *vif, * - multiple rates: if it's not in range format i.e 0-{7,8,9} for VHT * then multiple MCS setting (MCS 4,5,6) is not supported. */ - ieee80211_iterate_stations_atomic(hw, mt7996_link_rate_ctrl_update, + ieee80211_iterate_stations_atomic(hw, mt7996_sta_rate_ctrl_update, &changed); ieee80211_queue_work(hw, &dev->rc_work); From fe5fffadc6c77c56f122cf1042dc830f59e904bf Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sat, 30 Aug 2025 00:26:47 +0200 Subject: [PATCH 050/167] wifi: mt76: mt7996: Check phy before init msta_link in mt7996_mac_sta_add_links() In order to avoid a possible NULL pointer dereference in mt7996_mac_sta_init_link routine, move the phy pointer check before running mt7996_mac_sta_init_link() in mt7996_mac_sta_add_links routine. Fixes: dd82a9e02c054 ("wifi: mt76: mt7996: Rely on mt7996_sta_link in sta_add/sta_remove callbacks") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250830-mt7996_mac_sta_add_links-fix-v1-1-4219fb8755ee@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 05d894182676..c0144c7af94f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1076,16 +1076,17 @@ mt7996_mac_sta_add_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, goto error_unlink; } - err = mt7996_mac_sta_init_link(dev, link_conf, link_sta, link, - link_id); - if (err) - goto error_unlink; - mphy = mt76_vif_link_phy(&link->mt76); if (!mphy) { err = -EINVAL; goto error_unlink; } + + err = mt7996_mac_sta_init_link(dev, link_conf, link_sta, link, + link_id); + if (err) + goto error_unlink; + mphy->num_sta++; } From d54424fbc53b4d6be00f90a8b529cd368f20d357 Mon Sep 17 00:00:00 2001 From: Jack Kao Date: Mon, 1 Sep 2025 15:32:00 +0800 Subject: [PATCH 051/167] wifi: mt76: mt7925: add pci restore for hibernate Due to hibernation causing a power off and power on, this modification adds mt7925_pci_restore callback function for kernel. When hibernation resumes, it calls mt7925_pci_restore to reset the device, allowing it to return to the state it was in before the power off. Signed-off-by: Jack Kao Signed-off-by: Ming Yen Hsieh Link: https://patch.msgid.link/20250901073200.230033-1-mingyen.hsieh@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/pci.c | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c index 89dc30f7c6b7..8eb1fe1082d1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c @@ -529,7 +529,7 @@ restore_suspend: return err; } -static int mt7925_pci_resume(struct device *device) +static int _mt7925_pci_resume(struct device *device, bool restore) { struct pci_dev *pdev = to_pci_dev(device); struct mt76_dev *mdev = pci_get_drvdata(pdev); @@ -569,6 +569,9 @@ static int mt7925_pci_resume(struct device *device) napi_schedule(&mdev->tx_napi); local_bh_enable(); + if (restore) + goto failed; + mt76_connac_mcu_set_hif_suspend(mdev, false, false); ret = wait_event_timeout(dev->wait, dev->hif_resumed, 3 * HZ); @@ -585,7 +588,7 @@ static int mt7925_pci_resume(struct device *device) failed: pm->suspended = false; - if (err < 0) + if (err < 0 || restore) mt792x_reset(&dev->mt76); return err; @@ -596,7 +599,24 @@ static void mt7925_pci_shutdown(struct pci_dev *pdev) mt7925_pci_remove(pdev); } -static DEFINE_SIMPLE_DEV_PM_OPS(mt7925_pm_ops, mt7925_pci_suspend, mt7925_pci_resume); +static int mt7925_pci_resume(struct device *device) +{ + return _mt7925_pci_resume(device, false); +} + +static int mt7925_pci_restore(struct device *device) +{ + return _mt7925_pci_resume(device, true); +} + +static const struct dev_pm_ops mt7925_pm_ops = { + .suspend = pm_sleep_ptr(mt7925_pci_suspend), + .resume = pm_sleep_ptr(mt7925_pci_resume), + .freeze = pm_sleep_ptr(mt7925_pci_suspend), + .thaw = pm_sleep_ptr(mt7925_pci_resume), + .poweroff = pm_sleep_ptr(mt7925_pci_suspend), + .restore = pm_sleep_ptr(mt7925_pci_restore), +}; static struct pci_driver mt7925_pci_driver = { .name = KBUILD_MODNAME, From 25ef5b5d02ac03fe8dd91cf25bd011a570fbeba2 Mon Sep 17 00:00:00 2001 From: Quan Zhou Date: Thu, 28 Aug 2025 20:39:42 +0800 Subject: [PATCH 052/167] wifi: mt76: mt7921: Add 160MHz beamformee capability for mt7922 device Enable 160MHz beamformee support on mt7922 by updating HE capability element configuration. Previously, only 160MHz channel width was set, but beamformee for 160MHz was not properly advertised. This patch adds BEAMFORMEE_MAX_STS_ABOVE_80MHZ_4 capability to allow devices to utilize 160MHz BW for beamforming. Tested by connecting to 160MHz-bandwidth beamforming AP and verified HE capability. Signed-off-by: Quan Zhou Link: https://patch.msgid.link/ae637afaffed387018fdc43709470ef65898ff0b.1756383627.git.quan.zhou@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c index 5881040ac195..67383c41a319 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c @@ -135,6 +135,8 @@ mt7921_init_he_caps(struct mt792x_phy *phy, enum nl80211_band band, if (is_mt7922(phy->mt76->dev)) { he_cap_elem->phy_cap_info[0] |= IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G; + he_cap_elem->phy_cap_info[4] |= + IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_ABOVE_80MHZ_4; he_cap_elem->phy_cap_info[8] |= IEEE80211_HE_PHY_CAP8_20MHZ_IN_160MHZ_HE_PPDU | IEEE80211_HE_PHY_CAP8_80MHZ_IN_160MHZ_HE_PPDU; From 141f0c9a89f9591b312845c4fe5df5e965706c91 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 2 Sep 2025 08:55:12 +0200 Subject: [PATCH 053/167] wifi: mt76: mt7996: Use proper link info in mt7996_mcu_add_group Do not always use default link in mt7996_mcu_add_group routine. Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250902-mt7996_mcu_add_group-fix-v1-1-312e14794dee@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index d63978b4c3d2..c525abd0a7ac 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -2325,13 +2325,10 @@ error_unlock: } static int -mt7996_mcu_add_group(struct mt7996_dev *dev, struct ieee80211_vif *vif, - struct ieee80211_sta *sta) +mt7996_mcu_add_group(struct mt7996_dev *dev, struct mt7996_vif_link *link, + struct mt76_wcid *wcid) { #define MT_STA_BSS_GROUP 1 - struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; - struct mt7996_sta_link *msta_link; - struct mt7996_sta *msta; struct { u8 __rsv1[4]; @@ -2346,13 +2343,10 @@ mt7996_mcu_add_group(struct mt7996_dev *dev, struct ieee80211_vif *vif, .tag = cpu_to_le16(UNI_VOW_DRR_CTRL), .len = cpu_to_le16(sizeof(req) - 4), .action = cpu_to_le32(MT_STA_BSS_GROUP), - .val = cpu_to_le32(mvif->deflink.mt76.idx % 16), + .val = cpu_to_le32(link->mt76.idx % 16), + .wlan_idx = cpu_to_le16(wcid->idx), }; - msta = sta ? (struct mt7996_sta *)sta->drv_priv : NULL; - msta_link = msta ? &msta->deflink : &mvif->deflink.msta_link; - req.wlan_idx = cpu_to_le16(msta_link->wcid.idx); - return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(VOW), &req, sizeof(req), true); } @@ -2492,7 +2486,7 @@ int mt7996_mcu_add_sta(struct mt7996_dev *dev, } } - ret = mt7996_mcu_add_group(dev, link_conf->vif, sta); + ret = mt7996_mcu_add_group(dev, link, wcid); if (ret) { dev_kfree_skb(skb); return ret; From a3ea1c309bf32fdb3665898c40b3ff8ca29ba6c4 Mon Sep 17 00:00:00 2001 From: Shayne Chen Date: Thu, 4 Sep 2025 09:56:39 +0200 Subject: [PATCH 054/167] wifi: mt76: mt7996: Fix mt7996_reverse_frag0_hdr_trans for MLO Update mt7996_reverse_frag0_hdr_trans routine to support MLO. Co-developed-by: Bo Jiao Signed-off-by: Bo Jiao Signed-off-by: Shayne Chen Co-developed-by: Lorenzo Bianconi Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250904-mt7996-mlo-more-fixes-v1-1-89d8fed67f20@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 78f4c48c3617..f9514444f93d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -229,7 +229,9 @@ static int mt7996_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap) { struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb; struct ethhdr *eth_hdr = (struct ethhdr *)(skb->data + hdr_gap); - struct mt7996_sta *msta = (struct mt7996_sta *)status->wcid; + struct mt7996_sta_link *msta_link = (void *)status->wcid; + struct mt7996_sta *msta = msta_link->sta; + struct ieee80211_bss_conf *link_conf; __le32 *rxd = (__le32 *)skb->data; struct ieee80211_sta *sta; struct ieee80211_vif *vif; @@ -246,8 +248,11 @@ static int mt7996_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap) if (!msta || !msta->vif) return -EINVAL; - sta = container_of((void *)msta, struct ieee80211_sta, drv_priv); + sta = wcid_to_sta(status->wcid); vif = container_of((void *)msta->vif, struct ieee80211_vif, drv_priv); + link_conf = rcu_dereference(vif->link_conf[msta_link->wcid.link_id]); + if (!link_conf) + return -EINVAL; /* store the info from RXD and ethhdr to avoid being overridden */ frame_control = le32_get_bits(rxd[8], MT_RXD8_FRAME_CONTROL); @@ -260,7 +265,7 @@ static int mt7996_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap) switch (frame_control & (IEEE80211_FCTL_TODS | IEEE80211_FCTL_FROMDS)) { case 0: - ether_addr_copy(hdr.addr3, vif->bss_conf.bssid); + ether_addr_copy(hdr.addr3, link_conf->bssid); break; case IEEE80211_FCTL_FROMDS: ether_addr_copy(hdr.addr3, eth_hdr->h_source); From 9aa03d182343e51164f56ee269b571f740e2b804 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 4 Sep 2025 09:56:40 +0200 Subject: [PATCH 055/167] wifi: mt76: mt7996: Add all active links to poll list in mt7996_mac_tx_free() Add all valid links to poll list for Airtime Fairness/AQL accounting when tx-free event occurs. Co-developed-by: Bo Jiao Signed-off-by: Bo Jiao Co-developed-by: Shayne Chen Signed-off-by: Shayne Chen Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250904-mt7996-mlo-more-fixes-v1-2-89d8fed67f20@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/mac.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index f9514444f93d..45d621f5fe22 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -1253,6 +1253,9 @@ mt7996_mac_tx_free(struct mt7996_dev *dev, void *data, int len) info = le32_to_cpu(*cur_info); if (info & MT_TXFREE_INFO_PAIR) { struct ieee80211_sta *sta; + unsigned long valid_links; + struct mt7996_sta *msta; + unsigned int id; u16 idx; idx = FIELD_GET(MT_TXFREE_INFO_WLAN_ID, info); @@ -1267,7 +1270,21 @@ mt7996_mac_tx_free(struct mt7996_dev *dev, void *data, int len) if (!link_sta) goto next; - mt76_wcid_add_poll(&dev->mt76, wcid); + msta = (struct mt7996_sta *)sta->drv_priv; + valid_links = sta->valid_links ?: BIT(0); + + /* For MLD STA, add all link's wcid to sta_poll_list */ + for_each_set_bit(id, &valid_links, + IEEE80211_MLD_MAX_NUM_LINKS) { + struct mt7996_sta_link *msta_link; + + msta_link = rcu_dereference(msta->link[id]); + if (!msta_link) + continue; + + mt76_wcid_add_poll(&dev->mt76, + &msta_link->wcid); + } next: /* ver 7 has a new DW with pair = 1, skip it */ if (ver == 7 && ((void *)(cur_info + 1) < end) && From 7ef0c7ad735b0c38140259519a3f165bee2b857c Mon Sep 17 00:00:00 2001 From: Shayne Chen Date: Thu, 4 Sep 2025 09:56:41 +0200 Subject: [PATCH 056/167] wifi: mt76: mt7996: Implement MLD address translation for EAPOL Do the MLD to link address translation for EAPOL frames in driver. Signed-off-by: Shayne Chen Co-developed-by: Lorenzo Bianconi Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250904-mt7996-mlo-more-fixes-v1-3-89d8fed67f20@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/mac.c | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 45d621f5fe22..8a75d7a7b0c0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -966,7 +966,8 @@ void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi, txwi[5] = cpu_to_le32(val); val = MT_TXD6_DAS; - if (q_idx >= MT_LMAC_ALTX0 && q_idx <= MT_LMAC_BCN0) + if ((q_idx >= MT_LMAC_ALTX0 && q_idx <= MT_LMAC_BCN0) || + skb->protocol == cpu_to_be16(ETH_P_PAE)) val |= MT_TXD6_DIS_MAT; if (is_mt7996(&dev->mt76)) @@ -1053,6 +1054,41 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, if (id < 0) return id; + /* Since the rules of HW MLD address translation are not fully + * compatible with 802.11 EAPOL frame, we do the translation by + * software + */ + if (tx_info->skb->protocol == cpu_to_be16(ETH_P_PAE) && sta->mlo) { + struct ieee80211_hdr *hdr = (void *)tx_info->skb->data; + struct ieee80211_bss_conf *link_conf; + struct ieee80211_link_sta *link_sta; + + link_conf = rcu_dereference(vif->link_conf[wcid->link_id]); + if (!link_conf) + return -EINVAL; + + link_sta = rcu_dereference(sta->link[wcid->link_id]); + if (!link_sta) + return -EINVAL; + + dma_sync_single_for_cpu(mdev->dma_dev, tx_info->buf[1].addr, + tx_info->buf[1].len, DMA_TO_DEVICE); + + memcpy(hdr->addr1, link_sta->addr, ETH_ALEN); + memcpy(hdr->addr2, link_conf->addr, ETH_ALEN); + if (ieee80211_has_a4(hdr->frame_control)) { + memcpy(hdr->addr3, sta->addr, ETH_ALEN); + memcpy(hdr->addr4, vif->addr, ETH_ALEN); + } else if (ieee80211_has_tods(hdr->frame_control)) { + memcpy(hdr->addr3, sta->addr, ETH_ALEN); + } else if (ieee80211_has_fromds(hdr->frame_control)) { + memcpy(hdr->addr3, vif->addr, ETH_ALEN); + } + + dma_sync_single_for_device(mdev->dma_dev, tx_info->buf[1].addr, + tx_info->buf[1].len, DMA_TO_DEVICE); + } + pid = mt76_tx_status_skb_add(mdev, wcid, tx_info->skb); memset(txwi_ptr, 0, MT_TXD_SIZE); /* Transmit non qos data by 802.11 header and need to fill txd by host*/ From e6291bb7a5935b2f1d337fd7a58eab7ada6678ad Mon Sep 17 00:00:00 2001 From: Benjamin Lin Date: Thu, 4 Sep 2025 09:56:42 +0200 Subject: [PATCH 057/167] wifi: mt76: mt7996: Temporarily disable EPCS EPCS is not yet ready, so do not claim to support it. Signed-off-by: Benjamin Lin Co-developed-by: Lorenzo Bianconi Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250904-mt7996-mlo-more-fixes-v1-4-89d8fed67f20@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index a9599c286328..90450017d995 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -1321,7 +1321,6 @@ mt7996_init_eht_caps(struct mt7996_phy *phy, enum nl80211_band band, eht_cap->has_eht = true; eht_cap_elem->mac_cap_info[0] = - IEEE80211_EHT_MAC_CAP0_EPCS_PRIO_ACCESS | IEEE80211_EHT_MAC_CAP0_OM_CONTROL | u8_encode_bits(IEEE80211_EHT_MAC_CAP0_MAX_MPDU_LEN_11454, IEEE80211_EHT_MAC_CAP0_MAX_MPDU_LEN_MASK); From a9eae65d97f3cb3f899c4bc7ae6304f64fcedce3 Mon Sep 17 00:00:00 2001 From: Shayne Chen Date: Wed, 27 Aug 2025 10:01:02 +0200 Subject: [PATCH 058/167] wifi: mt76: mt7996: Export MLO AP capabilities to mac80211 Report MT7996 MLO AP capabilities to mac80211 stack. Signed-off-by: Shayne Chen Co-developed-by: Lorenzo Bianconi Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250827-mt7996-mlo-ap-capa-v1-1-b5cfbcafa25f@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/init.c | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 90450017d995..ce4d5b925bfc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -63,6 +63,24 @@ static const struct ieee80211_iface_combination if_comb = { .beacon_int_min_gcd = 100, }; +static const u8 if_types_ext_capa_ap[] = { + [0] = WLAN_EXT_CAPA1_EXT_CHANNEL_SWITCHING, + [2] = WLAN_EXT_CAPA3_MULTI_BSSID_SUPPORT, + [7] = WLAN_EXT_CAPA8_OPMODE_NOTIF, +}; + +static const struct wiphy_iftype_ext_capab iftypes_ext_capa[] = { + { + .iftype = NL80211_IFTYPE_AP, + .extended_capabilities = if_types_ext_capa_ap, + .extended_capabilities_mask = if_types_ext_capa_ap, + .extended_capabilities_len = sizeof(if_types_ext_capa_ap), + .mld_capa_and_ops = + FIELD_PREP_CONST(IEEE80211_MLD_CAP_OP_MAX_SIMUL_LINKS, + MT7996_MAX_RADIOS - 1), + }, +}; + static ssize_t mt7996_thermal_temp_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -463,8 +481,11 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed) wiphy->radio = dev->radios; wiphy->reg_notifier = mt7996_regd_notifier; - wiphy->flags |= WIPHY_FLAG_HAS_CHANNEL_SWITCH; + wiphy->flags |= WIPHY_FLAG_HAS_CHANNEL_SWITCH | + WIPHY_FLAG_SUPPORTS_MLO; wiphy->mbssid_max_interfaces = 16; + wiphy->iftype_ext_capab = iftypes_ext_capa; + wiphy->num_iftype_ext_capab = ARRAY_SIZE(iftypes_ext_capa); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_BSS_COLOR); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_VHT_IBSS); From 24e2846f15b045f323692adcf24cabfcba7f7de6 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sat, 30 Aug 2025 19:12:50 +0200 Subject: [PATCH 059/167] wifi: mt76: mt7996: Set EML capabilities for AP interface Report EML capabilities to hostapd for AP interface. Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250830-mt7996_ap_eml_capa-v1-1-ef69c97c6adc@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index ce4d5b925bfc..a1fce8349dd4 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -75,6 +75,7 @@ static const struct wiphy_iftype_ext_capab iftypes_ext_capa[] = { .extended_capabilities = if_types_ext_capa_ap, .extended_capabilities_mask = if_types_ext_capa_ap, .extended_capabilities_len = sizeof(if_types_ext_capa_ap), + .eml_capabilities = IEEE80211_EML_CAP_EMLSR_SUPP, .mld_capa_and_ops = FIELD_PREP_CONST(IEEE80211_MLD_CAP_OP_MAX_SIMUL_LINKS, MT7996_MAX_RADIOS - 1), From f5160304d57c5543c63404a15cc8737ef714b669 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Mon, 1 Sep 2025 15:02:33 +0200 Subject: [PATCH 060/167] wifi: mt76: mt7996: Enable MLO support for client interfaces Report MT7996 MLO STA capabilities to mac80211 stack. Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250901-mt7996-enable-mlo-client-v1-1-50c46317325d@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mac80211.c | 52 +++++++++++++++++++ drivers/net/wireless/mediatek/mt76/mt76.h | 1 + .../net/wireless/mediatek/mt76/mt7925/main.c | 52 +------------------ .../net/wireless/mediatek/mt76/mt7996/init.c | 8 +++ .../net/wireless/mediatek/mt76/mt7996/main.c | 32 +++++++++--- 5 files changed, 88 insertions(+), 57 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index 59adf3312617..6ef186107782 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -2060,3 +2060,55 @@ void mt76_vif_cleanup(struct mt76_dev *dev, struct ieee80211_vif *vif) mt76_abort_roc(mvif->roc_phy); } EXPORT_SYMBOL_GPL(mt76_vif_cleanup); + +u16 mt76_select_links(struct ieee80211_vif *vif, int max_active_links) +{ + unsigned long usable_links = ieee80211_vif_usable_links(vif); + struct { + u8 link_id; + enum nl80211_band band; + } data[IEEE80211_MLD_MAX_NUM_LINKS]; + unsigned int link_id; + int i, n_data = 0; + u16 sel_links = 0; + + if (!ieee80211_vif_is_mld(vif)) + return 0; + + if (vif->active_links == usable_links) + return vif->active_links; + + rcu_read_lock(); + for_each_set_bit(link_id, &usable_links, IEEE80211_MLD_MAX_NUM_LINKS) { + struct ieee80211_bss_conf *link_conf; + + link_conf = rcu_dereference(vif->link_conf[link_id]); + if (WARN_ON_ONCE(!link_conf)) + continue; + + data[n_data].link_id = link_id; + data[n_data].band = link_conf->chanreq.oper.chan->band; + n_data++; + } + rcu_read_unlock(); + + for (i = 0; i < n_data; i++) { + int j; + + if (!(BIT(data[i].link_id) & vif->active_links)) + continue; + + sel_links = BIT(data[i].link_id); + for (j = 0; j < n_data; j++) { + if (data[i].band != data[j].band) { + sel_links |= BIT(data[j].link_id); + if (hweight16(sel_links) == max_active_links) + break; + } + } + break; + } + + return sel_links; +} +EXPORT_SYMBOL_GPL(mt76_select_links); diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 127637454c82..5310b2a34edb 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -1872,6 +1872,7 @@ mt76_vif_init(struct ieee80211_vif *vif, struct mt76_vif_data *mvif) } void mt76_vif_cleanup(struct mt76_dev *dev, struct ieee80211_vif *vif); +u16 mt76_select_links(struct ieee80211_vif *vif, int max_active_links); static inline struct mt76_vif_link * mt76_vif_link(struct mt76_dev *dev, struct ieee80211_vif *vif, int link_id) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index c7903972b1d5..7416098db9fc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -988,56 +988,6 @@ int mt7925_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif, } EXPORT_SYMBOL_GPL(mt7925_mac_sta_add); -static u16 -mt7925_mac_select_links(struct mt76_dev *mdev, struct ieee80211_vif *vif) -{ - unsigned long usable_links = ieee80211_vif_usable_links(vif); - struct { - u8 link_id; - enum nl80211_band band; - } data[IEEE80211_MLD_MAX_NUM_LINKS]; - u8 link_id, i, j, n_data = 0; - u16 sel_links = 0; - - if (!ieee80211_vif_is_mld(vif)) - return 0; - - if (vif->active_links == usable_links) - return vif->active_links; - - rcu_read_lock(); - for_each_set_bit(link_id, &usable_links, IEEE80211_MLD_MAX_NUM_LINKS) { - struct ieee80211_bss_conf *link_conf = - rcu_dereference(vif->link_conf[link_id]); - - if (WARN_ON_ONCE(!link_conf)) - continue; - - data[n_data].link_id = link_id; - data[n_data].band = link_conf->chanreq.oper.chan->band; - n_data++; - } - rcu_read_unlock(); - - for (i = 0; i < n_data; i++) { - if (!(BIT(data[i].link_id) & vif->active_links)) - continue; - - sel_links = BIT(data[i].link_id); - - for (j = 0; j < n_data; j++) { - if (data[i].band != data[j].band) { - sel_links |= BIT(data[j].link_id); - break; - } - } - - break; - } - - return sel_links; -} - static void mt7925_mac_set_links(struct mt76_dev *mdev, struct ieee80211_vif *vif) { @@ -1048,7 +998,7 @@ mt7925_mac_set_links(struct mt76_dev *mdev, struct ieee80211_vif *vif) struct cfg80211_chan_def *chandef = &link_conf->chanreq.oper; enum nl80211_band band = chandef->chan->band, secondary_band; - u16 sel_links = mt7925_mac_select_links(mdev, vif); + u16 sel_links = mt76_select_links(vif, 2); u8 secondary_link_id = __ffs(~BIT(mvif->deflink_id) & sel_links); if (!ieee80211_vif_is_mld(vif) || hweight16(sel_links) < 2) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index a1fce8349dd4..21ac618d1c83 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -79,6 +79,14 @@ static const struct wiphy_iftype_ext_capab iftypes_ext_capa[] = { .mld_capa_and_ops = FIELD_PREP_CONST(IEEE80211_MLD_CAP_OP_MAX_SIMUL_LINKS, MT7996_MAX_RADIOS - 1), + }, { + .iftype = NL80211_IFTYPE_STATION, + .extended_capabilities = if_types_ext_capa_ap, + .extended_capabilities_mask = if_types_ext_capa_ap, + .extended_capabilities_len = sizeof(if_types_ext_capa_ap), + .mld_capa_and_ops = + FIELD_PREP_CONST(IEEE80211_MLD_CAP_OP_MAX_SIMUL_LINKS, + MT7996_MAX_RADIOS - 1), }, }; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index c0144c7af94f..0d5866ac951f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1220,6 +1220,24 @@ mt7996_mac_sta_remove(struct mt7996_dev *dev, struct ieee80211_vif *vif, mutex_unlock(&dev->mt76.mutex); } +static void +mt7996_set_active_links(struct ieee80211_vif *vif) +{ + u16 active_links; + + if (vif->type != NL80211_IFTYPE_STATION) + return; + + if (!ieee80211_vif_is_mld(vif)) + return; + + active_links = mt76_select_links(vif, MT7996_MAX_RADIOS); + if (hweight16(active_links) < 2) + return; + + ieee80211_set_active_links_async(vif, active_links); +} + static int mt7996_sta_state(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta, enum ieee80211_sta_state old_state, @@ -1237,16 +1255,18 @@ mt7996_sta_state(struct ieee80211_hw *hw, struct ieee80211_vif *vif, mt7996_mac_sta_remove(dev, vif, sta); if (old_state == IEEE80211_STA_AUTH && - new_state == IEEE80211_STA_ASSOC) + new_state == IEEE80211_STA_ASSOC) { + mt7996_set_active_links(vif); ev = MT76_STA_EVENT_ASSOC; - else if (old_state == IEEE80211_STA_ASSOC && - new_state == IEEE80211_STA_AUTHORIZED) + } else if (old_state == IEEE80211_STA_ASSOC && + new_state == IEEE80211_STA_AUTHORIZED) { ev = MT76_STA_EVENT_AUTHORIZE; - else if (old_state == IEEE80211_STA_ASSOC && - new_state == IEEE80211_STA_AUTH) + } else if (old_state == IEEE80211_STA_ASSOC && + new_state == IEEE80211_STA_AUTH) { ev = MT76_STA_EVENT_DISASSOC; - else + } else { return 0; + } return mt7996_mac_sta_event(dev, vif, sta, ev); } From f6b29367b04219c6d7053f160f3096c9d48b0254 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 9 Sep 2025 11:45:09 +0200 Subject: [PATCH 061/167] wifi: mt76: Add reset_idx to reset_q mt76_queue_ops signature. Remove __mt76_dma_queue_reset routine and use mt76_dma_queue_reset directly instead in mt76_queue_ops struct. This is a preliminary patch to enable WED support for MT7992 Kite chipset supported by MT7996 driver. Co-developed-by: Rex Lu Signed-off-by: Rex Lu Co-developed-by: Sujuan Chen Signed-off-by: Sujuan Chen Co-developed-by: Benjamin Lin Signed-off-by: Benjamin Lin Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250909-mt7996-rro-rework-v5-1-7d66f6eb7795@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.c | 11 +++-------- drivers/net/wireless/mediatek/mt76/dma.h | 7 +++---- drivers/net/wireless/mediatek/mt76/mt76.h | 3 ++- drivers/net/wireless/mediatek/mt76/mt7915/dma.c | 4 ++-- drivers/net/wireless/mediatek/mt76/mt792x_dma.c | 6 +++--- drivers/net/wireless/mediatek/mt76/mt7996/dma.c | 4 ++-- drivers/net/wireless/mediatek/mt76/wed.c | 8 ++++---- 7 files changed, 19 insertions(+), 24 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index 87f531297f85..25c26ff8c8e2 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -197,8 +197,8 @@ mt76_dma_sync_idx(struct mt76_dev *dev, struct mt76_queue *q) q->tail = q->head; } -void __mt76_dma_queue_reset(struct mt76_dev *dev, struct mt76_queue *q, - bool reset_idx) +void mt76_dma_queue_reset(struct mt76_dev *dev, struct mt76_queue *q, + bool reset_idx) { if (!q || !q->ndesc) return; @@ -218,11 +218,6 @@ void __mt76_dma_queue_reset(struct mt76_dev *dev, struct mt76_queue *q, mt76_dma_sync_idx(dev, q); } -void mt76_dma_queue_reset(struct mt76_dev *dev, struct mt76_queue *q) -{ - __mt76_dma_queue_reset(dev, q, true); -} - static int mt76_dma_add_rx_buf(struct mt76_dev *dev, struct mt76_queue *q, struct mt76_queue_buf *buf, void *data) @@ -740,7 +735,7 @@ mt76_dma_alloc_queue(struct mt76_dev *dev, struct mt76_queue *q, return 0; } - mt76_dma_queue_reset(dev, q); + mt76_dma_queue_reset(dev, q, true); return 0; } diff --git a/drivers/net/wireless/mediatek/mt76/dma.h b/drivers/net/wireless/mediatek/mt76/dma.h index e3ddc7a83757..320d2cbbbd45 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.h +++ b/drivers/net/wireless/mediatek/mt76/dma.h @@ -81,14 +81,13 @@ void mt76_dma_attach(struct mt76_dev *dev); void mt76_dma_cleanup(struct mt76_dev *dev); int mt76_dma_rx_fill(struct mt76_dev *dev, struct mt76_queue *q, bool allow_direct); -void __mt76_dma_queue_reset(struct mt76_dev *dev, struct mt76_queue *q, - bool reset_idx); -void mt76_dma_queue_reset(struct mt76_dev *dev, struct mt76_queue *q); +void mt76_dma_queue_reset(struct mt76_dev *dev, struct mt76_queue *q, + bool reset_idx); static inline void mt76_dma_reset_tx_queue(struct mt76_dev *dev, struct mt76_queue *q) { - dev->queue_ops->reset_q(dev, q); + dev->queue_ops->reset_q(dev, q, true); if (mtk_wed_device_active(&dev->mmio.wed)) mt76_wed_dma_setup(dev, q, true); } diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 5310b2a34edb..c2fe9a9315e2 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -290,7 +290,8 @@ struct mt76_queue_ops { void (*kick)(struct mt76_dev *dev, struct mt76_queue *q); - void (*reset_q)(struct mt76_dev *dev, struct mt76_queue *q); + void (*reset_q)(struct mt76_dev *dev, struct mt76_queue *q, + bool reset_idx); }; enum mt76_phy_type { diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/dma.c b/drivers/net/wireless/mediatek/mt76/mt7915/dma.c index 0c62272fe7d0..009ef713f437 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/dma.c @@ -624,13 +624,13 @@ int mt7915_dma_reset(struct mt7915_dev *dev, bool force) } for (i = 0; i < __MT_MCUQ_MAX; i++) - mt76_queue_reset(dev, dev->mt76.q_mcu[i]); + mt76_queue_reset(dev, dev->mt76.q_mcu[i], true); mt76_for_each_q_rx(&dev->mt76, i) { if (mt76_queue_is_wed_tx_free(&dev->mt76.q_rx[i])) continue; - mt76_queue_reset(dev, &dev->mt76.q_rx[i]); + mt76_queue_reset(dev, &dev->mt76.q_rx[i], true); } mt76_tx_status_check(&dev->mt76, true); diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c index 6f9db782338e..69217ce91130 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c @@ -181,13 +181,13 @@ mt792x_dma_reset(struct mt792x_dev *dev, bool force) /* reset hw queues */ for (i = 0; i < __MT_TXQ_MAX; i++) - mt76_queue_reset(dev, dev->mphy.q_tx[i]); + mt76_queue_reset(dev, dev->mphy.q_tx[i], true); for (i = 0; i < __MT_MCUQ_MAX; i++) - mt76_queue_reset(dev, dev->mt76.q_mcu[i]); + mt76_queue_reset(dev, dev->mt76.q_mcu[i], true); mt76_for_each_q_rx(&dev->mt76, i) - mt76_queue_reset(dev, &dev->mt76.q_rx[i]); + mt76_queue_reset(dev, &dev->mt76.q_rx[i], true); mt76_tx_status_check(&dev->mt76, true); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c index c8bef0b2a144..c77e619070d3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c @@ -775,7 +775,7 @@ void mt7996_dma_reset(struct mt7996_dev *dev, bool force) } for (i = 0; i < __MT_MCUQ_MAX; i++) - mt76_queue_reset(dev, dev->mt76.q_mcu[i]); + mt76_queue_reset(dev, dev->mt76.q_mcu[i], true); mt76_for_each_q_rx(&dev->mt76, i) { if (mtk_wed_device_active(&dev->mt76.mmio.wed)) @@ -783,7 +783,7 @@ void mt7996_dma_reset(struct mt7996_dev *dev, bool force) mt76_queue_is_wed_tx_free(&dev->mt76.q_rx[i])) continue; - mt76_queue_reset(dev, &dev->mt76.q_rx[i]); + mt76_queue_reset(dev, &dev->mt76.q_rx[i], true); } mt76_tx_status_check(&dev->mt76, true); diff --git a/drivers/net/wireless/mediatek/mt76/wed.c b/drivers/net/wireless/mediatek/mt76/wed.c index 63f69e152b1c..907a8e43e72a 100644 --- a/drivers/net/wireless/mediatek/mt76/wed.c +++ b/drivers/net/wireless/mediatek/mt76/wed.c @@ -118,7 +118,7 @@ int mt76_wed_dma_setup(struct mt76_dev *dev, struct mt76_queue *q, bool reset) case MT76_WED_Q_TXFREE: /* WED txfree queue needs ring to be initialized before setup */ q->flags = 0; - mt76_dma_queue_reset(dev, q); + mt76_dma_queue_reset(dev, q, true); mt76_dma_rx_fill(dev, q, false); ret = mtk_wed_device_txfree_ring_setup(q->wed, q->regs); @@ -133,21 +133,21 @@ int mt76_wed_dma_setup(struct mt76_dev *dev, struct mt76_queue *q, bool reset) break; case MT76_WED_RRO_Q_DATA: q->flags &= ~MT_QFLAG_WED; - __mt76_dma_queue_reset(dev, q, false); + mt76_dma_queue_reset(dev, q, false); mtk_wed_device_rro_rx_ring_setup(q->wed, ring, q->regs); q->head = q->ndesc - 1; q->queued = q->head; break; case MT76_WED_RRO_Q_MSDU_PG: q->flags &= ~MT_QFLAG_WED; - __mt76_dma_queue_reset(dev, q, false); + mt76_dma_queue_reset(dev, q, false); mtk_wed_device_msdu_pg_rx_ring_setup(q->wed, ring, q->regs); q->head = q->ndesc - 1; q->queued = q->head; break; case MT76_WED_RRO_Q_IND: q->flags &= ~MT_QFLAG_WED; - mt76_dma_queue_reset(dev, q); + mt76_dma_queue_reset(dev, q, true); mt76_dma_rx_fill(dev, q, false); mtk_wed_device_ind_rx_ring_setup(q->wed, q->regs); break; From 2182974e9fd06a2cf78f60ee0ae38a22db4d2126 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 9 Sep 2025 11:45:10 +0200 Subject: [PATCH 062/167] wifi: mt76: Remove q->ndesc check in mt76_dma_rx_fill() Remove q->ndesc check in mt76_dma_rx_fill routine since this is already done in mt76_dma_rx_fill_buf(). Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250909-mt7996-rro-rework-v5-2-7d66f6eb7795@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index 25c26ff8c8e2..18c2a2de5989 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -671,9 +671,6 @@ int mt76_dma_rx_fill(struct mt76_dev *dev, struct mt76_queue *q, { int frames; - if (!q->ndesc) - return 0; - spin_lock_bh(&q->lock); frames = mt76_dma_rx_fill_buf(dev, q, allow_direct); spin_unlock_bh(&q->lock); From ba9f68bb77f86b48f6a7dd1190c4cc8d6156fbcb Mon Sep 17 00:00:00 2001 From: Rex Lu Date: Tue, 9 Sep 2025 11:45:11 +0200 Subject: [PATCH 063/167] wifi: mt76: Differentiate between RRO data and RRO MSDU queues This is a preliminary patch to enable WED support for MT7992 Kite chipset supported by MT7996 driver. Co-developed-by: Sujuan Chen Signed-off-by: Sujuan Chen Co-developed-by: Benjamin Lin Signed-off-by: Benjamin Lin Signed-off-by: Rex Lu Co-developed-by: Lorenzo Bianconi Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250909-mt7996-rro-rework-v5-3-7d66f6eb7795@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.c | 2 +- drivers/net/wireless/mediatek/mt76/mt76.h | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index 18c2a2de5989..68b1dd1dbbe0 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -477,7 +477,7 @@ mt76_dma_dequeue(struct mt76_dev *dev, struct mt76_queue *q, bool flush, if (!q->queued) return NULL; - if (mt76_queue_is_wed_rro_data(q)) + if (mt76_queue_is_wed_rro_data(q) || mt76_queue_is_wed_rro_msdu_pg(q)) return NULL; if (!mt76_queue_is_wed_rro_ind(q)) { diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index c2fe9a9315e2..0c54ae47923f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -1786,8 +1786,14 @@ static inline bool mt76_queue_is_wed_rro_ind(struct mt76_queue *q) static inline bool mt76_queue_is_wed_rro_data(struct mt76_queue *q) { return mt76_queue_is_wed_rro(q) && - (FIELD_GET(MT_QFLAG_WED_TYPE, q->flags) == MT76_WED_RRO_Q_DATA || - FIELD_GET(MT_QFLAG_WED_TYPE, q->flags) == MT76_WED_RRO_Q_MSDU_PG); + FIELD_GET(MT_QFLAG_WED_TYPE, q->flags) == MT76_WED_RRO_Q_DATA; +} + +static inline bool mt76_queue_is_wed_rro_msdu_pg(struct mt76_queue *q) +{ + return mt76_queue_is_wed_rro(q) && + FIELD_GET(MT_QFLAG_WED_TYPE, q->flags) == + MT76_WED_RRO_Q_MSDU_PG; } static inline bool mt76_queue_is_wed_rx(struct mt76_queue *q) @@ -1796,7 +1802,8 @@ static inline bool mt76_queue_is_wed_rx(struct mt76_queue *q) return false; return FIELD_GET(MT_QFLAG_WED_TYPE, q->flags) == MT76_WED_Q_RX || - mt76_queue_is_wed_rro_ind(q) || mt76_queue_is_wed_rro_data(q); + mt76_queue_is_wed_rro_ind(q) || mt76_queue_is_wed_rro_data(q) || + mt76_queue_is_wed_rro_msdu_pg(q); } From d77f77ff45445062411fa2770c804c9c6e8c2c95 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 9 Sep 2025 11:45:12 +0200 Subject: [PATCH 064/167] wifi: mt76: Do not always enable NAPIs for WED RRO queues Do not initialize NAPIs for WED RRO queues if WED is active. Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250909-mt7996-rro-rework-v5-4-7d66f6eb7795@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index 68b1dd1dbbe0..f882b4e10858 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -967,6 +967,10 @@ mt76_dma_init(struct mt76_dev *dev, init_completion(&dev->mmio.wed_reset_complete); mt76_for_each_q_rx(dev, i) { + if (mtk_wed_device_active(&dev->mmio.wed) && + mt76_queue_is_wed_rro(&dev->q_rx[i])) + continue; + netif_napi_add(dev->napi_dev, &dev->napi[i], poll); mt76_dma_rx_fill_buf(dev, &dev->q_rx[i], false); napi_enable(&dev->napi[i]); From 3bc2f02f5cd49c6e17d67974f561611608548652 Mon Sep 17 00:00:00 2001 From: Rex Lu Date: Tue, 9 Sep 2025 11:45:13 +0200 Subject: [PATCH 065/167] wifi: mt76: mt7996: Initial DMA configuration for MT7992 WED support Manage differences in DMA and RX queues configuration for MT7992 and MT7996. This is a preliminary patch to enable WED support for MT7992 Kite chipset supported by MT7996 driver. Co-developed-by: Sujuan Chen Signed-off-by: Sujuan Chen Co-developed-by: Benjamin Lin Signed-off-by: Benjamin Lin Signed-off-by: Rex Lu Co-developed-by: Lorenzo Bianconi Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250909-mt7996-rro-rework-v5-5-7d66f6eb7795@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/dma.c | 169 +++++++++++++----- .../net/wireless/mediatek/mt76/mt7996/mmio.c | 8 +- .../wireless/mediatek/mt76/mt7996/mt7996.h | 2 +- .../net/wireless/mediatek/mt76/mt7996/regs.h | 8 +- 4 files changed, 144 insertions(+), 43 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c index c77e619070d3..b3665bb0a433 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c @@ -89,30 +89,60 @@ static void mt7996_dma_config(struct mt7996_dev *dev) MT7996_RXQ_RRO_BAND0); RXQ_CONFIG(MT_RXQ_MSDU_PAGE_BAND0, WFDMA0, MT_INT_RX_DONE_MSDU_PG_BAND0, MT7996_RXQ_MSDU_PG_BAND0); - RXQ_CONFIG(MT_RXQ_TXFREE_BAND0, WFDMA0, MT_INT_RX_TXFREE_MAIN, - MT7996_RXQ_TXFREE0); - /* band1 */ - RXQ_CONFIG(MT_RXQ_MSDU_PAGE_BAND1, WFDMA0, MT_INT_RX_DONE_MSDU_PG_BAND1, - MT7996_RXQ_MSDU_PG_BAND1); - /* band2 */ - RXQ_CONFIG(MT_RXQ_RRO_BAND2, WFDMA0, MT_INT_RX_DONE_RRO_BAND2, - MT7996_RXQ_RRO_BAND2); - RXQ_CONFIG(MT_RXQ_MSDU_PAGE_BAND2, WFDMA0, MT_INT_RX_DONE_MSDU_PG_BAND2, - MT7996_RXQ_MSDU_PG_BAND2); - RXQ_CONFIG(MT_RXQ_TXFREE_BAND2, WFDMA0, MT_INT_RX_TXFREE_TRI, - MT7996_RXQ_TXFREE2); + if (is_mt7996(&dev->mt76)) { + RXQ_CONFIG(MT_RXQ_TXFREE_BAND0, WFDMA0, + MT_INT_RX_TXFREE_MAIN, MT7996_RXQ_TXFREE0); + /* band1 */ + RXQ_CONFIG(MT_RXQ_MSDU_PAGE_BAND1, WFDMA0, + MT_INT_RX_DONE_MSDU_PG_BAND1, + MT7996_RXQ_MSDU_PG_BAND1); + /* band2 */ + RXQ_CONFIG(MT_RXQ_RRO_BAND2, WFDMA0, + MT_INT_RX_DONE_RRO_BAND2, + MT7996_RXQ_RRO_BAND2); + RXQ_CONFIG(MT_RXQ_MSDU_PAGE_BAND2, WFDMA0, + MT_INT_RX_DONE_MSDU_PG_BAND2, + MT7996_RXQ_MSDU_PG_BAND2); + RXQ_CONFIG(MT_RXQ_TXFREE_BAND2, WFDMA0, + MT_INT_RX_TXFREE_TRI, MT7996_RXQ_TXFREE2); + } else { + RXQ_CONFIG(MT_RXQ_RRO_BAND1, WFDMA0, + MT_INT_RX_DONE_RRO_BAND1, + MT7996_RXQ_RRO_BAND1); + } RXQ_CONFIG(MT_RXQ_RRO_IND, WFDMA0, MT_INT_RX_DONE_RRO_IND, MT7996_RXQ_RRO_IND); } /* data tx queue */ - TXQ_CONFIG(0, WFDMA0, MT_INT_TX_DONE_BAND0, MT7996_TXQ_BAND0); if (is_mt7996(&dev->mt76)) { - TXQ_CONFIG(1, WFDMA0, MT_INT_TX_DONE_BAND1, MT7996_TXQ_BAND1); - TXQ_CONFIG(2, WFDMA0, MT_INT_TX_DONE_BAND2, MT7996_TXQ_BAND2); + TXQ_CONFIG(0, WFDMA0, MT_INT_TX_DONE_BAND0, MT7996_TXQ_BAND0); + if (dev->hif2) { + /* default bn1:ring19 bn2:ring21 */ + TXQ_CONFIG(1, WFDMA0, MT_INT_TX_DONE_BAND1, + MT7996_TXQ_BAND1); + TXQ_CONFIG(2, WFDMA0, MT_INT_TX_DONE_BAND2, + MT7996_TXQ_BAND2); + } else { + /* single pcie bn0/1:ring18 bn2:ring19 */ + TXQ_CONFIG(2, WFDMA0, MT_INT_TX_DONE_BAND1, + MT7996_TXQ_BAND1); + } } else { - TXQ_CONFIG(1, WFDMA0, MT_INT_TX_DONE_BAND1, MT7996_TXQ_BAND1); + if (dev->hif2) { + /* bn0:ring18 bn1:ring21 */ + TXQ_CONFIG(0, WFDMA0, MT_INT_TX_DONE_BAND0, + MT7996_TXQ_BAND0); + TXQ_CONFIG(1, WFDMA0, MT_INT_TX_DONE_BAND2, + MT7996_TXQ_BAND2); + } else { + /* single pcie bn0:ring18 bn1:ring19 */ + TXQ_CONFIG(0, WFDMA0, MT_INT_TX_DONE_BAND0, + MT7996_TXQ_BAND0); + TXQ_CONFIG(1, WFDMA0, MT_INT_TX_DONE_BAND1, + MT7996_TXQ_BAND1); + } } /* mcu tx queue */ @@ -288,8 +318,11 @@ void mt7996_dma_start(struct mt7996_dev *dev, bool reset, bool wed_reset) if (mt7996_band_valid(dev, MT_BAND0)) irq_mask |= MT_INT_BAND0_RX_DONE; - if (mt7996_band_valid(dev, MT_BAND1)) + if (mt7996_band_valid(dev, MT_BAND1)) { irq_mask |= MT_INT_BAND1_RX_DONE; + if (is_mt7992(&dev->mt76) && dev->hif2) + irq_mask |= MT_INT_RX_TXFREE_BAND1_EXT; + } if (mt7996_band_valid(dev, MT_BAND2)) irq_mask |= MT_INT_BAND2_RX_DONE; @@ -378,8 +411,19 @@ static void mt7996_dma_enable(struct mt7996_dev *dev, bool reset) WF_WFDMA0_GLO_CFG_EXT1_TX_FCTRL_MODE); mt76_set(dev, MT_WFDMA_HOST_CONFIG, - MT_WFDMA_HOST_CONFIG_PDMA_BAND | - MT_WFDMA_HOST_CONFIG_BAND2_PCIE1); + MT_WFDMA_HOST_CONFIG_PDMA_BAND); + + mt76_clear(dev, MT_WFDMA_HOST_CONFIG, + MT_WFDMA_HOST_CONFIG_BAND0_PCIE1 | + MT_WFDMA_HOST_CONFIG_BAND1_PCIE1 | + MT_WFDMA_HOST_CONFIG_BAND2_PCIE1); + + if (is_mt7996(&dev->mt76)) + mt76_set(dev, MT_WFDMA_HOST_CONFIG, + MT_WFDMA_HOST_CONFIG_BAND2_PCIE1); + else + mt76_set(dev, MT_WFDMA_HOST_CONFIG, + MT_WFDMA_HOST_CONFIG_BAND1_PCIE1); /* AXI read outstanding number */ mt76_rmw(dev, MT_WFDMA_AXI_R2A_CTRL, @@ -397,12 +441,18 @@ static void mt7996_dma_enable(struct mt7996_dev *dev, bool reset) * so, redirect pcie0 rx ring3 interrupt to pcie1 */ if (mtk_wed_device_active(&dev->mt76.mmio.wed) && - dev->has_rro) + dev->has_rro) { + u32 intr = is_mt7996(&dev->mt76) ? + MT_WFDMA0_RX_INT_SEL_RING6 : + MT_WFDMA0_RX_INT_SEL_RING9 | + MT_WFDMA0_RX_INT_SEL_RING5; + mt76_set(dev, MT_WFDMA0_RX_INT_PCIE_SEL + hif1_ofs, - MT_WFDMA0_RX_INT_SEL_RING6); - else + intr); + } else { mt76_set(dev, MT_WFDMA0_RX_INT_PCIE_SEL, MT_WFDMA0_RX_INT_SEL_RING3); + } } mt7996_dma_start(dev, reset, true); @@ -437,7 +487,7 @@ int mt7996_dma_rro_init(struct mt7996_dev *dev) if (ret) return ret; - if (mt7996_band_valid(dev, MT_BAND1)) { + if (mt7996_band_valid(dev, MT_BAND1) && is_mt7996(&dev->mt76)) { /* rx msdu page queue for band1 */ mdev->q_rx[MT_RXQ_MSDU_PAGE_BAND1].flags = MT_WED_RRO_Q_MSDU_PG(1) | MT_QFLAG_WED_RRO_EN; @@ -560,7 +610,9 @@ int mt7996_dma_init(struct mt7996_dev *dev) return ret; /* tx free notify event from WA for band0 */ - if (mtk_wed_device_active(wed) && !dev->has_rro) { + if (mtk_wed_device_active(wed) && + ((is_mt7996(&dev->mt76) && !dev->has_rro) || + (is_mt7992(&dev->mt76)))) { dev->mt76.q_rx[MT_RXQ_MAIN_WA].flags = MT_WED_Q_TXFREE; dev->mt76.q_rx[MT_RXQ_MAIN_WA].wed = wed; } @@ -630,6 +682,11 @@ int mt7996_dma_init(struct mt7996_dev *dev) } else if (mt7996_band_valid(dev, MT_BAND1)) { /* rx data queue for mt7992 band1 */ rx_base = MT_RXQ_RING_BASE(MT_RXQ_BAND1) + hif1_ofs; + if (mtk_wed_device_active(wed) && mtk_wed_get_rx_capa(wed)) { + dev->mt76.q_rx[MT_RXQ_BAND1].flags = MT_WED_Q_RX(1); + dev->mt76.q_rx[MT_RXQ_BAND1].wed = wed; + } + ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_BAND1], MT_RXQ_ID(MT_RXQ_BAND1), MT7996_RX_RING_SIZE, @@ -641,6 +698,12 @@ int mt7996_dma_init(struct mt7996_dev *dev) /* tx free notify event from WA for mt7992 band1 */ if (mt7996_has_wa(dev)) { rx_base = MT_RXQ_RING_BASE(MT_RXQ_BAND1_WA) + hif1_ofs; + if (mtk_wed_device_active(wed_hif2)) { + dev->mt76.q_rx[MT_RXQ_BAND1_WA].flags = + MT_WED_Q_TXFREE; + dev->mt76.q_rx[MT_RXQ_BAND1_WA].wed = wed_hif2; + } + ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_BAND1_WA], MT_RXQ_ID(MT_RXQ_BAND1_WA), MT7996_RX_MCU_RING_SIZE, @@ -665,17 +728,32 @@ int mt7996_dma_init(struct mt7996_dev *dev) if (ret) return ret; - /* tx free notify event from WA for band0 */ - dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0].flags = MT_WED_Q_TXFREE; - dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0].wed = wed; + if (is_mt7992(&dev->mt76)) { + dev->mt76.q_rx[MT_RXQ_RRO_BAND1].flags = + MT_WED_RRO_Q_DATA(1) | MT_QFLAG_WED_RRO_EN; + dev->mt76.q_rx[MT_RXQ_RRO_BAND1].wed = wed; + ret = mt76_queue_alloc(dev, + &dev->mt76.q_rx[MT_RXQ_RRO_BAND1], + MT_RXQ_ID(MT_RXQ_RRO_BAND1), + MT7996_RX_RING_SIZE, + MT7996_RX_BUF_SIZE, + MT_RXQ_RING_BASE(MT_RXQ_RRO_BAND1) + hif1_ofs); + if (ret) + return ret; + } else { + /* tx free notify event from WA for band0 */ + dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0].flags = MT_WED_Q_TXFREE; + dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0].wed = wed; - ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0], - MT_RXQ_ID(MT_RXQ_TXFREE_BAND0), - MT7996_RX_MCU_RING_SIZE, - MT7996_RX_BUF_SIZE, - MT_RXQ_RING_BASE(MT_RXQ_TXFREE_BAND0)); - if (ret) - return ret; + ret = mt76_queue_alloc(dev, + &dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0], + MT_RXQ_ID(MT_RXQ_TXFREE_BAND0), + MT7996_RX_MCU_RING_SIZE, + MT7996_RX_BUF_SIZE, + MT_RXQ_RING_BASE(MT_RXQ_TXFREE_BAND0)); + if (ret) + return ret; + } if (mt7996_band_valid(dev, MT_BAND2)) { /* rx rro data queue for band2 */ @@ -778,18 +856,29 @@ void mt7996_dma_reset(struct mt7996_dev *dev, bool force) mt76_queue_reset(dev, dev->mt76.q_mcu[i], true); mt76_for_each_q_rx(&dev->mt76, i) { - if (mtk_wed_device_active(&dev->mt76.mmio.wed)) - if (mt76_queue_is_wed_rro(&dev->mt76.q_rx[i]) || - mt76_queue_is_wed_tx_free(&dev->mt76.q_rx[i])) - continue; + struct mt76_queue *q = &dev->mt76.q_rx[i]; - mt76_queue_reset(dev, &dev->mt76.q_rx[i], true); + if (mtk_wed_device_active(&dev->mt76.mmio.wed)) { + if (mt76_queue_is_wed_rro(q) || + mt76_queue_is_wed_tx_free(q)) { + if (force && mt76_queue_is_wed_rro_data(q)) + mt76_queue_reset(dev, q, false); + continue; + } + } + mt76_queue_reset(dev, q, true); } mt76_tx_status_check(&dev->mt76, true); - mt76_for_each_q_rx(&dev->mt76, i) + mt76_for_each_q_rx(&dev->mt76, i) { + if (mtk_wed_device_active(&dev->mt76.mmio.wed) && force && + (mt76_queue_is_wed_rro_ind(&dev->mt76.q_rx[i]) || + mt76_queue_is_wed_rro_msdu_pg(&dev->mt76.q_rx[i]))) + continue; + mt76_queue_rx_reset(dev, i); + } mt7996_dma_enable(dev, !force); } diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c index fb2428a9b877..a8b4ef433c2b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c @@ -690,12 +690,18 @@ static void mt7996_irq_tasklet(struct tasklet_struct *t) dev->mt76.mmio.irqmask); if (intr1 & MT_INT_RX_TXFREE_EXT) napi_schedule(&dev->mt76.napi[MT_RXQ_TXFREE_BAND2]); + + if (intr1 & MT_INT_RX_DONE_BAND2_EXT) + napi_schedule(&dev->mt76.napi[MT_RXQ_BAND2]); + + if (intr1 & MT_INT_RX_TXFREE_BAND1_EXT) + napi_schedule(&dev->mt76.napi[MT_RXQ_BAND1_WA]); } if (mtk_wed_device_active(wed)) { mtk_wed_device_irq_set_mask(wed, 0); intr = mtk_wed_device_irq_get(wed, dev->mt76.mmio.irqmask); - intr |= (intr1 & ~MT_INT_RX_TXFREE_EXT); + intr |= (intr1 & ~MT_INT_TX_RX_DONE_EXT); } else { mt76_wr(dev, MT_INT_MASK_CSR, 0); if (dev->hif2) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 97b84710667f..c210dfd5e37a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -178,7 +178,7 @@ enum mt7996_rxq_id { MT7996_RXQ_BAND1 = 5, /* for mt7992 */ MT7996_RXQ_BAND2 = 5, MT7996_RXQ_RRO_BAND0 = 8, - MT7996_RXQ_RRO_BAND1 = 8,/* unused */ + MT7996_RXQ_RRO_BAND1 = 9, MT7996_RXQ_RRO_BAND2 = 6, MT7996_RXQ_MSDU_PG_BAND0 = 10, MT7996_RXQ_MSDU_PG_BAND1 = 11, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h index e942c0058731..4b8bc008ab31 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h @@ -412,7 +412,9 @@ enum offs_rev { #define MT_WFDMA0_RX_INT_PCIE_SEL MT_WFDMA0(0x154) #define MT_WFDMA0_RX_INT_SEL_RING3 BIT(3) +#define MT_WFDMA0_RX_INT_SEL_RING5 BIT(5) #define MT_WFDMA0_RX_INT_SEL_RING6 BIT(6) +#define MT_WFDMA0_RX_INT_SEL_RING9 BIT(9) #define MT_WFDMA0_MCU_HOST_INT_ENA MT_WFDMA0(0x1f4) @@ -451,6 +453,8 @@ enum offs_rev { #define MT_WFDMA_HOST_CONFIG MT_WFDMA_EXT_CSR(0x30) #define MT_WFDMA_HOST_CONFIG_PDMA_BAND BIT(0) +#define MT_WFDMA_HOST_CONFIG_BAND0_PCIE1 BIT(20) +#define MT_WFDMA_HOST_CONFIG_BAND1_PCIE1 BIT(21) #define MT_WFDMA_HOST_CONFIG_BAND2_PCIE1 BIT(22) #define MT_WFDMA_EXT_CSR_HIF_MISC MT_WFDMA_EXT_CSR(0x44) @@ -514,7 +518,9 @@ enum offs_rev { #define MT_INT_RX_DONE_WA_EXT BIT(3) /* for mt7992 */ #define MT_INT_RX_DONE_WA_TRI BIT(3) #define MT_INT_RX_TXFREE_MAIN BIT(17) +#define MT_INT_RX_TXFREE_BAND1 BIT(15) #define MT_INT_RX_TXFREE_TRI BIT(15) +#define MT_INT_RX_TXFREE_BAND1_EXT BIT(19) /* for mt7992 two PCIE*/ #define MT_INT_RX_TXFREE_BAND0_MT7990 BIT(14) #define MT_INT_RX_TXFREE_BAND1_MT7990 BIT(15) #define MT_INT_RX_DONE_BAND2_EXT BIT(23) @@ -522,7 +528,7 @@ enum offs_rev { #define MT_INT_MCU_CMD BIT(29) #define MT_INT_RX_DONE_RRO_BAND0 BIT(16) -#define MT_INT_RX_DONE_RRO_BAND1 BIT(16) +#define MT_INT_RX_DONE_RRO_BAND1 BIT(17) #define MT_INT_RX_DONE_RRO_BAND2 BIT(14) #define MT_INT_RX_DONE_RRO_IND BIT(11) #define MT_INT_RX_DONE_MSDU_PG_BAND0 BIT(18) From eedb427eb26061ee1bfed3b19da6ffbc832f54f4 Mon Sep 17 00:00:00 2001 From: Rex Lu Date: Tue, 9 Sep 2025 11:45:14 +0200 Subject: [PATCH 066/167] wifi: mt76: mt7996: Enable HW RRO for MT7992 chipset This is a preliminary patch to enable WED support for MT7992 Kite chipset supported by MT7996 driver. Co-developed-by: Sujuan Chen Signed-off-by: Sujuan Chen Co-developed-by: Benjamin Lin Signed-off-by: Benjamin Lin Signed-off-by: Rex Lu Co-developed-by: Lorenzo Bianconi Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250909-mt7996-rro-rework-v5-6-7d66f6eb7795@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/init.c | 168 ++++++++++++------ .../wireless/mediatek/mt76/mt7996/mt7996.h | 9 + .../net/wireless/mediatek/mt76/mt7996/regs.h | 10 ++ 3 files changed, 137 insertions(+), 50 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 21ac618d1c83..93144b5e9564 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -605,19 +605,21 @@ void mt7996_mac_init(struct mt7996_dev *dev) } /* rro module init */ - if (is_mt7996(&dev->mt76)) - mt7996_mcu_set_rro(dev, UNI_RRO_SET_PLATFORM_TYPE, 2); - else + if (dev->hif2) mt7996_mcu_set_rro(dev, UNI_RRO_SET_PLATFORM_TYPE, - dev->hif2 ? 7 : 0); + is_mt7996(&dev->mt76) ? 2 : 7); + else + mt7996_mcu_set_rro(dev, UNI_RRO_SET_PLATFORM_TYPE, 0); if (dev->has_rro) { u16 timeout; timeout = mt76_rr(dev, MT_HW_REV) == MT_HW_REV1 ? 512 : 128; mt7996_mcu_set_rro(dev, UNI_RRO_SET_FLUSH_TIMEOUT, timeout); - mt7996_mcu_set_rro(dev, UNI_RRO_SET_BYPASS_MODE, 1); - mt7996_mcu_set_rro(dev, UNI_RRO_SET_TXFREE_PATH, 0); + mt7996_mcu_set_rro(dev, UNI_RRO_SET_BYPASS_MODE, + is_mt7996(&dev->mt76) ? 1 : 2); + mt7996_mcu_set_rro(dev, UNI_RRO_SET_TXFREE_PATH, + !is_mt7996(&dev->mt76)); } else { mt7996_mcu_set_rro(dev, UNI_RRO_SET_BYPASS_MODE, 3); mt7996_mcu_set_rro(dev, UNI_RRO_SET_TXFREE_PATH, 1); @@ -754,11 +756,95 @@ void mt7996_wfsys_reset(struct mt7996_dev *dev) msleep(20); } +#ifdef CONFIG_NET_MEDIATEK_SOC_WED +static void mt7996_rro_hw_init(struct mt7996_dev *dev) +{ + struct mtk_wed_device *wed = &dev->mt76.mmio.wed; + u32 reg = MT_RRO_ADDR_ELEM_SEG_ADDR0; + int i; + + if (!dev->has_rro) + return; + + if (is_mt7992(&dev->mt76)) { + /* Set emul 3.0 function */ + mt76_wr(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); + } else { + /* TODO: remove line after WM has set */ + mt76_clear(dev, WF_RRO_AXI_MST_CFG, + WF_RRO_AXI_MST_CFG_DIDX_OK); + /* setup BA bitmap cache address */ + mt76_wr(dev, MT_RRO_BA_BITMAP_BASE0, + dev->wed_rro.ba_bitmap[0].phy_addr); + mt76_wr(dev, MT_RRO_BA_BITMAP_BASE1, 0); + mt76_wr(dev, MT_RRO_BA_BITMAP_BASE_EXT0, + dev->wed_rro.ba_bitmap[1].phy_addr); + mt76_wr(dev, MT_RRO_BA_BITMAP_BASE_EXT1, 0); + + /* Setup Address element address */ + for (i = 0; i < ARRAY_SIZE(dev->wed_rro.addr_elem); i++) { + mt76_wr(dev, reg, + dev->wed_rro.addr_elem[i].phy_addr >> 4); + reg += 4; + } + + /* Setup Address element address - separate address + * segment mode + */ + mt76_wr(dev, MT_RRO_ADDR_ARRAY_BASE1, + MT_RRO_ADDR_ARRAY_ELEM_ADDR_SEG_MODE); + } + wed->wlan.ind_cmd.win_size = ffs(MT7996_RRO_WINDOW_MAX_LEN) - 6; + if (is_mt7996(&dev->mt76)) + wed->wlan.ind_cmd.particular_sid = MT7996_RRO_MAX_SESSION; + else + wed->wlan.ind_cmd.particular_sid = 1; + wed->wlan.ind_cmd.particular_se_phys = dev->wed_rro.session.phy_addr; + wed->wlan.ind_cmd.se_group_nums = MT7996_RRO_ADDR_ELEM_LEN; + wed->wlan.ind_cmd.ack_sn_addr = MT_RRO_ACK_SN_CTRL; + + mt76_wr(dev, MT_RRO_IND_CMD_SIGNATURE_BASE0, 0x15010e00); + mt76_set(dev, MT_RRO_IND_CMD_SIGNATURE_BASE1, + MT_RRO_IND_CMD_SIGNATURE_BASE1_EN); + + /* particular session configure */ + /* use max session idx + 1 as particular session id */ + mt76_wr(dev, MT_RRO_PARTICULAR_CFG0, dev->wed_rro.session.phy_addr); + + if (is_mt7992(&dev->mt76)) { + reg = MT_RRO_MSDU_PG_SEG_ADDR0; + + mt76_set(dev, MT_RRO_3_1_GLOBAL_CONFIG, + MT_RRO_3_1_GLOBAL_CONFIG_INTERLEAVE_EN); + + /* setup Msdu page address */ + for (i = 0; i < ARRAY_SIZE(dev->wed_rro.msdu_pg); i++) { + mt76_wr(dev, reg, + dev->wed_rro.msdu_pg[i].phy_addr >> 4); + reg += 4; + } + mt76_wr(dev, MT_RRO_PARTICULAR_CFG1, + MT_RRO_PARTICULAR_CONFG_EN | + FIELD_PREP(MT_RRO_PARTICULAR_SID, 1)); + } else { + mt76_wr(dev, MT_RRO_PARTICULAR_CFG1, + MT_RRO_PARTICULAR_CONFG_EN | + FIELD_PREP(MT_RRO_PARTICULAR_SID, + MT7996_RRO_MAX_SESSION)); + } + /* interrupt enable */ + mt76_wr(dev, MT_RRO_HOST_INT_ENA, + MT_RRO_HOST_INT_ENA_HOST_RRO_DONE_ENA); +} +#endif + static int mt7996_wed_rro_init(struct mt7996_dev *dev) { #ifdef CONFIG_NET_MEDIATEK_SOC_WED struct mtk_wed_device *wed = &dev->mt76.mmio.wed; - u32 reg = MT_RRO_ADDR_ELEM_SEG_ADDR0; struct mt7996_wed_rro_addr *addr; void *ptr; int i; @@ -804,6 +890,20 @@ static int mt7996_wed_rro_init(struct mt7996_dev *dev) dev->wed_rro.addr_elem[i].phy_addr; } + for (i = 0; i < ARRAY_SIZE(dev->wed_rro.msdu_pg); i++) { + ptr = dmam_alloc_coherent(dev->mt76.dma_dev, + MT7996_RRO_MSDU_PG_SIZE_PER_CR, + &dev->wed_rro.msdu_pg[i].phy_addr, + GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + dev->wed_rro.msdu_pg[i].ptr = ptr; + + memset(dev->wed_rro.msdu_pg[i].ptr, 0, + MT7996_RRO_MSDU_PG_SIZE_PER_CR); + } + ptr = dmam_alloc_coherent(dev->mt76.dma_dev, MT7996_RRO_WINDOW_MAX_LEN * sizeof(*addr), &dev->wed_rro.session.phy_addr, @@ -818,50 +918,8 @@ static int mt7996_wed_rro_init(struct mt7996_dev *dev) addr++; } - /* rro hw init */ - /* TODO: remove line after WM has set */ - mt76_clear(dev, WF_RRO_AXI_MST_CFG, WF_RRO_AXI_MST_CFG_DIDX_OK); + mt7996_rro_hw_init(dev); - /* setup BA bitmap cache address */ - mt76_wr(dev, MT_RRO_BA_BITMAP_BASE0, - dev->wed_rro.ba_bitmap[0].phy_addr); - mt76_wr(dev, MT_RRO_BA_BITMAP_BASE1, 0); - mt76_wr(dev, MT_RRO_BA_BITMAP_BASE_EXT0, - dev->wed_rro.ba_bitmap[1].phy_addr); - mt76_wr(dev, MT_RRO_BA_BITMAP_BASE_EXT1, 0); - - /* setup Address element address */ - for (i = 0; i < ARRAY_SIZE(dev->wed_rro.addr_elem); i++) { - mt76_wr(dev, reg, dev->wed_rro.addr_elem[i].phy_addr >> 4); - reg += 4; - } - - /* setup Address element address - separate address segment mode */ - mt76_wr(dev, MT_RRO_ADDR_ARRAY_BASE1, - MT_RRO_ADDR_ARRAY_ELEM_ADDR_SEG_MODE); - - wed->wlan.ind_cmd.win_size = ffs(MT7996_RRO_WINDOW_MAX_LEN) - 6; - wed->wlan.ind_cmd.particular_sid = MT7996_RRO_MAX_SESSION; - wed->wlan.ind_cmd.particular_se_phys = dev->wed_rro.session.phy_addr; - wed->wlan.ind_cmd.se_group_nums = MT7996_RRO_ADDR_ELEM_LEN; - wed->wlan.ind_cmd.ack_sn_addr = MT_RRO_ACK_SN_CTRL; - - mt76_wr(dev, MT_RRO_IND_CMD_SIGNATURE_BASE0, 0x15010e00); - mt76_set(dev, MT_RRO_IND_CMD_SIGNATURE_BASE1, - MT_RRO_IND_CMD_SIGNATURE_BASE1_EN); - - /* particular session configure */ - /* use max session idx + 1 as particular session id */ - mt76_wr(dev, MT_RRO_PARTICULAR_CFG0, dev->wed_rro.session.phy_addr); - mt76_wr(dev, MT_RRO_PARTICULAR_CFG1, - MT_RRO_PARTICULAR_CONFG_EN | - FIELD_PREP(MT_RRO_PARTICULAR_SID, MT7996_RRO_MAX_SESSION)); - - /* interrupt enable */ - mt76_wr(dev, MT_RRO_HOST_INT_ENA, - MT_RRO_HOST_INT_ENA_HOST_RRO_DONE_ENA); - - /* rro ind cmd queue init */ return mt7996_dma_rro_init(dev); #else return 0; @@ -900,6 +958,16 @@ static void mt7996_wed_rro_free(struct mt7996_dev *dev) dev->wed_rro.addr_elem[i].phy_addr); } + for (i = 0; i < ARRAY_SIZE(dev->wed_rro.msdu_pg); i++) { + if (!dev->wed_rro.msdu_pg[i].ptr) + continue; + + dmam_free_coherent(dev->mt76.dma_dev, + MT7996_RRO_MSDU_PG_SIZE_PER_CR, + dev->wed_rro.msdu_pg[i].ptr, + dev->wed_rro.msdu_pg[i].phy_addr); + } + if (!dev->wed_rro.session.ptr) return; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index c210dfd5e37a..094ea070369a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -112,6 +112,7 @@ #define MT7996_CRIT_TEMP 110 #define MT7996_MAX_TEMP 120 +#define MT7996_RRO_MSDU_PG_HASH_SIZE 127 #define MT7996_RRO_MAX_SESSION 1024 #define MT7996_RRO_WINDOW_MAX_LEN 1024 #define MT7996_RRO_ADDR_ELEM_LEN 128 @@ -128,6 +129,10 @@ #define MT7996_RX_MSDU_PAGE_SIZE (128 + \ SKB_DATA_ALIGN(sizeof(struct skb_shared_info))) +/* RRO 3.1 */ +#define MT7996_RRO_MSDU_PG_CR_CNT 8 +#define MT7996_RRO_MSDU_PG_SIZE_PER_CR 0x10000 + struct mt7996_vif; struct mt7996_sta; struct mt7996_dfs_pulse; @@ -400,6 +405,10 @@ struct mt7996_dev { void *ptr; dma_addr_t phy_addr; } session; + struct { + void *ptr; + dma_addr_t phy_addr; + } msdu_pg[MT7996_RRO_MSDU_PG_CR_CNT]; struct work_struct work; struct list_head poll_list; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h index 4b8bc008ab31..070cdebcd19d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h @@ -88,6 +88,8 @@ enum offs_rev { #define MT_RRO_BA_BITMAP_BASE1 MT_RRO_TOP(0xC) #define WF_RRO_AXI_MST_CFG MT_RRO_TOP(0xB8) #define WF_RRO_AXI_MST_CFG_DIDX_OK BIT(12) + +#define MT_RRO_ADDR_ARRAY_BASE0 MT_RRO_TOP(0x30) #define MT_RRO_ADDR_ARRAY_BASE1 MT_RRO_TOP(0x34) #define MT_RRO_ADDR_ARRAY_ELEM_ADDR_SEG_MODE BIT(31) @@ -108,6 +110,14 @@ enum offs_rev { #define MT_RRO_ADDR_ELEM_SEG_ADDR0 MT_RRO_TOP(0x400) +#define MT_RRO_3_0_EMU_CONF MT_RRO_TOP(0x600) +#define MT_RRO_3_0_EMU_CONF_EN_MASK BIT(11) + +#define MT_RRO_3_1_GLOBAL_CONFIG MT_RRO_TOP(0x604) +#define MT_RRO_3_1_GLOBAL_CONFIG_INTERLEAVE_EN BIT(0) + +#define MT_RRO_MSDU_PG_SEG_ADDR0 MT_RRO_TOP(0x620) + #define MT_RRO_ACK_SN_CTRL MT_RRO_TOP(0x50) #define MT_RRO_ACK_SN_CTRL_SN_MASK GENMASK(27, 16) #define MT_RRO_ACK_SN_CTRL_SESSION_MASK GENMASK(11, 0) From 9dd5beb7f0393049ae3076adc77bd2920ff7d8da Mon Sep 17 00:00:00 2001 From: Rex Lu Date: Tue, 9 Sep 2025 11:45:15 +0200 Subject: [PATCH 067/167] wifi: mt76: mt7996: Introduce the capability to reset MT7992 WED device This is a preliminary patch to enable WED support for MT7992 Kite chipset supported by MT7996 driver. Co-developed-by: Sujuan Chen Signed-off-by: Sujuan Chen Co-developed-by: Benjamin Lin Signed-off-by: Benjamin Lin Signed-off-by: Rex Lu Co-developed-by: Lorenzo Bianconi Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250909-mt7996-rro-rework-v5-7-7d66f6eb7795@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/init.c | 6 ++-- .../net/wireless/mediatek/mt76/mt7996/mac.c | 30 +++++++++++++++++++ .../wireless/mediatek/mt76/mt7996/mt7996.h | 1 + 3 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 93144b5e9564..73a55eea5358 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -756,9 +756,9 @@ void mt7996_wfsys_reset(struct mt7996_dev *dev) msleep(20); } -#ifdef CONFIG_NET_MEDIATEK_SOC_WED -static void mt7996_rro_hw_init(struct mt7996_dev *dev) +void mt7996_rro_hw_init(struct mt7996_dev *dev) { +#ifdef CONFIG_NET_MEDIATEK_SOC_WED struct mtk_wed_device *wed = &dev->mt76.mmio.wed; u32 reg = MT_RRO_ADDR_ELEM_SEG_ADDR0; int i; @@ -838,8 +838,8 @@ static void mt7996_rro_hw_init(struct mt7996_dev *dev) /* interrupt enable */ mt76_wr(dev, MT_RRO_HOST_INT_ENA, MT_RRO_HOST_INT_ENA_HOST_RRO_DONE_ENA); -} #endif +} static int mt7996_wed_rro_init(struct mt7996_dev *dev) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 8a75d7a7b0c0..8aeea7ecc0da 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -1913,6 +1913,32 @@ mt7996_mac_restart(struct mt7996_dev *dev) if (ret) goto out; + if (mtk_wed_device_active(&dev->mt76.mmio.wed) && dev->has_rro) { + u32 wed_irq_mask = dev->mt76.mmio.irqmask | + MT_INT_RRO_RX_DONE | + MT_INT_TX_DONE_BAND2; + + mt7996_rro_hw_init(dev); + mt76_for_each_q_rx(&dev->mt76, i) { + if (mt76_queue_is_wed_rro_ind(&dev->mt76.q_rx[i]) || + mt76_queue_is_wed_rro_msdu_pg(&dev->mt76.q_rx[i])) + mt76_queue_rx_reset(dev, i); + } + + mt76_wr(dev, MT_INT_MASK_CSR, wed_irq_mask); + mtk_wed_device_start_hw_rro(&dev->mt76.mmio.wed, wed_irq_mask, + false); + mt7996_irq_enable(dev, wed_irq_mask); + mt7996_irq_disable(dev, 0); + } + + if (mtk_wed_device_active(&dev->mt76.mmio.wed_hif2)) { + mt76_wr(dev, MT_INT_PCIE1_MASK_CSR, + MT_INT_TX_RX_DONE_EXT); + mtk_wed_device_start(&dev->mt76.mmio.wed_hif2, + MT_INT_TX_RX_DONE_EXT); + } + /* set the necessary init items */ ret = mt7996_mcu_set_eeprom(dev); if (ret) @@ -2125,6 +2151,10 @@ void mt7996_mac_reset_work(struct work_struct *work) mt76_wr(dev, MT_INT_MASK_CSR, wed_irq_mask); + if (is_mt7992(&dev->mt76) && dev->has_rro) + mt76_wr(dev, MT_RRO_3_0_EMU_CONF, + MT_RRO_3_0_EMU_CONF_EN_MASK); + mtk_wed_device_start_hw_rro(&dev->mt76.mmio.wed, wed_irq_mask, true); mt7996_irq_enable(dev, wed_irq_mask); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 094ea070369a..9af3382003bc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -569,6 +569,7 @@ extern struct pci_driver mt7996_hif_driver; struct mt7996_dev *mt7996_mmio_probe(struct device *pdev, void __iomem *mem_base, u32 device_id); +void mt7996_rro_hw_init(struct mt7996_dev *dev); void mt7996_wfsys_reset(struct mt7996_dev *dev); irqreturn_t mt7996_irq_handler(int irq, void *dev_instance); u64 __mt7996_get_tsf(struct ieee80211_hw *hw, struct mt7996_vif_link *link); From 77ff8caf3b17626ad91568cef63d75e288aa4052 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 9 Sep 2025 11:45:16 +0200 Subject: [PATCH 068/167] wifi: mt76: mt7996: Fix tx-queues initialization for second phy on mt7996 Fix the second phy tx queue initialization if hif device is not available for MT7990 chipset. Fixes: 83eafc9251d6d ("wifi: mt76: mt7996: add wed tx support") Co-developed-by: Sujuan Chen Signed-off-by: Sujuan Chen Co-developed-by: Benjamin Lin Signed-off-by: Benjamin Lin Co-developed-by: Rex Lu Signed-off-by: Rex Lu Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250909-mt7996-rro-rework-v5-8-7d66f6eb7795@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/init.c | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 73a55eea5358..a472c9bd4fd8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -703,13 +703,20 @@ static int mt7996_register_phy(struct mt7996_dev *dev, enum mt76_band_id band) /* init wiphy according to mphy and phy */ mt7996_init_wiphy_band(mphy->hw, phy); - ret = mt7996_init_tx_queues(mphy->priv, - MT_TXQ_ID(band), - MT7996_TX_RING_SIZE, - MT_TXQ_RING_BASE(band) + hif1_ofs, - wed); - if (ret) - goto error; + + if (is_mt7996(&dev->mt76) && !dev->hif2 && band == MT_BAND1) { + int i; + + for (i = 0; i <= MT_TXQ_PSD; i++) + mphy->q_tx[i] = dev->mt76.phys[MT_BAND0]->q_tx[0]; + } else { + ret = mt7996_init_tx_queues(mphy->priv, MT_TXQ_ID(band), + MT7996_TX_RING_SIZE, + MT_TXQ_RING_BASE(band) + hif1_ofs, + wed); + if (ret) + goto error; + } ret = mt76_register_phy(mphy, true, mt76_rates, ARRAY_SIZE(mt76_rates)); From cffed52dbf0ddd0db11f9df63f9976fe58ac9628 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 9 Sep 2025 11:45:17 +0200 Subject: [PATCH 069/167] wifi: mt76: mt7996: Fix RX packets configuration for primary WED device In order to properly set the number of rx packets for primary WED device if hif device is available, move hif pointer initialization before running mt7996_mmio_wed_init routine. Fixes: 83eafc9251d6d ("wifi: mt76: mt7996: add wed tx support") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250909-mt7996-rro-rework-v5-9-7d66f6eb7795@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/pci.c b/drivers/net/wireless/mediatek/mt76/mt7996/pci.c index 19e99bc1c6c4..f5ce50056ee9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/pci.c @@ -137,6 +137,7 @@ static int mt7996_pci_probe(struct pci_dev *pdev, mdev = &dev->mt76; mt7996_wfsys_reset(dev); hif2 = mt7996_pci_init_hif2(pdev); + dev->hif2 = hif2; ret = mt7996_mmio_wed_init(dev, pdev, false, &irq); if (ret < 0) @@ -161,7 +162,6 @@ static int mt7996_pci_probe(struct pci_dev *pdev, if (hif2) { hif2_dev = container_of(hif2->dev, struct pci_dev, dev); - dev->hif2 = hif2; ret = mt7996_mmio_wed_init(dev, hif2_dev, true, &hif2_irq); if (ret < 0) From 0d4dafacc1f7b4fb34ef8b87c19b9f95c6ea5f67 Mon Sep 17 00:00:00 2001 From: Rex Lu Date: Tue, 9 Sep 2025 11:45:18 +0200 Subject: [PATCH 070/167] wifi: mt76: mt7996: Enable WED for MT7992 chipset Introduce WED offload support for MT7992 chipset in MT7996 driver. Co-developed-by: Sujuan Chen Signed-off-by: Sujuan Chen Co-developed-by: Benjamin Lin Signed-off-by: Benjamin Lin Signed-off-by: Rex Lu Co-developed-by: Lorenzo Bianconi Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250909-mt7996-rro-rework-v5-10-7d66f6eb7795@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/dma.c | 26 ++++++- .../net/wireless/mediatek/mt76/mt7996/init.c | 11 +-- .../net/wireless/mediatek/mt76/mt7996/main.c | 17 +++-- .../net/wireless/mediatek/mt76/mt7996/mmio.c | 72 ++++++++++++++----- .../wireless/mediatek/mt76/mt7996/mt7996.h | 3 + .../net/wireless/mediatek/mt76/mt7996/pci.c | 1 + .../net/wireless/mediatek/mt76/mt7996/regs.h | 4 ++ 7 files changed, 100 insertions(+), 34 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c index b3665bb0a433..c5fd25acf9a1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c @@ -17,7 +17,7 @@ int mt7996_init_tx_queues(struct mt7996_phy *phy, int idx, int n_desc, ring_base += MT_TXQ_ID(0) * MT_RING_SIZE; idx -= MT_TXQ_ID(0); - if (phy->mt76->band_idx == MT_BAND2) + if (wed == &dev->mt76.mmio.wed_hif2) flags = MT_WED_Q_TX(0); else flags = MT_WED_Q_TX(idx); @@ -429,6 +429,30 @@ static void mt7996_dma_enable(struct mt7996_dev *dev, bool reset) mt76_rmw(dev, MT_WFDMA_AXI_R2A_CTRL, MT_WFDMA_AXI_R2A_CTRL_OUTSTAND_MASK, 0x14); + if (dev->hif2->speed < PCIE_SPEED_5_0GT || + (dev->hif2->speed == PCIE_SPEED_5_0GT && + dev->hif2->width < PCIE_LNK_X2)) { + mt76_rmw(dev, WF_WFDMA0_GLO_CFG_EXT0 + hif1_ofs, + WF_WFDMA0_GLO_CFG_EXT0_OUTSTAND_MASK, + FIELD_PREP(WF_WFDMA0_GLO_CFG_EXT0_OUTSTAND_MASK, + 0x1)); + mt76_rmw(dev, MT_WFDMA_AXI_R2A_CTRL2, + MT_WFDMA_AXI_R2A_CTRL2_OUTSTAND_MASK, + FIELD_PREP(MT_WFDMA_AXI_R2A_CTRL2_OUTSTAND_MASK, + 0x1)); + } else if (dev->hif2->speed < PCIE_SPEED_8_0GT || + (dev->hif2->speed == PCIE_SPEED_8_0GT && + dev->hif2->width < PCIE_LNK_X2)) { + mt76_rmw(dev, WF_WFDMA0_GLO_CFG_EXT0 + hif1_ofs, + WF_WFDMA0_GLO_CFG_EXT0_OUTSTAND_MASK, + FIELD_PREP(WF_WFDMA0_GLO_CFG_EXT0_OUTSTAND_MASK, + 0x2)); + mt76_rmw(dev, MT_WFDMA_AXI_R2A_CTRL2, + MT_WFDMA_AXI_R2A_CTRL2_OUTSTAND_MASK, + FIELD_PREP(MT_WFDMA_AXI_R2A_CTRL2_OUTSTAND_MASK, + 0x2)); + } + /* WFDMA rx threshold */ mt76_wr(dev, MT_WFDMA0_PAUSE_RX_Q_45_TH + hif1_ofs, 0xc000c); mt76_wr(dev, MT_WFDMA0_PAUSE_RX_Q_67_TH + hif1_ofs, 0x10008); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index a472c9bd4fd8..0104b50ce3f6 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -666,7 +666,9 @@ static int mt7996_register_phy(struct mt7996_dev *dev, enum mt76_band_id band) if (!mt7996_band_valid(dev, band)) return 0; - if (is_mt7996(&dev->mt76) && band == MT_BAND2 && dev->hif2) { + if (dev->hif2 && + ((is_mt7996(&dev->mt76) && band == MT_BAND2) || + (is_mt7992(&dev->mt76) && band == MT_BAND1))) { hif1_ofs = MT_WFDMA0_PCIE1(0) - MT_WFDMA0(0); wed = &dev->mt76.mmio.wed_hif2; } @@ -724,10 +726,9 @@ static int mt7996_register_phy(struct mt7996_dev *dev, enum mt76_band_id band) goto error; if (wed == &dev->mt76.mmio.wed_hif2 && mtk_wed_device_active(wed)) { - u32 irq_mask = dev->mt76.mmio.irqmask | MT_INT_TX_DONE_BAND2; - - mt76_wr(dev, MT_INT1_MASK_CSR, irq_mask); - mtk_wed_device_start(&dev->mt76.mmio.wed_hif2, irq_mask); + mt76_wr(dev, MT_INT_PCIE1_MASK_CSR, MT_INT_TX_RX_DONE_EXT); + mtk_wed_device_start(&dev->mt76.mmio.wed_hif2, + MT_INT_TX_RX_DONE_EXT); } return 0; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 0d5866ac951f..6adb7f7bdd6f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -2158,9 +2158,7 @@ mt7996_net_fill_forward_path(struct ieee80211_hw *hw, struct mt7996_dev *dev = mt7996_hw_dev(hw); struct mtk_wed_device *wed = &dev->mt76.mmio.wed; struct mt7996_sta_link *msta_link; - struct mt7996_vif_link *link; struct mt76_vif_link *mlink; - struct mt7996_phy *phy; mlink = rcu_dereference(mvif->mt76.link[msta->deflink_id]); if (!mlink) @@ -2173,12 +2171,9 @@ mt7996_net_fill_forward_path(struct ieee80211_hw *hw, if (!msta_link->wcid.sta || msta_link->wcid.idx > MT7996_WTBL_STA) return -EIO; - link = (struct mt7996_vif_link *)mlink; - phy = mt7996_vif_link_phy(link); - if (!phy) - return -ENODEV; - - if (phy != &dev->phy && phy->mt76->band_idx == MT_BAND2) + if (dev->hif2 && + ((is_mt7996(&dev->mt76) && msta_link->wcid.phy_idx == MT_BAND2) || + (is_mt7992(&dev->mt76) && msta_link->wcid.phy_idx == MT_BAND1))) wed = &dev->mt76.mmio.wed_hif2; if (!mtk_wed_device_active(wed)) @@ -2191,7 +2186,11 @@ mt7996_net_fill_forward_path(struct ieee80211_hw *hw, path->mtk_wdma.queue = 0; path->mtk_wdma.wcid = msta_link->wcid.idx; - path->mtk_wdma.amsdu = mtk_wed_is_amsdu_supported(wed); + if (ieee80211_hw_check(hw, SUPPORTS_AMSDU_IN_AMPDU) && + mtk_wed_is_amsdu_supported(wed)) + path->mtk_wdma.amsdu = msta_link->wcid.amsdu; + else + path->mtk_wdma.amsdu = 0; ctx->dev = NULL; return 0; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c index a8b4ef433c2b..aa70e5fce98f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c @@ -459,14 +459,14 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, #ifdef CONFIG_NET_MEDIATEK_SOC_WED struct mtk_wed_device *wed = &dev->mt76.mmio.wed; struct pci_dev *pci_dev = pdev_ptr; - u32 hif1_ofs = 0; + u32 hif1_ofs; if (!wed_enable) return 0; dev->has_rro = true; - hif1_ofs = MT_WFDMA0_PCIE1(0) - MT_WFDMA0(0); + hif1_ofs = dev->hif2 ? MT_WFDMA0_PCIE1(0) - MT_WFDMA0(0) : 0; if (hif2) wed = &dev->mt76.mmio.wed_hif2; @@ -491,10 +491,17 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, MT_TXQ_RING_BASE(0) + MT7996_TXQ_BAND2 * MT_RING_SIZE; if (dev->has_rro) { - wed->wlan.wpdma_txfree = wed->wlan.phy_base + hif1_ofs + - MT_RXQ_RING_BASE(0) + - MT7996_RXQ_TXFREE2 * MT_RING_SIZE; - wed->wlan.txfree_tbit = ffs(MT_INT_RX_TXFREE_EXT) - 1; + if (is_mt7996(&dev->mt76)) { + wed->wlan.txfree_tbit = ffs(MT_INT_RX_TXFREE_EXT) - 1; + wed->wlan.wpdma_txfree = wed->wlan.phy_base + hif1_ofs + + MT_RXQ_RING_BASE(0) + + MT7996_RXQ_TXFREE2 * MT_RING_SIZE; + } else { + wed->wlan.txfree_tbit = ffs(MT_INT_RX_TXFREE_BAND1_EXT) - 1; + wed->wlan.wpdma_txfree = wed->wlan.phy_base + hif1_ofs + + MT_RXQ_RING_BASE(0) + + MT7996_RXQ_MCU_WA_EXT * MT_RING_SIZE; + } } else { wed->wlan.wpdma_txfree = wed->wlan.phy_base + hif1_ofs + MT_RXQ_RING_BASE(0) + @@ -504,8 +511,8 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, wed->wlan.wpdma_rx_glo = wed->wlan.phy_base + hif1_ofs + MT_WFDMA0_GLO_CFG; wed->wlan.wpdma_rx[0] = wed->wlan.phy_base + hif1_ofs + - MT_RXQ_RING_BASE(MT7996_RXQ_BAND0) + - MT7996_RXQ_BAND0 * MT_RING_SIZE; + MT_RXQ_RING_BASE(MT7996_RXQ_BAND2) + + MT7996_RXQ_BAND2 * MT_RING_SIZE; wed->wlan.id = MT7996_DEVICE_ID_2; wed->wlan.tx_tbit[0] = ffs(MT_INT_TX_DONE_BAND2) - 1; @@ -525,9 +532,19 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, wed->wlan.wpdma_rx_rro[0] = wed->wlan.phy_base + MT_RXQ_RING_BASE(MT7996_RXQ_RRO_BAND0) + MT7996_RXQ_RRO_BAND0 * MT_RING_SIZE; - wed->wlan.wpdma_rx_rro[1] = wed->wlan.phy_base + hif1_ofs + - MT_RXQ_RING_BASE(MT7996_RXQ_RRO_BAND2) + - MT7996_RXQ_RRO_BAND2 * MT_RING_SIZE; + if (is_mt7996(&dev->mt76)) { + wed->wlan.wpdma_rx_rro[1] = wed->wlan.phy_base + hif1_ofs + + MT_RXQ_RING_BASE(MT7996_RXQ_RRO_BAND2) + + MT7996_RXQ_RRO_BAND2 * MT_RING_SIZE; + } else { + wed->wlan.wpdma_rx_rro[1] = wed->wlan.phy_base + hif1_ofs + + MT_RXQ_RING_BASE(MT7996_RXQ_RRO_BAND1) + + MT7996_RXQ_RRO_BAND1 * MT_RING_SIZE; + wed->wlan.wpdma_rx[1] = wed->wlan.phy_base + hif1_ofs + + MT_RXQ_RING_BASE(MT7996_RXQ_BAND1) + + MT7996_RXQ_BAND1 * MT_RING_SIZE; + } + wed->wlan.wpdma_rx_pg = wed->wlan.phy_base + MT_RXQ_RING_BASE(MT7996_RXQ_MSDU_PG_BAND0) + MT7996_RXQ_MSDU_PG_BAND0 * MT_RING_SIZE; @@ -537,10 +554,14 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, wed->wlan.rx_size = SKB_WITH_OVERHEAD(MT_RX_BUF_SIZE); wed->wlan.rx_tbit[0] = ffs(MT_INT_RX_DONE_BAND0) - 1; - wed->wlan.rx_tbit[1] = ffs(MT_INT_RX_DONE_BAND2) - 1; - wed->wlan.rro_rx_tbit[0] = ffs(MT_INT_RX_DONE_RRO_BAND0) - 1; - wed->wlan.rro_rx_tbit[1] = ffs(MT_INT_RX_DONE_RRO_BAND2) - 1; + if (is_mt7996(&dev->mt76)) { + wed->wlan.rx_tbit[1] = ffs(MT_INT_RX_DONE_BAND2) - 1; + wed->wlan.rro_rx_tbit[1] = ffs(MT_INT_RX_DONE_RRO_BAND2) - 1; + } else { + wed->wlan.rx_tbit[1] = ffs(MT_INT_RX_DONE_BAND1) - 1; + wed->wlan.rro_rx_tbit[1] = ffs(MT_INT_RX_DONE_RRO_BAND1) - 1; + } wed->wlan.rx_pg_tbit[0] = ffs(MT_INT_RX_DONE_MSDU_PG_BAND0) - 1; wed->wlan.rx_pg_tbit[1] = ffs(MT_INT_RX_DONE_MSDU_PG_BAND1) - 1; @@ -548,16 +569,27 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, wed->wlan.tx_tbit[0] = ffs(MT_INT_TX_DONE_BAND0) - 1; wed->wlan.tx_tbit[1] = ffs(MT_INT_TX_DONE_BAND1) - 1; - if (dev->has_rro) { - wed->wlan.wpdma_txfree = wed->wlan.phy_base + MT_RXQ_RING_BASE(0) + - MT7996_RXQ_TXFREE0 * MT_RING_SIZE; - wed->wlan.txfree_tbit = ffs(MT_INT_RX_TXFREE_MAIN) - 1; + if (is_mt7996(&dev->mt76)) { + if (dev->has_rro) { + wed->wlan.wpdma_txfree = wed->wlan.phy_base + + MT_RXQ_RING_BASE(0) + + MT7996_RXQ_TXFREE0 * MT_RING_SIZE; + wed->wlan.txfree_tbit = ffs(MT_INT_RX_TXFREE_MAIN) - 1; + } else { + wed->wlan.wpdma_txfree = wed->wlan.phy_base + + MT_RXQ_RING_BASE(0) + + MT7996_RXQ_MCU_WA_MAIN * MT_RING_SIZE; + wed->wlan.txfree_tbit = ffs(MT_INT_RX_DONE_WA_MAIN) - 1; + } } else { wed->wlan.txfree_tbit = ffs(MT_INT_RX_DONE_WA_MAIN) - 1; wed->wlan.wpdma_txfree = wed->wlan.phy_base + MT_RXQ_RING_BASE(0) + MT7996_RXQ_MCU_WA_MAIN * MT_RING_SIZE; } dev->mt76.rx_token_size = MT7996_TOKEN_SIZE + wed->wlan.rx_npkt; + + if (dev->hif2 && is_mt7992(&dev->mt76)) + wed->wlan.id = 0x7992; } wed->wlan.nbuf = MT7996_HW_TOKEN_SIZE; @@ -576,8 +608,10 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, wed->wlan.reset_complete = mt76_wed_reset_complete; } - if (mtk_wed_device_attach(wed)) + if (mtk_wed_device_attach(wed)) { + dev->has_rro = false; return 0; + } *irq = wed->irq; dev->mt76.dma_dev = wed->dev; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 9af3382003bc..f6dfd36a44c0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -280,6 +280,9 @@ struct mt7996_hif { struct device *dev; void __iomem *regs; int irq; + + enum pci_bus_speed speed; + enum pcie_link_width width; }; struct mt7996_wed_rro_addr { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/pci.c b/drivers/net/wireless/mediatek/mt76/mt7996/pci.c index f5ce50056ee9..3f49bbbba3b9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/pci.c @@ -87,6 +87,7 @@ static int mt7996_pci_hif2_probe(struct pci_dev *pdev) hif->dev = &pdev->dev; hif->regs = pcim_iomap_table(pdev)[0]; hif->irq = pdev->irq; + pcie_bandwidth_available(pdev, NULL, &hif->speed, &hif->width); spin_lock_bh(&hif_lock); list_add(&hif->list, &hif_list); spin_unlock_bh(&hif_lock); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h index 070cdebcd19d..d239fa3f375f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h @@ -442,6 +442,7 @@ enum offs_rev { #define MT_WFDMA0_PAUSE_RX_Q_RRO_TH MT_WFDMA0(0x27c) #define WF_WFDMA0_GLO_CFG_EXT0 MT_WFDMA0(0x2b0) +#define WF_WFDMA0_GLO_CFG_EXT0_OUTSTAND_MASK GENMASK(27, 24) #define WF_WFDMA0_GLO_CFG_EXT0_RX_WB_RXD BIT(18) #define WF_WFDMA0_GLO_CFG_EXT0_WED_MERGE_MODE BIT(14) @@ -473,6 +474,9 @@ enum offs_rev { #define MT_WFDMA_AXI_R2A_CTRL MT_WFDMA_EXT_CSR(0x500) #define MT_WFDMA_AXI_R2A_CTRL_OUTSTAND_MASK GENMASK(4, 0) +#define MT_WFDMA_AXI_R2A_CTRL2 MT_WFDMA_EXT_CSR(0x508) +#define MT_WFDMA_AXI_R2A_CTRL2_OUTSTAND_MASK GENMASK(31, 28) + #define MT_PCIE_RECOG_ID 0xd7090 #define MT_PCIE_RECOG_ID_MASK GENMASK(30, 0) #define MT_PCIE_RECOG_ID_SEM BIT(31) From 809054a60d613ccca6e7f243bc68966b58044163 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 9 Sep 2025 11:45:19 +0200 Subject: [PATCH 071/167] wifi: mt76: mt7996: Convert mt7996_wed_rro_addr to LE Do not use bitmask in mt7996_wed_rro_addr DMA descriptor in order to not break endianness Fixes: 950d0abb5cd94 ("wifi: mt76: mt7996: add wed rx support") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250909-mt7996-rro-rework-v5-11-7d66f6eb7795@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 8 +++++--- drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h | 11 +++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 0104b50ce3f6..f70e24d989cb 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -852,6 +852,7 @@ void mt7996_rro_hw_init(struct mt7996_dev *dev) static int mt7996_wed_rro_init(struct mt7996_dev *dev) { #ifdef CONFIG_NET_MEDIATEK_SOC_WED + u32 val = FIELD_PREP(WED_RRO_ADDR_SIGNATURE_MASK, 0xff); struct mtk_wed_device *wed = &dev->mt76.mmio.wed; struct mt7996_wed_rro_addr *addr; void *ptr; @@ -890,7 +891,7 @@ static int mt7996_wed_rro_init(struct mt7996_dev *dev) addr = dev->wed_rro.addr_elem[i].ptr; for (j = 0; j < MT7996_RRO_WINDOW_MAX_SIZE; j++) { - addr->signature = 0xff; + addr->data = cpu_to_le32(val); addr++; } @@ -922,7 +923,7 @@ static int mt7996_wed_rro_init(struct mt7996_dev *dev) dev->wed_rro.session.ptr = ptr; addr = dev->wed_rro.session.ptr; for (i = 0; i < MT7996_RRO_WINDOW_MAX_LEN; i++) { - addr->signature = 0xff; + addr->data = cpu_to_le32(val); addr++; } @@ -990,6 +991,7 @@ static void mt7996_wed_rro_free(struct mt7996_dev *dev) static void mt7996_wed_rro_work(struct work_struct *work) { #ifdef CONFIG_NET_MEDIATEK_SOC_WED + u32 val = FIELD_PREP(WED_RRO_ADDR_SIGNATURE_MASK, 0xff); struct mt7996_dev *dev; LIST_HEAD(list); @@ -1026,7 +1028,7 @@ static void mt7996_wed_rro_work(struct work_struct *work) MT7996_RRO_WINDOW_MAX_LEN; reset: elem = ptr + elem_id * sizeof(*elem); - elem->signature = 0xff; + elem->data |= cpu_to_le32(val); } mt7996_mcu_wed_rro_reset_sessions(dev, e->id); out: diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index f6dfd36a44c0..313f6923d071 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -285,13 +285,12 @@ struct mt7996_hif { enum pcie_link_width width; }; +#define WED_RRO_ADDR_SIGNATURE_MASK GENMASK(31, 24) +#define WED_RRO_ADDR_COUNT_MASK GENMASK(14, 4) +#define WED_RRO_ADDR_HEAD_HIGH_MASK GENMASK(3, 0) struct mt7996_wed_rro_addr { - u32 head_low; - u32 head_high : 4; - u32 count: 11; - u32 oor: 1; - u32 rsv : 8; - u32 signature : 8; + __le32 head_low; + __le32 data; }; struct mt7996_wed_rro_session_id { From b1e58e137b61693f12da037d2d50aca9c2140a43 Mon Sep 17 00:00:00 2001 From: Rex Lu Date: Tue, 9 Sep 2025 11:45:20 +0200 Subject: [PATCH 072/167] wifi: mt76: mt7996: Introduce RRO MSDU callbacks Introduce rx_rro_ind_process and rx_rro_add_msdu_page callbacks and the related logic in the MT7996 driver. This is a preliminary patch to decouple RRO logic from WED support and reuse RRO when WED module is not available. Signed-off-by: Rex Lu Co-developed-by: Lorenzo Bianconi Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250909-mt7996-rro-rework-v5-12-7d66f6eb7795@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.c | 2 + drivers/net/wireless/mediatek/mt76/mt76.h | 6 + .../net/wireless/mediatek/mt76/mt7996/dma.c | 3 + .../net/wireless/mediatek/mt76/mt7996/init.c | 6 + .../net/wireless/mediatek/mt76/mt7996/mac.c | 352 ++++++++++++++++++ .../net/wireless/mediatek/mt76/mt7996/mmio.c | 2 + .../wireless/mediatek/mt76/mt7996/mt7996.h | 38 ++ 7 files changed, 409 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index f882b4e10858..b8bb8cdfb69b 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -256,6 +256,8 @@ mt76_dma_add_rx_buf(struct mt76_dev *dev, struct mt76_queue *q, buf1 |= FIELD_PREP(MT_DMA_CTL_TOKEN, rx_token); ctrl |= MT_DMA_CTL_TO_HOST; + + txwi->qid = q - dev->q_rx; } WRITE_ONCE(desc->buf0, cpu_to_le32(buf->addr)); diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 0c54ae47923f..76f274ad9d30 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -418,6 +418,8 @@ struct mt76_txwi_cache { struct sk_buff *skb; void *ptr; }; + + u8 qid; }; struct mt76_rx_tid { @@ -534,6 +536,10 @@ struct mt76_driver_ops { void (*rx_poll_complete)(struct mt76_dev *dev, enum mt76_rxq_id q); + void (*rx_rro_ind_process)(struct mt76_dev *dev, void *data); + int (*rx_rro_add_msdu_page)(struct mt76_dev *dev, struct mt76_queue *q, + dma_addr_t p, void *data); + void (*sta_ps)(struct mt76_dev *dev, struct ieee80211_sta *sta, bool ps); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c index c5fd25acf9a1..2412767bfaa7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c @@ -854,6 +854,9 @@ void mt7996_dma_reset(struct mt7996_dev *dev, bool force) mt76_tx_status_check(&dev->mt76, true); + if (dev->has_rro && !mtk_wed_device_active(&dev->mt76.mmio.wed)) + mt7996_rro_msdu_page_map_free(dev); + /* reset wfsys */ if (force) mt7996_wfsys_reset(dev); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index f70e24d989cb..701efbf20d12 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -774,6 +774,10 @@ void mt7996_rro_hw_init(struct mt7996_dev *dev) if (!dev->has_rro) return; + INIT_LIST_HEAD(&dev->wed_rro.page_cache); + for (i = 0; i < ARRAY_SIZE(dev->wed_rro.page_map); i++) + INIT_LIST_HEAD(&dev->wed_rro.page_map[i]); + if (is_mt7992(&dev->mt76)) { /* Set emul 3.0 function */ mt76_wr(dev, MT_RRO_3_0_EMU_CONF, @@ -1658,6 +1662,8 @@ void mt7996_unregister_device(struct mt7996_dev *dev) mt7996_mcu_exit(dev); mt7996_tx_token_put(dev); mt7996_dma_cleanup(dev); + if (dev->has_rro && !mtk_wed_device_active(&dev->mt76.mmio.wed)) + mt7996_rro_msdu_page_map_free(dev); tasklet_disable(&dev->mt76.irq_tasklet); mt76_free_device(&dev->mt76); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 8aeea7ecc0da..894338cc868e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -1638,6 +1638,358 @@ void mt7996_queue_rx_skb(struct mt76_dev *mdev, enum mt76_rxq_id q, } } +static struct mt7996_msdu_page * +mt7996_msdu_page_get_from_cache(struct mt7996_dev *dev) +{ + struct mt7996_msdu_page *p = NULL; + + spin_lock(&dev->wed_rro.lock); + + if (!list_empty(&dev->wed_rro.page_cache)) { + p = list_first_entry(&dev->wed_rro.page_cache, + struct mt7996_msdu_page, list); + if (p) + list_del(&p->list); + } + + spin_unlock(&dev->wed_rro.lock); + + return p; +} + +static struct mt7996_msdu_page *mt7996_msdu_page_get(struct mt7996_dev *dev) +{ + struct mt7996_msdu_page *p; + + p = mt7996_msdu_page_get_from_cache(dev); + if (!p) { + p = kzalloc(L1_CACHE_ALIGN(sizeof(*p)), GFP_ATOMIC); + if (p) + INIT_LIST_HEAD(&p->list); + } + + return p; +} + +static void mt7996_msdu_page_put_to_cache(struct mt7996_dev *dev, + struct mt7996_msdu_page *p) +{ + if (p->buf) { + mt76_put_page_pool_buf(p->buf, false); + p->buf = NULL; + } + + spin_lock(&dev->wed_rro.lock); + list_add(&p->list, &dev->wed_rro.page_cache); + spin_unlock(&dev->wed_rro.lock); +} + +static void mt7996_msdu_page_free_cache(struct mt7996_dev *dev) +{ + while (true) { + struct mt7996_msdu_page *p; + + p = mt7996_msdu_page_get_from_cache(dev); + if (!p) + break; + + if (p->buf) + mt76_put_page_pool_buf(p->buf, false); + + kfree(p); + } +} + +static u32 mt7996_msdu_page_hash_from_addr(dma_addr_t dma_addr) +{ + u32 val = 0; + int i = 0; + + while (dma_addr) { + val += (u32)((dma_addr & 0xff) + i) % MT7996_RRO_MSDU_PG_HASH_SIZE; + dma_addr >>= 8; + i += 13; + } + + return val % MT7996_RRO_MSDU_PG_HASH_SIZE; +} + +static struct mt7996_msdu_page * +mt7996_rro_msdu_page_get(struct mt7996_dev *dev, dma_addr_t dma_addr) +{ + u32 hash = mt7996_msdu_page_hash_from_addr(dma_addr); + struct mt7996_msdu_page *p, *tmp, *addr = NULL; + + spin_lock(&dev->wed_rro.lock); + + list_for_each_entry_safe(p, tmp, &dev->wed_rro.page_map[hash], + list) { + if (p->dma_addr == dma_addr) { + list_del(&p->list); + addr = p; + break; + } + } + + spin_unlock(&dev->wed_rro.lock); + + return addr; +} + +static void mt7996_rx_token_put(struct mt7996_dev *dev) +{ + int i; + + for (i = 0; i < dev->mt76.rx_token_size; i++) { + struct mt76_txwi_cache *t; + + t = mt76_rx_token_release(&dev->mt76, i); + if (!t || !t->ptr) + continue; + + mt76_put_page_pool_buf(t->ptr, false); + t->dma_addr = 0; + t->ptr = NULL; + + mt76_put_rxwi(&dev->mt76, t); + } +} + +void mt7996_rro_msdu_page_map_free(struct mt7996_dev *dev) +{ + struct mt7996_msdu_page *p, *tmp; + int i; + + local_bh_disable(); + + for (i = 0; i < ARRAY_SIZE(dev->wed_rro.page_map); i++) { + list_for_each_entry_safe(p, tmp, &dev->wed_rro.page_map[i], + list) { + list_del_init(&p->list); + if (p->buf) + mt76_put_page_pool_buf(p->buf, false); + kfree(p); + } + } + mt7996_msdu_page_free_cache(dev); + + local_bh_enable(); + + mt7996_rx_token_put(dev); +} + +int mt7996_rro_msdu_page_add(struct mt76_dev *mdev, struct mt76_queue *q, + dma_addr_t dma_addr, void *data) +{ + struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76); + struct mt7996_msdu_page_info *pinfo = data; + struct mt7996_msdu_page *p; + u32 hash; + + pinfo->data |= cpu_to_le32(FIELD_PREP(MSDU_PAGE_INFO_OWNER_MASK, 1)); + p = mt7996_msdu_page_get(dev); + if (!p) + return -ENOMEM; + + p->buf = data; + p->dma_addr = dma_addr; + p->q = q; + + hash = mt7996_msdu_page_hash_from_addr(dma_addr); + + spin_lock(&dev->wed_rro.lock); + list_add_tail(&p->list, &dev->wed_rro.page_map[hash]); + spin_unlock(&dev->wed_rro.lock); + + return 0; +} + +static struct mt7996_wed_rro_addr * +mt7996_rro_addr_elem_get(struct mt7996_dev *dev, u16 session_id, u16 seq_num) +{ + u32 idx = 0; + void *addr; + + if (session_id == MT7996_RRO_MAX_SESSION) { + addr = dev->wed_rro.session.ptr; + } else { + idx = session_id / MT7996_RRO_BA_BITMAP_SESSION_SIZE; + addr = dev->wed_rro.addr_elem[idx].ptr; + + idx = session_id % MT7996_RRO_BA_BITMAP_SESSION_SIZE; + idx = idx * MT7996_RRO_WINDOW_MAX_LEN; + } + idx += seq_num % MT7996_RRO_WINDOW_MAX_LEN; + + return addr + idx * sizeof(struct mt7996_wed_rro_addr); +} + +#define MT996_RRO_SN_MASK GENMASK(11, 0) + +void mt7996_rro_rx_process(struct mt76_dev *mdev, void *data) +{ + struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76); + struct mt76_wed_rro_ind *cmd = (struct mt76_wed_rro_ind *)data; + struct mt7996_msdu_page_info *pinfo = NULL; + struct mt7996_msdu_page *p = NULL; + int i, seq_num = 0; + + for (i = 0; i < cmd->ind_cnt; i++) { + struct mt7996_wed_rro_addr *e; + struct mt76_rx_status *status; + struct mt7996_rro_hif *rxd; + int j, len, qid, data_len; + struct mt76_txwi_cache *t; + dma_addr_t dma_addr = 0; + u16 rx_token_id, count; + struct mt76_queue *q; + struct sk_buff *skb; + u32 info = 0, data; + u8 signature; + void *buf; + bool ls; + + seq_num = FIELD_GET(MT996_RRO_SN_MASK, cmd->start_sn + i); + e = mt7996_rro_addr_elem_get(dev, cmd->se_id, seq_num); + data = le32_to_cpu(e->data); + signature = FIELD_GET(WED_RRO_ADDR_SIGNATURE_MASK, data); + if (signature != (seq_num / MT7996_RRO_WINDOW_MAX_LEN)) { + u32 val = FIELD_PREP(WED_RRO_ADDR_SIGNATURE_MASK, + 0xff); + + e->data |= cpu_to_le32(val); + goto update_ack_seq_num; + } + +#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT + dma_addr = FIELD_GET(WED_RRO_ADDR_HEAD_HIGH_MASK, data); + dma_addr <<= 32; +#endif + dma_addr |= le32_to_cpu(e->head_low); + + count = FIELD_GET(WED_RRO_ADDR_COUNT_MASK, data); + for (j = 0; j < count; j++) { + if (!p) { + p = mt7996_rro_msdu_page_get(dev, dma_addr); + if (!p) + continue; + + dma_sync_single_for_cpu(mdev->dma_dev, p->dma_addr, + SKB_WITH_OVERHEAD(p->q->buf_size), + page_pool_get_dma_dir(p->q->page_pool)); + pinfo = (struct mt7996_msdu_page_info *)p->buf; + } + + rxd = &pinfo->rxd[j % MT7996_MAX_HIF_RXD_IN_PG]; + len = FIELD_GET(RRO_HIF_DATA1_SDL_MASK, + le32_to_cpu(rxd->data1)); + + rx_token_id = FIELD_GET(RRO_HIF_DATA4_RX_TOKEN_ID_MASK, + le32_to_cpu(rxd->data4)); + t = mt76_rx_token_release(mdev, rx_token_id); + if (!t) + goto next_page; + + qid = t->qid; + buf = t->ptr; + q = &mdev->q_rx[qid]; + dma_sync_single_for_cpu(mdev->dma_dev, t->dma_addr, + SKB_WITH_OVERHEAD(q->buf_size), + page_pool_get_dma_dir(q->page_pool)); + + t->dma_addr = 0; + t->ptr = NULL; + mt76_put_rxwi(mdev, t); + if (!buf) + goto next_page; + + if (q->rx_head) + data_len = q->buf_size; + else + data_len = SKB_WITH_OVERHEAD(q->buf_size); + + if (data_len < len + q->buf_offset) { + dev_kfree_skb(q->rx_head); + mt76_put_page_pool_buf(buf, false); + q->rx_head = NULL; + goto next_page; + } + + ls = FIELD_GET(RRO_HIF_DATA1_LS_MASK, + le32_to_cpu(rxd->data1)); + if (q->rx_head) { + /* TODO: Take into account non-linear skb. */ + mt76_put_page_pool_buf(buf, false); + if (ls) { + dev_kfree_skb(q->rx_head); + q->rx_head = NULL; + } + goto next_page; + } + + if (ls && !mt7996_rx_check(mdev, buf, len)) + goto next_page; + + skb = build_skb(buf, q->buf_size); + if (!skb) + goto next_page; + + skb_reserve(skb, q->buf_offset); + skb_mark_for_recycle(skb); + __skb_put(skb, len); + + if (cmd->ind_reason == 1 || cmd->ind_reason == 2) { + dev_kfree_skb(skb); + goto next_page; + } + + if (!ls) { + q->rx_head = skb; + goto next_page; + } + + status = (struct mt76_rx_status *)skb->cb; + if (cmd->se_id != MT7996_RRO_MAX_SESSION) + status->aggr = true; + + mt7996_queue_rx_skb(mdev, qid, skb, &info); +next_page: + if ((j + 1) % MT7996_MAX_HIF_RXD_IN_PG == 0) { +#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT + dma_addr = + FIELD_GET(MSDU_PAGE_INFO_PG_HIGH_MASK, + le32_to_cpu(pinfo->data)); + dma_addr <<= 32; + dma_addr |= le32_to_cpu(pinfo->pg_low); +#else + dma_addr = le32_to_cpu(pinfo->pg_low); +#endif + mt7996_msdu_page_put_to_cache(dev, p); + p = NULL; + } + } + +update_ack_seq_num: + if ((i + 1) % 4 == 0) + mt76_wr(dev, MT_RRO_ACK_SN_CTRL, + FIELD_PREP(MT_RRO_ACK_SN_CTRL_SESSION_MASK, + cmd->se_id) | + FIELD_PREP(MT_RRO_ACK_SN_CTRL_SN_MASK, + seq_num)); + if (p) { + mt7996_msdu_page_put_to_cache(dev, p); + p = NULL; + } + } + + /* Update ack_seq_num for remaining addr_elem */ + if (i % 4) + mt76_wr(dev, MT_RRO_ACK_SN_CTRL, + FIELD_PREP(MT_RRO_ACK_SN_CTRL_SESSION_MASK, + cmd->se_id) | + FIELD_PREP(MT_RRO_ACK_SN_CTRL_SN_MASK, seq_num)); +} + void mt7996_mac_cca_stats_reset(struct mt7996_phy *phy) { struct mt7996_dev *dev = phy->dev; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c index aa70e5fce98f..38c15b061dff 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c @@ -821,6 +821,8 @@ struct mt7996_dev *mt7996_mmio_probe(struct device *pdev, .rx_skb = mt7996_queue_rx_skb, .rx_check = mt7996_rx_check, .rx_poll_complete = mt7996_rx_poll_complete, + .rx_rro_ind_process = mt7996_rro_rx_process, + .rx_rro_add_msdu_page = mt7996_rro_msdu_page_add, .update_survey = mt7996_update_channel, .set_channel = mt7996_set_channel, .vif_link_add = mt7996_vif_link_add, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 313f6923d071..1b3ec264fad5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -112,6 +112,7 @@ #define MT7996_CRIT_TEMP 110 #define MT7996_MAX_TEMP 120 +#define MT7996_MAX_HIF_RXD_IN_PG 5 #define MT7996_RRO_MSDU_PG_HASH_SIZE 127 #define MT7996_RRO_MAX_SESSION 1024 #define MT7996_RRO_WINDOW_MAX_LEN 1024 @@ -298,6 +299,36 @@ struct mt7996_wed_rro_session_id { u16 id; }; +struct mt7996_msdu_page { + struct list_head list; + + struct mt76_queue *q; + dma_addr_t dma_addr; + void *buf; +}; + +/* data1 */ +#define RRO_HIF_DATA1_LS_MASK BIT(30) +#define RRO_HIF_DATA1_SDL_MASK GENMASK(29, 16) +/* data4 */ +#define RRO_HIF_DATA4_RX_TOKEN_ID_MASK GENMASK(15, 0) +struct mt7996_rro_hif { + __le32 data0; + __le32 data1; + __le32 data2; + __le32 data3; + __le32 data4; + __le32 data5; +}; + +#define MSDU_PAGE_INFO_OWNER_MASK BIT(31) +#define MSDU_PAGE_INFO_PG_HIGH_MASK GENMASK(3, 0) +struct mt7996_msdu_page_info { + struct mt7996_rro_hif rxd[MT7996_MAX_HIF_RXD_IN_PG]; + __le32 pg_low; + __le32 data; +}; + struct mt7996_phy { struct mt76_phy *mt76; struct mt7996_dev *dev; @@ -415,6 +446,9 @@ struct mt7996_dev { struct work_struct work; struct list_head poll_list; spinlock_t lock; + + struct list_head page_cache; + struct list_head page_map[MT7996_RRO_MSDU_PG_HASH_SIZE]; } wed_rro; bool ibf; @@ -772,6 +806,10 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, void mt7996_tx_token_put(struct mt7996_dev *dev); void mt7996_queue_rx_skb(struct mt76_dev *mdev, enum mt76_rxq_id q, struct sk_buff *skb, u32 *info); +void mt7996_rro_msdu_page_map_free(struct mt7996_dev *dev); +int mt7996_rro_msdu_page_add(struct mt76_dev *mdev, struct mt76_queue *q, + dma_addr_t dma_addr, void *data); +void mt7996_rro_rx_process(struct mt76_dev *mdev, void *data); bool mt7996_rx_check(struct mt76_dev *mdev, void *data, int len); void mt7996_stats_work(struct work_struct *work); int mt76_dfs_start_rdd(struct mt7996_dev *dev, bool force); From 1a7c1bffd33bc32e59cefb66f236d7170eb15b90 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 9 Sep 2025 11:45:21 +0200 Subject: [PATCH 073/167] wifi: mt76: Add rx_queue_init callback Introduce rx_queue_init DMA callback. This is a preliminary patch to configure RRO RX queues and decouple RRO logic from WED support. Co-developed-by: Rex Lu Signed-off-by: Rex Lu Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250909-mt7996-rro-rework-v5-13-7d66f6eb7795@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.c | 14 +++++++++++--- drivers/net/wireless/mediatek/mt76/mt76.h | 4 ++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index b8bb8cdfb69b..081a3f5d3878 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -937,6 +937,15 @@ int mt76_dma_rx_poll(struct napi_struct *napi, int budget) } EXPORT_SYMBOL_GPL(mt76_dma_rx_poll); +static void +mt76_dma_rx_queue_init(struct mt76_dev *dev, enum mt76_rxq_id qid, + int (*poll)(struct napi_struct *napi, int budget)) +{ + netif_napi_add(dev->napi_dev, &dev->napi[qid], poll); + mt76_dma_rx_fill_buf(dev, &dev->q_rx[qid], false); + napi_enable(&dev->napi[qid]); +} + static int mt76_dma_init(struct mt76_dev *dev, int (*poll)(struct napi_struct *napi, int budget)) @@ -973,9 +982,7 @@ mt76_dma_init(struct mt76_dev *dev, mt76_queue_is_wed_rro(&dev->q_rx[i])) continue; - netif_napi_add(dev->napi_dev, &dev->napi[i], poll); - mt76_dma_rx_fill_buf(dev, &dev->q_rx[i], false); - napi_enable(&dev->napi[i]); + mt76_dma_rx_queue_init(dev, i, poll); } return 0; @@ -988,6 +995,7 @@ static const struct mt76_queue_ops mt76_dma_ops = { .tx_queue_skb_raw = mt76_dma_tx_queue_skb_raw, .tx_queue_skb = mt76_dma_tx_queue_skb, .tx_cleanup = mt76_dma_tx_cleanup, + .rx_queue_init = mt76_dma_rx_queue_init, .rx_cleanup = mt76_dma_rx_cleanup, .rx_reset = mt76_dma_rx_reset, .kick = mt76_dma_kick_queue, diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 76f274ad9d30..0e38334a9004 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -286,6 +286,9 @@ struct mt76_queue_ops { void (*tx_cleanup)(struct mt76_dev *dev, struct mt76_queue *q, bool flush); + void (*rx_queue_init)(struct mt76_dev *dev, enum mt76_rxq_id qid, + int (*poll)(struct napi_struct *napi, int budget)); + void (*rx_cleanup)(struct mt76_dev *dev, struct mt76_queue *q); void (*kick)(struct mt76_dev *dev, struct mt76_queue *q); @@ -1221,6 +1224,7 @@ static inline int mt76_wed_dma_setup(struct mt76_dev *dev, struct mt76_queue *q, #define mt76_tx_queue_skb(dev, ...) (dev)->mt76.queue_ops->tx_queue_skb(&((dev)->mphy), __VA_ARGS__) #define mt76_queue_rx_reset(dev, ...) (dev)->mt76.queue_ops->rx_reset(&((dev)->mt76), __VA_ARGS__) #define mt76_queue_tx_cleanup(dev, ...) (dev)->mt76.queue_ops->tx_cleanup(&((dev)->mt76), __VA_ARGS__) +#define mt76_queue_rx_init(dev, ...) (dev)->mt76.queue_ops->rx_queue_init(&((dev)->mt76), __VA_ARGS__) #define mt76_queue_rx_cleanup(dev, ...) (dev)->mt76.queue_ops->rx_cleanup(&((dev)->mt76), __VA_ARGS__) #define mt76_queue_kick(dev, ...) (dev)->mt76.queue_ops->kick(&((dev)->mt76), __VA_ARGS__) #define mt76_queue_reset(dev, ...) (dev)->mt76.queue_ops->reset_q(&((dev)->mt76), __VA_ARGS__) From 7b3c83dd873707f1b70e251bd904d5c3e949d5fb Mon Sep 17 00:00:00 2001 From: Rex Lu Date: Tue, 9 Sep 2025 11:45:22 +0200 Subject: [PATCH 074/167] wifi: mt76: mt7996: Decouple RRO logic from WED support Decouple RRO logic from WED support in MT7996 driver in order to reuse it when WED module is not available. Signed-off-by: Rex Lu Co-developed-by: Lorenzo Bianconi Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250909-mt7996-rro-rework-v5-14-7d66f6eb7795@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.c | 54 +++++++++++--- drivers/net/wireless/mediatek/mt76/dma.h | 7 +- drivers/net/wireless/mediatek/mt76/mt76.h | 10 +-- .../net/wireless/mediatek/mt76/mt7996/dma.c | 73 ++++++++++++++----- .../net/wireless/mediatek/mt76/mt7996/init.c | 60 +++++++-------- .../net/wireless/mediatek/mt76/mt7996/mac.c | 14 +--- .../wireless/mediatek/mt76/mt7996/mt7996.h | 2 - .../net/wireless/mediatek/mt76/mt7996/regs.h | 1 + 8 files changed, 140 insertions(+), 81 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index 081a3f5d3878..fc30a8ea54ca 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -224,9 +224,9 @@ mt76_dma_add_rx_buf(struct mt76_dev *dev, struct mt76_queue *q, { struct mt76_queue_entry *entry = &q->entry[q->head]; struct mt76_txwi_cache *txwi = NULL; + u32 buf1 = 0, ctrl, info = 0; struct mt76_desc *desc; int idx = q->head; - u32 buf1 = 0, ctrl; int rx_token; if (mt76_queue_is_wed_rro_ind(q)) { @@ -243,7 +243,7 @@ mt76_dma_add_rx_buf(struct mt76_dev *dev, struct mt76_queue *q, buf1 = FIELD_PREP(MT_DMA_CTL_SDP0_H, buf->addr >> 32); #endif - if (mt76_queue_is_wed_rx(q)) { + if (mt76_queue_is_wed_rx(q) || mt76_queue_is_wed_rro_data(q)) { txwi = mt76_get_rxwi(dev); if (!txwi) return -ENOMEM; @@ -260,10 +260,22 @@ mt76_dma_add_rx_buf(struct mt76_dev *dev, struct mt76_queue *q, txwi->qid = q - dev->q_rx; } + if (mt76_queue_is_wed_rro_msdu_pg(q) && + dev->drv->rx_rro_add_msdu_page) { + if (dev->drv->rx_rro_add_msdu_page(dev, q, buf->addr, data)) + return -ENOMEM; + } + + if (q->flags & MT_QFLAG_WED_RRO_EN) { + info |= FIELD_PREP(MT_DMA_MAGIC_MASK, q->magic_cnt); + if ((q->head + 1) == q->ndesc) + q->magic_cnt = (q->magic_cnt + 1) % MT_DMA_MAGIC_CNT; + } + WRITE_ONCE(desc->buf0, cpu_to_le32(buf->addr)); WRITE_ONCE(desc->buf1, cpu_to_le32(buf1)); WRITE_ONCE(desc->ctrl, cpu_to_le32(ctrl)); - WRITE_ONCE(desc->info, 0); + WRITE_ONCE(desc->info, cpu_to_le32(info)); done: entry->dma_addr[0] = buf->addr; @@ -424,7 +436,7 @@ mt76_dma_get_buf(struct mt76_dev *dev, struct mt76_queue *q, int idx, u32 ctrl, desc_info, buf1; void *buf = e->buf; - if (mt76_queue_is_wed_rro_ind(q)) + if (mt76_queue_is_wed_rro(q)) goto done; ctrl = le32_to_cpu(READ_ONCE(desc->ctrl)); @@ -480,15 +492,27 @@ mt76_dma_dequeue(struct mt76_dev *dev, struct mt76_queue *q, bool flush, return NULL; if (mt76_queue_is_wed_rro_data(q) || mt76_queue_is_wed_rro_msdu_pg(q)) - return NULL; + goto done; - if (!mt76_queue_is_wed_rro_ind(q)) { + if (mt76_queue_is_wed_rro_ind(q)) { + struct mt76_wed_rro_ind *cmd; + + if (flush) + goto done; + + cmd = q->entry[idx].buf; + if (cmd->magic_cnt != q->magic_cnt) + return NULL; + + if (q->tail == q->ndesc - 1) + q->magic_cnt = (q->magic_cnt + 1) % MT_DMA_WED_IND_CMD_CNT; + } else { if (flush) q->desc[idx].ctrl |= cpu_to_le32(MT_DMA_CTL_DMA_DONE); else if (!(q->desc[idx].ctrl & cpu_to_le32(MT_DMA_CTL_DMA_DONE))) return NULL; } - +done: q->tail = (q->tail + 1) % q->ndesc; q->queued--; @@ -837,8 +861,9 @@ mt76_dma_rx_process(struct mt76_dev *dev, struct mt76_queue *q, int budget) bool allow_direct = !mt76_queue_is_wed_rx(q); bool more; - if (IS_ENABLED(CONFIG_NET_MEDIATEK_SOC_WED) && - mt76_queue_is_wed_tx_free(q)) { + if ((q->flags & MT_QFLAG_WED_RRO_EN) || + (IS_ENABLED(CONFIG_NET_MEDIATEK_SOC_WED) && + mt76_queue_is_wed_tx_free(q))) { dma_idx = Q_READ(q, dma_idx); check_ddone = true; } @@ -860,6 +885,14 @@ mt76_dma_rx_process(struct mt76_dev *dev, struct mt76_queue *q, int budget) if (!data) break; + if (mt76_queue_is_wed_rro_ind(q) && dev->drv->rx_rro_ind_process) + dev->drv->rx_rro_ind_process(dev, data); + + if (mt76_queue_is_wed_rro(q)) { + done++; + continue; + } + if (drop) goto free_frag; @@ -978,8 +1011,7 @@ mt76_dma_init(struct mt76_dev *dev, init_completion(&dev->mmio.wed_reset_complete); mt76_for_each_q_rx(dev, i) { - if (mtk_wed_device_active(&dev->mmio.wed) && - mt76_queue_is_wed_rro(&dev->q_rx[i])) + if (mt76_queue_is_wed_rro(&dev->q_rx[i])) continue; mt76_dma_rx_queue_init(dev, i, poll); diff --git a/drivers/net/wireless/mediatek/mt76/dma.h b/drivers/net/wireless/mediatek/mt76/dma.h index 320d2cbbbd45..f53c21368580 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.h +++ b/drivers/net/wireless/mediatek/mt76/dma.h @@ -31,7 +31,12 @@ #define MT_DMA_CTL_PN_CHK_FAIL BIT(13) #define MT_DMA_CTL_VER_MASK BIT(7) -#define MT_DMA_RRO_EN BIT(13) +#define MT_DMA_SDP0 GENMASK(15, 0) +#define MT_DMA_TOKEN_ID GENMASK(31, 16) +#define MT_DMA_MAGIC_MASK GENMASK(31, 28) +#define MT_DMA_RRO_EN BIT(13) + +#define MT_DMA_MAGIC_CNT 16 #define MT_DMA_WED_IND_CMD_CNT 8 #define MT_DMA_WED_IND_REASON GENMASK(15, 12) diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 0e38334a9004..ed267d66a9c4 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -232,6 +232,7 @@ struct mt76_queue { u8 buf_offset; u16 flags; + u8 magic_cnt; struct mtk_wed_device *wed; u32 wed_regs; @@ -1808,13 +1809,8 @@ static inline bool mt76_queue_is_wed_rro_msdu_pg(struct mt76_queue *q) static inline bool mt76_queue_is_wed_rx(struct mt76_queue *q) { - if (!(q->flags & MT_QFLAG_WED)) - return false; - - return FIELD_GET(MT_QFLAG_WED_TYPE, q->flags) == MT76_WED_Q_RX || - mt76_queue_is_wed_rro_ind(q) || mt76_queue_is_wed_rro_data(q) || - mt76_queue_is_wed_rro_msdu_pg(q); - + return (q->flags & MT_QFLAG_WED) && + FIELD_GET(MT_QFLAG_WED_TYPE, q->flags) == MT76_WED_Q_RX; } struct mt76_txwi_cache * diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c index 2412767bfaa7..9663029fa087 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c @@ -325,7 +325,7 @@ void mt7996_dma_start(struct mt7996_dev *dev, bool reset, bool wed_reset) } if (mt7996_band_valid(dev, MT_BAND2)) - irq_mask |= MT_INT_BAND2_RX_DONE; + irq_mask |= MT_INT_BAND2_RX_DONE | MT_INT_TX_RX_DONE_EXT; if (mtk_wed_device_active(wed) && wed_reset) { u32 wed_irq_mask = irq_mask; @@ -482,7 +482,6 @@ static void mt7996_dma_enable(struct mt7996_dev *dev, bool reset) mt7996_dma_start(dev, reset, true); } -#ifdef CONFIG_NET_MEDIATEK_SOC_WED int mt7996_dma_rro_init(struct mt7996_dev *dev) { struct mt76_dev *mdev = &dev->mt76; @@ -491,7 +490,9 @@ int mt7996_dma_rro_init(struct mt7996_dev *dev) /* ind cmd */ mdev->q_rx[MT_RXQ_RRO_IND].flags = MT_WED_RRO_Q_IND; - mdev->q_rx[MT_RXQ_RRO_IND].wed = &mdev->mmio.wed; + if (mtk_wed_device_active(&mdev->mmio.wed) && + mtk_wed_get_rx_capa(&mdev->mmio.wed)) + mdev->q_rx[MT_RXQ_RRO_IND].wed = &mdev->mmio.wed; ret = mt76_queue_alloc(dev, &mdev->q_rx[MT_RXQ_RRO_IND], MT_RXQ_ID(MT_RXQ_RRO_IND), MT7996_RX_RING_SIZE, @@ -502,7 +503,9 @@ int mt7996_dma_rro_init(struct mt7996_dev *dev) /* rx msdu page queue for band0 */ mdev->q_rx[MT_RXQ_MSDU_PAGE_BAND0].flags = MT_WED_RRO_Q_MSDU_PG(0) | MT_QFLAG_WED_RRO_EN; - mdev->q_rx[MT_RXQ_MSDU_PAGE_BAND0].wed = &mdev->mmio.wed; + if (mtk_wed_device_active(&mdev->mmio.wed) && + mtk_wed_get_rx_capa(&mdev->mmio.wed)) + mdev->q_rx[MT_RXQ_MSDU_PAGE_BAND0].wed = &mdev->mmio.wed; ret = mt76_queue_alloc(dev, &mdev->q_rx[MT_RXQ_MSDU_PAGE_BAND0], MT_RXQ_ID(MT_RXQ_MSDU_PAGE_BAND0), MT7996_RX_RING_SIZE, @@ -515,7 +518,9 @@ int mt7996_dma_rro_init(struct mt7996_dev *dev) /* rx msdu page queue for band1 */ mdev->q_rx[MT_RXQ_MSDU_PAGE_BAND1].flags = MT_WED_RRO_Q_MSDU_PG(1) | MT_QFLAG_WED_RRO_EN; - mdev->q_rx[MT_RXQ_MSDU_PAGE_BAND1].wed = &mdev->mmio.wed; + if (mtk_wed_device_active(&mdev->mmio.wed) && + mtk_wed_get_rx_capa(&mdev->mmio.wed)) + mdev->q_rx[MT_RXQ_MSDU_PAGE_BAND1].wed = &mdev->mmio.wed; ret = mt76_queue_alloc(dev, &mdev->q_rx[MT_RXQ_MSDU_PAGE_BAND1], MT_RXQ_ID(MT_RXQ_MSDU_PAGE_BAND1), MT7996_RX_RING_SIZE, @@ -529,7 +534,9 @@ int mt7996_dma_rro_init(struct mt7996_dev *dev) /* rx msdu page queue for band2 */ mdev->q_rx[MT_RXQ_MSDU_PAGE_BAND2].flags = MT_WED_RRO_Q_MSDU_PG(2) | MT_QFLAG_WED_RRO_EN; - mdev->q_rx[MT_RXQ_MSDU_PAGE_BAND2].wed = &mdev->mmio.wed; + if (mtk_wed_device_active(&mdev->mmio.wed) && + mtk_wed_get_rx_capa(&mdev->mmio.wed)) + mdev->q_rx[MT_RXQ_MSDU_PAGE_BAND2].wed = &mdev->mmio.wed; ret = mt76_queue_alloc(dev, &mdev->q_rx[MT_RXQ_MSDU_PAGE_BAND2], MT_RXQ_ID(MT_RXQ_MSDU_PAGE_BAND2), MT7996_RX_RING_SIZE, @@ -539,15 +546,35 @@ int mt7996_dma_rro_init(struct mt7996_dev *dev) return ret; } - irq_mask = mdev->mmio.irqmask | MT_INT_RRO_RX_DONE | - MT_INT_TX_DONE_BAND2; - mt76_wr(dev, MT_INT_MASK_CSR, irq_mask); - mtk_wed_device_start_hw_rro(&mdev->mmio.wed, irq_mask, false); - mt7996_irq_enable(dev, irq_mask); + if (mtk_wed_device_active(&mdev->mmio.wed)) { + irq_mask = mdev->mmio.irqmask | + MT_INT_TX_DONE_BAND2; + + mt76_wr(dev, MT_INT_MASK_CSR, irq_mask); + mtk_wed_device_start_hw_rro(&mdev->mmio.wed, irq_mask, false); + mt7996_irq_enable(dev, irq_mask); + } else { + if (is_mt7996(&dev->mt76)) { + mt76_queue_rx_init(dev, MT_RXQ_MSDU_PAGE_BAND1, + mt76_dma_rx_poll); + mt76_queue_rx_init(dev, MT_RXQ_MSDU_PAGE_BAND2, + mt76_dma_rx_poll); + mt76_queue_rx_init(dev, MT_RXQ_RRO_BAND2, + mt76_dma_rx_poll); + } else { + mt76_queue_rx_init(dev, MT_RXQ_RRO_BAND1, + mt76_dma_rx_poll); + } + + mt76_queue_rx_init(dev, MT_RXQ_RRO_BAND0, mt76_dma_rx_poll); + mt76_queue_rx_init(dev, MT_RXQ_RRO_IND, mt76_dma_rx_poll); + mt76_queue_rx_init(dev, MT_RXQ_MSDU_PAGE_BAND0, + mt76_dma_rx_poll); + mt7996_irq_enable(dev, MT_INT_RRO_RX_DONE); + } return 0; } -#endif /* CONFIG_NET_MEDIATEK_SOC_WED */ int mt7996_dma_init(struct mt7996_dev *dev) { @@ -738,12 +765,12 @@ int mt7996_dma_init(struct mt7996_dev *dev) } } - if (mtk_wed_device_active(wed) && mtk_wed_get_rx_capa(wed) && - dev->has_rro) { + if (dev->has_rro) { /* rx rro data queue for band0 */ dev->mt76.q_rx[MT_RXQ_RRO_BAND0].flags = MT_WED_RRO_Q_DATA(0) | MT_QFLAG_WED_RRO_EN; - dev->mt76.q_rx[MT_RXQ_RRO_BAND0].wed = wed; + if (mtk_wed_device_active(wed) && mtk_wed_get_rx_capa(wed)) + dev->mt76.q_rx[MT_RXQ_RRO_BAND0].wed = wed; ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_RRO_BAND0], MT_RXQ_ID(MT_RXQ_RRO_BAND0), MT7996_RX_RING_SIZE, @@ -755,7 +782,9 @@ int mt7996_dma_init(struct mt7996_dev *dev) if (is_mt7992(&dev->mt76)) { dev->mt76.q_rx[MT_RXQ_RRO_BAND1].flags = MT_WED_RRO_Q_DATA(1) | MT_QFLAG_WED_RRO_EN; - dev->mt76.q_rx[MT_RXQ_RRO_BAND1].wed = wed; + if (mtk_wed_device_active(wed) && + mtk_wed_get_rx_capa(wed)) + dev->mt76.q_rx[MT_RXQ_RRO_BAND1].wed = wed; ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_RRO_BAND1], MT_RXQ_ID(MT_RXQ_RRO_BAND1), @@ -765,9 +794,11 @@ int mt7996_dma_init(struct mt7996_dev *dev) if (ret) return ret; } else { - /* tx free notify event from WA for band0 */ - dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0].flags = MT_WED_Q_TXFREE; - dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0].wed = wed; + if (mtk_wed_device_active(wed)) { + /* tx free notify event from WA for band0 */ + dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0].flags = MT_WED_Q_TXFREE; + dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0].wed = wed; + } ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0], @@ -783,7 +814,9 @@ int mt7996_dma_init(struct mt7996_dev *dev) /* rx rro data queue for band2 */ dev->mt76.q_rx[MT_RXQ_RRO_BAND2].flags = MT_WED_RRO_Q_DATA(1) | MT_QFLAG_WED_RRO_EN; - dev->mt76.q_rx[MT_RXQ_RRO_BAND2].wed = wed; + if (mtk_wed_device_active(wed) && + mtk_wed_get_rx_capa(wed)) + dev->mt76.q_rx[MT_RXQ_RRO_BAND2].wed = wed; ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_RRO_BAND2], MT_RXQ_ID(MT_RXQ_RRO_BAND2), MT7996_RX_RING_SIZE, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 701efbf20d12..11ac0cef4a06 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -766,7 +766,6 @@ void mt7996_wfsys_reset(struct mt7996_dev *dev) void mt7996_rro_hw_init(struct mt7996_dev *dev) { -#ifdef CONFIG_NET_MEDIATEK_SOC_WED struct mtk_wed_device *wed = &dev->mt76.mmio.wed; u32 reg = MT_RRO_ADDR_ELEM_SEG_ADDR0; int i; @@ -809,18 +808,28 @@ void mt7996_rro_hw_init(struct mt7996_dev *dev) mt76_wr(dev, MT_RRO_ADDR_ARRAY_BASE1, MT_RRO_ADDR_ARRAY_ELEM_ADDR_SEG_MODE); } - wed->wlan.ind_cmd.win_size = ffs(MT7996_RRO_WINDOW_MAX_LEN) - 6; - if (is_mt7996(&dev->mt76)) - wed->wlan.ind_cmd.particular_sid = MT7996_RRO_MAX_SESSION; - else - wed->wlan.ind_cmd.particular_sid = 1; - wed->wlan.ind_cmd.particular_se_phys = dev->wed_rro.session.phy_addr; - wed->wlan.ind_cmd.se_group_nums = MT7996_RRO_ADDR_ELEM_LEN; - wed->wlan.ind_cmd.ack_sn_addr = MT_RRO_ACK_SN_CTRL; - mt76_wr(dev, MT_RRO_IND_CMD_SIGNATURE_BASE0, 0x15010e00); - mt76_set(dev, MT_RRO_IND_CMD_SIGNATURE_BASE1, - MT_RRO_IND_CMD_SIGNATURE_BASE1_EN); +#ifdef CONFIG_NET_MEDIATEK_SOC_WED + if (mtk_wed_device_active(wed) && mtk_wed_get_rx_capa(wed)) { + wed->wlan.ind_cmd.win_size = ffs(MT7996_RRO_WINDOW_MAX_LEN) - 6; + if (is_mt7996(&dev->mt76)) + wed->wlan.ind_cmd.particular_sid = MT7996_RRO_MAX_SESSION; + else + wed->wlan.ind_cmd.particular_sid = 1; + wed->wlan.ind_cmd.particular_se_phys = dev->wed_rro.session.phy_addr; + wed->wlan.ind_cmd.se_group_nums = MT7996_RRO_ADDR_ELEM_LEN; + wed->wlan.ind_cmd.ack_sn_addr = MT_RRO_ACK_SN_CTRL; + } +#endif /* CONFIG_NET_MEDIATEK_SOC_WED */ + + if (mtk_wed_device_active(wed) && mtk_wed_get_rx_capa(wed)) { + mt76_wr(dev, MT_RRO_IND_CMD_SIGNATURE_BASE0, 0x15010e00); + mt76_set(dev, MT_RRO_IND_CMD_SIGNATURE_BASE1, + MT_RRO_IND_CMD_SIGNATURE_BASE1_EN); + } else { + mt76_wr(dev, MT_RRO_IND_CMD_SIGNATURE_BASE0, 0); + mt76_wr(dev, MT_RRO_IND_CMD_SIGNATURE_BASE1, 0); + } /* particular session configure */ /* use max session idx + 1 as particular session id */ @@ -850,14 +859,11 @@ void mt7996_rro_hw_init(struct mt7996_dev *dev) /* interrupt enable */ mt76_wr(dev, MT_RRO_HOST_INT_ENA, MT_RRO_HOST_INT_ENA_HOST_RRO_DONE_ENA); -#endif } static int mt7996_wed_rro_init(struct mt7996_dev *dev) { -#ifdef CONFIG_NET_MEDIATEK_SOC_WED u32 val = FIELD_PREP(WED_RRO_ADDR_SIGNATURE_MASK, 0xff); - struct mtk_wed_device *wed = &dev->mt76.mmio.wed; struct mt7996_wed_rro_addr *addr; void *ptr; int i; @@ -865,9 +871,6 @@ static int mt7996_wed_rro_init(struct mt7996_dev *dev) if (!dev->has_rro) return 0; - if (!mtk_wed_device_active(wed)) - return 0; - for (i = 0; i < ARRAY_SIZE(dev->wed_rro.ba_bitmap); i++) { ptr = dmam_alloc_coherent(dev->mt76.dma_dev, MT7996_RRO_BA_BITMAP_CR_SIZE, @@ -899,8 +902,15 @@ static int mt7996_wed_rro_init(struct mt7996_dev *dev) addr++; } - wed->wlan.ind_cmd.addr_elem_phys[i] = - dev->wed_rro.addr_elem[i].phy_addr; +#ifdef CONFIG_NET_MEDIATEK_SOC_WED + if (mtk_wed_device_active(&dev->mt76.mmio.wed) && + mtk_wed_get_rx_capa(&dev->mt76.mmio.wed)) { + struct mtk_wed_device *wed = &dev->mt76.mmio.wed; + + wed->wlan.ind_cmd.addr_elem_phys[i] = + dev->wed_rro.addr_elem[i].phy_addr; + } +#endif /* CONFIG_NET_MEDIATEK_SOC_WED */ } for (i = 0; i < ARRAY_SIZE(dev->wed_rro.msdu_pg); i++) { @@ -934,22 +944,15 @@ static int mt7996_wed_rro_init(struct mt7996_dev *dev) mt7996_rro_hw_init(dev); return mt7996_dma_rro_init(dev); -#else - return 0; -#endif } static void mt7996_wed_rro_free(struct mt7996_dev *dev) { -#ifdef CONFIG_NET_MEDIATEK_SOC_WED int i; if (!dev->has_rro) return; - if (!mtk_wed_device_active(&dev->mt76.mmio.wed)) - return; - for (i = 0; i < ARRAY_SIZE(dev->wed_rro.ba_bitmap); i++) { if (!dev->wed_rro.ba_bitmap[i].ptr) continue; @@ -989,12 +992,10 @@ static void mt7996_wed_rro_free(struct mt7996_dev *dev) sizeof(struct mt7996_wed_rro_addr), dev->wed_rro.session.ptr, dev->wed_rro.session.phy_addr); -#endif } static void mt7996_wed_rro_work(struct work_struct *work) { -#ifdef CONFIG_NET_MEDIATEK_SOC_WED u32 val = FIELD_PREP(WED_RRO_ADDR_SIGNATURE_MASK, 0xff); struct mt7996_dev *dev; LIST_HEAD(list); @@ -1038,7 +1039,6 @@ reset: out: kfree(e); } -#endif } static int mt7996_variant_type_init(struct mt7996_dev *dev) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 894338cc868e..f7b491b034db 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2267,7 +2267,6 @@ mt7996_mac_restart(struct mt7996_dev *dev) if (mtk_wed_device_active(&dev->mt76.mmio.wed) && dev->has_rro) { u32 wed_irq_mask = dev->mt76.mmio.irqmask | - MT_INT_RRO_RX_DONE | MT_INT_TX_DONE_BAND2; mt7996_rro_hw_init(dev); @@ -2494,19 +2493,14 @@ void mt7996_mac_reset_work(struct work_struct *work) /* enable DMA Tx/Tx and interrupt */ mt7996_dma_start(dev, false, false); + if (!is_mt7996(&dev->mt76) && dev->has_rro) + mt76_wr(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_RRO_RX_DONE | MT_INT_TX_DONE_BAND2 | + u32 wed_irq_mask = MT_INT_TX_DONE_BAND2 | dev->mt76.mmio.irqmask; - if (mtk_wed_get_rx_capa(&dev->mt76.mmio.wed)) - wed_irq_mask &= ~MT_INT_RX_DONE_RRO_IND; - mt76_wr(dev, MT_INT_MASK_CSR, wed_irq_mask); - - if (is_mt7992(&dev->mt76) && dev->has_rro) - mt76_wr(dev, MT_RRO_3_0_EMU_CONF, - MT_RRO_3_0_EMU_CONF_EN_MASK); - mtk_wed_device_start_hw_rro(&dev->mt76.mmio.wed, wed_irq_mask, true); mt7996_irq_enable(dev, wed_irq_mask); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 1b3ec264fad5..6467dc9ad44d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -844,8 +844,6 @@ u32 mt7996_wed_init_buf(void *ptr, dma_addr_t phys, int token_id); int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir); #endif -#ifdef CONFIG_NET_MEDIATEK_SOC_WED int mt7996_dma_rro_init(struct mt7996_dev *dev); -#endif /* CONFIG_NET_MEDIATEK_SOC_WED */ #endif diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h index d239fa3f375f..9ca1490c2cf3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h @@ -572,6 +572,7 @@ enum offs_rev { #define MT_INT_RRO_RX_DONE (MT_INT_RX(MT_RXQ_RRO_BAND0) | \ MT_INT_RX(MT_RXQ_RRO_BAND1) | \ MT_INT_RX(MT_RXQ_RRO_BAND2) | \ + MT_INT_RX(MT_RXQ_RRO_IND) | \ MT_INT_RX(MT_RXQ_MSDU_PAGE_BAND0) | \ MT_INT_RX(MT_RXQ_MSDU_PAGE_BAND1) | \ MT_INT_RX(MT_RXQ_MSDU_PAGE_BAND2)) From e50d4d710efd2dbc46965e9608bbb502bcfc5c99 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 9 Sep 2025 11:45:23 +0200 Subject: [PATCH 075/167] wifi: mt76: Add mt76_dma_get_rxdmad_c_buf utility routione Introduce mt76_dma_get_rxdmad_c_buf routine to process packets received by HW-RRO v3.1 module. This is a preliminary patch to introduce SW path for HW-RRO v3.1 module available on MT7992 chipset. Co-developed-by: Rex Lu Signed-off-by: Rex Lu Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250909-mt7996-rro-rework-v5-15-7d66f6eb7795@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.c | 58 +++++++++++++++++++++-- drivers/net/wireless/mediatek/mt76/dma.h | 15 ++++++ drivers/net/wireless/mediatek/mt76/mt76.h | 8 ++++ 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index fc30a8ea54ca..7ad4a3a4cf09 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -427,15 +427,61 @@ mt76_dma_tx_cleanup(struct mt76_dev *dev, struct mt76_queue *q, bool flush) wake_up(&dev->tx_wait); } +static void * +mt76_dma_get_rxdmad_c_buf(struct mt76_dev *dev, struct mt76_queue *q, + int idx, int *len, bool *more) +{ + struct mt76_queue_entry *e = &q->entry[idx]; + struct mt76_rro_rxdmad_c *dmad = e->buf; + u32 data1 = le32_to_cpu(dmad->data1); + u32 data2 = le32_to_cpu(dmad->data2); + struct mt76_txwi_cache *t; + u16 rx_token_id; + u8 ind_reason; + void *buf; + + rx_token_id = FIELD_GET(RRO_RXDMAD_DATA2_RX_TOKEN_ID_MASK, data2); + t = mt76_rx_token_release(dev, rx_token_id); + if (!t) + return ERR_PTR(-EAGAIN); + + q = &dev->q_rx[t->qid]; + dma_sync_single_for_cpu(dev->dma_dev, t->dma_addr, + SKB_WITH_OVERHEAD(q->buf_size), + page_pool_get_dma_dir(q->page_pool)); + + if (len) + *len = FIELD_GET(RRO_RXDMAD_DATA1_SDL0_MASK, data1); + if (more) + *more = !FIELD_GET(RRO_RXDMAD_DATA1_LS_MASK, data1); + + buf = t->ptr; + ind_reason = FIELD_GET(RRO_RXDMAD_DATA2_IND_REASON_MASK, data2); + if (ind_reason == MT_DMA_WED_IND_REASON_REPEAT || + ind_reason == MT_DMA_WED_IND_REASON_OLDPKT) { + mt76_put_page_pool_buf(buf, false); + buf = ERR_PTR(-EAGAIN); + } + t->ptr = NULL; + t->dma_addr = 0; + + mt76_put_rxwi(dev, t); + + return buf; +} + static void * mt76_dma_get_buf(struct mt76_dev *dev, struct mt76_queue *q, int idx, - int *len, u32 *info, bool *more, bool *drop) + int *len, u32 *info, bool *more, bool *drop, bool flush) { struct mt76_queue_entry *e = &q->entry[idx]; struct mt76_desc *desc = &q->desc[idx]; u32 ctrl, desc_info, buf1; void *buf = e->buf; + if (mt76_queue_is_wed_rro_rxdmad_c(q) && !flush) + buf = mt76_dma_get_rxdmad_c_buf(dev, q, idx, len, more); + if (mt76_queue_is_wed_rro(q)) goto done; @@ -516,7 +562,7 @@ done: q->tail = (q->tail + 1) % q->ndesc; q->queued--; - return mt76_dma_get_buf(dev, q, idx, len, info, more, drop); + return mt76_dma_get_buf(dev, q, idx, len, info, more, drop, flush); } static int @@ -885,10 +931,16 @@ mt76_dma_rx_process(struct mt76_dev *dev, struct mt76_queue *q, int budget) if (!data) break; + if (PTR_ERR(data) == -EAGAIN) { + done++; + continue; + } + if (mt76_queue_is_wed_rro_ind(q) && dev->drv->rx_rro_ind_process) dev->drv->rx_rro_ind_process(dev, data); - if (mt76_queue_is_wed_rro(q)) { + if (mt76_queue_is_wed_rro(q) && + !mt76_queue_is_wed_rro_rxdmad_c(q)) { done++; continue; } diff --git a/drivers/net/wireless/mediatek/mt76/dma.h b/drivers/net/wireless/mediatek/mt76/dma.h index f53c21368580..17a80e1757fc 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.h +++ b/drivers/net/wireless/mediatek/mt76/dma.h @@ -58,6 +58,21 @@ struct mt76_wed_rro_desc { __le32 buf1; } __packed __aligned(4); +/* data1 */ +#define RRO_RXDMAD_DATA1_LS_MASK BIT(30) +#define RRO_RXDMAD_DATA1_SDL0_MASK GENMASK(29, 16) +/* data2 */ +#define RRO_RXDMAD_DATA2_RX_TOKEN_ID_MASK GENMASK(31, 16) +#define RRO_RXDMAD_DATA2_IND_REASON_MASK GENMASK(15, 12) +/* data3 */ +#define RRO_RXDMAD_DATA3_MAGIC_CNT_MASK GENMASK(31, 28) +struct mt76_rro_rxdmad_c { + __le32 data0; + __le32 data1; + __le32 data2; + __le32 data3; +}; + enum mt76_qsel { MT_QSEL_MGMT, MT_QSEL_HCCA, diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index ed267d66a9c4..84e5537bffae 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -45,6 +45,7 @@ #define MT_WED_RRO_Q_DATA(_n) __MT_WED_RRO_Q(MT76_WED_RRO_Q_DATA, _n) #define MT_WED_RRO_Q_MSDU_PG(_n) __MT_WED_RRO_Q(MT76_WED_RRO_Q_MSDU_PG, _n) #define MT_WED_RRO_Q_IND __MT_WED_RRO_Q(MT76_WED_RRO_Q_IND, 0) +#define MT_WED_RRO_Q_RXDMAD_C __MT_WED_RRO_Q(MT76_WED_RRO_Q_RXDMAD_C, 0) struct mt76_dev; struct mt76_phy; @@ -71,6 +72,7 @@ enum mt76_wed_type { MT76_WED_RRO_Q_DATA, MT76_WED_RRO_Q_MSDU_PG, MT76_WED_RRO_Q_IND, + MT76_WED_RRO_Q_RXDMAD_C, }; struct mt76_bus_ops { @@ -1794,6 +1796,12 @@ static inline bool mt76_queue_is_wed_rro_ind(struct mt76_queue *q) FIELD_GET(MT_QFLAG_WED_TYPE, q->flags) == MT76_WED_RRO_Q_IND; } +static inline bool mt76_queue_is_wed_rro_rxdmad_c(struct mt76_queue *q) +{ + return mt76_queue_is_wed_rro(q) && + FIELD_GET(MT_QFLAG_WED_TYPE, q->flags) == MT76_WED_RRO_Q_RXDMAD_C; +} + static inline bool mt76_queue_is_wed_rro_data(struct mt76_queue *q) { return mt76_queue_is_wed_rro(q) && From 3a29164425e927eaf4dfe21512c5de3b8339b1eb Mon Sep 17 00:00:00 2001 From: Rex Lu Date: Tue, 9 Sep 2025 11:45:24 +0200 Subject: [PATCH 076/167] wifi: mt76: mt7996: Add SW path for HW-RRO v3.1 Introduce HW-RRO v3.1 support to be reused when Wireless Ethernet Dispatcher (WED) is not available. HW-RRO v3.1 is supported by MT7992 chipset. Signed-off-by: Rex Lu Co-developed-by: Lorenzo Bianconi Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250909-mt7996-rro-rework-v5-16-7d66f6eb7795@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.c | 86 +++++++-- drivers/net/wireless/mediatek/mt76/mt76.h | 16 ++ .../net/wireless/mediatek/mt76/mt7996/dma.c | 73 ++++++-- .../net/wireless/mediatek/mt76/mt7996/init.c | 176 +++++++++++------- .../net/wireless/mediatek/mt76/mt7996/mac.c | 5 +- .../net/wireless/mediatek/mt76/mt7996/mcu.c | 4 +- .../net/wireless/mediatek/mt76/mt7996/mmio.c | 11 +- .../wireless/mediatek/mt76/mt7996/mt7996.h | 23 ++- .../net/wireless/mediatek/mt76/mt7996/regs.h | 9 + 9 files changed, 295 insertions(+), 108 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index 7ad4a3a4cf09..5dc093b21838 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -185,6 +185,35 @@ mt76_free_pending_rxwi(struct mt76_dev *dev) } EXPORT_SYMBOL_GPL(mt76_free_pending_rxwi); +static void +mt76_dma_queue_magic_cnt_init(struct mt76_dev *dev, struct mt76_queue *q) +{ + if (!mt76_queue_is_wed_rro(q)) + return; + + q->magic_cnt = 0; + if (mt76_queue_is_wed_rro_ind(q)) { + struct mt76_wed_rro_desc *rro_desc; + int i; + + rro_desc = (struct mt76_wed_rro_desc *)q->desc; + for (i = 0; i < q->ndesc; i++) { + struct mt76_wed_rro_ind *cmd; + + cmd = (struct mt76_wed_rro_ind *)&rro_desc[i]; + cmd->magic_cnt = MT_DMA_WED_IND_CMD_CNT - 1; + } + } else if (mt76_queue_is_wed_rro_rxdmad_c(q)) { + struct mt76_rro_rxdmad_c *dmad = (void *)q->desc; + u32 data3 = FIELD_PREP(RRO_RXDMAD_DATA3_MAGIC_CNT_MASK, + MT_DMA_MAGIC_CNT - 1); + int i; + + for (i = 0; i < q->ndesc; i++) + dmad[i].data3 = cpu_to_le32(data3); + } +} + static void mt76_dma_sync_idx(struct mt76_dev *dev, struct mt76_queue *q) { @@ -203,7 +232,8 @@ void mt76_dma_queue_reset(struct mt76_dev *dev, struct mt76_queue *q, if (!q || !q->ndesc) return; - if (!mt76_queue_is_wed_rro_ind(q)) { + if (!mt76_queue_is_wed_rro_ind(q) && + !mt76_queue_is_wed_rro_rxdmad_c(q)) { int i; /* clear descriptors */ @@ -211,8 +241,12 @@ void mt76_dma_queue_reset(struct mt76_dev *dev, struct mt76_queue *q, q->desc[i].ctrl = cpu_to_le32(MT_DMA_CTL_DMA_DONE); } + mt76_dma_queue_magic_cnt_init(dev, q); if (reset_idx) { - Q_WRITE(q, cpu_idx, 0); + if (mt76_queue_is_emi(q)) + *q->emi_cpu_idx = 0; + else + Q_WRITE(q, cpu_idx, 0); Q_WRITE(q, dma_idx, 0); } mt76_dma_sync_idx(dev, q); @@ -235,6 +269,9 @@ mt76_dma_add_rx_buf(struct mt76_dev *dev, struct mt76_queue *q, rro_desc = (struct mt76_wed_rro_desc *)q->desc; data = &rro_desc[q->head]; goto done; + } else if (mt76_queue_is_wed_rro_rxdmad_c(q)) { + data = &q->desc[q->head]; + goto done; } desc = &q->desc[q->head]; @@ -384,7 +421,10 @@ static void mt76_dma_kick_queue(struct mt76_dev *dev, struct mt76_queue *q) { wmb(); - Q_WRITE(q, cpu_idx, q->head); + if (mt76_queue_is_emi(q)) + *q->emi_cpu_idx = cpu_to_le16(q->head); + else + Q_WRITE(q, cpu_idx, q->head); } static void @@ -552,6 +592,21 @@ mt76_dma_dequeue(struct mt76_dev *dev, struct mt76_queue *q, bool flush, if (q->tail == q->ndesc - 1) q->magic_cnt = (q->magic_cnt + 1) % MT_DMA_WED_IND_CMD_CNT; + } else if (mt76_queue_is_wed_rro_rxdmad_c(q)) { + struct mt76_rro_rxdmad_c *dmad; + u16 magic_cnt; + + if (flush) + goto done; + + dmad = q->entry[idx].buf; + magic_cnt = FIELD_GET(RRO_RXDMAD_DATA3_MAGIC_CNT_MASK, + le32_to_cpu(dmad->data3)); + if (magic_cnt != q->magic_cnt) + return NULL; + + if (q->tail == q->ndesc - 1) + q->magic_cnt = (q->magic_cnt + 1) % MT_DMA_MAGIC_CNT; } else { if (flush) q->desc[idx].ctrl |= cpu_to_le32(MT_DMA_CTL_DMA_DONE); @@ -713,7 +768,8 @@ mt76_dma_rx_fill_buf(struct mt76_dev *dev, struct mt76_queue *q, void *buf = NULL; int offset; - if (mt76_queue_is_wed_rro_ind(q)) + if (mt76_queue_is_wed_rro_ind(q) || + mt76_queue_is_wed_rro_rxdmad_c(q)) goto done; buf = mt76_get_page_pool_buf(q, &offset, q->buf_size); @@ -772,19 +828,7 @@ mt76_dma_alloc_queue(struct mt76_dev *dev, struct mt76_queue *q, if (!q->desc) return -ENOMEM; - if (mt76_queue_is_wed_rro_ind(q)) { - struct mt76_wed_rro_desc *rro_desc; - int i; - - rro_desc = (struct mt76_wed_rro_desc *)q->desc; - for (i = 0; i < q->ndesc; i++) { - struct mt76_wed_rro_ind *cmd; - - cmd = (struct mt76_wed_rro_ind *)&rro_desc[i]; - cmd->magic_cnt = MT_DMA_WED_IND_CMD_CNT - 1; - } - } - + mt76_dma_queue_magic_cnt_init(dev, q); size = q->ndesc * sizeof(*q->entry); q->entry = devm_kzalloc(dev->dev, size, GFP_KERNEL); if (!q->entry) @@ -804,7 +848,10 @@ mt76_dma_alloc_queue(struct mt76_dev *dev, struct mt76_queue *q, return 0; } - mt76_dma_queue_reset(dev, q, true); + /* HW specific driver is supposed to reset brand-new EMI queues since + * it needs to set cpu index pointer. + */ + mt76_dma_queue_reset(dev, q, !mt76_queue_is_emi(q)); return 0; } @@ -847,7 +894,8 @@ mt76_dma_rx_reset(struct mt76_dev *dev, enum mt76_rxq_id qid) if (!q->ndesc) return; - if (!mt76_queue_is_wed_rro_ind(q)) { + if (!mt76_queue_is_wed_rro_ind(q) && + !mt76_queue_is_wed_rro_rxdmad_c(q)) { int i; for (i = 0; i < q->ndesc; i++) diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 84e5537bffae..b102b6d798b8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -33,6 +33,7 @@ #define MT_QFLAG_WED BIT(5) #define MT_QFLAG_WED_RRO BIT(6) #define MT_QFLAG_WED_RRO_EN BIT(7) +#define MT_QFLAG_EMI_EN BIT(8) #define __MT_WED_Q(_type, _n) (MT_QFLAG_WED | \ FIELD_PREP(MT_QFLAG_WED_TYPE, _type) | \ @@ -75,6 +76,12 @@ enum mt76_wed_type { MT76_WED_RRO_Q_RXDMAD_C, }; +enum mt76_hwrro_mode { + MT76_HWRRO_OFF, + MT76_HWRRO_V3, + MT76_HWRRO_V3_1, +}; + struct mt76_bus_ops { u32 (*rr)(struct mt76_dev *dev, u32 offset); void (*wr)(struct mt76_dev *dev, u32 offset, u32 val); @@ -131,6 +138,7 @@ enum mt76_rxq_id { MT_RXQ_TXFREE_BAND1, MT_RXQ_TXFREE_BAND2, MT_RXQ_RRO_IND, + MT_RXQ_RRO_RXDMAD_C, __MT_RXQ_MAX }; @@ -236,6 +244,8 @@ struct mt76_queue { u16 flags; u8 magic_cnt; + __le16 *emi_cpu_idx; + struct mtk_wed_device *wed; u32 wed_regs; @@ -923,6 +933,7 @@ struct mt76_dev { struct mt76_queue q_rx[__MT_RXQ_MAX]; const struct mt76_queue_ops *queue_ops; int tx_dma_idx[4]; + enum mt76_hwrro_mode hwrro_mode; struct mt76_worker tx_worker; struct napi_struct tx_napi; @@ -1821,6 +1832,11 @@ static inline bool mt76_queue_is_wed_rx(struct mt76_queue *q) FIELD_GET(MT_QFLAG_WED_TYPE, q->flags) == MT76_WED_Q_RX; } +static inline bool mt76_queue_is_emi(struct mt76_queue *q) +{ + return q->flags & MT_QFLAG_EMI_EN; +} + struct mt76_txwi_cache * mt76_token_release(struct mt76_dev *dev, int token, bool *wake); int mt76_token_consume(struct mt76_dev *dev, struct mt76_txwi_cache **ptxwi); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c index 9663029fa087..659015f93d32 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c @@ -83,12 +83,14 @@ static void mt7996_dma_config(struct mt7996_dev *dev) break; } - if (dev->has_rro) { + if (mt7996_has_hwrro(dev)) { /* band0 */ RXQ_CONFIG(MT_RXQ_RRO_BAND0, WFDMA0, MT_INT_RX_DONE_RRO_BAND0, MT7996_RXQ_RRO_BAND0); - RXQ_CONFIG(MT_RXQ_MSDU_PAGE_BAND0, WFDMA0, MT_INT_RX_DONE_MSDU_PG_BAND0, - MT7996_RXQ_MSDU_PG_BAND0); + if (dev->mt76.hwrro_mode == MT76_HWRRO_V3) + RXQ_CONFIG(MT_RXQ_MSDU_PAGE_BAND0, WFDMA0, + MT_INT_RX_DONE_MSDU_PG_BAND0, + MT7996_RXQ_MSDU_PG_BAND0); if (is_mt7996(&dev->mt76)) { RXQ_CONFIG(MT_RXQ_TXFREE_BAND0, WFDMA0, MT_INT_RX_TXFREE_MAIN, MT7996_RXQ_TXFREE0); @@ -111,8 +113,14 @@ static void mt7996_dma_config(struct mt7996_dev *dev) MT7996_RXQ_RRO_BAND1); } - RXQ_CONFIG(MT_RXQ_RRO_IND, WFDMA0, MT_INT_RX_DONE_RRO_IND, - MT7996_RXQ_RRO_IND); + if (dev->mt76.hwrro_mode == MT76_HWRRO_V3) + RXQ_CONFIG(MT_RXQ_RRO_IND, WFDMA0, + MT_INT_RX_DONE_RRO_IND, + MT7996_RXQ_RRO_IND); + else + RXQ_CONFIG(MT_RXQ_RRO_RXDMAD_C, WFDMA0, + MT_INT_RX_DONE_RRO_RXDMAD_C, + MT7996_RXQ_RRO_RXDMAD_C); } /* data tx queue */ @@ -196,11 +204,12 @@ static void __mt7996_dma_prefetch(struct mt7996_dev *dev, u32 ofs) /* Rx TxFreeDone From MAC Rings */ val = is_mt7996(&dev->mt76) ? 4 : 8; - if (is_mt7990(&dev->mt76) || (is_mt7996(&dev->mt76) && dev->has_rro)) + if ((is_mt7996(&dev->mt76) && mt7996_has_hwrro(dev)) || + is_mt7990(&dev->mt76)) mt76_wr(dev, MT_RXQ_EXT_CTRL(MT_RXQ_TXFREE_BAND0) + ofs, PREFETCH(val)); if (is_mt7990(&dev->mt76) && dev->hif2) mt76_wr(dev, MT_RXQ_EXT_CTRL(MT_RXQ_TXFREE_BAND1) + ofs, PREFETCH(val)); - else if (is_mt7996(&dev->mt76) && dev->has_rro) + else if (is_mt7996(&dev->mt76) && mt7996_has_hwrro(dev)) mt76_wr(dev, MT_RXQ_EXT_CTRL(MT_RXQ_TXFREE_BAND2) + ofs, PREFETCH(val)); /* Rx Data Rings */ @@ -209,7 +218,7 @@ static void __mt7996_dma_prefetch(struct mt7996_dev *dev, u32 ofs) mt76_wr(dev, MT_RXQ_EXT_CTRL(queue) + ofs, PREFETCH(0x10)); /* Rx RRO Rings */ - if (dev->has_rro) { + if (mt7996_has_hwrro(dev)) { mt76_wr(dev, MT_RXQ_EXT_CTRL(MT_RXQ_RRO_BAND0) + ofs, PREFETCH(0x10)); queue = is_mt7996(&dev->mt76) ? MT_RXQ_RRO_BAND2 : MT_RXQ_RRO_BAND1; mt76_wr(dev, MT_RXQ_EXT_CTRL(queue) + ofs, PREFETCH(0x10)); @@ -465,7 +474,7 @@ static void mt7996_dma_enable(struct mt7996_dev *dev, bool reset) * so, redirect pcie0 rx ring3 interrupt to pcie1 */ if (mtk_wed_device_active(&dev->mt76.mmio.wed) && - dev->has_rro) { + mt7996_has_hwrro(dev)) { u32 intr = is_mt7996(&dev->mt76) ? MT_WFDMA0_RX_INT_SEL_RING6 : MT_WFDMA0_RX_INT_SEL_RING9 | @@ -488,6 +497,30 @@ int mt7996_dma_rro_init(struct mt7996_dev *dev) u32 irq_mask; int ret; + if (dev->mt76.hwrro_mode == MT76_HWRRO_V3_1) { + /* rxdmad_c */ + mdev->q_rx[MT_RXQ_RRO_RXDMAD_C].flags = MT_WED_RRO_Q_RXDMAD_C; + if (mtk_wed_device_active(&mdev->mmio.wed)) + mdev->q_rx[MT_RXQ_RRO_RXDMAD_C].wed = &mdev->mmio.wed; + else + mdev->q_rx[MT_RXQ_RRO_RXDMAD_C].flags |= MT_QFLAG_EMI_EN; + ret = mt76_queue_alloc(dev, &mdev->q_rx[MT_RXQ_RRO_RXDMAD_C], + MT_RXQ_ID(MT_RXQ_RRO_RXDMAD_C), + MT7996_RX_RING_SIZE, + MT7996_RX_BUF_SIZE, + MT_RXQ_RRO_AP_RING_BASE); + if (ret) + return ret; + + /* We need to set cpu idx pointer before resetting the EMI + * queues. + */ + mdev->q_rx[MT_RXQ_RRO_RXDMAD_C].emi_cpu_idx = + &dev->wed_rro.emi_rings_cpu.ptr->ring[0].idx; + mt76_queue_reset(dev, &mdev->q_rx[MT_RXQ_RRO_RXDMAD_C], true); + goto start_hw_rro; + } + /* ind cmd */ mdev->q_rx[MT_RXQ_RRO_IND].flags = MT_WED_RRO_Q_IND; if (mtk_wed_device_active(&mdev->mmio.wed) && @@ -546,6 +579,7 @@ int mt7996_dma_rro_init(struct mt7996_dev *dev) return ret; } +start_hw_rro: if (mtk_wed_device_active(&mdev->mmio.wed)) { irq_mask = mdev->mmio.irqmask | MT_INT_TX_DONE_BAND2; @@ -567,9 +601,15 @@ int mt7996_dma_rro_init(struct mt7996_dev *dev) } mt76_queue_rx_init(dev, MT_RXQ_RRO_BAND0, mt76_dma_rx_poll); - mt76_queue_rx_init(dev, MT_RXQ_RRO_IND, mt76_dma_rx_poll); - mt76_queue_rx_init(dev, MT_RXQ_MSDU_PAGE_BAND0, - mt76_dma_rx_poll); + if (dev->mt76.hwrro_mode == MT76_HWRRO_V3_1) { + mt76_queue_rx_init(dev, MT_RXQ_RRO_RXDMAD_C, + mt76_dma_rx_poll); + } else { + mt76_queue_rx_init(dev, MT_RXQ_RRO_IND, + mt76_dma_rx_poll); + mt76_queue_rx_init(dev, MT_RXQ_MSDU_PAGE_BAND0, + mt76_dma_rx_poll); + } mt7996_irq_enable(dev, MT_INT_RRO_RX_DONE); } @@ -662,7 +702,7 @@ int mt7996_dma_init(struct mt7996_dev *dev) /* tx free notify event from WA for band0 */ if (mtk_wed_device_active(wed) && - ((is_mt7996(&dev->mt76) && !dev->has_rro) || + ((is_mt7996(&dev->mt76) && !mt7996_has_hwrro(dev)) || (is_mt7992(&dev->mt76)))) { dev->mt76.q_rx[MT_RXQ_MAIN_WA].flags = MT_WED_Q_TXFREE; dev->mt76.q_rx[MT_RXQ_MAIN_WA].wed = wed; @@ -718,7 +758,7 @@ int mt7996_dma_init(struct mt7996_dev *dev) /* tx free notify event from WA for mt7996 band2 * use pcie0's rx ring3, but, redirect pcie0 rx ring3 interrupt to pcie1 */ - if (mtk_wed_device_active(wed_hif2) && !dev->has_rro) { + if (mtk_wed_device_active(wed_hif2) && !mt7996_has_hwrro(dev)) { dev->mt76.q_rx[MT_RXQ_BAND2_WA].flags = MT_WED_Q_TXFREE; dev->mt76.q_rx[MT_RXQ_BAND2_WA].wed = wed_hif2; } @@ -765,7 +805,7 @@ int mt7996_dma_init(struct mt7996_dev *dev) } } - if (dev->has_rro) { + if (mt7996_has_hwrro(dev)) { /* rx rro data queue for band0 */ dev->mt76.q_rx[MT_RXQ_RRO_BAND0].flags = MT_WED_RRO_Q_DATA(0) | MT_QFLAG_WED_RRO_EN; @@ -887,7 +927,8 @@ void mt7996_dma_reset(struct mt7996_dev *dev, bool force) mt76_tx_status_check(&dev->mt76, true); - if (dev->has_rro && !mtk_wed_device_active(&dev->mt76.mmio.wed)) + if (mt7996_has_hwrro(dev) && + !mtk_wed_device_active(&dev->mt76.mmio.wed)) mt7996_rro_msdu_page_map_free(dev); /* reset wfsys */ diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 11ac0cef4a06..d0ed43a78c85 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -611,7 +611,7 @@ void mt7996_mac_init(struct mt7996_dev *dev) else mt7996_mcu_set_rro(dev, UNI_RRO_SET_PLATFORM_TYPE, 0); - if (dev->has_rro) { + if (mt7996_has_hwrro(dev)) { u16 timeout; timeout = mt76_rr(dev, MT_HW_REV) == MT_HW_REV1 ? 512 : 128; @@ -764,59 +764,25 @@ void mt7996_wfsys_reset(struct mt7996_dev *dev) msleep(20); } -void mt7996_rro_hw_init(struct mt7996_dev *dev) +static void mt7996_rro_hw_init_v3(struct mt7996_dev *dev) { struct mtk_wed_device *wed = &dev->mt76.mmio.wed; - u32 reg = MT_RRO_ADDR_ELEM_SEG_ADDR0; - int i; + u32 session_id; - if (!dev->has_rro) + if (dev->mt76.hwrro_mode == MT76_HWRRO_V3_1) return; - INIT_LIST_HEAD(&dev->wed_rro.page_cache); - for (i = 0; i < ARRAY_SIZE(dev->wed_rro.page_map); i++) - INIT_LIST_HEAD(&dev->wed_rro.page_map[i]); - - if (is_mt7992(&dev->mt76)) { - /* Set emul 3.0 function */ - mt76_wr(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); - } else { - /* TODO: remove line after WM has set */ - mt76_clear(dev, WF_RRO_AXI_MST_CFG, - WF_RRO_AXI_MST_CFG_DIDX_OK); - /* setup BA bitmap cache address */ - mt76_wr(dev, MT_RRO_BA_BITMAP_BASE0, - dev->wed_rro.ba_bitmap[0].phy_addr); - mt76_wr(dev, MT_RRO_BA_BITMAP_BASE1, 0); - mt76_wr(dev, MT_RRO_BA_BITMAP_BASE_EXT0, - dev->wed_rro.ba_bitmap[1].phy_addr); - mt76_wr(dev, MT_RRO_BA_BITMAP_BASE_EXT1, 0); - - /* Setup Address element address */ - for (i = 0; i < ARRAY_SIZE(dev->wed_rro.addr_elem); i++) { - mt76_wr(dev, reg, - dev->wed_rro.addr_elem[i].phy_addr >> 4); - reg += 4; - } - - /* Setup Address element address - separate address - * segment mode - */ - mt76_wr(dev, MT_RRO_ADDR_ARRAY_BASE1, - MT_RRO_ADDR_ARRAY_ELEM_ADDR_SEG_MODE); - } - #ifdef CONFIG_NET_MEDIATEK_SOC_WED if (mtk_wed_device_active(wed) && mtk_wed_get_rx_capa(wed)) { - wed->wlan.ind_cmd.win_size = ffs(MT7996_RRO_WINDOW_MAX_LEN) - 6; + wed->wlan.ind_cmd.win_size = + ffs(MT7996_RRO_WINDOW_MAX_LEN) - 6; if (is_mt7996(&dev->mt76)) - wed->wlan.ind_cmd.particular_sid = MT7996_RRO_MAX_SESSION; + wed->wlan.ind_cmd.particular_sid = + MT7996_RRO_MAX_SESSION; else wed->wlan.ind_cmd.particular_sid = 1; - wed->wlan.ind_cmd.particular_se_phys = dev->wed_rro.session.phy_addr; + wed->wlan.ind_cmd.particular_se_phys = + dev->wed_rro.session.phy_addr; wed->wlan.ind_cmd.se_group_nums = MT7996_RRO_ADDR_ELEM_LEN; wed->wlan.ind_cmd.ack_sn_addr = MT_RRO_ACK_SN_CTRL; } @@ -835,9 +801,50 @@ void mt7996_rro_hw_init(struct mt7996_dev *dev) /* use max session idx + 1 as particular session id */ mt76_wr(dev, MT_RRO_PARTICULAR_CFG0, dev->wed_rro.session.phy_addr); - if (is_mt7992(&dev->mt76)) { + session_id = is_mt7996(&dev->mt76) ? MT7996_RRO_MAX_SESSION : 1; + mt76_wr(dev, MT_RRO_PARTICULAR_CFG1, + MT_RRO_PARTICULAR_CONFG_EN | + FIELD_PREP(MT_RRO_PARTICULAR_SID, session_id)); +} + +void mt7996_rro_hw_init(struct mt7996_dev *dev) +{ + u32 reg = MT_RRO_ADDR_ELEM_SEG_ADDR0; + int i; + + if (!mt7996_has_hwrro(dev)) + return; + + INIT_LIST_HEAD(&dev->wed_rro.page_cache); + for (i = 0; i < ARRAY_SIZE(dev->wed_rro.page_map); i++) + INIT_LIST_HEAD(&dev->wed_rro.page_map[i]); + + if (!is_mt7996(&dev->mt76)) { reg = MT_RRO_MSDU_PG_SEG_ADDR0; + if (dev->mt76.hwrro_mode == MT76_HWRRO_V3_1) { + mt76_clear(dev, MT_RRO_3_0_EMU_CONF, + MT_RRO_3_0_EMU_CONF_EN_MASK); + mt76_set(dev, MT_RRO_3_1_GLOBAL_CONFIG, + MT_RRO_3_1_GLOBAL_CONFIG_RXDMAD_SEL); + if (!mtk_wed_device_active(&dev->mt76.mmio.wed)) { + mt76_set(dev, MT_RRO_3_1_GLOBAL_CONFIG, + MT_RRO_3_1_GLOBAL_CONFIG_RX_DIDX_WR_EN | + MT_RRO_3_1_GLOBAL_CONFIG_RX_CIDX_RD_EN); + mt76_wr(dev, MT_RRO_RX_RING_AP_CIDX_ADDR, + dev->wed_rro.emi_rings_cpu.phy_addr >> 4); + mt76_wr(dev, MT_RRO_RX_RING_AP_DIDX_ADDR, + dev->wed_rro.emi_rings_dma.phy_addr >> 4); + } + } else { + /* set emul 3.0 function */ + mt76_wr(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); + } + mt76_set(dev, MT_RRO_3_1_GLOBAL_CONFIG, MT_RRO_3_1_GLOBAL_CONFIG_INTERLEAVE_EN); @@ -847,15 +854,35 @@ void mt7996_rro_hw_init(struct mt7996_dev *dev) dev->wed_rro.msdu_pg[i].phy_addr >> 4); reg += 4; } - mt76_wr(dev, MT_RRO_PARTICULAR_CFG1, - MT_RRO_PARTICULAR_CONFG_EN | - FIELD_PREP(MT_RRO_PARTICULAR_SID, 1)); } else { - mt76_wr(dev, MT_RRO_PARTICULAR_CFG1, - MT_RRO_PARTICULAR_CONFG_EN | - FIELD_PREP(MT_RRO_PARTICULAR_SID, - MT7996_RRO_MAX_SESSION)); + /* TODO: remove line after WM has set */ + mt76_clear(dev, WF_RRO_AXI_MST_CFG, + WF_RRO_AXI_MST_CFG_DIDX_OK); + + /* setup BA bitmap cache address */ + mt76_wr(dev, MT_RRO_BA_BITMAP_BASE0, + dev->wed_rro.ba_bitmap[0].phy_addr); + mt76_wr(dev, MT_RRO_BA_BITMAP_BASE1, 0); + mt76_wr(dev, MT_RRO_BA_BITMAP_BASE_EXT0, + dev->wed_rro.ba_bitmap[1].phy_addr); + mt76_wr(dev, MT_RRO_BA_BITMAP_BASE_EXT1, 0); + + /* Setup Address element address */ + for (i = 0; i < ARRAY_SIZE(dev->wed_rro.addr_elem); i++) { + mt76_wr(dev, reg, + dev->wed_rro.addr_elem[i].phy_addr >> 4); + reg += 4; + } + + /* Setup Address element address - separate address segment + * mode. + */ + mt76_wr(dev, MT_RRO_ADDR_ARRAY_BASE1, + MT_RRO_ADDR_ARRAY_ELEM_ADDR_SEG_MODE); } + + mt7996_rro_hw_init_v3(dev); + /* interrupt enable */ mt76_wr(dev, MT_RRO_HOST_INT_ENA, MT_RRO_HOST_INT_ENA_HOST_RRO_DONE_ENA); @@ -868,18 +895,20 @@ static int mt7996_wed_rro_init(struct mt7996_dev *dev) void *ptr; int i; - if (!dev->has_rro) + if (!mt7996_has_hwrro(dev)) return 0; - for (i = 0; i < ARRAY_SIZE(dev->wed_rro.ba_bitmap); i++) { - ptr = dmam_alloc_coherent(dev->mt76.dma_dev, - MT7996_RRO_BA_BITMAP_CR_SIZE, - &dev->wed_rro.ba_bitmap[i].phy_addr, - GFP_KERNEL); - if (!ptr) - return -ENOMEM; + if (dev->mt76.hwrro_mode == MT76_HWRRO_V3) { + for (i = 0; i < ARRAY_SIZE(dev->wed_rro.ba_bitmap); i++) { + ptr = dmam_alloc_coherent(dev->mt76.dma_dev, + MT7996_RRO_BA_BITMAP_CR_SIZE, + &dev->wed_rro.ba_bitmap[i].phy_addr, + GFP_KERNEL); + if (!ptr) + return -ENOMEM; - dev->wed_rro.ba_bitmap[i].ptr = ptr; + dev->wed_rro.ba_bitmap[i].ptr = ptr; + } } for (i = 0; i < ARRAY_SIZE(dev->wed_rro.addr_elem); i++) { @@ -927,6 +956,26 @@ static int mt7996_wed_rro_init(struct mt7996_dev *dev) MT7996_RRO_MSDU_PG_SIZE_PER_CR); } + if (dev->mt76.hwrro_mode == MT76_HWRRO_V3_1) { + ptr = dmam_alloc_coherent(dev->mt76.dma_dev, + sizeof(dev->wed_rro.emi_rings_cpu.ptr), + &dev->wed_rro.emi_rings_cpu.phy_addr, + GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + dev->wed_rro.emi_rings_cpu.ptr = ptr; + + ptr = dmam_alloc_coherent(dev->mt76.dma_dev, + sizeof(dev->wed_rro.emi_rings_dma.ptr), + &dev->wed_rro.emi_rings_dma.phy_addr, + GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + dev->wed_rro.emi_rings_dma.ptr = ptr; + } + ptr = dmam_alloc_coherent(dev->mt76.dma_dev, MT7996_RRO_WINDOW_MAX_LEN * sizeof(*addr), &dev->wed_rro.session.phy_addr, @@ -950,7 +999,7 @@ static void mt7996_wed_rro_free(struct mt7996_dev *dev) { int i; - if (!dev->has_rro) + if (!mt7996_has_hwrro(dev)) return; for (i = 0; i < ARRAY_SIZE(dev->wed_rro.ba_bitmap); i++) { @@ -1662,7 +1711,8 @@ void mt7996_unregister_device(struct mt7996_dev *dev) mt7996_mcu_exit(dev); mt7996_tx_token_put(dev); mt7996_dma_cleanup(dev); - if (dev->has_rro && !mtk_wed_device_active(&dev->mt76.mmio.wed)) + if (mt7996_has_hwrro(dev) && + !mtk_wed_device_active(&dev->mt76.mmio.wed)) mt7996_rro_msdu_page_map_free(dev); tasklet_disable(&dev->mt76.irq_tasklet); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index f7b491b034db..f4a897e7935e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2265,7 +2265,8 @@ mt7996_mac_restart(struct mt7996_dev *dev) if (ret) goto out; - if (mtk_wed_device_active(&dev->mt76.mmio.wed) && dev->has_rro) { + if (mtk_wed_device_active(&dev->mt76.mmio.wed) && + mt7996_has_hwrro(dev)) { u32 wed_irq_mask = dev->mt76.mmio.irqmask | MT_INT_TX_DONE_BAND2; @@ -2493,7 +2494,7 @@ void mt7996_mac_reset_work(struct work_struct *work) /* enable DMA Tx/Tx and interrupt */ mt7996_dma_start(dev, false, false); - if (!is_mt7996(&dev->mt76) && dev->has_rro) + 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); if (mtk_wed_device_active(&dev->mt76.mmio.wed)) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index c525abd0a7ac..1c89d235026d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -660,7 +660,7 @@ mt7996_mcu_wed_rro_event(struct mt7996_dev *dev, struct sk_buff *skb) { struct mt7996_mcu_wed_rro_event *event = (void *)skb->data; - if (!dev->has_rro) + if (!mt7996_has_hwrro(dev)) return; skb_pull(skb, sizeof(struct mt7996_mcu_rxd) + 4); @@ -1183,7 +1183,7 @@ mt7996_mcu_sta_ba(struct mt7996_dev *dev, struct mt76_vif_link *mvif, ba->ba_en = enable << params->tid; ba->amsdu = params->amsdu; ba->tid = params->tid; - ba->ba_rdd_rro = !tx && enable && dev->has_rro; + ba->ba_rdd_rro = !tx && enable && mt7996_has_hwrro(dev); return mt76_mcu_skb_send_msg(&dev->mt76, skb, MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c index 38c15b061dff..d14b626ee511 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c @@ -464,7 +464,8 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, if (!wed_enable) return 0; - dev->has_rro = true; + dev->mt76.hwrro_mode = is_mt7996(&dev->mt76) ? MT76_HWRRO_V3 + : MT76_HWRRO_V3_1; hif1_ofs = dev->hif2 ? MT_WFDMA0_PCIE1(0) - MT_WFDMA0(0) : 0; @@ -490,7 +491,7 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, wed->wlan.wpdma_tx = wed->wlan.phy_base + hif1_ofs + MT_TXQ_RING_BASE(0) + MT7996_TXQ_BAND2 * MT_RING_SIZE; - if (dev->has_rro) { + if (mt7996_has_hwrro(dev)) { if (is_mt7996(&dev->mt76)) { wed->wlan.txfree_tbit = ffs(MT_INT_RX_TXFREE_EXT) - 1; wed->wlan.wpdma_txfree = wed->wlan.phy_base + hif1_ofs + @@ -517,7 +518,7 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, wed->wlan.id = MT7996_DEVICE_ID_2; wed->wlan.tx_tbit[0] = ffs(MT_INT_TX_DONE_BAND2) - 1; } else { - wed->wlan.hw_rro = dev->has_rro; /* default on */ + wed->wlan.hw_rro = mt7996_has_hwrro(dev); wed->wlan.wpdma_int = wed->wlan.phy_base + MT_INT_SOURCE_CSR; wed->wlan.wpdma_mask = wed->wlan.phy_base + MT_INT_MASK_CSR; wed->wlan.wpdma_tx = wed->wlan.phy_base + MT_TXQ_RING_BASE(0) + @@ -570,7 +571,7 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, wed->wlan.tx_tbit[0] = ffs(MT_INT_TX_DONE_BAND0) - 1; wed->wlan.tx_tbit[1] = ffs(MT_INT_TX_DONE_BAND1) - 1; if (is_mt7996(&dev->mt76)) { - if (dev->has_rro) { + if (mt7996_has_hwrro(dev)) { wed->wlan.wpdma_txfree = wed->wlan.phy_base + MT_RXQ_RING_BASE(0) + MT7996_RXQ_TXFREE0 * MT_RING_SIZE; @@ -609,7 +610,7 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, } if (mtk_wed_device_attach(wed)) { - dev->has_rro = false; + dev->mt76.hwrro_mode = MT76_HWRRO_OFF; return 0; } diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 6467dc9ad44d..be26e04b7da7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -193,6 +193,7 @@ enum mt7996_rxq_id { MT7996_RXQ_TXFREE1 = 9, MT7996_RXQ_TXFREE2 = 7, MT7996_RXQ_RRO_IND = 0, + MT7996_RXQ_RRO_RXDMAD_C = 0, MT7990_RXQ_TXFREE0 = 6, MT7990_RXQ_TXFREE1 = 7, }; @@ -329,6 +330,14 @@ struct mt7996_msdu_page_info { __le32 data; }; +#define MT7996_MAX_RRO_RRS_RING 4 +struct mt7996_rro_queue_regs_emi { + struct { + __le16 idx; + __le16 rsv; + } ring[MT7996_MAX_RRO_RRS_RING]; +}; + struct mt7996_phy { struct mt76_phy *mt76; struct mt7996_dev *dev; @@ -423,7 +432,6 @@ struct mt7996_dev { bool flash_mode:1; bool has_eht:1; - bool has_rro:1; struct { struct { @@ -442,6 +450,14 @@ struct mt7996_dev { void *ptr; dma_addr_t phy_addr; } msdu_pg[MT7996_RRO_MSDU_PG_CR_CNT]; + struct { + struct mt7996_rro_queue_regs_emi *ptr; + dma_addr_t phy_addr; + } emi_rings_cpu; + struct { + struct mt7996_rro_queue_regs_emi *ptr; + dma_addr_t phy_addr; + } emi_rings_dma; struct work_struct work; struct list_head poll_list; @@ -722,6 +738,11 @@ int mt7996_mcu_get_all_sta_info(struct mt7996_phy *phy, u16 tag); int mt7996_mcu_wed_rro_reset_sessions(struct mt7996_dev *dev, u16 id); int mt7996_mcu_set_sniffer_mode(struct mt7996_phy *phy, bool enabled); +static inline bool mt7996_has_hwrro(struct mt7996_dev *dev) +{ + return dev->mt76.hwrro_mode != MT76_HWRRO_OFF; +} + static inline u8 mt7996_max_interface_num(struct mt7996_dev *dev) { return min(MT7996_MAX_INTERFACES * (1 + mt7996_band_valid(dev, MT_BAND1) + diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h index 9ca1490c2cf3..0fa325f87fcd 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h @@ -114,9 +114,14 @@ enum offs_rev { #define MT_RRO_3_0_EMU_CONF_EN_MASK BIT(11) #define MT_RRO_3_1_GLOBAL_CONFIG MT_RRO_TOP(0x604) +#define MT_RRO_3_1_GLOBAL_CONFIG_RXDMAD_SEL BIT(6) +#define MT_RRO_3_1_GLOBAL_CONFIG_RX_CIDX_RD_EN BIT(3) +#define MT_RRO_3_1_GLOBAL_CONFIG_RX_DIDX_WR_EN BIT(2) #define MT_RRO_3_1_GLOBAL_CONFIG_INTERLEAVE_EN BIT(0) #define MT_RRO_MSDU_PG_SEG_ADDR0 MT_RRO_TOP(0x620) +#define MT_RRO_RX_RING_AP_CIDX_ADDR MT_RRO_TOP(0x6f0) +#define MT_RRO_RX_RING_AP_DIDX_ADDR MT_RRO_TOP(0x6f4) #define MT_RRO_ACK_SN_CTRL MT_RRO_TOP(0x50) #define MT_RRO_ACK_SN_CTRL_SN_MASK GENMASK(27, 16) @@ -510,6 +515,8 @@ enum offs_rev { #define MT_RXQ_RING_BASE(q) (MT_Q_BASE(__RXQ(q)) + 0x500) #define MT_RXQ_RRO_IND_RING_BASE MT_RRO_TOP(0x40) +#define MT_RXQ_RRO_AP_RING_BASE MT_RRO_TOP(0x650) + #define MT_MCUQ_EXT_CTRL(q) (MT_Q_BASE(q) + 0x600 + \ MT_MCUQ_ID(q) * 0x4) #define MT_RXQ_EXT_CTRL(q) (MT_Q_BASE(__RXQ(q)) + 0x680 + \ @@ -545,6 +552,7 @@ enum offs_rev { #define MT_INT_RX_DONE_RRO_BAND1 BIT(17) #define MT_INT_RX_DONE_RRO_BAND2 BIT(14) #define MT_INT_RX_DONE_RRO_IND BIT(11) +#define MT_INT_RX_DONE_RRO_RXDMAD_C BIT(11) #define MT_INT_RX_DONE_MSDU_PG_BAND0 BIT(18) #define MT_INT_RX_DONE_MSDU_PG_BAND1 BIT(19) #define MT_INT_RX_DONE_MSDU_PG_BAND2 BIT(23) @@ -573,6 +581,7 @@ enum offs_rev { MT_INT_RX(MT_RXQ_RRO_BAND1) | \ MT_INT_RX(MT_RXQ_RRO_BAND2) | \ MT_INT_RX(MT_RXQ_RRO_IND) | \ + MT_INT_RX(MT_RXQ_RRO_RXDMAD_C) | \ MT_INT_RX(MT_RXQ_MSDU_PAGE_BAND0) | \ MT_INT_RX(MT_RXQ_MSDU_PAGE_BAND1) | \ MT_INT_RX(MT_RXQ_MSDU_PAGE_BAND2)) From a09d2f9d69afba4365ff507d18aeeb4ff1aa0e61 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 9 Sep 2025 12:46:37 +0200 Subject: [PATCH 077/167] wifi: mt76: Convert mt76_wed_rro_ind to LE Do not use bitmask in mt76_wed_rro_ind DMA descriptor in order to not break endianness. This patch is based on the following series: https://lore.kernel.org/linux-wireless/20250909-mt7996-rro-rework-v5-0-7d66f6eb7795@kernel.org/T/#m8b488004d69036cd3672b9eeca8005a937ec0313 Fixes: 950d0abb5cd94 ("wifi: mt76: mt7996: add wed rx support") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/301d5f2982ddb729c876fb65f9ac2443ce3f5ff1.1757414621.git.lorenzo@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.c | 9 ++++++-- drivers/net/wireless/mediatek/mt76/mt76.h | 17 ++++++++------- .../net/wireless/mediatek/mt76/mt7996/mac.c | 21 ++++++++++++------- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index 5dc093b21838..1fa7de1d2c45 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -194,6 +194,8 @@ mt76_dma_queue_magic_cnt_init(struct mt76_dev *dev, struct mt76_queue *q) q->magic_cnt = 0; if (mt76_queue_is_wed_rro_ind(q)) { struct mt76_wed_rro_desc *rro_desc; + u32 data1 = FIELD_PREP(RRO_IND_DATA1_MAGIC_CNT_MASK, + MT_DMA_WED_IND_CMD_CNT - 1); int i; rro_desc = (struct mt76_wed_rro_desc *)q->desc; @@ -201,7 +203,7 @@ mt76_dma_queue_magic_cnt_init(struct mt76_dev *dev, struct mt76_queue *q) struct mt76_wed_rro_ind *cmd; cmd = (struct mt76_wed_rro_ind *)&rro_desc[i]; - cmd->magic_cnt = MT_DMA_WED_IND_CMD_CNT - 1; + cmd->data1 = cpu_to_le32(data1); } } else if (mt76_queue_is_wed_rro_rxdmad_c(q)) { struct mt76_rro_rxdmad_c *dmad = (void *)q->desc; @@ -582,12 +584,15 @@ mt76_dma_dequeue(struct mt76_dev *dev, struct mt76_queue *q, bool flush, if (mt76_queue_is_wed_rro_ind(q)) { struct mt76_wed_rro_ind *cmd; + u8 magic_cnt; if (flush) goto done; cmd = q->entry[idx].buf; - if (cmd->magic_cnt != q->magic_cnt) + magic_cnt = FIELD_GET(RRO_IND_DATA1_MAGIC_CNT_MASK, + le32_to_cpu(cmd->data1)); + if (magic_cnt != q->magic_cnt) return NULL; if (q->tail == q->ndesc - 1) diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index b102b6d798b8..edda01ff1117 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -415,15 +415,16 @@ struct mt76_txq { bool aggr; }; +/* data0 */ +#define RRO_IND_DATA0_IND_REASON_MASK GENMASK(31, 28) +#define RRO_IND_DATA0_START_SEQ_MASK GENMASK(27, 16) +#define RRO_IND_DATA0_SEQ_ID_MASK GENMASK(11, 0) +/* data1 */ +#define RRO_IND_DATA1_MAGIC_CNT_MASK GENMASK(31, 29) +#define RRO_IND_DATA1_IND_COUNT_MASK GENMASK(12, 0) struct mt76_wed_rro_ind { - u32 se_id : 12; - u32 rsv : 4; - u32 start_sn : 12; - u32 ind_reason : 4; - u32 ind_cnt : 13; - u32 win_sz : 3; - u32 rsv2 : 13; - u32 magic_cnt : 3; + __le32 data0; + __le32 data1; }; struct mt76_txwi_cache { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index f4a897e7935e..2183193b26a6 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -1830,11 +1830,17 @@ void mt7996_rro_rx_process(struct mt76_dev *mdev, void *data) { struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76); struct mt76_wed_rro_ind *cmd = (struct mt76_wed_rro_ind *)data; + u32 cmd_data0 = le32_to_cpu(cmd->data0); + u32 cmd_data1 = le32_to_cpu(cmd->data1); + u8 ind_reason = FIELD_GET(RRO_IND_DATA0_IND_REASON_MASK, cmd_data0); + u16 start_seq = FIELD_GET(RRO_IND_DATA0_START_SEQ_MASK, cmd_data0); + u16 seq_id = FIELD_GET(RRO_IND_DATA0_SEQ_ID_MASK, cmd_data0); + u16 ind_count = FIELD_GET(RRO_IND_DATA1_IND_COUNT_MASK, cmd_data1); struct mt7996_msdu_page_info *pinfo = NULL; struct mt7996_msdu_page *p = NULL; int i, seq_num = 0; - for (i = 0; i < cmd->ind_cnt; i++) { + for (i = 0; i < ind_count; i++) { struct mt7996_wed_rro_addr *e; struct mt76_rx_status *status; struct mt7996_rro_hif *rxd; @@ -1849,8 +1855,8 @@ void mt7996_rro_rx_process(struct mt76_dev *mdev, void *data) void *buf; bool ls; - seq_num = FIELD_GET(MT996_RRO_SN_MASK, cmd->start_sn + i); - e = mt7996_rro_addr_elem_get(dev, cmd->se_id, seq_num); + seq_num = FIELD_GET(MT996_RRO_SN_MASK, start_seq + i); + e = mt7996_rro_addr_elem_get(dev, seq_id, seq_num); data = le32_to_cpu(e->data); signature = FIELD_GET(WED_RRO_ADDR_SIGNATURE_MASK, data); if (signature != (seq_num / MT7996_RRO_WINDOW_MAX_LEN)) { @@ -1938,7 +1944,7 @@ void mt7996_rro_rx_process(struct mt76_dev *mdev, void *data) skb_mark_for_recycle(skb); __skb_put(skb, len); - if (cmd->ind_reason == 1 || cmd->ind_reason == 2) { + if (ind_reason == 1 || ind_reason == 2) { dev_kfree_skb(skb); goto next_page; } @@ -1949,7 +1955,7 @@ void mt7996_rro_rx_process(struct mt76_dev *mdev, void *data) } status = (struct mt76_rx_status *)skb->cb; - if (cmd->se_id != MT7996_RRO_MAX_SESSION) + if (seq_id != MT7996_RRO_MAX_SESSION) status->aggr = true; mt7996_queue_rx_skb(mdev, qid, skb, &info); @@ -1973,7 +1979,7 @@ update_ack_seq_num: if ((i + 1) % 4 == 0) mt76_wr(dev, MT_RRO_ACK_SN_CTRL, FIELD_PREP(MT_RRO_ACK_SN_CTRL_SESSION_MASK, - cmd->se_id) | + seq_id) | FIELD_PREP(MT_RRO_ACK_SN_CTRL_SN_MASK, seq_num)); if (p) { @@ -1985,8 +1991,7 @@ update_ack_seq_num: /* Update ack_seq_num for remaining addr_elem */ if (i % 4) mt76_wr(dev, MT_RRO_ACK_SN_CTRL, - FIELD_PREP(MT_RRO_ACK_SN_CTRL_SESSION_MASK, - cmd->se_id) | + FIELD_PREP(MT_RRO_ACK_SN_CTRL_SESSION_MASK, seq_id) | FIELD_PREP(MT_RRO_ACK_SN_CTRL_SN_MASK, seq_num)); } From 2b660ee10a0c25b209d7fda3c41b821b75dd85d9 Mon Sep 17 00:00:00 2001 From: Zhi-Jun You Date: Tue, 9 Sep 2025 14:48:24 +0800 Subject: [PATCH 078/167] wifi: mt76: mt7915: fix mt7981 pre-calibration In vendor driver, size of group cal and dpd cal for mt7981 includes 6G although the chip doesn't support it. mt76 doesn't take this into account which results in reading from the incorrect offset. For devices with precal, this would lead to lower bitrate. Fix this by aligning groupcal size with vendor driver and switch to freq_list_v2 in mt7915_dpd_freq_idx in order to get the correct offset. Below are iwinfo of the test device with two clients connected (iPhone 16, Intel AX210). Before : Mode: Master Channel: 36 (5.180 GHz) HT Mode: HE80 Center Channel 1: 42 2: unknown Tx-Power: 23 dBm Link Quality: 43/70 Signal: -67 dBm Noise: -92 dBm Bit Rate: 612.4 MBit/s Encryption: WPA3 SAE (CCMP) Type: nl80211 HW Mode(s): 802.11ac/ax/n Hardware: embedded [MediaTek MT7981] After: Mode: Master Channel: 36 (5.180 GHz) HT Mode: HE80 Center Channel 1: 42 2: unknown Tx-Power: 23 dBm Link Quality: 43/70 Signal: -67 dBm Noise: -92 dBm Bit Rate: 900.6 MBit/s Encryption: WPA3 SAE (CCMP) Type: nl80211 HW Mode(s): 802.11ac/ax/n Hardware: embedded [MediaTek MT7981] Tested-on: mt7981 20240823 Fixes: 19a954edec63 ("wifi: mt76: mt7915: add mt7986, mt7916 and mt7981 pre-calibration") Signed-off-by: Zhi-Jun You Link: https://patch.msgid.link/20250909064824.16847-1-hujy652@gmail.com Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt7915/eeprom.h | 6 ++-- .../net/wireless/mediatek/mt76/mt7915/mcu.c | 29 +++++-------------- 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.h b/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.h index 31aec0f40232..73611c9d26e1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.h +++ b/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.h @@ -50,9 +50,9 @@ enum mt7915_eeprom_field { #define MT_EE_CAL_GROUP_SIZE_7975 (54 * MT_EE_CAL_UNIT + 16) #define MT_EE_CAL_GROUP_SIZE_7976 (94 * MT_EE_CAL_UNIT + 16) #define MT_EE_CAL_GROUP_SIZE_7916_6G (94 * MT_EE_CAL_UNIT + 16) +#define MT_EE_CAL_GROUP_SIZE_7981 (144 * MT_EE_CAL_UNIT + 16) #define MT_EE_CAL_DPD_SIZE_V1 (54 * MT_EE_CAL_UNIT) #define MT_EE_CAL_DPD_SIZE_V2 (300 * MT_EE_CAL_UNIT) -#define MT_EE_CAL_DPD_SIZE_V2_7981 (102 * MT_EE_CAL_UNIT) /* no 6g dpd data */ #define MT_EE_WIFI_CONF0_TX_PATH GENMASK(2, 0) #define MT_EE_WIFI_CONF0_RX_PATH GENMASK(5, 3) @@ -180,6 +180,8 @@ mt7915_get_cal_group_size(struct mt7915_dev *dev) val = FIELD_GET(MT_EE_WIFI_CONF0_BAND_SEL, val); return (val == MT_EE_V2_BAND_SEL_6GHZ) ? MT_EE_CAL_GROUP_SIZE_7916_6G : MT_EE_CAL_GROUP_SIZE_7916; + } else if (is_mt7981(&dev->mt76)) { + return MT_EE_CAL_GROUP_SIZE_7981; } else if (mt7915_check_adie(dev, false)) { return MT_EE_CAL_GROUP_SIZE_7976; } else { @@ -192,8 +194,6 @@ mt7915_get_cal_dpd_size(struct mt7915_dev *dev) { if (is_mt7915(&dev->mt76)) return MT_EE_CAL_DPD_SIZE_V1; - else if (is_mt7981(&dev->mt76)) - return MT_EE_CAL_DPD_SIZE_V2_7981; else return MT_EE_CAL_DPD_SIZE_V2; } diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c index 2928e75b2397..c1fdd3c4f1ba 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c @@ -3052,30 +3052,15 @@ static int mt7915_dpd_freq_idx(struct mt7915_dev *dev, u16 freq, u8 bw) /* 5G BW160 */ 5250, 5570, 5815 }; - static const u16 freq_list_v2_7981[] = { - /* 5G BW20 */ - 5180, 5200, 5220, 5240, - 5260, 5280, 5300, 5320, - 5500, 5520, 5540, 5560, - 5580, 5600, 5620, 5640, - 5660, 5680, 5700, 5720, - 5745, 5765, 5785, 5805, - 5825, 5845, 5865, 5885, - /* 5G BW160 */ - 5250, 5570, 5815 - }; - const u16 *freq_list = freq_list_v1; - int n_freqs = ARRAY_SIZE(freq_list_v1); - int idx; + const u16 *freq_list; + int idx, n_freqs; if (!is_mt7915(&dev->mt76)) { - if (is_mt7981(&dev->mt76)) { - freq_list = freq_list_v2_7981; - n_freqs = ARRAY_SIZE(freq_list_v2_7981); - } else { - freq_list = freq_list_v2; - n_freqs = ARRAY_SIZE(freq_list_v2); - } + freq_list = freq_list_v2; + n_freqs = ARRAY_SIZE(freq_list_v2); + } else { + freq_list = freq_list_v1; + n_freqs = ARRAY_SIZE(freq_list_v1); } if (freq < 4000) { From 9557b6fe0c8b58d32a5c857183a7b431dd5e7b21 Mon Sep 17 00:00:00 2001 From: Ming Yen Hsieh Date: Mon, 8 Sep 2025 15:12:45 +0800 Subject: [PATCH 079/167] wifi: mt76: mt7925: refine the txpower initialization flow Refactor the initialization and reset flow for tx power setting to eliminate redundant configurations Signed-off-by: Ming Yen Hsieh Link: https://patch.msgid.link/20250908071245.1833006-1-mingyen.hsieh@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 5 ++++- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 14 -------------- drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 5 +++-- drivers/net/wireless/mediatek/mt76/mt792x.h | 1 - 4 files changed, 7 insertions(+), 18 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index b581ab9427f2..1e44e96f034e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -1300,7 +1300,6 @@ void mt7925_mac_reset_work(struct work_struct *work) cancel_delayed_work_sync(&dev->mphy.mac_work); cancel_delayed_work_sync(&pm->ps_work); cancel_work_sync(&pm->wake_work); - dev->sar_inited = false; for (i = 0; i < 10; i++) { mutex_lock(&dev->mt76.mutex); @@ -1329,6 +1328,10 @@ void mt7925_mac_reset_work(struct work_struct *work) IEEE80211_IFACE_ITER_RESUME_ALL, mt7925_vif_connect_iter, NULL); mt76_connac_power_save_sched(&dev->mt76.phy, pm); + + mt792x_mutex_acquire(dev); + mt7925_mcu_set_clc(dev, "00", ENVIRON_INDOOR); + mt792x_mutex_release(dev); } void mt7925_coredump_work(struct work_struct *work) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 7416098db9fc..ac3d485a2f78 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -311,7 +311,6 @@ void mt7925_set_stream_he_eht_caps(struct mt792x_phy *phy) int __mt7925_start(struct mt792x_phy *phy) { struct mt76_phy *mphy = phy->mt76; - struct mt792x_dev *dev = phy->dev; int err; err = mt7925_mcu_set_channel_domain(mphy); @@ -322,13 +321,6 @@ int __mt7925_start(struct mt792x_phy *phy) if (err) return err; - if (!dev->sar_inited) { - err = mt7925_set_tx_sar_pwr(mphy->hw, NULL); - if (err) - return err; - dev->sar_inited = true; - } - mt792x_mac_reset_counters(phy); set_bit(MT76_STATE_RUNNING, &mphy->state); @@ -1682,13 +1674,7 @@ static int mt7925_set_sar_specs(struct ieee80211_hw *hw, int err; mt792x_mutex_acquire(dev); - err = mt7925_mcu_set_clc(dev, dev->mt76.alpha2, - dev->country_ie_env); - if (err < 0) - goto out; - err = mt7925_set_tx_sar_pwr(hw, sar); -out: mt792x_mutex_release(dev); return err; diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index 10d68d241ba1..8eda407e4135 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -759,7 +759,6 @@ static int mt7925_load_clc(struct mt792x_dev *dev, const char *fw_name) } } - ret = mt7925_mcu_set_clc(dev, "00", ENVIRON_INDOOR); out: release_firmware(fw); @@ -3724,6 +3723,8 @@ out: int mt7925_mcu_set_rate_txpower(struct mt76_phy *phy) { + struct mt76_dev *mdev = phy->dev; + struct mt792x_dev *dev = mt792x_hw_dev(mdev->hw); int err; if (phy->cap.has_2ghz) { @@ -3740,7 +3741,7 @@ int mt7925_mcu_set_rate_txpower(struct mt76_phy *phy) return err; } - if (phy->cap.has_6ghz) { + if (phy->cap.has_6ghz && dev->phy.clc_chan_conf) { err = mt7925_mcu_rate_txpower_band(phy, NL80211_BAND_6GHZ); if (err < 0) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index 443d397d9961..f2c8b9e4aa0f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -234,7 +234,6 @@ struct mt792x_dev { bool aspm_supported:1; bool hif_idle:1; bool hif_resumed:1; - bool sar_inited:1; bool regd_change:1; wait_queue_head_t wait; From cb6ebbdffef2a888b95f121637cd1fad473919c6 Mon Sep 17 00:00:00 2001 From: Howard Hsu Date: Tue, 9 Sep 2025 17:35:49 +0200 Subject: [PATCH 080/167] wifi: mt76: mt7996: support writing MAC TXD for AddBA Request Support writing MAC TXD for the AddBA Req. Without this commit, the start sequence number in AddBA Req will be unexpected value for MT7996 and MT7992. This can result in certain stations (e.g., AX200) dropping packets, leading to ping failures and degraded connectivity. Ensuring the correct MAC TXD and TXP helps maintain reliable packet transmission and prevents interoperability issues with affected stations. Signed-off-by: Howard Hsu Link: https://patch.msgid.link/20250909-mt7996-addba-txd-fix-v1-1-feec16f0c6f0@kernel.org Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt76_connac3_mac.h | 7 ++ .../net/wireless/mediatek/mt76/mt7996/mac.c | 105 ++++++++++++------ 2 files changed, 76 insertions(+), 36 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac3_mac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac3_mac.h index 1013cad57a7f..c5eaedca11e0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac3_mac.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac3_mac.h @@ -294,6 +294,13 @@ enum tx_frag_idx { #define MT_TXP_BUF_LEN GENMASK(11, 0) #define MT_TXP_DMA_ADDR_H GENMASK(15, 12) +#define MT_TXP0_TOKEN_ID0 GENMASK(14, 0) +#define MT_TXP0_TOKEN_ID0_VALID_MASK BIT(15) + +#define MT_TXP1_TID_ADDBA GENMASK(14, 12) +#define MT_TXP3_ML0_MASK BIT(15) +#define MT_TXP3_DMA_ADDR_H GENMASK(13, 12) + #define MT_TX_RATE_STBC BIT(14) #define MT_TX_RATE_NSS GENMASK(13, 10) #define MT_TX_RATE_MODE GENMASK(9, 6) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 2183193b26a6..c1c4014e1475 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -802,6 +802,9 @@ mt7996_mac_write_txwi_80211(struct mt7996_dev *dev, __le32 *txwi, mgmt->u.action.u.addba_req.action_code == WLAN_ACTION_ADDBA_REQ) { if (is_mt7990(&dev->mt76)) txwi[6] |= cpu_to_le32(FIELD_PREP(MT_TXD6_TID_ADDBA, tid)); + else + txwi[7] |= cpu_to_le32(MT_TXD7_MAC_TXD); + tid = MT_TX_ADDBA; } else if (ieee80211_is_mgmt(hdr->frame_control)) { tid = MT_TX_NORMAL; @@ -1035,10 +1038,10 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx_info->skb); struct ieee80211_key_conf *key = info->control.hw_key; struct ieee80211_vif *vif = info->control.vif; - struct mt76_connac_txp_common *txp; struct mt76_txwi_cache *t; int id, i, pid, nbuf = tx_info->nbuf - 1; bool is_8023 = info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP; + __le32 *ptr = (__le32 *)txwi_ptr; u8 *txwi = (u8 *)txwi_ptr; if (unlikely(tx_info->skb->len <= ETH_HLEN)) @@ -1096,46 +1099,76 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, mt7996_mac_write_txwi(dev, txwi_ptr, tx_info->skb, wcid, key, pid, qid, 0); - txp = (struct mt76_connac_txp_common *)(txwi + MT_TXD_SIZE); - for (i = 0; i < nbuf; i++) { - u16 len; + /* MT7996 and MT7992 require driver to provide the MAC TXP for AddBA + * req + */ + if (le32_to_cpu(ptr[7]) & MT_TXD7_MAC_TXD) { + u32 val; - len = FIELD_PREP(MT_TXP_BUF_LEN, tx_info->buf[i + 1].len); + ptr = (__le32 *)(txwi + MT_TXD_SIZE); + memset((void *)ptr, 0, sizeof(struct mt76_connac_fw_txp)); + + val = FIELD_PREP(MT_TXP0_TOKEN_ID0, id) | + MT_TXP0_TOKEN_ID0_VALID_MASK; + ptr[0] = cpu_to_le32(val); + + val = FIELD_PREP(MT_TXP1_TID_ADDBA, + tx_info->skb->priority & + IEEE80211_QOS_CTL_TID_MASK); + ptr[1] = cpu_to_le32(val); + ptr[2] = cpu_to_le32(tx_info->buf[1].addr & 0xFFFFFFFF); + + val = FIELD_PREP(MT_TXP_BUF_LEN, tx_info->buf[1].len) | + MT_TXP3_ML0_MASK; #ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT - len |= FIELD_PREP(MT_TXP_DMA_ADDR_H, - tx_info->buf[i + 1].addr >> 32); + val |= FIELD_PREP(MT_TXP3_DMA_ADDR_H, + tx_info->buf[1].addr >> 32); +#endif + ptr[3] = cpu_to_le32(val); + } else { + struct mt76_connac_txp_common *txp; + + txp = (struct mt76_connac_txp_common *)(txwi + MT_TXD_SIZE); + for (i = 0; i < nbuf; i++) { + u16 len; + + len = FIELD_PREP(MT_TXP_BUF_LEN, tx_info->buf[i + 1].len); +#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT + len |= FIELD_PREP(MT_TXP_DMA_ADDR_H, + tx_info->buf[i + 1].addr >> 32); #endif - txp->fw.buf[i] = cpu_to_le32(tx_info->buf[i + 1].addr); - txp->fw.len[i] = cpu_to_le16(len); + txp->fw.buf[i] = cpu_to_le32(tx_info->buf[i + 1].addr); + txp->fw.len[i] = cpu_to_le16(len); + } + txp->fw.nbuf = nbuf; + + txp->fw.flags = cpu_to_le16(MT_CT_INFO_FROM_HOST); + + if (!is_8023 || pid >= MT_PACKET_ID_FIRST) + txp->fw.flags |= cpu_to_le16(MT_CT_INFO_APPLY_TXD); + + if (!key) + txp->fw.flags |= cpu_to_le16(MT_CT_INFO_NONE_CIPHER_FRAME); + + if (!is_8023 && mt7996_tx_use_mgmt(dev, tx_info->skb)) + txp->fw.flags |= cpu_to_le16(MT_CT_INFO_MGMT_FRAME); + + if (vif) { + struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; + struct mt76_vif_link *mlink = NULL; + + if (wcid->offchannel) + mlink = rcu_dereference(mvif->mt76.offchannel_link); + if (!mlink) + mlink = rcu_dereference(mvif->mt76.link[wcid->link_id]); + + txp->fw.bss_idx = mlink ? mlink->idx : mvif->deflink.mt76.idx; + } + + txp->fw.token = cpu_to_le16(id); + txp->fw.rept_wds_wcid = cpu_to_le16(sta ? wcid->idx : 0xfff); } - txp->fw.nbuf = nbuf; - - txp->fw.flags = cpu_to_le16(MT_CT_INFO_FROM_HOST); - - if (!is_8023 || pid >= MT_PACKET_ID_FIRST) - txp->fw.flags |= cpu_to_le16(MT_CT_INFO_APPLY_TXD); - - if (!key) - txp->fw.flags |= cpu_to_le16(MT_CT_INFO_NONE_CIPHER_FRAME); - - if (!is_8023 && mt7996_tx_use_mgmt(dev, tx_info->skb)) - txp->fw.flags |= cpu_to_le16(MT_CT_INFO_MGMT_FRAME); - - if (vif) { - struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; - struct mt76_vif_link *mlink = NULL; - - if (wcid->offchannel) - mlink = rcu_dereference(mvif->mt76.offchannel_link); - if (!mlink) - mlink = rcu_dereference(mvif->mt76.link[wcid->link_id]); - - txp->fw.bss_idx = mlink ? mlink->idx : mvif->deflink.mt76.idx; - } - - txp->fw.token = cpu_to_le16(id); - txp->fw.rept_wds_wcid = cpu_to_le16(sta ? wcid->idx : 0xfff); tx_info->skb = NULL; From c7c682100cec97b699fe24b26d89278fd459cc84 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Thu, 11 Sep 2025 15:16:19 -0700 Subject: [PATCH 081/167] wifi: mt76: mt76_eeprom_override to int mt76_eeprom_override has of_get_mac_address, which can return -EPROBE_DEFER if the nvmem driver gets loaded after mt76 for some reason. Make sure this gets passed to probe so that nvmem mac overrides always work. Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20250911221619.16035-1-rosenp@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/eeprom.c | 9 +++++++-- drivers/net/wireless/mediatek/mt76/mt76.h | 2 +- drivers/net/wireless/mediatek/mt76/mt7603/eeprom.c | 3 +-- drivers/net/wireless/mediatek/mt76/mt7615/eeprom.c | 4 +--- drivers/net/wireless/mediatek/mt76/mt7615/init.c | 5 ++++- drivers/net/wireless/mediatek/mt76/mt76x0/eeprom.c | 6 +++++- drivers/net/wireless/mediatek/mt76/mt76x2/eeprom.c | 4 +++- drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c | 4 +--- drivers/net/wireless/mediatek/mt76/mt7915/init.c | 4 +++- drivers/net/wireless/mediatek/mt76/mt7921/init.c | 4 +++- drivers/net/wireless/mediatek/mt76/mt7925/init.c | 4 +++- drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c | 3 +-- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 4 +++- 13 files changed, 36 insertions(+), 20 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/eeprom.c b/drivers/net/wireless/mediatek/mt76/eeprom.c index 443517d06c9f..a987c5e4eff6 100644 --- a/drivers/net/wireless/mediatek/mt76/eeprom.c +++ b/drivers/net/wireless/mediatek/mt76/eeprom.c @@ -163,13 +163,16 @@ static int mt76_get_of_eeprom(struct mt76_dev *dev, void *eep, int len) return mt76_get_of_data_from_nvmem(dev, eep, "eeprom", len); } -void +int mt76_eeprom_override(struct mt76_phy *phy) { struct mt76_dev *dev = phy->dev; struct device_node *np = dev->dev->of_node; + int err; - of_get_mac_address(np, phy->macaddr); + err = of_get_mac_address(np, phy->macaddr); + if (err == -EPROBE_DEFER) + return err; if (!is_valid_ether_addr(phy->macaddr)) { eth_random_addr(phy->macaddr); @@ -177,6 +180,8 @@ mt76_eeprom_override(struct mt76_phy *phy) "Invalid MAC address, using random address %pM\n", phy->macaddr); } + + return 0; } EXPORT_SYMBOL_GPL(mt76_eeprom_override); diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index edda01ff1117..883356cd0c0b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -1294,7 +1294,7 @@ void mt76_seq_puts_array(struct seq_file *file, const char *str, s8 *val, int len); int mt76_eeprom_init(struct mt76_dev *dev, int len); -void mt76_eeprom_override(struct mt76_phy *phy); +int mt76_eeprom_override(struct mt76_phy *phy); int mt76_get_of_data_from_mtd(struct mt76_dev *dev, void *eep, int offset, int len); int mt76_get_of_data_from_nvmem(struct mt76_dev *dev, void *eep, const char *cell_name, int len); diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/eeprom.c b/drivers/net/wireless/mediatek/mt76/mt7603/eeprom.c index f5a6b03bc61d..88382b537a33 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/eeprom.c +++ b/drivers/net/wireless/mediatek/mt76/mt7603/eeprom.c @@ -182,7 +182,6 @@ int mt7603_eeprom_init(struct mt7603_dev *dev) dev->mphy.antenna_mask = 1; dev->mphy.chainmask = dev->mphy.antenna_mask; - mt76_eeprom_override(&dev->mphy); - return 0; + return mt76_eeprom_override(&dev->mphy); } diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/eeprom.c b/drivers/net/wireless/mediatek/mt76/mt7615/eeprom.c index ccedea7e8a50..d4bc7e11e772 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7615/eeprom.c +++ b/drivers/net/wireless/mediatek/mt76/mt7615/eeprom.c @@ -351,8 +351,6 @@ int mt7615_eeprom_init(struct mt7615_dev *dev, u32 addr) memcpy(dev->mphy.macaddr, dev->mt76.eeprom.data + MT_EE_MAC_ADDR, ETH_ALEN); - mt76_eeprom_override(&dev->mphy); - - return 0; + return mt76_eeprom_override(&dev->mphy); } EXPORT_SYMBOL_GPL(mt7615_eeprom_init); diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/init.c b/drivers/net/wireless/mediatek/mt76/mt7615/init.c index aae80005a3c1..3e7af3e58736 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7615/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7615/init.c @@ -570,7 +570,10 @@ int mt7615_register_ext_phy(struct mt7615_dev *dev) ETH_ALEN); mphy->macaddr[0] |= 2; mphy->macaddr[0] ^= BIT(7); - mt76_eeprom_override(mphy); + + ret = mt76_eeprom_override(mphy); + if (ret) + return ret; /* second phy can only handle 5 GHz */ mphy->cap.has_5ghz = true; diff --git a/drivers/net/wireless/mediatek/mt76/mt76x0/eeprom.c b/drivers/net/wireless/mediatek/mt76/mt76x0/eeprom.c index 4de45a56812d..d4506b8b46fa 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76x0/eeprom.c +++ b/drivers/net/wireless/mediatek/mt76/mt76x0/eeprom.c @@ -332,7 +332,11 @@ int mt76x0_eeprom_init(struct mt76x02_dev *dev) memcpy(dev->mphy.macaddr, (u8 *)dev->mt76.eeprom.data + MT_EE_MAC_ADDR, ETH_ALEN); - mt76_eeprom_override(&dev->mphy); + + err = mt76_eeprom_override(&dev->mphy); + if (err) + return err; + mt76x02_mac_setaddr(dev, dev->mphy.macaddr); mt76x0_set_chip_cap(dev); diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2/eeprom.c b/drivers/net/wireless/mediatek/mt76/mt76x2/eeprom.c index 156b16c17b2b..221805deb42f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76x2/eeprom.c +++ b/drivers/net/wireless/mediatek/mt76/mt76x2/eeprom.c @@ -499,7 +499,9 @@ int mt76x2_eeprom_init(struct mt76x02_dev *dev) mt76x02_eeprom_parse_hw_cap(dev); mt76x2_eeprom_get_macaddr(dev); - mt76_eeprom_override(&dev->mphy); + ret = mt76_eeprom_override(&dev->mphy); + if (ret) + return ret; dev->mphy.macaddr[0] &= ~BIT(1); return 0; diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c b/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c index c0f3402d30bb..38dfd5de365c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c @@ -284,9 +284,7 @@ int mt7915_eeprom_init(struct mt7915_dev *dev) memcpy(dev->mphy.macaddr, dev->mt76.eeprom.data + MT_EE_MAC_ADDR, ETH_ALEN); - mt76_eeprom_override(&dev->mphy); - - return 0; + return mt76_eeprom_override(&dev->mphy); } int mt7915_eeprom_get_target_power(struct mt7915_dev *dev, diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/init.c b/drivers/net/wireless/mediatek/mt76/mt7915/init.c index 3e30ca5155d2..5ea8b46e092e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/init.c @@ -702,7 +702,9 @@ mt7915_register_ext_phy(struct mt7915_dev *dev, struct mt7915_phy *phy) mphy->macaddr[0] |= 2; mphy->macaddr[0] ^= BIT(7); } - mt76_eeprom_override(mphy); + ret = mt76_eeprom_override(mphy); + if (ret) + return ret; /* init wiphy according to mphy and phy */ mt7915_init_wiphy(phy); diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/init.c b/drivers/net/wireless/mediatek/mt76/mt7921/init.c index 14e17dc90256..b9098a7331b1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/init.c @@ -189,7 +189,9 @@ static int __mt7921_init_hardware(struct mt792x_dev *dev) if (ret) goto out; - mt76_eeprom_override(&dev->mphy); + ret = mt76_eeprom_override(&dev->mphy); + if (ret) + goto out; ret = mt7921_mcu_set_eeprom(dev); if (ret) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/init.c b/drivers/net/wireless/mediatek/mt76/mt7925/init.c index 4249bad83c93..d7d5afe365ed 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/init.c @@ -249,7 +249,9 @@ static int __mt7925_init_hardware(struct mt792x_dev *dev) if (ret) goto out; - mt76_eeprom_override(&dev->mphy); + ret = mt76_eeprom_override(&dev->mphy); + if (ret) + goto out; ret = mt7925_mcu_set_eeprom(dev); if (ret) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c b/drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c index 87c6192b6384..da3231c9aa11 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c @@ -334,9 +334,8 @@ int mt7996_eeprom_init(struct mt7996_dev *dev) return ret; memcpy(dev->mphy.macaddr, dev->mt76.eeprom.data + MT_EE_MAC_ADDR, ETH_ALEN); - mt76_eeprom_override(&dev->mphy); - return 0; + return mt76_eeprom_override(&dev->mphy); } int mt7996_eeprom_get_target_power(struct mt7996_dev *dev, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index d0ed43a78c85..89546bf4c7aa 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -701,7 +701,9 @@ static int mt7996_register_phy(struct mt7996_dev *dev, enum mt76_band_id band) if (band == MT_BAND2) mphy->macaddr[0] ^= BIT(6); } - mt76_eeprom_override(mphy); + ret = mt76_eeprom_override(mphy); + if (ret) + goto error; /* init wiphy according to mphy and phy */ mt7996_init_wiphy_band(mphy->hw, phy); From fc6627ca8a5f811b601aea74e934cf8a048c88ac Mon Sep 17 00:00:00 2001 From: Nick Morrow Date: Fri, 12 Sep 2025 15:45:56 -0500 Subject: [PATCH 082/167] wifi: mt76: mt7921u: Add VID/PID for Netgear A7500 Add VID/PID 0846/9065 for Netgear A7500. Reported-by: Autumn Dececco Tested-by: Autumn Dececco Signed-off-by: Nick Morrow Cc: stable@vger.kernel.org Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/80bacfd6-6073-4ce5-be32-ae9580832337@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/usb.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c index fe9751851ff7..100bdba32ba5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c @@ -21,6 +21,9 @@ static const struct usb_device_id mt7921u_device_table[] = { /* Netgear, Inc. [A8000,AXE3000] */ { USB_DEVICE_AND_INTERFACE_INFO(0x0846, 0x9060, 0xff, 0xff, 0xff), .driver_info = (kernel_ulong_t)MT7921_FIRMWARE_WM }, + /* Netgear, Inc. A7500 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x0846, 0x9065, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)MT7921_FIRMWARE_WM }, /* TP-Link TXE50UH */ { USB_DEVICE_AND_INTERFACE_INFO(0x35bc, 0x0107, 0xff, 0xff, 0xff), .driver_info = (kernel_ulong_t)MT7921_FIRMWARE_WM }, From 0a5df0ec47f7edc04957925a9644101682041d27 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 15 Sep 2025 09:58:56 +0200 Subject: [PATCH 083/167] wifi: mt76: mt7996: remove redundant per-phy mac80211 calls during restart There is only one wiphy, so extra calls must be removed. For calls that need to remain per-wiphy, use mt7996_for_each_phy Fixes: 69d54ce7491d ("wifi: mt76: mt7996: switch to single multi-radio wiphy") Link: https://patch.msgid.link/20250915075910.47558-1-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/mac.c | 137 +++++------------- 1 file changed, 35 insertions(+), 102 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index c1c4014e1475..f6c725f52c4a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2220,13 +2220,10 @@ void mt7996_tx_token_put(struct mt7996_dev *dev) static int mt7996_mac_restart(struct mt7996_dev *dev) { - struct mt7996_phy *phy2, *phy3; struct mt76_dev *mdev = &dev->mt76; + struct mt7996_phy *phy; int i, ret; - phy2 = mt7996_phy2(dev); - phy3 = mt7996_phy3(dev); - if (dev->hif2) { mt76_wr(dev, MT_INT1_MASK_CSR, 0x0); mt76_wr(dev, MT_INT1_SOURCE_CSR, ~0); @@ -2238,20 +2235,14 @@ mt7996_mac_restart(struct mt7996_dev *dev) mt76_wr(dev, MT_PCIE1_MAC_INT_ENABLE, 0x0); } - set_bit(MT76_RESET, &dev->mphy.state); set_bit(MT76_MCU_RESET, &dev->mphy.state); + mt7996_for_each_phy(dev, phy) + set_bit(MT76_RESET, &phy->mt76->state); wake_up(&dev->mt76.mcu.wait); - if (phy2) - set_bit(MT76_RESET, &phy2->mt76->state); - if (phy3) - set_bit(MT76_RESET, &phy3->mt76->state); /* lock/unlock all queues to ensure that no tx is pending */ - mt76_txq_schedule_all(&dev->mphy); - if (phy2) - mt76_txq_schedule_all(phy2->mt76); - if (phy3) - mt76_txq_schedule_all(phy3->mt76); + mt7996_for_each_phy(dev, phy) + mt76_txq_schedule_all(phy->mt76); /* disable all tx/rx napi */ mt76_worker_disable(&dev->mt76.tx_worker); @@ -2335,36 +2326,25 @@ mt7996_mac_restart(struct mt7996_dev *dev) goto out; mt7996_mac_init(dev); - mt7996_init_txpower(&dev->phy); - mt7996_init_txpower(phy2); - mt7996_init_txpower(phy3); + mt7996_for_each_phy(dev, phy) + mt7996_init_txpower(phy); ret = mt7996_txbf_init(dev); + if (ret) + goto out; + + mt7996_for_each_phy(dev, phy) { + if (!test_bit(MT76_STATE_RUNNING, &phy->mt76->state)) + continue; - if (test_bit(MT76_STATE_RUNNING, &dev->mphy.state)) { ret = mt7996_run(&dev->phy); if (ret) goto out; } - if (phy2 && test_bit(MT76_STATE_RUNNING, &phy2->mt76->state)) { - ret = mt7996_run(phy2); - if (ret) - goto out; - } - - if (phy3 && test_bit(MT76_STATE_RUNNING, &phy3->mt76->state)) { - ret = mt7996_run(phy3); - if (ret) - goto out; - } - out: /* reset done */ - clear_bit(MT76_RESET, &dev->mphy.state); - if (phy2) - clear_bit(MT76_RESET, &phy2->mt76->state); - if (phy3) - clear_bit(MT76_RESET, &phy3->mt76->state); + mt7996_for_each_phy(dev, phy) + clear_bit(MT76_RESET, &phy->mt76->state); napi_enable(&dev->mt76.tx_napi); local_bh_disable(); @@ -2378,26 +2358,18 @@ out: static void mt7996_mac_full_reset(struct mt7996_dev *dev) { - struct mt7996_phy *phy2, *phy3; + struct ieee80211_hw *hw = mt76_hw(dev); + struct mt7996_phy *phy; int i; - phy2 = mt7996_phy2(dev); - phy3 = mt7996_phy3(dev); dev->recovery.hw_full_reset = true; wake_up(&dev->mt76.mcu.wait); - ieee80211_stop_queues(mt76_hw(dev)); - if (phy2) - ieee80211_stop_queues(phy2->mt76->hw); - if (phy3) - ieee80211_stop_queues(phy3->mt76->hw); + ieee80211_stop_queues(hw); cancel_work_sync(&dev->wed_rro.work); - cancel_delayed_work_sync(&dev->mphy.mac_work); - if (phy2) - cancel_delayed_work_sync(&phy2->mt76->mac_work); - if (phy3) - cancel_delayed_work_sync(&phy3->mt76->mac_work); + mt7996_for_each_phy(dev, phy) + cancel_delayed_work_sync(&phy->mt76->mac_work); mutex_lock(&dev->mt76.mutex); for (i = 0; i < 10; i++) { @@ -2410,40 +2382,23 @@ mt7996_mac_full_reset(struct mt7996_dev *dev) dev_err(dev->mt76.dev, "chip full reset failed\n"); ieee80211_restart_hw(mt76_hw(dev)); - if (phy2) - ieee80211_restart_hw(phy2->mt76->hw); - if (phy3) - ieee80211_restart_hw(phy3->mt76->hw); - ieee80211_wake_queues(mt76_hw(dev)); - if (phy2) - ieee80211_wake_queues(phy2->mt76->hw); - if (phy3) - ieee80211_wake_queues(phy3->mt76->hw); dev->recovery.hw_full_reset = false; - ieee80211_queue_delayed_work(mt76_hw(dev), - &dev->mphy.mac_work, - MT7996_WATCHDOG_TIME); - if (phy2) - ieee80211_queue_delayed_work(phy2->mt76->hw, - &phy2->mt76->mac_work, - MT7996_WATCHDOG_TIME); - if (phy3) - ieee80211_queue_delayed_work(phy3->mt76->hw, - &phy3->mt76->mac_work, + mt7996_for_each_phy(dev, phy) + ieee80211_queue_delayed_work(hw, &phy->mt76->mac_work, MT7996_WATCHDOG_TIME); } void mt7996_mac_reset_work(struct work_struct *work) { - struct mt7996_phy *phy2, *phy3; + struct ieee80211_hw *hw; struct mt7996_dev *dev; + struct mt7996_phy *phy; int i; dev = container_of(work, struct mt7996_dev, reset_work); - phy2 = mt7996_phy2(dev); - phy3 = mt7996_phy3(dev); + hw = mt76_hw(dev); /* chip full reset */ if (dev->recovery.restart) { @@ -2474,7 +2429,7 @@ void mt7996_mac_reset_work(struct work_struct *work) return; dev_info(dev->mt76.dev,"\n%s L1 SER recovery start.", - wiphy_name(dev->mt76.hw->wiphy)); + wiphy_name(hw->wiphy)); if (mtk_wed_device_active(&dev->mt76.mmio.wed_hif2)) mtk_wed_device_stop(&dev->mt76.mmio.wed_hif2); @@ -2483,25 +2438,17 @@ void mt7996_mac_reset_work(struct work_struct *work) mtk_wed_device_stop(&dev->mt76.mmio.wed); ieee80211_stop_queues(mt76_hw(dev)); - if (phy2) - ieee80211_stop_queues(phy2->mt76->hw); - if (phy3) - ieee80211_stop_queues(phy3->mt76->hw); set_bit(MT76_RESET, &dev->mphy.state); set_bit(MT76_MCU_RESET, &dev->mphy.state); wake_up(&dev->mt76.mcu.wait); cancel_work_sync(&dev->wed_rro.work); - cancel_delayed_work_sync(&dev->mphy.mac_work); - if (phy2) { - set_bit(MT76_RESET, &phy2->mt76->state); - cancel_delayed_work_sync(&phy2->mt76->mac_work); - } - if (phy3) { - set_bit(MT76_RESET, &phy3->mt76->state); - cancel_delayed_work_sync(&phy3->mt76->mac_work); + mt7996_for_each_phy(dev, phy) { + set_bit(MT76_RESET, &phy->mt76->state); + cancel_delayed_work_sync(&phy->mt76->mac_work); } + mt76_worker_disable(&dev->mt76.tx_worker); mt76_for_each_q_rx(&dev->mt76, i) { if (mtk_wed_device_active(&dev->mt76.mmio.wed) && @@ -2553,11 +2500,8 @@ void mt7996_mac_reset_work(struct work_struct *work) } clear_bit(MT76_MCU_RESET, &dev->mphy.state); - clear_bit(MT76_RESET, &dev->mphy.state); - if (phy2) - clear_bit(MT76_RESET, &phy2->mt76->state); - if (phy3) - clear_bit(MT76_RESET, &phy3->mt76->state); + mt7996_for_each_phy(dev, phy) + clear_bit(MT76_RESET, &phy->mt76->state); mt76_for_each_q_rx(&dev->mt76, i) { if (mtk_wed_device_active(&dev->mt76.mmio.wed) && @@ -2579,25 +2523,14 @@ void mt7996_mac_reset_work(struct work_struct *work) napi_schedule(&dev->mt76.tx_napi); local_bh_enable(); - ieee80211_wake_queues(mt76_hw(dev)); - if (phy2) - ieee80211_wake_queues(phy2->mt76->hw); - if (phy3) - ieee80211_wake_queues(phy3->mt76->hw); + ieee80211_wake_queues(hw); mutex_unlock(&dev->mt76.mutex); mt7996_update_beacons(dev); - ieee80211_queue_delayed_work(mt76_hw(dev), &dev->mphy.mac_work, - MT7996_WATCHDOG_TIME); - if (phy2) - ieee80211_queue_delayed_work(phy2->mt76->hw, - &phy2->mt76->mac_work, - MT7996_WATCHDOG_TIME); - if (phy3) - ieee80211_queue_delayed_work(phy3->mt76->hw, - &phy3->mt76->mac_work, + mt7996_for_each_phy(dev, phy) + ieee80211_queue_delayed_work(hw, &phy->mt76->mac_work, MT7996_WATCHDOG_TIME); dev_info(dev->mt76.dev,"\n%s L1 SER recovery completed.", wiphy_name(dev->mt76.hw->wiphy)); From ace5d3b6b49e8391beb4d7244348ba7da5298878 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 15 Sep 2025 09:58:57 +0200 Subject: [PATCH 084/167] wifi: mt76: mt7996: improve hardware restart reliability Port latest version of similar changes from mt7915: - use reconfig_complete to restart mac_work / queues - clear wcid and vif mask to avoid leak - fix sta poll list corruption - reset station links - reset interface links - clear rro list Link: https://patch.msgid.link/20250915075910.47558-2-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/mac.c | 86 +++++++++++++++++-- .../net/wireless/mediatek/mt76/mt7996/main.c | 25 ++++-- .../wireless/mediatek/mt76/mt7996/mt7996.h | 2 + 3 files changed, 100 insertions(+), 13 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index f6c725f52c4a..fd57569a7da7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2355,11 +2355,59 @@ out: return ret; } +static void +mt7996_mac_reset_sta_iter(void *data, struct ieee80211_sta *sta) +{ + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + struct mt7996_dev *dev = data; + int i; + + for (i = 0; i < ARRAY_SIZE(msta->link); i++) { + struct mt7996_sta_link *msta_link = NULL; + + msta_link = rcu_replace_pointer(msta->link[i], msta_link, + lockdep_is_held(&dev->mt76.mutex)); + if (!msta_link) + continue; + + mt7996_mac_sta_deinit_link(dev, msta_link); + + if (msta->deflink_id == i) { + msta->deflink_id = IEEE80211_LINK_UNSPECIFIED; + continue; + } + + kfree_rcu(msta_link, rcu_head); + } +} + +static void +mt7996_mac_reset_vif_iter(void *data, u8 *mac, struct ieee80211_vif *vif) +{ + struct mt76_vif_link *mlink = (struct mt76_vif_link *)vif->drv_priv; + struct mt76_vif_data *mvif = mlink->mvif; + struct mt7996_dev *dev = data; + int i; + + rcu_read_lock(); + for (i = 0; i < ARRAY_SIZE(mvif->link); i++) { + + mlink = mt76_dereference(mvif->link[i], &dev->mt76); + if (!mlink || mlink == (struct mt76_vif_link *)vif->drv_priv) + continue; + + rcu_assign_pointer(mvif->link[i], NULL); + kfree_rcu(mlink, rcu_head); + } + rcu_read_unlock(); +} + static void mt7996_mac_full_reset(struct mt7996_dev *dev) { struct ieee80211_hw *hw = mt76_hw(dev); struct mt7996_phy *phy; + LIST_HEAD(list); int i; dev->recovery.hw_full_reset = true; @@ -2376,18 +2424,42 @@ mt7996_mac_full_reset(struct mt7996_dev *dev) if (!mt7996_mac_restart(dev)) break; } - mutex_unlock(&dev->mt76.mutex); if (i == 10) dev_err(dev->mt76.dev, "chip full reset failed\n"); - ieee80211_restart_hw(mt76_hw(dev)); - ieee80211_wake_queues(mt76_hw(dev)); - - dev->recovery.hw_full_reset = false; mt7996_for_each_phy(dev, phy) - ieee80211_queue_delayed_work(hw, &phy->mt76->mac_work, - MT7996_WATCHDOG_TIME); + phy->omac_mask = 0; + + ieee80211_iterate_stations_atomic(hw, mt7996_mac_reset_sta_iter, dev); + 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); + + spin_lock_bh(&dev->wed_rro.lock); + list_splice_init(&dev->wed_rro.poll_list, &list); + spin_unlock_bh(&dev->wed_rro.lock); + + while (!list_empty(&list)) { + struct mt7996_wed_rro_session_id *e; + + e = list_first_entry(&list, struct mt7996_wed_rro_session_id, + list); + list_del_init(&e->list); + kfree(e); + } + + i = mt76_wcid_alloc(dev->mt76.wcid_mask, MT7996_WTBL_STA); + dev->mt76.global_wcid.idx = i; + dev->recovery.hw_full_reset = false; + + mutex_unlock(&dev->mt76.mutex); + + ieee80211_restart_hw(mt76_hw(dev)); } void mt7996_mac_reset_work(struct work_struct *work) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 6adb7f7bdd6f..8fbe60723071 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -986,13 +986,9 @@ mt7996_mac_sta_init_link(struct mt7996_dev *dev, return 0; } -static void -mt7996_mac_sta_deinit_link(struct mt7996_dev *dev, - struct mt7996_sta_link *msta_link) +void mt7996_mac_sta_deinit_link(struct mt7996_dev *dev, + struct mt7996_sta_link *msta_link) { - mt7996_mac_wtbl_update(dev, msta_link->wcid.idx, - MT_WTBL_UPDATE_ADM_COUNT_CLEAR); - spin_lock_bh(&dev->mt76.sta_poll_lock); if (!list_empty(&msta_link->wcid.poll_list)) list_del_init(&msta_link->wcid.poll_list); @@ -1022,6 +1018,9 @@ mt7996_mac_sta_remove_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, if (!msta_link) continue; + mt7996_mac_wtbl_update(dev, msta_link->wcid.idx, + MT_WTBL_UPDATE_ADM_COUNT_CLEAR); + mt7996_mac_sta_deinit_link(dev, msta_link); link = mt7996_vif_link(dev, vif, link_id); if (!link) @@ -2206,6 +2205,19 @@ mt7996_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, return 0; } +static void +mt7996_reconfig_complete(struct ieee80211_hw *hw, + enum ieee80211_reconfig_type reconfig_type) +{ + struct mt7996_dev *dev = mt7996_hw_dev(hw); + struct mt7996_phy *phy; + + ieee80211_wake_queues(hw); + mt7996_for_each_phy(dev, phy) + ieee80211_queue_delayed_work(hw, &phy->mt76->mac_work, + MT7996_WATCHDOG_TIME); +} + const struct ieee80211_ops mt7996_ops = { .add_chanctx = mt76_add_chanctx, .remove_chanctx = mt76_remove_chanctx, @@ -2264,4 +2276,5 @@ const struct ieee80211_ops mt7996_ops = { #endif .change_vif_links = mt7996_change_vif_links, .change_sta_links = mt7996_mac_sta_change_links, + .reconfig_complete = mt7996_reconfig_complete, }; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index be26e04b7da7..fa10aa6f517a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -817,6 +817,8 @@ void mt7996_mac_twt_teardown_flow(struct mt7996_dev *dev, struct mt7996_vif_link *link, struct mt7996_sta_link *msta_link, u8 flowid); +void mt7996_mac_sta_deinit_link(struct mt7996_dev *dev, + struct mt7996_sta_link *msta_link); void mt7996_mac_add_twt_setup(struct ieee80211_hw *hw, struct ieee80211_sta *sta, struct ieee80211_twt_setup *twt); From beb01caa570c52b87c8f234381eff72817c2a7ab Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 15 Sep 2025 09:58:58 +0200 Subject: [PATCH 085/167] wifi: mt76: mt7996: decrease timeout for commonly issued MCU commands This allows faster recovery from firmware issues. Based on patch by Chad Monroe and ported from mt7915. Link: https://patch.msgid.link/20250915075910.47558-3-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/mcu.c | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 1c89d235026d..29552ab16089 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -242,6 +242,30 @@ mt7996_mcu_parse_response(struct mt76_dev *mdev, int cmd, return ret; } +static void +mt7996_mcu_set_timeout(struct mt76_dev *mdev, int cmd) +{ + mdev->mcu.timeout = 5 * HZ; + + if (!(cmd & __MCU_CMD_FIELD_UNI)) + return; + + switch (FIELD_GET(__MCU_CMD_FIELD_ID, cmd)) { + case MCU_UNI_CMD_THERMAL: + case MCU_UNI_CMD_TWT: + case MCU_UNI_CMD_GET_MIB_INFO: + case MCU_UNI_CMD_STA_REC_UPDATE: + case MCU_UNI_CMD_BSS_INFO_UPDATE: + mdev->mcu.timeout = 2 * HZ; + return; + case MCU_UNI_CMD_EFUSE_CTRL: + mdev->mcu.timeout = 20 * HZ; + return; + default: + break; + } +} + static int mt7996_mcu_send_message(struct mt76_dev *mdev, struct sk_buff *skb, int cmd, int *wait_seq) @@ -255,7 +279,7 @@ mt7996_mcu_send_message(struct mt76_dev *mdev, struct sk_buff *skb, u32 val; u8 seq; - mdev->mcu.timeout = 20 * HZ; + mt7996_mcu_set_timeout(mdev, cmd); seq = ++dev->mt76.mcu.msg_seq & 0xf; if (!seq) From 0c45d52276fd744f5cc6b4129189506f83c0ef1d Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 15 Sep 2025 09:58:59 +0200 Subject: [PATCH 086/167] wifi: mt76: mt7996: fix setting beacon protection keys Include beacon key information in the STA_REC_UPDATE call. Remove mt7996_mcu_get_pn - when installing a new key, we should not reuse any existing PN value. Signed-off-by: Allen Ye Signed-off-by: Michael-CY Lee Link: https://patch.msgid.link/20250915075910.47558-4-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 7 - .../net/wireless/mediatek/mt76/mt7996/mcu.c | 156 +++++------------- .../net/wireless/mediatek/mt76/mt7996/mcu.h | 14 +- 3 files changed, 45 insertions(+), 132 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 8fbe60723071..84d5e0430a94 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -262,13 +262,6 @@ mt7996_set_hw_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, mt76_wcid_key_setup(&dev->mt76, &msta_link->wcid, key); - if (key->keyidx == 6 || key->keyidx == 7) { - err = mt7996_mcu_bcn_prot_enable(dev, link, - msta_link, key); - if (err) - return err; - } - err = mt7996_mcu_add_key(&dev->mt76, vif, key, MCU_WMWA_UNI_CMD(STA_REC_UPDATE), &msta_link->wcid, cmd); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 29552ab16089..5c16e4b780ad 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -2545,41 +2545,58 @@ mt7996_mcu_sta_key_tlv(struct mt76_wcid *wcid, enum set_key_cmd cmd) { struct sta_rec_sec_uni *sec; + struct sec_key_uni *sec_key; struct tlv *tlv; + u8 cipher; tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_KEY_V2, sizeof(*sec)); sec = (struct sta_rec_sec_uni *)tlv; - sec->add = cmd; + sec->add = 0; + sec->n_cipher = 1; + sec_key = &sec->key[0]; + sec_key->wlan_idx = cpu_to_le16(wcid->idx); + sec_key->key_id = key->keyidx; - if (cmd == SET_KEY) { - struct sec_key_uni *sec_key; - u8 cipher; + if (cmd != SET_KEY) + return 0; - cipher = mt76_connac_mcu_get_cipher(key->cipher); - if (cipher == MCU_CIPHER_NONE) - return -EOPNOTSUPP; + cipher = mt76_connac_mcu_get_cipher(key->cipher); + if (cipher == MCU_CIPHER_NONE) + return -EOPNOTSUPP; - sec_key = &sec->key[0]; - sec_key->wlan_idx = cpu_to_le16(wcid->idx); - sec_key->mgmt_prot = 0; - sec_key->cipher_id = cipher; - sec_key->cipher_len = sizeof(*sec_key); - sec_key->key_id = key->keyidx; - sec_key->key_len = key->keylen; - sec_key->need_resp = 0; - memcpy(sec_key->key, key->key, key->keylen); + sec_key->mgmt_prot = 0; + sec_key->cipher_id = cipher; + sec_key->cipher_len = sizeof(*sec_key); + sec_key->key_len = key->keylen; + sec_key->need_resp = 0; + memcpy(sec_key->key, key->key, key->keylen); - if (cipher == MCU_CIPHER_TKIP) { - /* Rx/Tx MIC keys are swapped */ - memcpy(sec_key->key + 16, key->key + 24, 8); - memcpy(sec_key->key + 24, key->key + 16, 8); - } - - sec->n_cipher = 1; - } else { - sec->n_cipher = 0; + if (cipher == MCU_CIPHER_TKIP) { + /* Rx/Tx MIC keys are swapped */ + memcpy(sec_key->key + 16, key->key + 24, 8); + memcpy(sec_key->key + 24, key->key + 16, 8); + return 0; } + if (sec_key->key_id != 6 && sec_key->key_id != 7) + return 0; + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_AES_CMAC: + sec_key->cipher_id = MCU_CIPHER_BCN_PROT_CMAC_128; + break; + case WLAN_CIPHER_SUITE_BIP_GMAC_128: + sec_key->cipher_id = MCU_CIPHER_BCN_PROT_GMAC_128; + break; + case WLAN_CIPHER_SUITE_BIP_GMAC_256: + sec_key->cipher_id = MCU_CIPHER_BCN_PROT_GMAC_256; + break; + default: + return -EOPNOTSUPP; + } + + sec_key->bcn_mode = BP_SW_MODE; + return 0; } @@ -2603,95 +2620,6 @@ int mt7996_mcu_add_key(struct mt76_dev *dev, struct ieee80211_vif *vif, return mt76_mcu_skb_send_msg(dev, skb, mcu_cmd, true); } -static int mt7996_mcu_get_pn(struct mt7996_dev *dev, - struct mt7996_vif_link *link, - struct mt7996_sta_link *msta_link, u8 *pn) -{ -#define TSC_TYPE_BIGTK_PN 2 - struct sta_rec_pn_info *pn_info; - struct sk_buff *skb, *rskb; - struct tlv *tlv; - int ret; - - skb = mt76_connac_mcu_alloc_sta_req(&dev->mt76, &link->mt76, - &msta_link->wcid); - if (IS_ERR(skb)) - return PTR_ERR(skb); - - tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_PN_INFO, sizeof(*pn_info)); - pn_info = (struct sta_rec_pn_info *)tlv; - - pn_info->tsc_type = TSC_TYPE_BIGTK_PN; - ret = mt76_mcu_skb_send_and_get_msg(&dev->mt76, skb, - MCU_WM_UNI_CMD_QUERY(STA_REC_UPDATE), - true, &rskb); - if (ret) - return ret; - - skb_pull(rskb, 4); - - pn_info = (struct sta_rec_pn_info *)rskb->data; - if (le16_to_cpu(pn_info->tag) == STA_REC_PN_INFO) - memcpy(pn, pn_info->pn, 6); - - dev_kfree_skb(rskb); - return 0; -} - -int mt7996_mcu_bcn_prot_enable(struct mt7996_dev *dev, - struct mt7996_vif_link *link, - struct mt7996_sta_link *msta_link, - struct ieee80211_key_conf *key) -{ - struct mt7996_mcu_bcn_prot_tlv *bcn_prot; - struct sk_buff *skb; - struct tlv *tlv; - u8 pn[6] = {}; - int len = sizeof(struct bss_req_hdr) + - sizeof(struct mt7996_mcu_bcn_prot_tlv); - int ret; - - skb = __mt7996_mcu_alloc_bss_req(&dev->mt76, &link->mt76, len); - if (IS_ERR(skb)) - return PTR_ERR(skb); - - tlv = mt76_connac_mcu_add_tlv(skb, UNI_BSS_INFO_BCN_PROT, sizeof(*bcn_prot)); - - bcn_prot = (struct mt7996_mcu_bcn_prot_tlv *)tlv; - - ret = mt7996_mcu_get_pn(dev, link, msta_link, pn); - if (ret) { - dev_kfree_skb(skb); - return ret; - } - - switch (key->cipher) { - case WLAN_CIPHER_SUITE_AES_CMAC: - bcn_prot->cipher_id = MCU_CIPHER_BCN_PROT_CMAC_128; - break; - case WLAN_CIPHER_SUITE_BIP_GMAC_128: - bcn_prot->cipher_id = MCU_CIPHER_BCN_PROT_GMAC_128; - break; - case WLAN_CIPHER_SUITE_BIP_GMAC_256: - bcn_prot->cipher_id = MCU_CIPHER_BCN_PROT_GMAC_256; - break; - case WLAN_CIPHER_SUITE_BIP_CMAC_256: - default: - dev_err(dev->mt76.dev, "Not supported Bigtk Cipher\n"); - dev_kfree_skb(skb); - return -EOPNOTSUPP; - } - - pn[0]++; - memcpy(bcn_prot->pn, pn, 6); - bcn_prot->enable = BP_SW_MODE; - memcpy(bcn_prot->key, key->key, WLAN_MAX_KEY_LEN); - bcn_prot->key_id = key->keyidx; - - return mt76_mcu_skb_send_msg(&dev->mt76, skb, - MCU_WMWA_UNI_CMD(BSS_INFO_UPDATE), true); -} - int mt7996_mcu_add_dev_info(struct mt7996_phy *phy, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link_conf, struct mt76_vif_link *mlink, bool enable) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index 7b21d6ae7e43..c841da1c60e5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -351,17 +351,6 @@ enum { BP_HW_MODE, }; -struct mt7996_mcu_bcn_prot_tlv { - __le16 tag; - __le16 len; - u8 pn[6]; - u8 enable; - u8 cipher_id; - u8 key[WLAN_MAX_KEY_LEN]; - u8 key_id; - u8 __rsv[3]; -} __packed; - struct bss_ra_tlv { __le16 tag; __le16 len; @@ -531,6 +520,9 @@ struct sec_key_uni { u8 key_len; u8 need_resp; u8 key[32]; + u8 pn[6]; + u8 bcn_mode; + u8 _rsv; } __packed; struct sta_rec_sec_uni { From 7c0f63fe37a5da2c13fc35c89053b31be8ead895 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 15 Sep 2025 09:59:00 +0200 Subject: [PATCH 087/167] wifi: mt76: mt7996: fix memory leak on mt7996_mcu_sta_key_tlv error Free the allocated skb on error Link: https://patch.msgid.link/20250915075910.47558-5-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 5c16e4b780ad..5707e6b59aea 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -2614,8 +2614,10 @@ int mt7996_mcu_add_key(struct mt76_dev *dev, struct ieee80211_vif *vif, return PTR_ERR(skb); ret = mt7996_mcu_sta_key_tlv(wcid, skb, key, cmd); - if (ret) + if (ret) { + dev_kfree_skb(skb); return ret; + } return mt76_mcu_skb_send_msg(dev, skb, mcu_cmd, true); } From 04414d7bba785a80400dff4b349f15f847ebc4ba Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 15 Sep 2025 09:59:01 +0200 Subject: [PATCH 088/167] wifi: mt76: mt7996: delete vif keys when requested While deleting sta keys can be omitted in order to fix race conditions, vif keys must be deleted before being replaced in order to prevent accidental reuse in firmware. Link: https://patch.msgid.link/20250915075910.47558-6-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 84d5e0430a94..a81f2133cdc9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -252,13 +252,13 @@ mt7996_set_hw_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, &link->mt76, msta_link, true); } - if (cmd == SET_KEY) { + if (cmd == SET_KEY) *wcid_keyidx = idx; - } else { - if (idx == *wcid_keyidx) - *wcid_keyidx = -1; + else if (idx == *wcid_keyidx) + *wcid_keyidx = -1; + + if (cmd != SET_KEY && sta) continue; - } mt76_wcid_key_setup(&dev->mt76, &msta_link->wcid, key); @@ -277,10 +277,12 @@ mt7996_key_iter(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta, struct ieee80211_key_conf *key, void *data) { + enum set_key_cmd *cmd = data; + if (sta) return; - WARN_ON(mt7996_set_hw_key(hw, SET_KEY, vif, NULL, key)); + WARN_ON(mt7996_set_hw_key(hw, *cmd, vif, NULL, key)); } int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, @@ -291,6 +293,7 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; struct mt7996_sta_link *msta_link = &link->msta_link; struct mt7996_phy *phy = mphy->priv; + enum set_key_cmd key_cmd = SET_KEY; struct mt7996_dev *dev = phy->dev; u8 band_idx = phy->mt76->band_idx; struct mt76_txq *mtxq; @@ -370,7 +373,7 @@ 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, NULL); + ieee80211_iter_keys(mphy->hw, vif, mt7996_key_iter, &key_cmd); if (mvif->mt76.deflink_id == IEEE80211_LINK_UNSPECIFIED) mvif->mt76.deflink_id = link_conf->link_id; @@ -385,10 +388,13 @@ void mt7996_vif_link_remove(struct mt76_phy *mphy, struct ieee80211_vif *vif, struct mt7996_vif_link *link = container_of(mlink, struct mt7996_vif_link, mt76); struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; struct mt7996_sta_link *msta_link = &link->msta_link; + enum set_key_cmd key_cmd = DISABLE_KEY; struct mt7996_phy *phy = mphy->priv; struct mt7996_dev *dev = phy->dev; int idx = msta_link->wcid.idx; + ieee80211_iter_keys(mphy->hw, vif, mt7996_key_iter, &key_cmd); + mt7996_mcu_add_sta(dev, link_conf, NULL, link, NULL, CONN_STATE_DISCONNECT, false); mt7996_mcu_add_bss_info(phy, vif, link_conf, mlink, msta_link, false); From eddc7286f6bbf574f8ab22cdf2afd349a893b447 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 15 Sep 2025 09:59:02 +0200 Subject: [PATCH 089/167] wifi: mt76: mt7996: fix key add/remove imbalance Ensure that a key for a link is only added and removed once. When bringing up a link, only upload keys for that particular link, instead of iterating over all of them. Link: https://patch.msgid.link/20250915075910.47558-7-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 183 +++++++++--------- 1 file changed, 95 insertions(+), 88 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index a81f2133cdc9..d706b8bb244e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -182,107 +182,96 @@ mt7996_init_bitrate_mask(struct ieee80211_vif *vif, struct mt7996_vif_link *mlin static int mt7996_set_hw_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, struct ieee80211_vif *vif, struct ieee80211_sta *sta, - struct ieee80211_key_conf *key) + unsigned int link_id, struct ieee80211_key_conf *key) { struct mt7996_dev *dev = mt7996_hw_dev(hw); + struct mt7996_sta_link *msta_link; + struct mt7996_vif_link *link; int idx = key->keyidx; - unsigned int link_id; - unsigned long links; + u8 *wcid_keyidx; - if (key->link_id >= 0) - links = BIT(key->link_id); - else if (sta && sta->valid_links) - links = sta->valid_links; - else if (vif->valid_links) - links = vif->valid_links; - else - links = BIT(0); + link = mt7996_vif_link(dev, vif, link_id); + if (!link) + return 0; - for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { - struct mt7996_sta_link *msta_link; - struct mt7996_vif_link *link; - u8 *wcid_keyidx; - int err; + if (!mt7996_vif_link_phy(link)) + return 0; - link = mt7996_vif_link(dev, vif, link_id); - if (!link) - continue; + if (sta) { + struct mt7996_sta *msta; - if (sta) { - struct mt7996_sta *msta; + msta = (struct mt7996_sta *)sta->drv_priv; + msta_link = mt76_dereference(msta->link[link_id], + &dev->mt76); + if (!msta_link) + return 0; - msta = (struct mt7996_sta *)sta->drv_priv; - msta_link = mt76_dereference(msta->link[link_id], - &dev->mt76); - if (!msta_link) - continue; + if (!msta_link->wcid.sta) + return -EOPNOTSUPP; + } else { + msta_link = &link->msta_link; + } + wcid_keyidx = &msta_link->wcid.hw_key_idx; - if (!msta_link->wcid.sta) - return -EOPNOTSUPP; - } else { - msta_link = &link->msta_link; + switch (key->cipher) { + case WLAN_CIPHER_SUITE_AES_CMAC: + case WLAN_CIPHER_SUITE_BIP_CMAC_256: + case WLAN_CIPHER_SUITE_BIP_GMAC_128: + case WLAN_CIPHER_SUITE_BIP_GMAC_256: + if (key->keyidx == 6 || key->keyidx == 7) { + wcid_keyidx = &msta_link->wcid.hw_key_idx2; + key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIE; } - wcid_keyidx = &msta_link->wcid.hw_key_idx; - - switch (key->cipher) { - case WLAN_CIPHER_SUITE_AES_CMAC: - case WLAN_CIPHER_SUITE_BIP_CMAC_256: - case WLAN_CIPHER_SUITE_BIP_GMAC_128: - case WLAN_CIPHER_SUITE_BIP_GMAC_256: - if (key->keyidx == 6 || key->keyidx == 7) { - wcid_keyidx = &msta_link->wcid.hw_key_idx2; - key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIE; - } - break; - default: - break; - } - - if (cmd == SET_KEY && !sta && !link->mt76.cipher) { - struct ieee80211_bss_conf *link_conf; - - link_conf = link_conf_dereference_protected(vif, - link_id); - if (!link_conf) - link_conf = &vif->bss_conf; - - link->mt76.cipher = - mt76_connac_mcu_get_cipher(key->cipher); - mt7996_mcu_add_bss_info(link->phy, vif, link_conf, - &link->mt76, msta_link, true); - } - - if (cmd == SET_KEY) - *wcid_keyidx = idx; - else if (idx == *wcid_keyidx) - *wcid_keyidx = -1; - - if (cmd != SET_KEY && sta) - continue; - - mt76_wcid_key_setup(&dev->mt76, &msta_link->wcid, key); - - err = mt7996_mcu_add_key(&dev->mt76, vif, key, - MCU_WMWA_UNI_CMD(STA_REC_UPDATE), - &msta_link->wcid, cmd); - if (err) - return err; + break; + default: + break; } - return 0; + if (cmd == SET_KEY && !sta && !link->mt76.cipher) { + struct ieee80211_bss_conf *link_conf; + + link_conf = link_conf_dereference_protected(vif, + link_id); + if (!link_conf) + link_conf = &vif->bss_conf; + + link->mt76.cipher = + mt76_connac_mcu_get_cipher(key->cipher); + mt7996_mcu_add_bss_info(link->phy, vif, link_conf, + &link->mt76, msta_link, true); + } + + if (cmd == SET_KEY) + *wcid_keyidx = idx; + else if (idx == *wcid_keyidx) + *wcid_keyidx = -1; + + if (cmd != SET_KEY && sta) + return 0; + + mt76_wcid_key_setup(&dev->mt76, &msta_link->wcid, key); + + return mt7996_mcu_add_key(&dev->mt76, vif, key, + MCU_WMWA_UNI_CMD(STA_REC_UPDATE), + &msta_link->wcid, cmd); } +struct mt7996_key_iter_data { + enum set_key_cmd cmd; + unsigned int link_id; +}; + static void mt7996_key_iter(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta, struct ieee80211_key_conf *key, void *data) { - enum set_key_cmd *cmd = data; + struct mt7996_key_iter_data *it = data; if (sta) return; - WARN_ON(mt7996_set_hw_key(hw, *cmd, vif, NULL, key)); + WARN_ON(mt7996_set_hw_key(hw, it->cmd, vif, NULL, it->link_id, key)); } int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, @@ -293,9 +282,12 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; struct mt7996_sta_link *msta_link = &link->msta_link; struct mt7996_phy *phy = mphy->priv; - enum set_key_cmd key_cmd = SET_KEY; struct mt7996_dev *dev = phy->dev; u8 band_idx = phy->mt76->band_idx; + struct mt7996_key_iter_data it = { + .cmd = SET_KEY, + .link_id = link_conf->link_id, + }; struct mt76_txq *mtxq; int mld_idx, idx, ret; @@ -373,7 +365,7 @@ 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, &key_cmd); + ieee80211_iter_keys(mphy->hw, vif, mt7996_key_iter, &it); if (mvif->mt76.deflink_id == IEEE80211_LINK_UNSPECIFIED) mvif->mt76.deflink_id = link_conf->link_id; @@ -388,12 +380,15 @@ void mt7996_vif_link_remove(struct mt76_phy *mphy, struct ieee80211_vif *vif, struct mt7996_vif_link *link = container_of(mlink, struct mt7996_vif_link, mt76); struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; struct mt7996_sta_link *msta_link = &link->msta_link; - enum set_key_cmd key_cmd = DISABLE_KEY; struct mt7996_phy *phy = mphy->priv; struct mt7996_dev *dev = phy->dev; + struct mt7996_key_iter_data it = { + .cmd = SET_KEY, + .link_id = link_conf->link_id, + }; int idx = msta_link->wcid.idx; - ieee80211_iter_keys(mphy->hw, vif, mt7996_key_iter, &key_cmd); + ieee80211_iter_keys(mphy->hw, vif, mt7996_key_iter, &it); mt7996_mcu_add_sta(dev, link_conf, NULL, link, NULL, CONN_STATE_DISCONNECT, false); @@ -594,8 +589,9 @@ static int mt7996_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, struct ieee80211_key_conf *key) { struct mt7996_dev *dev = mt7996_hw_dev(hw); - struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; - int err; + unsigned int link_id; + unsigned long links; + int err = 0; /* The hardware does not support per-STA RX GTK, fallback * to software mode for these. @@ -629,11 +625,22 @@ static int mt7996_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, return -EOPNOTSUPP; } - if (!mt7996_vif_link_phy(&mvif->deflink)) - return 0; /* defer until after link add */ - mutex_lock(&dev->mt76.mutex); - err = mt7996_set_hw_key(hw, cmd, vif, sta, key); + + if (key->link_id >= 0) + links = BIT(key->link_id); + else if (sta && sta->valid_links) + links = sta->valid_links; + else if (vif->valid_links) + links = vif->valid_links; + else + links = BIT(0); + + for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { + err = mt7996_set_hw_key(hw, cmd, vif, sta, link_id, key); + if (err) + break; + } mutex_unlock(&dev->mt76.mutex); return err; From 467cf7ae6036340a845cf81bdd22e20ab9ba8245 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 15 Sep 2025 09:59:03 +0200 Subject: [PATCH 090/167] wifi: mt76: mt7996: fix updating beacon protection with beacons enabled Disable and re-enable beacon after beacon protection key change, in order to fully apply the changes. Link: https://patch.msgid.link/20250915075910.47558-8-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/mac.c | 3 +- .../net/wireless/mediatek/mt76/mt7996/main.c | 35 ++++++++++++------- .../net/wireless/mediatek/mt76/mt7996/mcu.c | 3 +- .../wireless/mediatek/mt76/mt7996/mt7996.h | 2 +- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index fd57569a7da7..289f69cc2bdf 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2176,7 +2176,8 @@ mt7996_update_vif_beacon(void *priv, u8 *mac, struct ieee80211_vif *vif) if (!link || link->phy != phy) continue; - mt7996_mcu_add_beacon(dev->mt76.hw, vif, link_conf); + mt7996_mcu_add_beacon(dev->mt76.hw, vif, link_conf, + link_conf->enable_beacon); } } diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index d706b8bb244e..581314368c5b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -185,10 +185,13 @@ mt7996_set_hw_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, unsigned int link_id, struct ieee80211_key_conf *key) { struct mt7996_dev *dev = mt7996_hw_dev(hw); + struct ieee80211_bss_conf *link_conf; struct mt7996_sta_link *msta_link; struct mt7996_vif_link *link; int idx = key->keyidx; u8 *wcid_keyidx; + bool is_bigtk; + int err; link = mt7996_vif_link(dev, vif, link_id); if (!link) @@ -213,12 +216,13 @@ mt7996_set_hw_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, } wcid_keyidx = &msta_link->wcid.hw_key_idx; + is_bigtk = key->keyidx == 6 || key->keyidx == 7; switch (key->cipher) { case WLAN_CIPHER_SUITE_AES_CMAC: case WLAN_CIPHER_SUITE_BIP_CMAC_256: case WLAN_CIPHER_SUITE_BIP_GMAC_128: case WLAN_CIPHER_SUITE_BIP_GMAC_256: - if (key->keyidx == 6 || key->keyidx == 7) { + if (is_bigtk) { wcid_keyidx = &msta_link->wcid.hw_key_idx2; key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIE; } @@ -227,14 +231,11 @@ mt7996_set_hw_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, break; } + link_conf = link_conf_dereference_protected(vif, link_id); + if (!link_conf) + link_conf = &vif->bss_conf; + if (cmd == SET_KEY && !sta && !link->mt76.cipher) { - struct ieee80211_bss_conf *link_conf; - - link_conf = link_conf_dereference_protected(vif, - link_id); - if (!link_conf) - link_conf = &vif->bss_conf; - link->mt76.cipher = mt76_connac_mcu_get_cipher(key->cipher); mt7996_mcu_add_bss_info(link->phy, vif, link_conf, @@ -251,9 +252,17 @@ mt7996_set_hw_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, mt76_wcid_key_setup(&dev->mt76, &msta_link->wcid, key); - return mt7996_mcu_add_key(&dev->mt76, vif, key, - MCU_WMWA_UNI_CMD(STA_REC_UPDATE), - &msta_link->wcid, cmd); + err = mt7996_mcu_add_key(&dev->mt76, vif, key, + MCU_WMWA_UNI_CMD(STA_REC_UPDATE), + &msta_link->wcid, cmd); + + /* remove and add beacon in order to enable beacon protection */ + if (cmd == SET_KEY && is_bigtk && link_conf->enable_beacon) { + mt7996_mcu_add_beacon(hw, vif, link_conf, false); + mt7996_mcu_add_beacon(hw, vif, link_conf, true); + } + + return err; } struct mt7996_key_iter_data { @@ -900,7 +909,7 @@ mt7996_link_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, link->mt76.beacon_rates_idx = mt7996_get_rates_table(phy, info, true, false); - mt7996_mcu_add_beacon(hw, vif, info); + mt7996_mcu_add_beacon(hw, vif, info, info->enable_beacon); } if (changed & (BSS_CHANGED_UNSOL_BCAST_PROBE_RESP | @@ -928,7 +937,7 @@ mt7996_channel_switch_beacon(struct ieee80211_hw *hw, struct mt7996_dev *dev = mt7996_hw_dev(hw); mutex_lock(&dev->mt76.mutex); - mt7996_mcu_add_beacon(hw, vif, &vif->bss_conf); + mt7996_mcu_add_beacon(hw, vif, &vif->bss_conf, vif->bss_conf.enable_beacon); 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 5707e6b59aea..07a1e542571c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -2760,7 +2760,7 @@ mt7996_mcu_beacon_cont(struct mt7996_dev *dev, } int mt7996_mcu_add_beacon(struct ieee80211_hw *hw, struct ieee80211_vif *vif, - struct ieee80211_bss_conf *link_conf) + struct ieee80211_bss_conf *link_conf, bool enabled) { struct mt7996_dev *dev = mt7996_hw_dev(hw); struct mt7996_vif_link *link = mt7996_vif_conf_link(dev, vif, link_conf); @@ -2771,7 +2771,6 @@ int mt7996_mcu_add_beacon(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct tlv *tlv; struct bss_bcn_content_tlv *bcn; int len, extra_len = 0; - bool enabled = link_conf->enable_beacon; if (link_conf->nontransmitted) return 0; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index fa10aa6f517a..8ec2acdb3319 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -682,7 +682,7 @@ int mt7996_mcu_update_bss_color(struct mt7996_dev *dev, struct mt76_vif_link *mlink, struct cfg80211_he_bss_color *he_bss_color); int mt7996_mcu_add_beacon(struct ieee80211_hw *hw, struct ieee80211_vif *vif, - struct ieee80211_bss_conf *link_conf); + struct ieee80211_bss_conf *link_conf, bool enabled); int mt7996_mcu_beacon_inband_discov(struct mt7996_dev *dev, struct ieee80211_bss_conf *link_conf, struct mt7996_vif_link *link, u32 changed); From 12911593efa97abc27b75e98c530b8b1193c384b Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 15 Sep 2025 09:59:04 +0200 Subject: [PATCH 091/167] wifi: mt76: use altx queue for offchannel tx on connac+ This ensures that packets are sent out immediately and are not held by firmware internal buffering. Link: https://patch.msgid.link/20250915075910.47558-9-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/tx.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index 8ab5840fee57..b78ae6a34b65 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -618,7 +618,8 @@ mt76_txq_schedule_pending_wcid(struct mt76_phy *phy, struct mt76_wcid *wcid, !(info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) && !ieee80211_is_data(hdr->frame_control) && (!ieee80211_is_bufferable_mmpdu(skb) || - ieee80211_is_deauth(hdr->frame_control))) + ieee80211_is_deauth(hdr->frame_control) || + head == &wcid->tx_offchannel)) qid = MT_TXQ_PSD; q = phy->q_tx[qid]; From a4a66cbaa20f51cb953d09a95c67cb237a088ec9 Mon Sep 17 00:00:00 2001 From: Peter Chiu Date: Mon, 15 Sep 2025 09:59:05 +0200 Subject: [PATCH 092/167] wifi: mt76: mt7996: disable promiscuous mode by default Set MT_WF_RFCR_DROP_OTHER_UC by default and disable this flag in mt7996_set_monitor only if monitor mode is enabled. Without this patch, the MT_WF_RFCR_DROP_OTHER_UC would not be set so the driver would receive lots of packets meant for other devices. Signed-off-by: Peter Chiu Link: https://patch.msgid.link/20250915075910.47558-10-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 89546bf4c7aa..5e95a36b42d1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -410,6 +410,7 @@ mt7996_init_wiphy_band(struct ieee80211_hw *hw, struct mt7996_phy *phy) phy->slottime = 9; phy->beacon_rate = -1; + phy->rxfilter = MT_WF_RFCR_DROP_OTHER_UC; if (phy->mt76->cap.has_2ghz) { phy->mt76->sband_2g.sband.ht_cap.cap |= From e99113ac0984d5fcfdee3883367c5747d918d6e9 Mon Sep 17 00:00:00 2001 From: Howard Hsu Date: Mon, 15 Sep 2025 09:59:06 +0200 Subject: [PATCH 093/167] wifi: mt76: mt7996: remove the mem_total field of STA_REC_BF command It is not used by the firmware. Signed-off-by: Howard Hsu Link: https://patch.msgid.link/20250915075910.47558-11-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 07a1e542571c..0347ee0c2dd7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -1837,19 +1837,6 @@ mt7996_mcu_sta_bfer_tlv(struct mt7996_dev *dev, struct sk_buff *skb, bf->mem_20m = bf->nrow < BF_MAT_ORDER ? matrix[bf->nrow][bf->ncol] : 0; } - - switch (link_sta->bandwidth) { - case IEEE80211_STA_RX_BW_160: - case IEEE80211_STA_RX_BW_80: - bf->mem_total = bf->mem_20m * 2; - break; - case IEEE80211_STA_RX_BW_40: - bf->mem_total = bf->mem_20m; - break; - case IEEE80211_STA_RX_BW_20: - default: - break; - } } static void From 5847e7579e8924f7ef20cf4b2b4cea4ab145aa7d Mon Sep 17 00:00:00 2001 From: Peter Chiu Date: Mon, 15 Sep 2025 09:59:07 +0200 Subject: [PATCH 094/167] wifi: mt76: mt7996: set VTA in txwi Enable VTA flag in txwi to enable HQD in SPL which is needed by the PST. Without this patch, PST cannot get the correct delay of TxD and lead to a large latency. Signed-off-by: Peter Chiu Link: https://patch.msgid.link/20250915075910.47558-12-nbd@nbd.name Signed-off-by: Felix Fietkau --- 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 289f69cc2bdf..2d5dab535357 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -968,7 +968,7 @@ void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi, val |= MT_TXD5_TX_STATUS_HOST; txwi[5] = cpu_to_le32(val); - val = MT_TXD6_DAS; + val = MT_TXD6_DAS | MT_TXD6_VTA; if ((q_idx >= MT_LMAC_ALTX0 && q_idx <= MT_LMAC_BCN0) || skb->protocol == cpu_to_be16(ETH_P_PAE)) val |= MT_TXD6_DIS_MAT; From 6855bebea8f8935af1a193c26dc13ae5ba771116 Mon Sep 17 00:00:00 2001 From: Howard Hsu Date: Mon, 15 Sep 2025 09:59:08 +0200 Subject: [PATCH 095/167] wifi: mt76: mt7996: fill User Priority in skb->priority for rx packets Set UP in skb->priority to allow DSCP Learning at upper layers Signed-off-by: Howard Hsu Link: https://patch.msgid.link/20250915075910.47558-13-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mac80211.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index 6ef186107782..cc7da3d5ab08 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -1235,6 +1235,8 @@ mt76_rx_convert(struct mt76_dev *dev, struct sk_buff *skb, mstat = *((struct mt76_rx_status *)skb->cb); memset(status, 0, sizeof(*status)); + skb->priority = mstat.qos_ctl & IEEE80211_QOS_CTL_TID_MASK; + status->flag = mstat.flag; status->freq = mstat.freq; status->enc_flags = mstat.enc_flags; From 3f34cced88a429872d1eefc393686f9a48ec01d9 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 15 Sep 2025 09:59:09 +0200 Subject: [PATCH 096/167] wifi: mt76: improve phy reset on hw restart - fix number of station accounting for scanning code. - reset channel context Link: https://patch.msgid.link/20250915075910.47558-14-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mac80211.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index cc7da3d5ab08..f6a494812fe1 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -824,6 +824,8 @@ static void mt76_reset_phy(struct mt76_phy *phy) return; INIT_LIST_HEAD(&phy->tx_list); + phy->num_sta = 0; + phy->chanctx = NULL; } void mt76_reset_device(struct mt76_dev *dev) From b36d55610215a976267197ddc914902c494705d7 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 15 Sep 2025 09:59:10 +0200 Subject: [PATCH 097/167] wifi: mt76: abort scan/roc on hw restart Avoid spurious channel changes and clean up allocated links Link: https://patch.msgid.link/20250915075910.47558-15-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/channel.c | 13 +++++++++---- drivers/net/wireless/mediatek/mt76/mac80211.c | 3 +++ drivers/net/wireless/mediatek/mt76/mt76.h | 1 + drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 2 ++ drivers/net/wireless/mediatek/mt76/scan.c | 10 +++++++--- 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/channel.c b/drivers/net/wireless/mediatek/mt76/channel.c index 77b75792eb48..130af1b254db 100644 --- a/drivers/net/wireless/mediatek/mt76/channel.c +++ b/drivers/net/wireless/mediatek/mt76/channel.c @@ -314,21 +314,24 @@ void mt76_put_vif_phy_link(struct mt76_phy *phy, struct ieee80211_vif *vif, kfree(mlink); } -static void mt76_roc_complete(struct mt76_phy *phy) +void mt76_roc_complete(struct mt76_phy *phy) { struct mt76_vif_link *mlink = phy->roc_link; + struct mt76_dev *dev = phy->dev; if (!phy->roc_vif) return; if (mlink) mlink->mvif->roc_phy = NULL; - if (phy->main_chandef.chan) + if (phy->main_chandef.chan && + !test_bit(MT76_MCU_RESET, &dev->phy.state)) mt76_set_channel(phy, &phy->main_chandef, false); mt76_put_vif_phy_link(phy, phy->roc_vif, phy->roc_link); phy->roc_vif = NULL; phy->roc_link = NULL; - ieee80211_remain_on_channel_expired(phy->hw); + if (!test_bit(MT76_MCU_RESET, &dev->phy.state)) + ieee80211_remain_on_channel_expired(phy->hw); } void mt76_roc_complete_work(struct work_struct *work) @@ -351,6 +354,7 @@ void mt76_abort_roc(struct mt76_phy *phy) mt76_roc_complete(phy); mutex_unlock(&dev->mutex); } +EXPORT_SYMBOL_GPL(mt76_abort_roc); int mt76_remain_on_channel(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_channel *chan, int duration, @@ -368,7 +372,8 @@ int mt76_remain_on_channel(struct ieee80211_hw *hw, struct ieee80211_vif *vif, mutex_lock(&dev->mutex); - if (phy->roc_vif || dev->scan.phy == phy) { + if (phy->roc_vif || dev->scan.phy == phy || + test_bit(MT76_MCU_RESET, &dev->phy.state)) { ret = -EBUSY; goto out; } diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index f6a494812fe1..5ceaf78c9ea0 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -826,6 +826,7 @@ static void mt76_reset_phy(struct mt76_phy *phy) INIT_LIST_HEAD(&phy->tx_list); phy->num_sta = 0; phy->chanctx = NULL; + mt76_roc_complete(phy); } void mt76_reset_device(struct mt76_dev *dev) @@ -846,6 +847,8 @@ void mt76_reset_device(struct mt76_dev *dev) } rcu_read_unlock(); + mt76_abort_scan(dev); + INIT_LIST_HEAD(&dev->wcid_list); INIT_LIST_HEAD(&dev->sta_poll_list); dev->vif_mask = 0; diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 883356cd0c0b..e0d50b58cd01 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -1643,6 +1643,7 @@ int mt76_set_channel(struct mt76_phy *phy, struct cfg80211_chan_def *chandef, void mt76_scan_work(struct work_struct *work); void mt76_abort_scan(struct mt76_dev *dev); void mt76_roc_complete_work(struct work_struct *work); +void mt76_roc_complete(struct mt76_phy *phy); void mt76_abort_roc(struct mt76_phy *phy); struct mt76_vif_link *mt76_get_vif_phy_link(struct mt76_phy *phy, struct ieee80211_vif *vif); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 2d5dab535357..9501def3e0e3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2514,10 +2514,12 @@ 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); cancel_work_sync(&dev->wed_rro.work); mt7996_for_each_phy(dev, phy) { + mt76_abort_roc(phy->mt76); set_bit(MT76_RESET, &phy->mt76->state); cancel_delayed_work_sync(&phy->mt76->mac_work); } diff --git a/drivers/net/wireless/mediatek/mt76/scan.c b/drivers/net/wireless/mediatek/mt76/scan.c index 458f8cdebc10..5a875aac410f 100644 --- a/drivers/net/wireless/mediatek/mt76/scan.c +++ b/drivers/net/wireless/mediatek/mt76/scan.c @@ -16,11 +16,13 @@ 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) + if (dev->scan.chan && phy->main_chandef.chan && + !test_bit(MT76_MCU_RESET, &dev->phy.state)) mt76_set_channel(phy, &phy->main_chandef, false); mt76_put_vif_phy_link(phy, dev->scan.vif, dev->scan.mlink); memset(&dev->scan, 0, sizeof(dev->scan)); - ieee80211_scan_completed(phy->hw, &info); + if (!test_bit(MT76_MCU_RESET, &dev->phy.state)) + ieee80211_scan_completed(phy->hw, &info); } void mt76_abort_scan(struct mt76_dev *dev) @@ -28,6 +30,7 @@ void mt76_abort_scan(struct mt76_dev *dev) cancel_delayed_work_sync(&dev->scan_work); mt76_scan_complete(dev, true); } +EXPORT_SYMBOL_GPL(mt76_abort_scan); static void mt76_scan_send_probe(struct mt76_dev *dev, struct cfg80211_ssid *ssid) @@ -136,7 +139,8 @@ int mt76_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif, mutex_lock(&dev->mutex); - if (dev->scan.req || phy->roc_vif) { + if (dev->scan.req || phy->roc_vif || + test_bit(MT76_MCU_RESET, &dev->phy.state)) { ret = -EBUSY; goto out; } From df3d55a63f9a9372f6530fedfc495b2164181c5d Mon Sep 17 00:00:00 2001 From: Zong-Zhe Yang Date: Mon, 15 Sep 2025 14:52:03 +0800 Subject: [PATCH 098/167] wifi: rtw89: chan: allow callers to check if a link has no managed chanctx Originally, to directly align with the chanctx design, getter of managed chanctx returned a default channel when a link doesn't own a chanctx yet. Then, callers could simply use the return without trivial NULL checking. But in MLD HW settings of next chip, there will be a special case that a caller needs to check if a link has owned chanctx or not to determine CCK hardware circuit working on HW-x. So, add a func *_or_null for this first. Signed-off-by: Zong-Zhe Yang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250915065213.38659-2-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/chan.c | 11 ++++++++++- drivers/net/wireless/realtek/rtw89/chan.h | 10 ++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/chan.c b/drivers/net/wireless/realtek/rtw89/chan.c index f7d1c5d3b92e..86f1b39a967f 100644 --- a/drivers/net/wireless/realtek/rtw89/chan.c +++ b/drivers/net/wireless/realtek/rtw89/chan.c @@ -281,6 +281,7 @@ void rtw89_entity_init(struct rtw89_dev *rtwdev) { struct rtw89_hal *hal = &rtwdev->hal; struct rtw89_entity_mgnt *mgnt = &hal->entity_mgnt; + int i, j; hal->entity_pause = false; bitmap_zero(hal->entity_map, NUM_OF_RTW89_CHANCTX); @@ -289,6 +290,11 @@ void rtw89_entity_init(struct rtw89_dev *rtwdev) INIT_LIST_HEAD(&mgnt->active_list); + for (i = 0; i < RTW89_MAX_INTERFACE_NUM; i++) { + for (j = 0; j < __RTW89_MLD_MAX_LINK_NUM; j++) + mgnt->chanctx_tbl[i][j] = RTW89_CHANCTX_IDLE; + } + rtw89_config_default_chandef(rtwdev); } @@ -353,7 +359,7 @@ static void rtw89_normalize_link_chanctx(struct rtw89_dev *rtwdev, const struct rtw89_chan *__rtw89_mgnt_chan_get(struct rtw89_dev *rtwdev, const char *caller_message, - u8 link_index) + u8 link_index, bool nullchk) { struct rtw89_hal *hal = &rtwdev->hal; struct rtw89_entity_mgnt *mgnt = &hal->entity_mgnt; @@ -400,6 +406,9 @@ const struct rtw89_chan *__rtw89_mgnt_chan_get(struct rtw89_dev *rtwdev, return rtw89_chan_get(rtwdev, chanctx_idx); dflt: + if (unlikely(nullchk)) + return NULL; + rtw89_debug(rtwdev, RTW89_DBG_CHAN, "%s (%s): prefetch NULL on link index %u\n", __func__, caller_message ?: "", link_index); diff --git a/drivers/net/wireless/realtek/rtw89/chan.h b/drivers/net/wireless/realtek/rtw89/chan.h index b1175419f92b..5b22764d5329 100644 --- a/drivers/net/wireless/realtek/rtw89/chan.h +++ b/drivers/net/wireless/realtek/rtw89/chan.h @@ -180,10 +180,16 @@ void rtw89_chanctx_proceed(struct rtw89_dev *rtwdev, const struct rtw89_chan *__rtw89_mgnt_chan_get(struct rtw89_dev *rtwdev, const char *caller_message, - u8 link_index); + u8 link_index, bool nullchk); #define rtw89_mgnt_chan_get(rtwdev, link_index) \ - __rtw89_mgnt_chan_get(rtwdev, __func__, link_index) + __rtw89_mgnt_chan_get(rtwdev, __func__, link_index, false) + +static inline const struct rtw89_chan * +rtw89_mgnt_chan_get_or_null(struct rtw89_dev *rtwdev, u8 link_index) +{ + return __rtw89_mgnt_chan_get(rtwdev, NULL, link_index, true); +} struct rtw89_mcc_links_info { struct rtw89_vif_link *links[NUM_OF_RTW89_MCC_ROLES]; From 19989c80734cc8c60e1529dba8641d2bfe162662 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Mon, 15 Sep 2025 14:52:04 +0800 Subject: [PATCH 099/167] wifi: rtw89: use ieee80211_tx_info::driver_data to store driver TX info It makes more sense to use ieee80211_tx_info::driver_data instead of ieee80211_tx_info::status.status_driver_data which is used to share TX status reporting to mac80211, because actually driver calls ieee80211_tx_info_clear_status() to clear the content including status_driver_data in rtw89_pci_tx_status() before filling the status. Review and point out the scope (by comments) driver can safely use ieee80211_tx_info::driver_data between rtw89_hci_tx_write() and calling ieee80211_tx_info_clear_status(). Add BUILD_BUG_ON() to assert that driver struct size is smaller than the size defined by mac80211. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250915065213.38659-3-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.h | 6 +++++- drivers/net/wireless/realtek/rtw89/pci.h | 9 +++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index ecfbcff85047..2841ea84bbc0 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -6390,9 +6390,13 @@ static inline void rtw89_hci_clear(struct rtw89_dev *rtwdev, struct pci_dev *pde static inline struct rtw89_tx_skb_data *RTW89_TX_SKB_CB(struct sk_buff *skb) { + /* + * This should be used by/after rtw89_hci_tx_write() and before doing + * ieee80211_tx_info_clear_status(). + */ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); - return (struct rtw89_tx_skb_data *)info->status.status_driver_data; + return (struct rtw89_tx_skb_data *)info->driver_data; } static inline u8 rtw89_read8(struct rtw89_dev *rtwdev, u32 addr) diff --git a/drivers/net/wireless/realtek/rtw89/pci.h b/drivers/net/wireless/realtek/rtw89/pci.h index fc8268eb44db..cb05c83dfd56 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.h +++ b/drivers/net/wireless/realtek/rtw89/pci.h @@ -1634,10 +1634,7 @@ struct rtw89_pci { static inline struct rtw89_pci_rx_info *RTW89_PCI_RX_SKB_CB(struct sk_buff *skb) { - struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); - - BUILD_BUG_ON(sizeof(struct rtw89_pci_tx_data) > - sizeof(info->status.status_driver_data)); + BUILD_BUG_ON(sizeof(struct rtw89_pci_rx_info) > sizeof(skb->cb)); return (struct rtw89_pci_rx_info *)skb->cb; } @@ -1668,6 +1665,10 @@ static inline struct rtw89_pci_tx_data *RTW89_PCI_TX_SKB_CB(struct sk_buff *skb) { struct rtw89_tx_skb_data *data = RTW89_TX_SKB_CB(skb); + BUILD_BUG_ON(sizeof(struct rtw89_tx_skb_data) + + sizeof(struct rtw89_pci_tx_data) > + sizeof_field(struct ieee80211_tx_info, driver_data)); + return (struct rtw89_pci_tx_data *)data->hci_priv; } From 4e79a5cc01c5e1f1ba393ed3b44b0c3611eaadf1 Mon Sep 17 00:00:00 2001 From: Chih-Kang Chang Date: Mon, 15 Sep 2025 14:52:05 +0800 Subject: [PATCH 100/167] wifi: rtw89: disable RTW89_PHYSTS_IE09_FTR_0 for ppdu status The IE length of RTW89_PHYSTS_IE09_FTR_0 is dynamic, need to calculate more to get it. This IE is not necessary now, disable it to avoid get wrong IE length to let the parse function check failed. Signed-off-by: Chih-Kang Chang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250915065213.38659-4-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/phy.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/phy.c b/drivers/net/wireless/realtek/rtw89/phy.c index c3181a301f7c..0e9cb0558eb3 100644 --- a/drivers/net/wireless/realtek/rtw89/phy.c +++ b/drivers/net/wireless/realtek/rtw89/phy.c @@ -6184,8 +6184,6 @@ static void __rtw89_physts_parsing_init(struct rtw89_dev *rtwdev, val |= BIT(RTW89_PHYSTS_IE13_DL_MU_DEF) | BIT(RTW89_PHYSTS_IE01_CMN_OFDM); } else if (i >= RTW89_CCK_PKT) { - val |= BIT(RTW89_PHYSTS_IE09_FTR_0); - val &= ~(GENMASK(RTW89_PHYSTS_IE07_CMN_EXT_PATH_D, RTW89_PHYSTS_IE04_CMN_EXT_PATH_A)); From e156d2ab36d7e47aec36845705e4ecb1e4e89976 Mon Sep 17 00:00:00 2001 From: Chih-Kang Chang Date: Mon, 15 Sep 2025 14:52:06 +0800 Subject: [PATCH 101/167] wifi: rtw89: obtain RX path from ppdu status IE00 The header v2 of ppdu status is optional, If it is not enabled, the RX path must be obtained from IE00 or IE01. Append the IE00 part. Signed-off-by: Chih-Kang Chang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250915065213.38659-5-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.c | 4 ++++ drivers/net/wireless/realtek/rtw89/txrx.h | 1 + 2 files changed, 5 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index 20d8f2221f81..b116f8d718ac 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -1897,6 +1897,10 @@ static void rtw89_core_parse_phy_status_ie00(struct rtw89_dev *rtwdev, tmp_rpl = le32_get_bits(ie->w0, RTW89_PHY_STS_IE00_W0_RPL); phy_ppdu->rpl_avg = tmp_rpl >> 1; + + if (!phy_ppdu->hdr_2_en) + phy_ppdu->rx_path_en = + le32_get_bits(ie->w3, RTW89_PHY_STS_IE00_W3_RX_PATH_EN); } static void rtw89_core_parse_phy_status_ie00_v2(struct rtw89_dev *rtwdev, diff --git a/drivers/net/wireless/realtek/rtw89/txrx.h b/drivers/net/wireless/realtek/rtw89/txrx.h index 82d6874337b5..984c9fdbb018 100644 --- a/drivers/net/wireless/realtek/rtw89/txrx.h +++ b/drivers/net/wireless/realtek/rtw89/txrx.h @@ -572,6 +572,7 @@ struct rtw89_phy_sts_ie00 { } __packed; #define RTW89_PHY_STS_IE00_W0_RPL GENMASK(15, 7) +#define RTW89_PHY_STS_IE00_W3_RX_PATH_EN GENMASK(31, 28) struct rtw89_phy_sts_ie00_v2 { __le32 w0; From 298f39f0d9c3ee381793e7a4ba1f54755e4687eb Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Mon, 15 Sep 2025 14:53:14 +0800 Subject: [PATCH 102/167] wifi: rtw89: phy: initialize AFE by firmware element table A power-on sequence table is introduced to initialize AFE (Analogue Front End) connecting to RF components. Build the sequence in firmware file, and use a parser to execute the sequences including write/poll/delay actions. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250915065314.38846-1-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.c | 2 + drivers/net/wireless/realtek/rtw89/core.h | 1 + drivers/net/wireless/realtek/rtw89/fw.c | 15 ++++ drivers/net/wireless/realtek/rtw89/fw.h | 36 ++++++++++ drivers/net/wireless/realtek/rtw89/phy.c | 85 +++++++++++++++++++++++ drivers/net/wireless/realtek/rtw89/phy.h | 1 + 6 files changed, 140 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index b116f8d718ac..e445fcd02187 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -5417,6 +5417,8 @@ int rtw89_core_start(struct rtw89_dev *rtwdev) { int ret; + rtw89_phy_init_bb_afe(rtwdev); + ret = rtw89_mac_init(rtwdev); if (ret) { rtw89_err(rtwdev, "mac init fail, ret:%d\n", ret); diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 2841ea84bbc0..708132363da3 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -4690,6 +4690,7 @@ struct rtw89_fw_elm_info { struct rtw89_fw_txpwr_track_cfg *txpwr_trk; struct rtw89_phy_rfk_log_fmt *rfk_log_fmt; const struct rtw89_regd_data *regd; + const struct rtw89_fw_element_hdr *afe; }; enum rtw89_fw_mss_dev_type { diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 5f330b625659..0ef3ee44e2f0 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -1285,6 +1285,18 @@ int rtw89_recognize_regd_from_elm(struct rtw89_dev *rtwdev, return 0; } +static +int rtw89_build_afe_pwr_seq_from_elm(struct rtw89_dev *rtwdev, + const struct rtw89_fw_element_hdr *elm, + const union rtw89_fw_element_arg arg) +{ + struct rtw89_fw_elm_info *elm_info = &rtwdev->fw.elm_info; + + elm_info->afe = elm; + + return 0; +} + static const struct rtw89_fw_element_handler __fw_element_handlers[] = { [RTW89_FW_ELEMENT_ID_BBMCU0] = {__rtw89_fw_recognize_from_elm, { .fw_type = RTW89_FW_BBMCU0 }, NULL}, @@ -1370,6 +1382,9 @@ static const struct rtw89_fw_element_handler __fw_element_handlers[] = { [RTW89_FW_ELEMENT_ID_REGD] = { rtw89_recognize_regd_from_elm, {}, "REGD", }, + [RTW89_FW_ELEMENT_ID_AFE_PWR_SEQ] = { + rtw89_build_afe_pwr_seq_from_elm, {}, "AFE", + }, }; int rtw89_fw_recognize_elements(struct rtw89_dev *rtwdev) diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index fad9d87d3e56..98b187305481 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -3984,6 +3984,7 @@ enum rtw89_fw_element_id { RTW89_FW_ELEMENT_ID_TXPWR_DA_LMT_RU_2GHZ = 24, RTW89_FW_ELEMENT_ID_TXPWR_DA_LMT_RU_5GHZ = 25, RTW89_FW_ELEMENT_ID_TXPWR_DA_LMT_RU_6GHZ = 26, + RTW89_FW_ELEMENT_ID_AFE_PWR_SEQ = 27, RTW89_FW_ELEMENT_ID_NUM, }; @@ -4089,6 +4090,30 @@ struct rtw89_fw_txpwr_track_cfg { BIT(RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_A_N) | \ BIT(RTW89_FW_TXPWR_TRK_TYPE_2G_CCK_A_P)) +enum rtw89_fw_afe_action { + RTW89_FW_AFE_ACTION_WRITE = 0, + RTW89_FW_AFE_ACTION_DELAY = 1, + RTW89_FW_AFE_ACTION_POLL = 2, +}; + +enum rtw89_fw_afe_cat { + RTW89_FW_AFE_CAT_BB = 0, + RTW89_FW_AFE_CAT_BB1 = 1, + RTW89_FW_AFE_CAT_MAC = 2, + RTW89_FW_AFE_CAT_MAC1 = 3, + RTW89_FW_AFE_CAT_AFEDIG = 4, + RTW89_FW_AFE_CAT_AFEDIG1 = 5, +}; + +enum rtw89_fw_afe_class { + RTW89_FW_AFE_CLASS_P0 = 0, + RTW89_FW_AFE_CLASS_P1 = 1, + RTW89_FW_AFE_CLASS_P2 = 2, + RTW89_FW_AFE_CLASS_P3 = 3, + RTW89_FW_AFE_CLASS_P4 = 4, + RTW89_FW_AFE_CLASS_CMN = 5, +}; + struct rtw89_fw_element_hdr { __le32 id; /* enum rtw89_fw_element_id */ __le32 size; /* exclude header size */ @@ -4126,6 +4151,17 @@ struct rtw89_fw_element_hdr { u8 rsvd1[3]; __le16 offset[]; } __packed rfk_log_fmt; + struct { + u8 rsvd[8]; + struct rtw89_phy_afe_info { + __le32 action; /* enum rtw89_fw_afe_action */ + __le32 cat; /* enum rtw89_fw_afe_cat */ + __le32 class; /* enum rtw89_fw_afe_class */ + __le32 addr; + __le32 mask; + __le32 val; + } __packed infos[]; + } __packed afe; struct __rtw89_fw_txpwr_element txpwr; struct __rtw89_fw_regd_element regd; } __packed u; diff --git a/drivers/net/wireless/realtek/rtw89/phy.c b/drivers/net/wireless/realtek/rtw89/phy.c index 0e9cb0558eb3..45823c9e9448 100644 --- a/drivers/net/wireless/realtek/rtw89/phy.c +++ b/drivers/net/wireless/realtek/rtw89/phy.c @@ -1702,6 +1702,91 @@ void rtw89_phy_init_bb_reg(struct rtw89_dev *rtwdev) rtw89_phy_bb_reset(rtwdev); } +void rtw89_phy_init_bb_afe(struct rtw89_dev *rtwdev) +{ + struct rtw89_fw_elm_info *elm_info = &rtwdev->fw.elm_info; + const struct rtw89_fw_element_hdr *afe_elm = elm_info->afe; + const struct rtw89_phy_afe_info *info; + u32 action, cat, class; + u32 addr, mask, val; + u32 poll, rpt; + u32 n, i; + + if (!afe_elm) + return; + + n = le32_to_cpu(afe_elm->size) / sizeof(*info); + + for (i = 0; i < n; i++) { + info = &afe_elm->u.afe.infos[i]; + + class = le32_to_cpu(info->class); + switch (class) { + case RTW89_FW_AFE_CLASS_P0: + case RTW89_FW_AFE_CLASS_P1: + case RTW89_FW_AFE_CLASS_CMN: + /* Currently support two paths */ + break; + case RTW89_FW_AFE_CLASS_P2: + case RTW89_FW_AFE_CLASS_P3: + case RTW89_FW_AFE_CLASS_P4: + default: + rtw89_warn(rtwdev, "unexpected AFE class %u\n", class); + continue; + } + + addr = le32_to_cpu(info->addr); + mask = le32_to_cpu(info->mask); + val = le32_to_cpu(info->val); + cat = le32_to_cpu(info->cat); + action = le32_to_cpu(info->action); + + switch (action) { + case RTW89_FW_AFE_ACTION_WRITE: + switch (cat) { + case RTW89_FW_AFE_CAT_MAC: + case RTW89_FW_AFE_CAT_MAC1: + rtw89_write32_mask(rtwdev, addr, mask, val); + break; + case RTW89_FW_AFE_CAT_AFEDIG: + case RTW89_FW_AFE_CAT_AFEDIG1: + rtw89_write32_mask(rtwdev, addr, mask, val); + break; + case RTW89_FW_AFE_CAT_BB: + rtw89_phy_write32_idx(rtwdev, addr, mask, val, RTW89_PHY_0); + break; + case RTW89_FW_AFE_CAT_BB1: + rtw89_phy_write32_idx(rtwdev, addr, mask, val, RTW89_PHY_1); + break; + default: + rtw89_warn(rtwdev, + "unexpected AFE writing action %u\n", action); + break; + } + break; + case RTW89_FW_AFE_ACTION_POLL: + for (poll = 0; poll <= 10; poll++) { + /* + * For CAT_BB, AFE reads register with mcu_offset 0, + * so both CAT_MAC and CAT_BB use the same method. + */ + rpt = rtw89_read32_mask(rtwdev, addr, mask); + if (rpt == val) + goto poll_done; + + fsleep(1); + } + rtw89_warn(rtwdev, "failed to poll AFE cat=%u addr=0x%x mask=0x%x\n", + cat, addr, mask); +poll_done: + break; + case RTW89_FW_AFE_ACTION_DELAY: + fsleep(addr); + break; + } + } +} + static u32 rtw89_phy_nctl_poll(struct rtw89_dev *rtwdev) { rtw89_phy_write32(rtwdev, 0x8080, 0x4); diff --git a/drivers/net/wireless/realtek/rtw89/phy.h b/drivers/net/wireless/realtek/rtw89/phy.h index 1184b3c16d6f..674397c4b9a9 100644 --- a/drivers/net/wireless/realtek/rtw89/phy.h +++ b/drivers/net/wireless/realtek/rtw89/phy.h @@ -829,6 +829,7 @@ bool rtw89_phy_write_rf_v1(struct rtw89_dev *rtwdev, enum rtw89_rf_path rf_path, bool rtw89_phy_write_rf_v2(struct rtw89_dev *rtwdev, enum rtw89_rf_path rf_path, u32 addr, u32 mask, u32 data); void rtw89_phy_init_bb_reg(struct rtw89_dev *rtwdev); +void rtw89_phy_init_bb_afe(struct rtw89_dev *rtwdev); void rtw89_phy_init_rf_reg(struct rtw89_dev *rtwdev, bool noio); void rtw89_phy_config_rf_reg_v1(struct rtw89_dev *rtwdev, const struct rtw89_reg2_def *reg, From e6badd999a87fd48d17929fdd594d016989202e8 Mon Sep 17 00:00:00 2001 From: Zong-Zhe Yang Date: Mon, 15 Sep 2025 14:53:29 +0800 Subject: [PATCH 103/167] wifi: rtw89: debug: support SER L0 simulation SER (system error recovery) can deal with different crash types by different levels of processes. Now, add a debug function to trigger MAC error in purpose for SER L0 simulation/verification. And, extend dbgfs fw_crash to accept different parameters. # simulate MAC error (one kind of SER L0) echo 3 > fw_crash Normally, FW won't report SER L0 cases to driver. Instead, they will be handled by FW directly. If unfortunately FW handling fails, SER will rise to L1 and be reported to driver. Signed-off-by: Zong-Zhe Yang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250915065329.38911-1-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/debug.c | 64 +++++++++++++++++++++- drivers/net/wireless/realtek/rtw89/reg.h | 5 ++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw89/debug.c b/drivers/net/wireless/realtek/rtw89/debug.c index a37cdd73ddc3..3dc7981c510f 100644 --- a/drivers/net/wireless/realtek/rtw89/debug.c +++ b/drivers/net/wireless/realtek/rtw89/debug.c @@ -3563,6 +3563,58 @@ static int rtw89_dbg_trigger_ctrl_error(struct rtw89_dev *rtwdev) return 0; } +static int rtw89_dbg_trigger_mac_error_ax(struct rtw89_dev *rtwdev) +{ + u16 val16; + u8 val8; + int ret; + + ret = rtw89_mac_check_mac_en(rtwdev, RTW89_MAC_0, RTW89_CMAC_SEL); + if (ret) + return ret; + + val8 = rtw89_read8(rtwdev, R_AX_CMAC_FUNC_EN); + rtw89_write8(rtwdev, R_AX_CMAC_FUNC_EN, val8 & ~B_AX_TMAC_EN); + mdelay(1); + rtw89_write8(rtwdev, R_AX_CMAC_FUNC_EN, val8); + + val16 = rtw89_read16(rtwdev, R_AX_PTCL_IMR0); + rtw89_write16(rtwdev, R_AX_PTCL_IMR0, val16 | B_AX_F2PCMD_EMPTY_ERR_INT_EN); + rtw89_write16(rtwdev, R_AX_PTCL_IMR0, val16); + + return 0; +} + +static int rtw89_dbg_trigger_mac_error_be(struct rtw89_dev *rtwdev) +{ + int ret; + + ret = rtw89_mac_check_mac_en(rtwdev, RTW89_MAC_0, RTW89_CMAC_SEL); + if (ret) + return ret; + + rtw89_write32_set(rtwdev, R_BE_CMAC_FW_TRIGGER_IDCT_ISR, + B_BE_CMAC_FW_TRIG_IDCT | B_BE_CMAC_FW_ERR_IDCT_IMR); + + return 0; +} + +static int rtw89_dbg_trigger_mac_error(struct rtw89_dev *rtwdev) +{ + const struct rtw89_chip_info *chip = rtwdev->chip; + + rtw89_leave_ps_mode(rtwdev); + + switch (chip->chip_gen) { + case RTW89_CHIP_AX: + return rtw89_dbg_trigger_mac_error_ax(rtwdev); + case RTW89_CHIP_BE: + return rtw89_dbg_trigger_mac_error_be(rtwdev); + default: + return -EOPNOTSUPP; + } +} + static ssize_t rtw89_debug_priv_fw_crash_get(struct rtw89_dev *rtwdev, struct rtw89_debugfs_priv *debugfs_priv, @@ -3578,6 +3630,7 @@ rtw89_debug_priv_fw_crash_get(struct rtw89_dev *rtwdev, enum rtw89_dbg_crash_simulation_type { RTW89_DBG_SIM_CPU_EXCEPTION = 1, RTW89_DBG_SIM_CTRL_ERROR = 2, + RTW89_DBG_SIM_MAC_ERROR = 3, }; static ssize_t @@ -3586,6 +3639,7 @@ rtw89_debug_priv_fw_crash_set(struct rtw89_dev *rtwdev, const char *buf, size_t count) { int (*sim)(struct rtw89_dev *rtwdev); + bool announce = true; u8 crash_type; int ret; @@ -3604,11 +3658,19 @@ rtw89_debug_priv_fw_crash_set(struct rtw89_dev *rtwdev, case RTW89_DBG_SIM_CTRL_ERROR: sim = rtw89_dbg_trigger_ctrl_error; break; + case RTW89_DBG_SIM_MAC_ERROR: + sim = rtw89_dbg_trigger_mac_error; + + /* Driver SER flow won't get involved; only FW will. */ + announce = false; + break; default: return -EINVAL; } - set_bit(RTW89_FLAG_CRASH_SIMULATING, rtwdev->flags); + if (announce) + set_bit(RTW89_FLAG_CRASH_SIMULATING, rtwdev->flags); + ret = sim(rtwdev); if (ret) diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h index d94d73e50e93..ed1d958bc49e 100644 --- a/drivers/net/wireless/realtek/rtw89/reg.h +++ b/drivers/net/wireless/realtek/rtw89/reg.h @@ -6262,6 +6262,11 @@ #define B_BE_PTCL_TOP_ERR_IND BIT(1) #define B_BE_SCHEDULE_TOP_ERR_IND BIT(0) +#define R_BE_CMAC_FW_TRIGGER_IDCT_ISR 0x10168 +#define R_BE_CMAC_FW_TRIGGER_IDCT_ISR_C1 0x14168 +#define B_BE_CMAC_FW_ERR_IDCT_IMR BIT(31) +#define B_BE_CMAC_FW_TRIG_IDCT BIT(0) + #define R_BE_SER_L0_DBG_CNT 0x10170 #define R_BE_SER_L0_DBG_CNT_C1 0x14170 #define B_BE_SER_L0_PHYINTF_CNT_MASK GENMASK(31, 24) From 5ff9e80de2a8bea5a7c0b30a4e273b399fbf2acc Mon Sep 17 00:00:00 2001 From: Kuan-Chung Chen Date: Mon, 15 Sep 2025 14:53:37 +0800 Subject: [PATCH 104/167] wifi: rtw89: 8852b: enable beacon tracking support Enable beacon tracking support on 8852B to improve connection stability. 8852B firmware has supported the power level H2C since version 0.29.128. This H2C is one of the required elements for beacon tracking, allowing control of the maximum receive window while in power save mode. Signed-off-by: Kuan-Chung Chen Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250915065337.38966-1-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/fw.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 0ef3ee44e2f0..53d3591e2397 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -830,6 +830,7 @@ static const struct __fw_feat_cfg fw_feat_tbl[] = { __CFG_FW_FEAT(RTL8852B, ge, 0, 29, 127, 0, LPS_DACK_BY_C2H_REG), __CFG_FW_FEAT(RTL8852B, ge, 0, 29, 128, 0, CRASH_TRIGGER_TYPE_1), __CFG_FW_FEAT(RTL8852B, ge, 0, 29, 128, 0, SCAN_OFFLOAD_EXTRA_OP), + __CFG_FW_FEAT(RTL8852B, ge, 0, 29, 128, 0, BEACON_TRACKING), __CFG_FW_FEAT(RTL8852BT, ge, 0, 29, 74, 0, NO_LPS_PG), __CFG_FW_FEAT(RTL8852BT, ge, 0, 29, 74, 0, TX_WAKE), __CFG_FW_FEAT(RTL8852BT, ge, 0, 29, 90, 0, CRASH_TRIGGER_TYPE_0), From bc2a5a12fa6259e190c7edb03e63b28ab480101b Mon Sep 17 00:00:00 2001 From: Zong-Zhe Yang Date: Mon, 15 Sep 2025 14:53:43 +0800 Subject: [PATCH 105/167] wifi: rtw89: renew a completion for each H2C command waiting C2H event Logically before a waiting side which has already timed out turns the atomic status back to idle, a completing side could still pass atomic condition and call complete. It will make the following H2C commands, waiting C2H events, get a completion unexpectedly early. Hence, renew a completion for each H2C command waiting a C2H event. Signed-off-by: Zong-Zhe Yang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250915065343.39023-1-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.c | 51 +++++++++++++++++++---- drivers/net/wireless/realtek/rtw89/core.h | 12 ++++-- drivers/net/wireless/realtek/rtw89/fw.c | 2 + 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index e445fcd02187..759b9f850df2 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -5340,37 +5340,74 @@ void rtw89_core_csa_beacon_work(struct wiphy *wiphy, struct wiphy_work *work) int rtw89_wait_for_cond(struct rtw89_wait_info *wait, unsigned int cond) { - struct completion *cmpl = &wait->completion; + struct rtw89_wait_response *prep; unsigned long time_left; unsigned int cur; + int err = 0; cur = atomic_cmpxchg(&wait->cond, RTW89_WAIT_COND_IDLE, cond); if (cur != RTW89_WAIT_COND_IDLE) return -EBUSY; - time_left = wait_for_completion_timeout(cmpl, RTW89_WAIT_FOR_COND_TIMEOUT); - if (time_left == 0) { - atomic_set(&wait->cond, RTW89_WAIT_COND_IDLE); - return -ETIMEDOUT; + prep = kzalloc(sizeof(*prep), GFP_KERNEL); + if (!prep) { + err = -ENOMEM; + goto reset; } + init_completion(&prep->completion); + + rcu_assign_pointer(wait->resp, prep); + + time_left = wait_for_completion_timeout(&prep->completion, + RTW89_WAIT_FOR_COND_TIMEOUT); + if (time_left == 0) { + err = -ETIMEDOUT; + goto cleanup; + } + + wait->data = prep->data; + +cleanup: + rcu_assign_pointer(wait->resp, NULL); + kfree_rcu(prep, rcu_head); + +reset: + atomic_set(&wait->cond, RTW89_WAIT_COND_IDLE); + + if (err) + return err; + if (wait->data.err) return -EFAULT; return 0; } +static void rtw89_complete_cond_resp(struct rtw89_wait_response *resp, + const struct rtw89_completion_data *data) +{ + resp->data = *data; + complete(&resp->completion); +} + void rtw89_complete_cond(struct rtw89_wait_info *wait, unsigned int cond, const struct rtw89_completion_data *data) { + struct rtw89_wait_response *resp; unsigned int cur; + guard(rcu)(); + + resp = rcu_dereference(wait->resp); + if (!resp) + return; + cur = atomic_cmpxchg(&wait->cond, cond, RTW89_WAIT_COND_IDLE); if (cur != cond) return; - wait->data = *data; - complete(&wait->completion); + rtw89_complete_cond_resp(resp, data); } void rtw89_core_ntfy_btc_event(struct rtw89_dev *rtwdev, enum rtw89_btc_hmsg event) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 708132363da3..2098c033b461 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -4550,17 +4550,23 @@ struct rtw89_completion_data { u8 buf[RTW89_COMPLETION_BUF_SIZE]; }; -struct rtw89_wait_info { - atomic_t cond; +struct rtw89_wait_response { + struct rcu_head rcu_head; struct completion completion; struct rtw89_completion_data data; }; +struct rtw89_wait_info { + atomic_t cond; + struct rtw89_completion_data data; + struct rtw89_wait_response __rcu *resp; +}; + #define RTW89_WAIT_FOR_COND_TIMEOUT msecs_to_jiffies(100) static inline void rtw89_init_wait(struct rtw89_wait_info *wait) { - init_completion(&wait->completion); + rcu_assign_pointer(wait->resp, NULL); atomic_set(&wait->cond, RTW89_WAIT_COND_IDLE); } diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 53d3591e2397..dbb94ed1f3c0 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -8788,6 +8788,8 @@ static int rtw89_h2c_tx_and_wait(struct rtw89_dev *rtwdev, struct sk_buff *skb, { int ret; + lockdep_assert_wiphy(rtwdev->hw->wiphy); + ret = rtw89_h2c_tx(rtwdev, skb, false); if (ret) { rtw89_err(rtwdev, "failed to send h2c\n"); From a27136f1050a653f8d267d6b906880186b695d70 Mon Sep 17 00:00:00 2001 From: Zong-Zhe Yang Date: Mon, 15 Sep 2025 14:53:52 +0800 Subject: [PATCH 106/167] wifi: rtw89: open C2H event waiting window first before sending H2C command Some H2C commands need to wait for target C2H events to confirm they are executed well. The characteristics of a target C2H event will be encoded into a value, called condition. Then, the corresponding H2C command will wait for it. And, C2H events will complete a condition according to their own characteristics. So, when conditions of both side match, the corresponding H2C command will be completed. Originally, condition waiting window is opened after the H2C command is sent. However, for CPU-bound systems, target C2H event might be already done before the H2C command opens condition waiting window. Without that, C2H event won't match condition, and it will complete nothing. Finally, H2C command wait will time out. Hence, now open condition waiting window first for H2C commands which need to wait for target C2H events. The waiting function is split to two parts, prepare and evaluate. And, waiting side becomes the below where prepare part and evaluate part must be a pair. waiting prepare: condition (open condition waiting window) Do the needed things to trigger completing side. Record errors that will cause no real completer. waiting evaluate: prepare, errors (start waiting for completion if things are fine; otherwise, clean up and return final result.) Signed-off-by: Zong-Zhe Yang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250915065352.39082-1-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.c | 36 ++++++++++++++++++----- drivers/net/wireless/realtek/rtw89/core.h | 7 ++++- drivers/net/wireless/realtek/rtw89/fw.c | 19 ++++++++---- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index 759b9f850df2..c7907949d3a2 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -5338,27 +5338,47 @@ void rtw89_core_csa_beacon_work(struct wiphy *wiphy, struct wiphy_work *work) } } -int rtw89_wait_for_cond(struct rtw89_wait_info *wait, unsigned int cond) +struct rtw89_wait_response * +rtw89_wait_for_cond_prep(struct rtw89_wait_info *wait, unsigned int cond) { struct rtw89_wait_response *prep; - unsigned long time_left; unsigned int cur; - int err = 0; + + /* use -EPERM _iff_ telling eval side not to make any changes */ cur = atomic_cmpxchg(&wait->cond, RTW89_WAIT_COND_IDLE, cond); if (cur != RTW89_WAIT_COND_IDLE) - return -EBUSY; + return ERR_PTR(-EPERM); prep = kzalloc(sizeof(*prep), GFP_KERNEL); - if (!prep) { - err = -ENOMEM; - goto reset; - } + if (!prep) + return ERR_PTR(-ENOMEM); init_completion(&prep->completion); rcu_assign_pointer(wait->resp, prep); + return prep; +} + +int rtw89_wait_for_cond_eval(struct rtw89_wait_info *wait, + struct rtw89_wait_response *prep, int err) +{ + unsigned long time_left; + + if (IS_ERR(prep)) { + err = err ?: PTR_ERR(prep); + + /* special error case: no permission to reset anything */ + if (PTR_ERR(prep) == -EPERM) + return err; + + goto reset; + } + + if (err) + goto cleanup; + time_left = wait_for_completion_timeout(&prep->completion, RTW89_WAIT_FOR_COND_TIMEOUT); if (time_left == 0) { diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 2098c033b461..8408d5d8d42d 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -7549,7 +7549,12 @@ int rtw89_regd_init_hint(struct rtw89_dev *rtwdev); const char *rtw89_regd_get_string(enum rtw89_regulation_type regd); void rtw89_traffic_stats_init(struct rtw89_dev *rtwdev, struct rtw89_traffic_stats *stats); -int rtw89_wait_for_cond(struct rtw89_wait_info *wait, unsigned int cond); +struct rtw89_wait_response * +rtw89_wait_for_cond_prep(struct rtw89_wait_info *wait, unsigned int cond) +__acquires(rtw89_wait); +int rtw89_wait_for_cond_eval(struct rtw89_wait_info *wait, + struct rtw89_wait_response *prep, int err) +__releases(rtw89_wait); void rtw89_complete_cond(struct rtw89_wait_info *wait, unsigned int cond, const struct rtw89_completion_data *data); int rtw89_core_start(struct rtw89_dev *rtwdev); diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index dbb94ed1f3c0..9c98c25c10ce 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -8786,21 +8786,30 @@ int rtw89_fw_h2c_wow_request_aoac(struct rtw89_dev *rtwdev) static int rtw89_h2c_tx_and_wait(struct rtw89_dev *rtwdev, struct sk_buff *skb, struct rtw89_wait_info *wait, unsigned int cond) { - int ret; + struct rtw89_wait_response *prep; + int ret = 0; lockdep_assert_wiphy(rtwdev->hw->wiphy); + prep = rtw89_wait_for_cond_prep(wait, cond); + if (IS_ERR(prep)) + goto out; + ret = rtw89_h2c_tx(rtwdev, skb, false); if (ret) { rtw89_err(rtwdev, "failed to send h2c\n"); dev_kfree_skb_any(skb); - return -EBUSY; + ret = -EBUSY; + goto out; } - if (test_bit(RTW89_FLAG_SER_HANDLING, rtwdev->flags)) - return 1; + if (test_bit(RTW89_FLAG_SER_HANDLING, rtwdev->flags)) { + ret = 1; + goto out; + } - return rtw89_wait_for_cond(wait, cond); +out: + return rtw89_wait_for_cond_eval(wait, prep, ret); } #define H2C_ADD_MCC_LEN 16 From 8e72c3a6255a2bf6f857c10468c670b4af29d600 Mon Sep 17 00:00:00 2001 From: Kuan-Chung Chen Date: Mon, 15 Sep 2025 14:54:29 +0800 Subject: [PATCH 107/167] wifi: rtw89: 8922a: add TAS feature support Add TAS support for 8922AE. Unlike AX ICs, BE ICs introduce a TAS timer switch. The firmware starts a TAS timer to periodically collect TX power information and notify the driver via C2H events. To avoid unnecessary C2H events, the TAS timer is enabled during core_start(). Signed-off-by: Kuan-Chung Chen Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250915065429.39269-1-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.c | 2 + drivers/net/wireless/realtek/rtw89/fw.c | 34 +++++++++ drivers/net/wireless/realtek/rtw89/fw.h | 14 +++- drivers/net/wireless/realtek/rtw89/phy.c | 74 ++++++++++++------- drivers/net/wireless/realtek/rtw89/phy.h | 3 +- drivers/net/wireless/realtek/rtw89/rtw8922a.c | 7 +- drivers/net/wireless/realtek/rtw89/sar.c | 15 ++++ drivers/net/wireless/realtek/rtw89/sar.h | 1 + 8 files changed, 119 insertions(+), 31 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index c7907949d3a2..fe059c4a6b55 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -5520,6 +5520,7 @@ int rtw89_core_start(struct rtw89_dev *rtwdev) rtw89_btc_ntfy_radio_state(rtwdev, BTC_RFCTRL_WL_ON); rtw89_fw_h2c_fw_log(rtwdev, rtwdev->fw.log.enable); rtw89_fw_h2c_init_ba_cam(rtwdev); + rtw89_tas_fw_timer_enable(rtwdev, true); return 0; } @@ -5535,6 +5536,7 @@ void rtw89_core_stop(struct rtw89_dev *rtwdev) if (!test_bit(RTW89_FLAG_RUNNING, rtwdev->flags)) return; + rtw89_tas_fw_timer_enable(rtwdev, false); rtw89_btc_ntfy_radio_state(rtwdev, BTC_RFCTRL_WL_OFF); clear_bit(RTW89_FLAG_RUNNING, rtwdev->flags); diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 9c98c25c10ce..2273dae8434a 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -6688,6 +6688,40 @@ fail: return ret; } +int rtw89_fw_h2c_rf_tas_trigger(struct rtw89_dev *rtwdev, bool enable) +{ + struct rtw89_h2c_rf_tas *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 RF TAS\n"); + return -ENOMEM; + } + skb_put(skb, len); + h2c = (struct rtw89_h2c_rf_tas *)skb->data; + + h2c->enable = cpu_to_le32(enable); + + rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C, + H2C_CAT_OUTSRC, H2C_CL_OUTSRC_RF_FW_RFK, + H2C_FUNC_RFK_TAS_OFFLOAD, 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; +} + int rtw89_fw_h2c_raw_with_hdr(struct rtw89_dev *rtwdev, u8 h2c_class, u8 h2c_func, u8 *buf, u16 len, bool rack, bool dack) diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h index 98b187305481..ddebf7972068 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.h +++ b/drivers/net/wireless/realtek/rtw89/fw.h @@ -4430,6 +4430,7 @@ enum rtw89_rfk_offload_h2c_func { H2C_FUNC_RFK_DACK_OFFLOAD = 0x5, H2C_FUNC_RFK_RXDCK_OFFLOAD = 0x6, H2C_FUNC_RFK_PRE_NOTIFY = 0x8, + H2C_FUNC_RFK_TAS_OFFLOAD = 0x9, }; struct rtw89_fw_h2c_rf_get_mccch { @@ -4611,6 +4612,10 @@ struct rtw89_h2c_rf_rxdck_v0 { u8 rxdck_dbg_en; } __packed; +struct rtw89_h2c_rf_tas { + __le32 enable; +} __packed; + struct rtw89_h2c_rf_rxdck { struct rtw89_h2c_rf_rxdck_v0 v0; u8 is_chl_k; @@ -4743,12 +4748,16 @@ struct rtw89_c2h_rfk_report { u8 version; } __packed; -struct rtw89_c2h_rf_tas_info { - struct rtw89_c2h_hdr hdr; +struct rtw89_c2h_rf_tas_rpt_log { __le32 cur_idx; __le16 txpwr_history[20]; } __packed; +struct rtw89_c2h_rf_tas_info { + struct rtw89_c2h_hdr hdr; + struct rtw89_c2h_rf_tas_rpt_log content; +} __packed; + #define RTW89_FW_RSVD_PLE_SIZE 0x800 #define RTW89_FW_BACKTRACE_INFO_SIZE 8 @@ -4889,6 +4898,7 @@ int rtw89_fw_h2c_rf_dack(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx, const struct rtw89_chan *chan); int rtw89_fw_h2c_rf_rxdck(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy_idx, const struct rtw89_chan *chan, bool is_chl_k); +int rtw89_fw_h2c_rf_tas_trigger(struct rtw89_dev *rtwdev, bool enable); int rtw89_fw_h2c_raw_with_hdr(struct rtw89_dev *rtwdev, u8 h2c_class, u8 h2c_func, u8 *buf, u16 len, bool rack, bool dack); diff --git a/drivers/net/wireless/realtek/rtw89/phy.c b/drivers/net/wireless/realtek/rtw89/phy.c index 45823c9e9448..ba7feadd7582 100644 --- a/drivers/net/wireless/realtek/rtw89/phy.c +++ b/drivers/net/wireless/realtek/rtw89/phy.c @@ -3172,6 +3172,34 @@ void (* const rtw89_phy_c2h_dm_handler[])(struct rtw89_dev *rtwdev, [RTW89_PHY_C2H_DM_FUNC_FW_SCAN] = rtw89_phy_c2h_fw_scan_rpt, }; +static +void rtw89_phy_c2h_rfk_tas_pwr(struct rtw89_dev *rtwdev, + const struct rtw89_c2h_rf_tas_rpt_log *content) +{ + const enum rtw89_sar_sources src = rtwdev->sar.src; + struct rtw89_tas_info *tas = &rtwdev->tas; + u64 linear = 0; + u32 i, cur_idx; + s16 txpwr; + + if (!tas->enable || src == RTW89_SAR_SOURCE_NONE) + return; + + cur_idx = le32_to_cpu(content->cur_idx); + for (i = 0; i < cur_idx; i++) { + txpwr = le16_to_cpu(content->txpwr_history[i]); + linear += rtw89_db_quarter_to_linear(txpwr); + + rtw89_debug(rtwdev, RTW89_DBG_SAR, + "tas: index: %u, txpwr: %d\n", i, txpwr); + } + + if (cur_idx == 0) + tas->instant_txpwr = rtw89_db_to_linear(0); + else + tas->instant_txpwr = DIV_ROUND_DOWN_ULL(linear, cur_idx); +} + static void rtw89_phy_c2h_rfk_rpt_log(struct rtw89_dev *rtwdev, enum rtw89_phy_c2h_rfk_log_func func, void *content, u16 len) @@ -3423,6 +3451,13 @@ static void rtw89_phy_c2h_rfk_rpt_log(struct rtw89_dev *rtwdev, rtw89_debug(rtwdev, RTW89_DBG_RFK, "[TXGAPK]rpt power_d[1] = %*ph\n", (int)sizeof(txgapk->power_d[1]), txgapk->power_d[1]); return; + case RTW89_PHY_C2H_RFK_LOG_FUNC_TAS_PWR: + if (len != sizeof(struct rtw89_c2h_rf_tas_rpt_log)) + goto out; + + rtw89_phy_c2h_rfk_tas_pwr(rtwdev, content); + + return; default: break; } @@ -3475,9 +3510,6 @@ static void rtw89_phy_c2h_rfk_log(struct rtw89_dev *rtwdev, struct sk_buff *c2h, u16 chunk_len; bool handled; - if (!rtw89_debug_is_enabled(rtwdev, RTW89_DBG_RFK)) - return; - log_ptr += sizeof(*c2h_hdr); len -= sizeof(*c2h_hdr); @@ -3554,6 +3586,13 @@ rtw89_phy_c2h_rfk_log_txgapk(struct rtw89_dev *rtwdev, struct sk_buff *c2h, u32 RTW89_PHY_C2H_RFK_LOG_FUNC_TXGAPK, "TXGAPK"); } +static void +rtw89_phy_c2h_rfk_log_tas_pwr(struct rtw89_dev *rtwdev, struct sk_buff *c2h, u32 len) +{ + rtw89_phy_c2h_rfk_log(rtwdev, c2h, len, + RTW89_PHY_C2H_RFK_LOG_FUNC_TAS_PWR, "TAS"); +} + static void (* const rtw89_phy_c2h_rfk_log_handler[])(struct rtw89_dev *rtwdev, struct sk_buff *c2h, u32 len) = { @@ -3563,6 +3602,7 @@ void (* const rtw89_phy_c2h_rfk_log_handler[])(struct rtw89_dev *rtwdev, [RTW89_PHY_C2H_RFK_LOG_FUNC_RXDCK] = rtw89_phy_c2h_rfk_log_rxdck, [RTW89_PHY_C2H_RFK_LOG_FUNC_TSSI] = rtw89_phy_c2h_rfk_log_tssi, [RTW89_PHY_C2H_RFK_LOG_FUNC_TXGAPK] = rtw89_phy_c2h_rfk_log_txgapk, + [RTW89_PHY_C2H_RFK_LOG_FUNC_TAS_PWR] = rtw89_phy_c2h_rfk_log_tas_pwr, }; static @@ -3625,39 +3665,19 @@ rtw89_phy_c2h_rfk_report_state(struct rtw89_dev *rtwdev, struct sk_buff *c2h, u3 } static void -rtw89_phy_c2h_rfk_log_tas_pwr(struct rtw89_dev *rtwdev, struct sk_buff *c2h, u32 len) +rtw89_phy_c2h_rfk_report_tas_pwr(struct rtw89_dev *rtwdev, struct sk_buff *c2h, u32 len) { - const struct rtw89_c2h_rf_tas_info *rf_tas = + const struct rtw89_c2h_rf_tas_info *report = (const struct rtw89_c2h_rf_tas_info *)c2h->data; - const enum rtw89_sar_sources src = rtwdev->sar.src; - struct rtw89_tas_info *tas = &rtwdev->tas; - u64 linear = 0; - u32 i, cur_idx; - s16 txpwr; - if (!tas->enable || src == RTW89_SAR_SOURCE_NONE) - return; - - cur_idx = le32_to_cpu(rf_tas->cur_idx); - for (i = 0; i < cur_idx; i++) { - txpwr = (s16)le16_to_cpu(rf_tas->txpwr_history[i]); - linear += rtw89_db_quarter_to_linear(txpwr); - - rtw89_debug(rtwdev, RTW89_DBG_SAR, - "tas: index: %u, txpwr: %d\n", i, txpwr); - } - - if (cur_idx == 0) - tas->instant_txpwr = rtw89_db_to_linear(0); - else - tas->instant_txpwr = DIV_ROUND_DOWN_ULL(linear, cur_idx); + rtw89_phy_c2h_rfk_tas_pwr(rtwdev, &report->content); } static void (* const rtw89_phy_c2h_rfk_report_handler[])(struct rtw89_dev *rtwdev, struct sk_buff *c2h, u32 len) = { [RTW89_PHY_C2H_RFK_REPORT_FUNC_STATE] = rtw89_phy_c2h_rfk_report_state, - [RTW89_PHY_C2H_RFK_LOG_TAS_PWR] = rtw89_phy_c2h_rfk_log_tas_pwr, + [RTW89_PHY_C2H_RFK_REPORT_FUNC_TAS_PWR] = rtw89_phy_c2h_rfk_report_tas_pwr, }; bool rtw89_phy_c2h_chk_atomic(struct rtw89_dev *rtwdev, u8 class, u8 func) diff --git a/drivers/net/wireless/realtek/rtw89/phy.h b/drivers/net/wireless/realtek/rtw89/phy.h index 674397c4b9a9..9caacffd0af8 100644 --- a/drivers/net/wireless/realtek/rtw89/phy.h +++ b/drivers/net/wireless/realtek/rtw89/phy.h @@ -149,13 +149,14 @@ enum rtw89_phy_c2h_rfk_log_func { RTW89_PHY_C2H_RFK_LOG_FUNC_RXDCK = 3, RTW89_PHY_C2H_RFK_LOG_FUNC_TSSI = 4, RTW89_PHY_C2H_RFK_LOG_FUNC_TXGAPK = 5, + RTW89_PHY_C2H_RFK_LOG_FUNC_TAS_PWR = 9, RTW89_PHY_C2H_RFK_LOG_FUNC_NUM, }; enum rtw89_phy_c2h_rfk_report_func { RTW89_PHY_C2H_RFK_REPORT_FUNC_STATE = 0, - RTW89_PHY_C2H_RFK_LOG_TAS_PWR = 6, + RTW89_PHY_C2H_RFK_REPORT_FUNC_TAS_PWR = 6, }; enum rtw89_phy_c2h_dm_func { diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922a.c b/drivers/net/wireless/realtek/rtw89/rtw8922a.c index 8eb0cb9bb23e..6aa19ad259ac 100644 --- a/drivers/net/wireless/realtek/rtw89/rtw8922a.c +++ b/drivers/net/wireless/realtek/rtw89/rtw8922a.c @@ -2765,6 +2765,10 @@ static int rtw8922a_mac_disable_bb_rf(struct rtw89_dev *rtwdev) return 0; } +static const struct rtw89_chanctx_listener rtw8922a_chanctx_listener = { + .callbacks[RTW89_CHANCTX_CALLBACK_TAS] = rtw89_tas_chanctx_cb, +}; + #ifdef CONFIG_PM static const struct wiphy_wowlan_support rtw_wowlan_stub_8922a = { .flags = WIPHY_WOWLAN_MAGIC_PKT | WIPHY_WOWLAN_DISCONNECT | @@ -2876,6 +2880,7 @@ const struct rtw89_chip_info rtw8922a_chip_info = { .nctl_post_table = NULL, .dflt_parms = NULL, /* load parm from fw */ .rfe_parms_conf = NULL, /* load parm from fw */ + .chanctx_listener = &rtw8922a_chanctx_listener, .txpwr_factor_bb = 3, .txpwr_factor_rf = 2, .txpwr_factor_mac = 1, @@ -2895,7 +2900,7 @@ const struct rtw89_chip_info rtw8922a_chip_info = { BIT(NL80211_CHAN_WIDTH_160), .support_unii4 = true, .support_ant_gain = true, - .support_tas = false, + .support_tas = true, .support_sar_by_ant = true, .support_noise = false, .ul_tb_waveform_ctrl = false, diff --git a/drivers/net/wireless/realtek/rtw89/sar.c b/drivers/net/wireless/realtek/rtw89/sar.c index 7f568ffb3766..ef7feccccd5e 100644 --- a/drivers/net/wireless/realtek/rtw89/sar.c +++ b/drivers/net/wireless/realtek/rtw89/sar.c @@ -4,6 +4,7 @@ #include "acpi.h" #include "debug.h" +#include "fw.h" #include "phy.h" #include "reg.h" #include "sar.h" @@ -843,6 +844,20 @@ void rtw89_tas_chanctx_cb(struct rtw89_dev *rtwdev, } EXPORT_SYMBOL(rtw89_tas_chanctx_cb); +void rtw89_tas_fw_timer_enable(struct rtw89_dev *rtwdev, bool enable) +{ + const struct rtw89_chip_info *chip = rtwdev->chip; + struct rtw89_tas_info *tas = &rtwdev->tas; + + if (!tas->enable) + return; + + if (chip->chip_gen == RTW89_CHIP_AX) + return; + + rtw89_fw_h2c_rf_tas_trigger(rtwdev, enable); +} + void rtw89_sar_init(struct rtw89_dev *rtwdev) { rtw89_set_sar_from_acpi(rtwdev); diff --git a/drivers/net/wireless/realtek/rtw89/sar.h b/drivers/net/wireless/realtek/rtw89/sar.h index 4b7f3d44f57b..60b3aec5b3be 100644 --- a/drivers/net/wireless/realtek/rtw89/sar.h +++ b/drivers/net/wireless/realtek/rtw89/sar.h @@ -37,6 +37,7 @@ void rtw89_tas_reset(struct rtw89_dev *rtwdev, bool force); void rtw89_tas_scan(struct rtw89_dev *rtwdev, bool start); void rtw89_tas_chanctx_cb(struct rtw89_dev *rtwdev, enum rtw89_chanctx_state state); +void rtw89_tas_fw_timer_enable(struct rtw89_dev *rtwdev, bool enable); void rtw89_sar_init(struct rtw89_dev *rtwdev); void rtw89_sar_track(struct rtw89_dev *rtwdev); From 533e60e1ca99dfb99412023c86d34b7a78b0e57b Mon Sep 17 00:00:00 2001 From: Chih-Kang Chang Date: Mon, 15 Sep 2025 14:54:34 +0800 Subject: [PATCH 108/167] wifi: rtw89: wow: enable TKIP related feature For chips that supports TKIP HW encryption and decryption, enable TKIP cipher for WoWLAN feature. Additionally, the TX MIC KEY and RX MIC KEY is opposite in FW. Therefore, reverse the MIC KEY direction in H2C format, and also reverse it from AOAC report before reporting to mac80211. Signed-off-by: Chih-Kang Chang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250915065434.39324-1-pkshih@realtek.com --- drivers/net/wireless/realtek/rtw89/core.h | 2 +- drivers/net/wireless/realtek/rtw89/fw.c | 5 +- drivers/net/wireless/realtek/rtw89/wow.c | 79 ++++++++++++++++++----- drivers/net/wireless/realtek/rtw89/wow.h | 6 ++ 4 files changed, 74 insertions(+), 18 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 8408d5d8d42d..3d4ad2ffb75c 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -5771,8 +5771,8 @@ struct rtw89_wow_gtk_info { u8 kck[32]; u8 kek[32]; u8 tk1[16]; - u8 txmickey[8]; u8 rxmickey[8]; + u8 txmickey[8]; __le32 igtk_keyid; __le64 ipn; u8 igtk[2][32]; diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 2273dae8434a..ab904a7def1b 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -8726,9 +8726,10 @@ int rtw89_fw_h2c_wow_gtk_ofld(struct rtw89_dev *rtwdev, goto fail; } - /* not support TKIP yet */ h2c->w0 = le32_encode_bits(enable, RTW89_H2C_WOW_GTK_OFLD_W0_EN) | - le32_encode_bits(0, RTW89_H2C_WOW_GTK_OFLD_W0_TKIP_EN) | + le32_encode_bits(!!memchr_inv(gtk_info->txmickey, 0, + sizeof(gtk_info->txmickey)), + RTW89_H2C_WOW_GTK_OFLD_W0_TKIP_EN) | le32_encode_bits(gtk_info->igtk_keyid ? 1 : 0, RTW89_H2C_WOW_GTK_OFLD_W0_IEEE80211W_EN) | le32_encode_bits(macid, RTW89_H2C_WOW_GTK_OFLD_W0_MAC_ID) | diff --git a/drivers/net/wireless/realtek/rtw89/wow.c b/drivers/net/wireless/realtek/rtw89/wow.c index 5bb7c1a42f1d..5faa51ad896a 100644 --- a/drivers/net/wireless/realtek/rtw89/wow.c +++ b/drivers/net/wireless/realtek/rtw89/wow.c @@ -99,13 +99,26 @@ static int rtw89_rx_pn_to_iv(struct rtw89_dev *rtwdev, ieee80211_get_key_rx_seq(key, 0, &seq); - /* seq.ccmp.pn[] is BE order array */ - pn = u64_encode_bits(seq.ccmp.pn[0], RTW89_KEY_PN_5) | - u64_encode_bits(seq.ccmp.pn[1], RTW89_KEY_PN_4) | - u64_encode_bits(seq.ccmp.pn[2], RTW89_KEY_PN_3) | - u64_encode_bits(seq.ccmp.pn[3], RTW89_KEY_PN_2) | - u64_encode_bits(seq.ccmp.pn[4], RTW89_KEY_PN_1) | - u64_encode_bits(seq.ccmp.pn[5], RTW89_KEY_PN_0); + switch (key->cipher) { + case WLAN_CIPHER_SUITE_TKIP: + pn = u64_encode_bits(seq.tkip.iv32, RTW89_KEY_TKIP_PN_IV32) | + u64_encode_bits(seq.tkip.iv16, RTW89_KEY_TKIP_PN_IV16); + break; + case WLAN_CIPHER_SUITE_CCMP: + case WLAN_CIPHER_SUITE_GCMP: + case WLAN_CIPHER_SUITE_CCMP_256: + case WLAN_CIPHER_SUITE_GCMP_256: + /* seq.ccmp.pn[] is BE order array */ + pn = u64_encode_bits(seq.ccmp.pn[0], RTW89_KEY_PN_5) | + u64_encode_bits(seq.ccmp.pn[1], RTW89_KEY_PN_4) | + u64_encode_bits(seq.ccmp.pn[2], RTW89_KEY_PN_3) | + u64_encode_bits(seq.ccmp.pn[3], RTW89_KEY_PN_2) | + u64_encode_bits(seq.ccmp.pn[4], RTW89_KEY_PN_1) | + u64_encode_bits(seq.ccmp.pn[5], RTW89_KEY_PN_0); + break; + default: + return -EINVAL; + } err = _pn_to_iv(rtwdev, key, iv, pn, key->keyidx); if (err) @@ -177,13 +190,26 @@ static int rtw89_rx_iv_to_pn(struct rtw89_dev *rtwdev, if (err) return err; - /* seq.ccmp.pn[] is BE order array */ - seq.ccmp.pn[0] = u64_get_bits(pn, RTW89_KEY_PN_5); - seq.ccmp.pn[1] = u64_get_bits(pn, RTW89_KEY_PN_4); - seq.ccmp.pn[2] = u64_get_bits(pn, RTW89_KEY_PN_3); - seq.ccmp.pn[3] = u64_get_bits(pn, RTW89_KEY_PN_2); - seq.ccmp.pn[4] = u64_get_bits(pn, RTW89_KEY_PN_1); - seq.ccmp.pn[5] = u64_get_bits(pn, RTW89_KEY_PN_0); + switch (key->cipher) { + case WLAN_CIPHER_SUITE_TKIP: + seq.tkip.iv32 = u64_get_bits(pn, RTW89_KEY_TKIP_PN_IV32); + seq.tkip.iv16 = u64_get_bits(pn, RTW89_KEY_TKIP_PN_IV16); + break; + case WLAN_CIPHER_SUITE_CCMP: + case WLAN_CIPHER_SUITE_GCMP: + case WLAN_CIPHER_SUITE_CCMP_256: + case WLAN_CIPHER_SUITE_GCMP_256: + /* seq.ccmp.pn[] is BE order array */ + seq.ccmp.pn[0] = u64_get_bits(pn, RTW89_KEY_PN_5); + seq.ccmp.pn[1] = u64_get_bits(pn, RTW89_KEY_PN_4); + seq.ccmp.pn[2] = u64_get_bits(pn, RTW89_KEY_PN_3); + seq.ccmp.pn[3] = u64_get_bits(pn, RTW89_KEY_PN_2); + seq.ccmp.pn[4] = u64_get_bits(pn, RTW89_KEY_PN_1); + seq.ccmp.pn[5] = u64_get_bits(pn, RTW89_KEY_PN_0); + break; + default: + return -EINVAL; + } ieee80211_set_key_rx_seq(key, 0, &seq); rtw89_debug(rtwdev, RTW89_DBG_WOW, "%s key %d iv-%*ph to pn-%*ph\n", @@ -285,6 +311,11 @@ static void rtw89_wow_get_key_info_iter(struct ieee80211_hw *hw, switch (key->cipher) { case WLAN_CIPHER_SUITE_TKIP: + if (sta) + memcpy(gtk_info->txmickey, + key->key + NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY, + sizeof(gtk_info->txmickey)); + fallthrough; case WLAN_CIPHER_SUITE_CCMP: case WLAN_CIPHER_SUITE_GCMP: case WLAN_CIPHER_SUITE_CCMP_256: @@ -348,10 +379,27 @@ static void rtw89_wow_set_key_info_iter(struct ieee80211_hw *hw, struct rtw89_wow_aoac_report *aoac_rpt = &rtw_wow->aoac_rpt; struct rtw89_set_key_info_iter_data *iter_data = data; bool update_tx_key_info = iter_data->rx_ready; + u8 tmp[RTW89_MIC_KEY_LEN]; int ret; switch (key->cipher) { case WLAN_CIPHER_SUITE_TKIP: + /* + * TX MIC KEY and RX MIC KEY is oppsite in FW, + * need to swap it before sending to mac80211. + */ + if (!sta && update_tx_key_info && aoac_rpt->rekey_ok && + !iter_data->tkip_gtk_swapped) { + memcpy(tmp, &aoac_rpt->gtk[NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY], + RTW89_MIC_KEY_LEN); + memcpy(&aoac_rpt->gtk[NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY], + &aoac_rpt->gtk[NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY], + RTW89_MIC_KEY_LEN); + memcpy(&aoac_rpt->gtk[NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY], + tmp, RTW89_MIC_KEY_LEN); + iter_data->tkip_gtk_swapped = true; + } + fallthrough; case WLAN_CIPHER_SUITE_CCMP: case WLAN_CIPHER_SUITE_GCMP: case WLAN_CIPHER_SUITE_CCMP_256: @@ -642,7 +690,8 @@ static void rtw89_wow_update_key_info(struct rtw89_dev *rtwdev, bool rx_ready) struct rtw89_wow_param *rtw_wow = &rtwdev->wow; struct rtw89_wow_aoac_report *aoac_rpt = &rtw_wow->aoac_rpt; struct rtw89_set_key_info_iter_data data = {.error = false, - .rx_ready = rx_ready}; + .rx_ready = rx_ready, + .tkip_gtk_swapped = false}; struct ieee80211_bss_conf *bss_conf; struct ieee80211_key_conf *key; diff --git a/drivers/net/wireless/realtek/rtw89/wow.h b/drivers/net/wireless/realtek/rtw89/wow.h index 6606528d31c7..d2ba6cebc2a6 100644 --- a/drivers/net/wireless/realtek/rtw89/wow.h +++ b/drivers/net/wireless/realtek/rtw89/wow.h @@ -5,6 +5,9 @@ #ifndef __RTW89_WOW_H__ #define __RTW89_WOW_H__ +#define RTW89_KEY_TKIP_PN_IV16 GENMASK_ULL(15, 0) +#define RTW89_KEY_TKIP_PN_IV32 GENMASK_ULL(47, 16) + #define RTW89_KEY_PN_0 GENMASK_ULL(7, 0) #define RTW89_KEY_PN_1 GENMASK_ULL(15, 8) #define RTW89_KEY_PN_2 GENMASK_ULL(23, 16) @@ -25,6 +28,8 @@ #define RTW89_WOW_SYMBOL_CHK_PTK BIT(0) #define RTW89_WOW_SYMBOL_CHK_GTK BIT(1) +#define RTW89_MIC_KEY_LEN 8 + enum rtw89_wake_reason { RTW89_WOW_RSN_RX_PTK_REKEY = 0x1, RTW89_WOW_RSN_RX_GTK_REKEY = 0x2, @@ -73,6 +78,7 @@ struct rtw89_set_key_info_iter_data { u32 igtk_cipher; bool rx_ready; bool error; + bool tkip_gtk_swapped; }; static inline int rtw89_wow_get_sec_hdr_len(struct rtw89_dev *rtwdev) From 87a67cc357a8d01f244ae04284eac4c41c221e34 Mon Sep 17 00:00:00 2001 From: Roopni Devanathan Date: Tue, 26 Aug 2025 16:27:14 +0530 Subject: [PATCH 109/167] wifi: ath12k: Add support to set per-radio RTS threshold Currently, command to set RTS threshold makes changes to the threshold of all radios in the multi-radio wiphy. But each radio in a multi-radio wiphy can have different RTS threshold requirements. To support this requirement, use the index of radio for which the RTS threshold needs to be changed from mac80211 - radio_idx. Based on the value passed, set the RTS threshold value for the corresponding radios. Following are the possible values of radio_idx and the corresponding behavior in multi-radio wiphys: 1. radio_idx is -1: consider RTS threshold as a global parameter, i.e., make changes to all the radios in a wiphy. If setting RTS threshold fails for any radio, then the previous RTS threshold values of respective radios will be restored. 2. radio_idx denotes a specific radio: make changes in RTS threshold to that radio alone. 3. radio_idx is any other number: report it as an invalid number. In case of single-radio wiphys, continue with the existing behavior, i.e., set the passed RTS threshold value to the radio present. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3 Signed-off-by: Roopni Devanathan Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20250826105714.1188131-1-quic_rdevanat@quicinc.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.h | 3 +- drivers/net/wireless/ath/ath12k/mac.c | 50 ++++++++++++++++++++++---- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index 519f826f56c8..da7e99f2ca0b 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-3-Clause-Clear */ /* * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #ifndef ATH12K_CORE_H @@ -730,6 +730,7 @@ struct ath12k { u32 txpower_scale; u32 power_scale; u32 chan_tx_pwr; + u32 rts_threshold; u32 num_stations; u32 max_num_stations; diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 3a3965b79942..61a4f5af62a7 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include @@ -9860,6 +9860,7 @@ int ath12k_mac_vdev_create(struct ath12k *ar, struct ath12k_link_vif *arvif) param_id = WMI_VDEV_PARAM_RTS_THRESHOLD; param_value = hw->wiphy->rts_threshold; + ar->rts_threshold = param_value; ret = ath12k_wmi_vdev_set_param_cmd(ar, arvif->vdev_id, param_id, param_value); if (ret) { @@ -11687,16 +11688,32 @@ static int ath12k_mac_op_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, u32 value) { struct ath12k_hw *ah = ath12k_hw_to_ah(hw); + struct wiphy *wiphy = hw->wiphy; struct ath12k *ar; - int param_id = WMI_VDEV_PARAM_RTS_THRESHOLD, ret = 0, i; + int param_id = WMI_VDEV_PARAM_RTS_THRESHOLD; + int ret = 0, ret_err, i; lockdep_assert_wiphy(hw->wiphy); - /* Currently we set the rts threshold value to all the vifs across - * all radios of the single wiphy. - * TODO Once support for vif specific RTS threshold in mac80211 is - * available, ath12k can make use of it. - */ + if (radio_idx >= wiphy->n_radio || radio_idx < -1) + return -EINVAL; + + if (radio_idx != -1) { + /* Update RTS threshold in specified radio */ + ar = ath12k_ah_to_ar(ah, radio_idx); + ret = ath12k_set_vdev_param_to_all_vifs(ar, param_id, value); + if (ret) { + ath12k_warn(ar->ab, + "failed to set RTS config for all vdevs of pdev %d", + ar->pdev->pdev_id); + return ret; + } + + ar->rts_threshold = value; + return 0; + } + + /* Radio_index passed is -1, so set RTS threshold for all radios. */ for_each_ar(ah, ar, i) { ret = ath12k_set_vdev_param_to_all_vifs(ar, param_id, value); if (ret) { @@ -11705,6 +11722,25 @@ static int ath12k_mac_op_set_rts_threshold(struct ieee80211_hw *hw, break; } } + if (!ret) { + /* Setting new RTS threshold for vdevs of all radios passed, so update + * the RTS threshold value for all radios + */ + for_each_ar(ah, ar, i) + ar->rts_threshold = value; + return 0; + } + + /* RTS threshold config failed, revert to the previous RTS threshold */ + for (i = i - 1; i >= 0; i--) { + ar = ath12k_ah_to_ar(ah, i); + ret_err = ath12k_set_vdev_param_to_all_vifs(ar, param_id, + ar->rts_threshold); + if (ret_err) + ath12k_warn(ar->ab, + "failed to restore RTS threshold for all vdevs of pdev %d", + ar->pdev->pdev_id); + } return ret; } From bba2f9faf41ee9607c78fcd669527b7654543cfe Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 4 Aug 2025 11:03:10 +0800 Subject: [PATCH 110/167] wifi: ath12k: initialize eirp_power before use Currently, at the end of ath12k_mac_fill_reg_tpc_info(), the reg_tpc_info struct is populated, including the following: reg_tpc_info->is_psd_power = is_psd_power; reg_tpc_info->eirp_power = eirp_power; Kernel test robot complains on uninitialized symbol: drivers/net/wireless/ath/ath12k/mac.c:10069 ath12k_mac_fill_reg_tpc_info() error: uninitialized symbol 'eirp_power' This is because there are some code paths that never set eirp_power, so the assignment of reg_tpc_info->eirp_power can come from an uninitialized variable. Functionally this is OK since the eirp_power only has meaning when is_psd_power is true, and all code paths which set is_psd_power to true also set eirp_power. However, to keep the robot happy, always initialize eirp_power before use. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00284-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1 Fixes: aeda163bb0c7 ("wifi: ath12k: fill parameters for vdev set TPC power WMI command") Reported-by: kernel test robot Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/202505180927.tbNWr3vE-lkp@intel.com/ Signed-off-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250804-ath12k-fix-smatch-warning-on-6g-vlp-v1-1-56f1e54152ab@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 61a4f5af62a7..63b7129f2245 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -11241,8 +11241,8 @@ void ath12k_mac_fill_reg_tpc_info(struct ath12k *ar, struct ieee80211_channel *chan, *temp_chan; u8 pwr_lvl_idx, num_pwr_levels, pwr_reduction; bool is_psd_power = false, is_tpe_present = false; - s8 max_tx_power[ATH12K_NUM_PWR_LEVELS], - psd_power, tx_power, eirp_power; + s8 max_tx_power[ATH12K_NUM_PWR_LEVELS], psd_power, tx_power; + s8 eirp_power = 0; struct ath12k_vif *ahvif = arvif->ahvif; u16 start_freq, center_freq; u8 reg_6ghz_power_mode; From ea2b0af4c9e3f7187b5be4b7fc1511ea239046c0 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 4 Aug 2025 11:03:11 +0800 Subject: [PATCH 111/167] wifi: ath12k: fix overflow warning on num_pwr_levels In ath12k_mac_parse_tx_pwr_env(), for the non-PSD case num_pwr_levels is limited by ATH12K_NUM_PWR_LEVELS which is 16: if (tpc_info->num_pwr_levels > ATH12K_NUM_PWR_LEVELS) tpc_info->num_pwr_levels = ATH12K_NUM_PWR_LEVELS; Then it is used to iterate entries in local_non_psd->power[] and reg_non_psd->power[]: for (i = 0; i < tpc_info->num_pwr_levels; i++) { tpc_info->tpe[i] = min(local_non_psd->power[i], reg_non_psd->power[i]) / 2; Since the two array are of size 5, Smatch warns: drivers/net/wireless/ath/ath12k/mac.c:9812 ath12k_mac_parse_tx_pwr_env() error: buffer overflow 'local_non_psd->power' 5 <= 15 drivers/net/wireless/ath/ath12k/mac.c:9812 ath12k_mac_parse_tx_pwr_env() error: buffer overflow 'reg_non_psd->power' 5 <= 15 This is a false positive as there is already implicit limitation: tpc_info->num_pwr_levels = max(local_non_psd->count, reg_non_psd->count); meaning it won't exceed 5. However, to make robot happy, add explicit limit there. Also add the same to the PSD case, although no warning due to ATH12K_NUM_PWR_LEVELS equals IEEE80211_TPE_PSD_ENTRIES_320MHZ. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00284-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1 Fixes: cccbb9d0dd6a ("wifi: ath12k: add parse of transmit power envelope element") Reported-by: kernel test robot Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/202505180703.Kr9OfQRP-lkp@intel.com/ Signed-off-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250804-ath12k-fix-smatch-warning-on-6g-vlp-v1-2-56f1e54152ab@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 63b7129f2245..7a43f2e8f905 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -11448,8 +11448,10 @@ static void ath12k_mac_parse_tx_pwr_env(struct ath12k *ar, tpc_info->num_pwr_levels = max(local_psd->count, reg_psd->count); - if (tpc_info->num_pwr_levels > ATH12K_NUM_PWR_LEVELS) - tpc_info->num_pwr_levels = ATH12K_NUM_PWR_LEVELS; + tpc_info->num_pwr_levels = + min3(tpc_info->num_pwr_levels, + IEEE80211_TPE_PSD_ENTRIES_320MHZ, + ATH12K_NUM_PWR_LEVELS); for (i = 0; i < tpc_info->num_pwr_levels; i++) { tpc_info->tpe[i] = min(local_psd->power[i], @@ -11464,8 +11466,10 @@ static void ath12k_mac_parse_tx_pwr_env(struct ath12k *ar, tpc_info->num_pwr_levels = max(local_non_psd->count, reg_non_psd->count); - if (tpc_info->num_pwr_levels > ATH12K_NUM_PWR_LEVELS) - tpc_info->num_pwr_levels = ATH12K_NUM_PWR_LEVELS; + tpc_info->num_pwr_levels = + min3(tpc_info->num_pwr_levels, + IEEE80211_TPE_EIRP_ENTRIES_320MHZ, + ATH12K_NUM_PWR_LEVELS); for (i = 0; i < tpc_info->num_pwr_levels; i++) { tpc_info->tpe[i] = min(local_non_psd->power[i], From cf412ae7b7124e2b3bfe472616ec24b117b6008a Mon Sep 17 00:00:00 2001 From: Kang Yang Date: Tue, 22 Jul 2025 17:59:32 +0800 Subject: [PATCH 112/167] wifi: ath12k: fix signal in radiotap for WCN7850 Currently host will add ATH12K_DEFAULT_NOISE_FLOOR to rssi_comb to convert RSSI from dB to dBm. For WCN7850, this conversion is unnecessary because the RSSI value is already reported in dBm units. No longer convert for those firmware that already support dBM conversion. This patch won't affect QCN chips. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3 Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Signed-off-by: Kang Yang Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250722095934.67-2-kang.yang@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp_mon.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/dp_mon.c b/drivers/net/wireless/ath/ath12k/dp_mon.c index 8189e52ed007..ec1587d0b917 100644 --- a/drivers/net/wireless/ath/ath12k/dp_mon.c +++ b/drivers/net/wireless/ath/ath12k/dp_mon.c @@ -2154,8 +2154,12 @@ static void ath12k_dp_mon_update_radiotap(struct ath12k *ar, spin_unlock_bh(&ar->data_lock); rxs->flag |= RX_FLAG_MACTIME_START; - rxs->signal = ppduinfo->rssi_comb + noise_floor; rxs->nss = ppduinfo->nss + 1; + if (test_bit(WMI_TLV_SERVICE_HW_DB2DBM_CONVERSION_SUPPORT, + ar->ab->wmi_ab.svc_map)) + rxs->signal = ppduinfo->rssi_comb; + else + rxs->signal = ppduinfo->rssi_comb + noise_floor; if (ppduinfo->userstats[ppduinfo->userid].ampdu_present) { rxs->flag |= RX_FLAG_AMPDU_DETAILS; From 6b46e85129185ec076f9c3bd2813dfd2f968522b Mon Sep 17 00:00:00 2001 From: Kang Yang Date: Tue, 22 Jul 2025 17:59:33 +0800 Subject: [PATCH 113/167] wifi: ath12k: fix HAL_PHYRX_COMMON_USER_INFO handling in monitor mode Current monitor mode will parse TLV HAL_PHYRX_OTHER_RECEIVE_INFO with struct hal_phyrx_common_user_info. Obviously, they do not match. The original intention here was to parse HAL_PHYRX_COMMON_USER_INFO. So fix it by correctly parsing HAL_PHYRX_COMMON_USER_INFO instead. Also add LTF parsing and report to radiotap along with GI. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3 Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ-1 Fixes: d939919a36f4 ("wifi: ath12k: Add HAL_PHYRX_OTHER_RECEIVE_INFO TLV parsing support") Signed-off-by: Kang Yang Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250722095934.67-3-kang.yang@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp_mon.c | 35 ++++++++++++++++++++---- drivers/net/wireless/ath/ath12k/hal_rx.h | 3 +- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/dp_mon.c b/drivers/net/wireless/ath/ath12k/dp_mon.c index ec1587d0b917..e93ede5e6197 100644 --- a/drivers/net/wireless/ath/ath12k/dp_mon.c +++ b/drivers/net/wireless/ath/ath12k/dp_mon.c @@ -1440,6 +1440,34 @@ static void ath12k_dp_mon_parse_rx_msdu_end_err(u32 info, u32 *errmap) *errmap |= HAL_RX_MPDU_ERR_MPDU_LEN; } +static void +ath12k_parse_cmn_usr_info(const struct hal_phyrx_common_user_info *cmn_usr_info, + struct hal_rx_mon_ppdu_info *ppdu_info) +{ + struct hal_rx_radiotap_eht *eht = &ppdu_info->eht_info.eht; + u32 known, data, cp_setting, ltf_size; + + known = __le32_to_cpu(eht->known); + known |= IEEE80211_RADIOTAP_EHT_KNOWN_GI | + IEEE80211_RADIOTAP_EHT_KNOWN_EHT_LTF; + eht->known = cpu_to_le32(known); + + cp_setting = le32_get_bits(cmn_usr_info->info0, + HAL_RX_CMN_USR_INFO0_CP_SETTING); + ltf_size = le32_get_bits(cmn_usr_info->info0, + HAL_RX_CMN_USR_INFO0_LTF_SIZE); + + data = __le32_to_cpu(eht->data[0]); + data |= u32_encode_bits(cp_setting, IEEE80211_RADIOTAP_EHT_DATA0_GI); + data |= u32_encode_bits(ltf_size, IEEE80211_RADIOTAP_EHT_DATA0_LTF); + eht->data[0] = cpu_to_le32(data); + + if (!ppdu_info->ltf_size) + ppdu_info->ltf_size = ltf_size; + if (!ppdu_info->gi) + ppdu_info->gi = cp_setting; +} + static void ath12k_dp_mon_parse_status_msdu_end(struct ath12k_mon_data *pmon, const struct hal_rx_msdu_end *msdu_end) @@ -1641,11 +1669,8 @@ ath12k_dp_mon_rx_parse_status_tlv(struct ath12k *ar, HAL_RX_PHYRX_RSSI_LEGACY_INFO_INFO0_RX_BW); break; } - case HAL_PHYRX_OTHER_RECEIVE_INFO: { - const struct hal_phyrx_common_user_info *cmn_usr_info = tlv_data; - - ppdu_info->gi = le32_get_bits(cmn_usr_info->info0, - HAL_RX_PHY_CMN_USER_INFO0_GI); + case HAL_PHYRX_COMMON_USER_INFO: { + ath12k_parse_cmn_usr_info(tlv_data, ppdu_info); break; } case HAL_RX_PPDU_START_USER_INFO: diff --git a/drivers/net/wireless/ath/ath12k/hal_rx.h b/drivers/net/wireless/ath/ath12k/hal_rx.h index a3ab588aae19..801a5f6d3458 100644 --- a/drivers/net/wireless/ath/ath12k/hal_rx.h +++ b/drivers/net/wireless/ath/ath12k/hal_rx.h @@ -695,7 +695,8 @@ struct hal_rx_resp_req_info { #define HAL_RX_MPDU_ERR_MPDU_LEN BIT(6) #define HAL_RX_MPDU_ERR_UNENCRYPTED_FRAME BIT(7) -#define HAL_RX_PHY_CMN_USER_INFO0_GI GENMASK(17, 16) +#define HAL_RX_CMN_USR_INFO0_CP_SETTING GENMASK(17, 16) +#define HAL_RX_CMN_USR_INFO0_LTF_SIZE GENMASK(19, 18) struct hal_phyrx_common_user_info { __le32 rsvd[2]; From 7695fa71c1d50a375e54426421acbc8d457bc5a3 Mon Sep 17 00:00:00 2001 From: Kang Yang Date: Tue, 22 Jul 2025 17:59:34 +0800 Subject: [PATCH 114/167] wifi: ath12k: fix the fetching of combined rssi Currently, host fetches combined rssi from rssi_comb in struct hal_rx_phyrx_rssi_legacy_info. rssi_comb is 8th to 15th bits of the second to last variable. rssi_comb_ppdu is the 0th to 7th of the last variable. When bandwidth = 20MHz, rssi_comb = rssi_comb_ppdu. But when bandwidth > 20MHz, rssi_comb < rssi_comb_ppdu because rssi_comb only includes power of primary 20 MHz while rssi_comb_ppdu includes power of active RUs/subchannels. So should fetch combined rssi from rssi_comb_ppdu. Also related macro definitions are too long, rename them. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3 Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ-1 Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Signed-off-by: Kang Yang Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250722095934.67-4-kang.yang@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp_mon.c | 8 ++++---- drivers/net/wireless/ath/ath12k/hal_rx.h | 9 +++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/dp_mon.c b/drivers/net/wireless/ath/ath12k/dp_mon.c index e93ede5e6197..abd611ac37f0 100644 --- a/drivers/net/wireless/ath/ath12k/dp_mon.c +++ b/drivers/net/wireless/ath/ath12k/dp_mon.c @@ -1655,18 +1655,18 @@ ath12k_dp_mon_rx_parse_status_tlv(struct ath12k *ar, const struct hal_rx_phyrx_rssi_legacy_info *rssi = tlv_data; info[0] = __le32_to_cpu(rssi->info0); - info[1] = __le32_to_cpu(rssi->info1); + info[2] = __le32_to_cpu(rssi->info2); /* TODO: Please note that the combined rssi will not be accurate * in MU case. Rssi in MU needs to be retrieved from * PHYRX_OTHER_RECEIVE_INFO TLV. */ ppdu_info->rssi_comb = - u32_get_bits(info[1], - HAL_RX_PHYRX_RSSI_LEGACY_INFO_INFO1_RSSI_COMB); + u32_get_bits(info[2], + HAL_RX_RSSI_LEGACY_INFO_INFO2_RSSI_COMB_PPDU); ppdu_info->bw = u32_get_bits(info[0], - HAL_RX_PHYRX_RSSI_LEGACY_INFO_INFO0_RX_BW); + HAL_RX_RSSI_LEGACY_INFO_INFO0_RX_BW); break; } case HAL_PHYRX_COMMON_USER_INFO: { diff --git a/drivers/net/wireless/ath/ath12k/hal_rx.h b/drivers/net/wireless/ath/ath12k/hal_rx.h index 801a5f6d3458..d1ad7747b82c 100644 --- a/drivers/net/wireless/ath/ath12k/hal_rx.h +++ b/drivers/net/wireless/ath/ath12k/hal_rx.h @@ -483,15 +483,16 @@ enum hal_rx_ul_reception_type { HAL_RECEPTION_TYPE_FRAMELESS }; -#define HAL_RX_PHYRX_RSSI_LEGACY_INFO_INFO0_RECEPTION GENMASK(3, 0) -#define HAL_RX_PHYRX_RSSI_LEGACY_INFO_INFO0_RX_BW GENMASK(7, 5) -#define HAL_RX_PHYRX_RSSI_LEGACY_INFO_INFO1_RSSI_COMB GENMASK(15, 8) +#define HAL_RX_RSSI_LEGACY_INFO_INFO0_RECEPTION GENMASK(3, 0) +#define HAL_RX_RSSI_LEGACY_INFO_INFO0_RX_BW GENMASK(7, 5) +#define HAL_RX_RSSI_LEGACY_INFO_INFO1_RSSI_COMB GENMASK(15, 8) +#define HAL_RX_RSSI_LEGACY_INFO_INFO2_RSSI_COMB_PPDU GENMASK(7, 0) struct hal_rx_phyrx_rssi_legacy_info { __le32 info0; __le32 rsvd0[39]; __le32 info1; - __le32 rsvd1; + __le32 info2; } __packed; #define HAL_RX_MPDU_START_INFO0_PPDU_ID GENMASK(31, 16) From 26f8fc0b24fd1a9dba1000bc9b5f2b199b7775a0 Mon Sep 17 00:00:00 2001 From: Sriram R Date: Thu, 24 Jul 2025 00:36:51 +0530 Subject: [PATCH 115/167] wifi: ath12k: Add fallback for invalid channel number in PHY metadata Currently, ath12k_dp_rx_h_ppdu() determines the band and frequency based on the channel number and center frequency from the RX descriptor's PHY metadata. However, in rare cases, it is observed that frequency retrieved from the metadata may be invalid or unexpected especially for 6 GHz frames. This can result in a NULL sband, which prevents proper frequency assignment in rx_status and potentially leading to incorrect RX packet classification. To fix this potential issue, add a fallback mechanism that uses ar->rx_channel to populate the band and frequency when the derived sband is invalid or missing. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ-1 Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Signed-off-by: Sriram R Co-developed-by: Vinith Kumar R Signed-off-by: Vinith Kumar R Signed-off-by: Aishwarya R Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250723190651.699828-1-aishwarya.r@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp_rx.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/dp_rx.c b/drivers/net/wireless/ath/ath12k/dp_rx.c index 8ab91273592c..adb0cfe109e6 100644 --- a/drivers/net/wireless/ath/ath12k/dp_rx.c +++ b/drivers/net/wireless/ath/ath12k/dp_rx.c @@ -2533,6 +2533,8 @@ void ath12k_dp_rx_h_ppdu(struct ath12k *ar, struct ath12k_dp_rx_info *rx_info) channel_num = meta_data; center_freq = meta_data >> 16; + rx_status->band = NUM_NL80211_BANDS; + if (center_freq >= ATH12K_MIN_6GHZ_FREQ && center_freq <= ATH12K_MAX_6GHZ_FREQ) { rx_status->band = NL80211_BAND_6GHZ; @@ -2541,21 +2543,33 @@ void ath12k_dp_rx_h_ppdu(struct ath12k *ar, struct ath12k_dp_rx_info *rx_info) rx_status->band = NL80211_BAND_2GHZ; } else if (channel_num >= 36 && channel_num <= 173) { rx_status->band = NL80211_BAND_5GHZ; - } else { + } + + if (unlikely(rx_status->band == NUM_NL80211_BANDS || + !ath12k_ar_to_hw(ar)->wiphy->bands[rx_status->band])) { + ath12k_warn(ar->ab, "sband is NULL for status band %d channel_num %d center_freq %d pdev_id %d\n", + rx_status->band, channel_num, center_freq, ar->pdev_idx); + spin_lock_bh(&ar->data_lock); channel = ar->rx_channel; if (channel) { rx_status->band = channel->band; channel_num = ieee80211_frequency_to_channel(channel->center_freq); + rx_status->freq = ieee80211_channel_to_frequency(channel_num, + rx_status->band); + } else { + ath12k_err(ar->ab, "unable to determine channel, band for rx packet"); } spin_unlock_bh(&ar->data_lock); + goto h_rate; } if (rx_status->band != NL80211_BAND_6GHZ) rx_status->freq = ieee80211_channel_to_frequency(channel_num, rx_status->band); +h_rate: ath12k_dp_rx_h_rate(ar, rx_info); } From 541a201e9f46c6633aa1a08df4ab17cc7c96bf89 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Fri, 15 Aug 2025 09:44:56 +0800 Subject: [PATCH 116/167] wifi: ath11k: downgrade log level for CE buffer enqueue failure There are two rings involved in the Copy Engine (CE) receive path handling, the CE status (STS) ring and the CE destination (DST) ring. Each time CE hardware needs to send an event (e.g. WMI event) to host, CE hardware finds a buffer (to which the tail pointer (TP) points) in DST ring and fills it with payload, then hardware fills meta data in STS ring and fires interrupt to host. Please note the TP of DST ring is expected to be advanced by CE hardware before interrupting host. While handling the interrupt, host finds that DST ring buffers are used hence increases rx_buf_needed to record the number of buffers to be replenished. Note before that, host compares TP and head pointer (HP) of DST ring to see if there is available space. Normally rx_buf_needed simply equals available space. But sometimes CE hardware doesn't (for whatever reason) update TP timely, making the comparison fails, then enqueue is cancelled and a warning is logged: ath11k_pci 0000:02:00.0: failed to enqueue rx buf: -28 However even enqueue fails this time, rx_buf_needed still records the numbers of needed buffers. Later when TP gets updated correctly, the missing buffer will be eventually replenished. And there is no doubt on the late update, it always comes (or lots of such warnings should be seen). Since this won't cause any functional issue, downgrade logging level to avoid misleading. Compile tested only. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220269 Signed-off-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250815-ath-dont-warn-on-ce-enqueue-fail-v1-1-f955ddc3ba7a@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/ce.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath11k/ce.c b/drivers/net/wireless/ath/ath11k/ce.c index c65fc9fb539e..a7a163621b21 100644 --- a/drivers/net/wireless/ath/ath11k/ce.c +++ b/drivers/net/wireless/ath/ath11k/ce.c @@ -354,7 +354,8 @@ static int ath11k_ce_rx_post_pipe(struct ath11k_ce_pipe *pipe) ret = ath11k_ce_rx_buf_enqueue_pipe(pipe, skb, paddr); if (ret) { - ath11k_warn(ab, "failed to enqueue rx buf: %d\n", ret); + ath11k_dbg(ab, ATH11K_DBG_CE, "failed to enqueue rx buf: %d\n", + ret); dma_unmap_single(ab->dev, paddr, skb->len + skb_tailroom(skb), DMA_FROM_DEVICE); From 43746f13fec67f6f223d64cfe96c095c9b468e70 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Fri, 15 Aug 2025 09:44:57 +0800 Subject: [PATCH 117/167] wifi: ath12k: fix wrong logging ID used for CE ATH12K_DBG_AHB is used for CE logging which is not proper. Add ATH12K_DBG_CE and replace ATH12K_DBG_AHB with it. Compile tested only. Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Signed-off-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250815-ath-dont-warn-on-ce-enqueue-fail-v1-2-f955ddc3ba7a@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/ce.c | 2 +- drivers/net/wireless/ath/ath12k/debug.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/ce.c b/drivers/net/wireless/ath/ath12k/ce.c index f93a419abf65..c5aadbc6367c 100644 --- a/drivers/net/wireless/ath/ath12k/ce.c +++ b/drivers/net/wireless/ath/ath12k/ce.c @@ -478,7 +478,7 @@ static void ath12k_ce_recv_process_cb(struct ath12k_ce_pipe *pipe) } while ((skb = __skb_dequeue(&list))) { - ath12k_dbg(ab, ATH12K_DBG_AHB, "rx ce pipe %d len %d\n", + ath12k_dbg(ab, ATH12K_DBG_CE, "rx ce pipe %d len %d\n", pipe->pipe_num, skb->len); pipe->recv_cb(ab, skb); } diff --git a/drivers/net/wireless/ath/ath12k/debug.h b/drivers/net/wireless/ath/ath12k/debug.h index 48916e4e1f60..bf254e43a68d 100644 --- a/drivers/net/wireless/ath/ath12k/debug.h +++ b/drivers/net/wireless/ath/ath12k/debug.h @@ -26,6 +26,7 @@ enum ath12k_debug_mask { ATH12K_DBG_DP_TX = 0x00002000, ATH12K_DBG_DP_RX = 0x00004000, ATH12K_DBG_WOW = 0x00008000, + ATH12K_DBG_CE = 0x00010000, ATH12K_DBG_ANY = 0xffffffff, }; From 8873edecb38888726ce411b32de91b96cf41bbdb Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Fri, 15 Aug 2025 09:44:58 +0800 Subject: [PATCH 118/167] wifi: ath12k: downgrade log level for CE buffer enqueue failure There are two rings involved in the Copy Engine (CE) receive path handling, the CE status (STS) ring and the CE destination (DST) ring. Each time CE hardware needs to send an event (e.g. WMI event) to host, CE hardware finds a buffer (to which the tail pointer (TP) points) in DST ring and fills it with payload, then hardware fills meta data in STS ring and fires interrupt to host. Please note the TP of DST ring is expected to be advanced by CE hardware before interrupting host. While handling the interrupt, host finds that DST ring buffers are used hence increases rx_buf_needed to record the number of buffers to be replenished. Note before that, host compares TP and head pointer (HP) of DST ring to see if there is available space. Normally rx_buf_needed simply equals available space. But sometimes CE hardware doesn't (for whatever reason) update TP timely, making the comparison fails, then enqueue is cancelled and a warning is logged. However even enqueue fails this time, rx_buf_needed still records the numbers of needed buffers. Later when TP gets updated correctly, the missing buffer will be eventually replenished. And there is no doubt on the late update, it always comes (or lots of such warnings should be seen). Since this won't cause any functional issue, downgrade logging level to avoid misleading. Compile tested only. Signed-off-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250815-ath-dont-warn-on-ce-enqueue-fail-v1-3-f955ddc3ba7a@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/ce.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/ce.c b/drivers/net/wireless/ath/ath12k/ce.c index c5aadbc6367c..9a63608838ac 100644 --- a/drivers/net/wireless/ath/ath12k/ce.c +++ b/drivers/net/wireless/ath/ath12k/ce.c @@ -392,7 +392,8 @@ static int ath12k_ce_rx_post_pipe(struct ath12k_ce_pipe *pipe) ret = ath12k_ce_rx_buf_enqueue_pipe(pipe, skb, paddr); if (ret) { - ath12k_warn(ab, "failed to enqueue rx buf: %d\n", ret); + ath12k_dbg(ab, ATH12K_DBG_CE, "failed to enqueue rx buf: %d\n", + ret); dma_unmap_single(ab->dev, paddr, skb->len + skb_tailroom(skb), DMA_FROM_DEVICE); From 2418fcf2006804425c12d85479b9ad17c4db5e90 Mon Sep 17 00:00:00 2001 From: Liao Yuanhong Date: Tue, 12 Aug 2025 15:52:58 +0800 Subject: [PATCH 119/167] wifi: ath11k: Remove redundant semicolon Remove unnecessary semicolon. Signed-off-by: Liao Yuanhong Link: https://patch.msgid.link/20250812075259.6921-1-liaoyuanhong@vivo.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/dp_rx.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath11k/dp_rx.c b/drivers/net/wireless/ath/ath11k/dp_rx.c index ffc7482c77b6..b9e976ddcbbf 100644 --- a/drivers/net/wireless/ath/ath11k/dp_rx.c +++ b/drivers/net/wireless/ath/ath11k/dp_rx.c @@ -4615,7 +4615,6 @@ static void ath11k_hal_rx_msdu_list_get(struct ath11k *ar, msdu_details[i].buf_addr_info.info0) == 0) { msdu_desc_info = &msdu_details[i - 1].rx_msdu_info; msdu_desc_info->info0 |= last; - ; break; } msdu_desc_info = &msdu_details[i].rx_msdu_info; From 5b345471752701ccfcfa6e86e15d2cebc6e17343 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 8 Aug 2025 17:18:00 +0200 Subject: [PATCH 120/167] wifi: ath10k: remove gpio number assignment The leds-gpio traditionally takes a global gpio number in its platform data, but the number assigned here is not actually such a number but only meant to be used internally to this driver. As part of the kernel-wide cleanup of the old gpiolib interfaces, the 'gpio' number field is going away, so to keep ath10k building, move the assignment into a private structure instead. Signed-off-by: Arnd Bergmann Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20250808151822.536879-17-arnd@kernel.org Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath10k/leds.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath10k/leds.c b/drivers/net/wireless/ath/ath10k/leds.c index 9b1d04eb4265..3a6c8111e7c6 100644 --- a/drivers/net/wireless/ath/ath10k/leds.c +++ b/drivers/net/wireless/ath/ath10k/leds.c @@ -27,7 +27,7 @@ static int ath10k_leds_set_brightness_blocking(struct led_classdev *led_cdev, goto out; ar->leds.gpio_state_pin = (brightness != LED_OFF) ^ led->active_low; - ath10k_wmi_gpio_output(ar, led->gpio, ar->leds.gpio_state_pin); + ath10k_wmi_gpio_output(ar, ar->hw_params.led_pin, ar->leds.gpio_state_pin); out: mutex_unlock(&ar->conf_mutex); @@ -64,7 +64,6 @@ int ath10k_leds_register(struct ath10k *ar) snprintf(ar->leds.label, sizeof(ar->leds.label), "ath10k-%s", wiphy_name(ar->hw->wiphy)); ar->leds.wifi_led.active_low = 1; - ar->leds.wifi_led.gpio = ar->hw_params.led_pin; ar->leds.wifi_led.name = ar->leds.label; ar->leds.wifi_led.default_state = LEDS_GPIO_DEFSTATE_KEEP; From 900730dc4705dc78f9bf69c4c513ec018bffee37 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Wed, 13 Aug 2025 16:49:32 -0500 Subject: [PATCH 121/167] wifi: ath: Use of_reserved_mem_region_to_resource() for "memory-region" Use the newly added of_reserved_mem_region_to_resource() function to handle "memory-region" properties. The error handling is a bit different for ath10k. "memory-region" is optional, so failed lookup is not an error. But then an error in of_address_to_resource() is treated as an error. However, that distinction is not really important. Either the region is available and usable or it is not. So now, it is just of_reserved_mem_region_to_resource() which is checked for an error. Signed-off-by: Rob Herring (Arm) Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250813214933.897486-1-robh@kernel.org Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath10k/snoc.c | 14 +++----------- drivers/net/wireless/ath/ath11k/ahb.c | 17 +++-------------- drivers/net/wireless/ath/ath11k/qmi.c | 17 ++++------------- 3 files changed, 10 insertions(+), 38 deletions(-) diff --git a/drivers/net/wireless/ath/ath10k/snoc.c b/drivers/net/wireless/ath/ath10k/snoc.c index f0713bd36173..b3f6424c17d3 100644 --- a/drivers/net/wireless/ath/ath10k/snoc.c +++ b/drivers/net/wireless/ath/ath10k/snoc.c @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include "ce.h" @@ -1559,19 +1559,11 @@ static void ath10k_modem_deinit(struct ath10k *ar) static int ath10k_setup_msa_resources(struct ath10k *ar, u32 msa_size) { struct device *dev = ar->dev; - struct device_node *node; struct resource r; int ret; - node = of_parse_phandle(dev->of_node, "memory-region", 0); - if (node) { - ret = of_address_to_resource(node, 0, &r); - of_node_put(node); - if (ret) { - dev_err(dev, "failed to resolve msa fixed region\n"); - return ret; - } - + ret = of_reserved_mem_region_to_resource(dev->of_node, 0, &r); + if (!ret) { ar->msa.paddr = r.start; ar->msa.mem_size = resource_size(&r); ar->msa.vaddr = devm_memremap(dev, ar->msa.paddr, diff --git a/drivers/net/wireless/ath/ath11k/ahb.c b/drivers/net/wireless/ath/ath11k/ahb.c index 50809cc1dad4..8dfe9b40c126 100644 --- a/drivers/net/wireless/ath/ath11k/ahb.c +++ b/drivers/net/wireless/ath/ath11k/ahb.c @@ -9,8 +9,8 @@ #include #include #include +#include #include -#include #include #include "ahb.h" #include "debug.h" @@ -919,16 +919,10 @@ static int ath11k_ahb_setup_msa_resources(struct ath11k_base *ab) { struct ath11k_ahb *ab_ahb = ath11k_ahb_priv(ab); struct device *dev = ab->dev; - struct device_node *node; struct resource r; int ret; - node = of_parse_phandle(dev->of_node, "memory-region", 0); - if (!node) - return -ENOENT; - - ret = of_address_to_resource(node, 0, &r); - of_node_put(node); + ret = of_reserved_mem_region_to_resource(dev->of_node, 0, &r); if (ret) { dev_err(dev, "failed to resolve msa fixed region\n"); return ret; @@ -937,12 +931,7 @@ static int ath11k_ahb_setup_msa_resources(struct ath11k_base *ab) ab_ahb->fw.msa_paddr = r.start; ab_ahb->fw.msa_size = resource_size(&r); - node = of_parse_phandle(dev->of_node, "memory-region", 1); - if (!node) - return -ENOENT; - - ret = of_address_to_resource(node, 0, &r); - of_node_put(node); + ret = of_reserved_mem_region_to_resource(dev->of_node, 1, &r); if (ret) { dev_err(dev, "failed to resolve ce fixed region\n"); return ret; diff --git a/drivers/net/wireless/ath/ath11k/qmi.c b/drivers/net/wireless/ath/ath11k/qmi.c index 378ac96b861b..2b547b26ed20 100644 --- a/drivers/net/wireless/ath/ath11k/qmi.c +++ b/drivers/net/wireless/ath/ath11k/qmi.c @@ -13,7 +13,7 @@ #include "debug.h" #include "hif.h" #include -#include +#include #include #include #include @@ -2040,23 +2040,14 @@ static int ath11k_qmi_alloc_target_mem_chunk(struct ath11k_base *ab) static int ath11k_qmi_assign_target_mem_chunk(struct ath11k_base *ab) { struct device *dev = ab->dev; - struct device_node *hremote_node = NULL; - struct resource res; + struct resource res = {}; u32 host_ddr_sz; int i, idx, ret; for (i = 0, idx = 0; i < ab->qmi.mem_seg_count; i++) { switch (ab->qmi.target_mem[i].type) { case HOST_DDR_REGION_TYPE: - hremote_node = of_parse_phandle(dev->of_node, "memory-region", 0); - if (!hremote_node) { - ath11k_dbg(ab, ATH11K_DBG_QMI, - "fail to get hremote_node\n"); - return -ENODEV; - } - - ret = of_address_to_resource(hremote_node, 0, &res); - of_node_put(hremote_node); + ret = of_reserved_mem_region_to_resource(dev->of_node, 0, &res); if (ret) { ath11k_dbg(ab, ATH11K_DBG_QMI, "fail to get reg from hremote\n"); @@ -2095,7 +2086,7 @@ static int ath11k_qmi_assign_target_mem_chunk(struct ath11k_base *ab) } if (ath11k_core_coldboot_cal_support(ab)) { - if (hremote_node) { + if (resource_size(&res)) { ab->qmi.target_mem[idx].paddr = res.start + host_ddr_sz; ab->qmi.target_mem[idx].iaddr = From 3fd2ef2ae2b5c955584a3bee8e83ae7d7a98f782 Mon Sep 17 00:00:00 2001 From: Matvey Kovalev Date: Wed, 17 Sep 2025 22:20:01 +0300 Subject: [PATCH 122/167] wifi: ath11k: fix NULL dereference in ath11k_qmi_m3_load() If ab->fw.m3_data points to data, then fw pointer remains null. Further, if m3_mem is not allocated, then fw is dereferenced to be passed to ath11k_err function. Replace fw->size by m3_len. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 7db88b962f06 ("wifi: ath11k: add firmware-2.bin support") Cc: stable@vger.kernel.org Signed-off-by: Matvey Kovalev Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250917192020.1340-1-matvey.kovalev@ispras.ru Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/qmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath11k/qmi.c b/drivers/net/wireless/ath/ath11k/qmi.c index 2b547b26ed20..aea56c38bf8f 100644 --- a/drivers/net/wireless/ath/ath11k/qmi.c +++ b/drivers/net/wireless/ath/ath11k/qmi.c @@ -2548,7 +2548,7 @@ static int ath11k_qmi_m3_load(struct ath11k_base *ab) GFP_KERNEL); if (!m3_mem->vaddr) { ath11k_err(ab, "failed to allocate memory for M3 with size %zu\n", - fw->size); + m3_len); ret = -ENOMEM; goto out; } From 51a73f1b2e56b0324b4a3bb8cebc4221b5be4c7a Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 11 Aug 2025 17:26:45 +0800 Subject: [PATCH 123/167] wifi: ath10k: avoid unnecessary wait for service ready message Commit e57b7d62a1b2 ("wifi: ath10k: poll service ready message before failing") works around the failure in waiting for the service ready message by active polling. Note the polling is triggered after initial wait timeout, which means that the wait-till-timeout can not be avoided even the message is ready. A possible fix is to do polling once before wait as well, however this can not handle the race that the message arrives right after polling. So the solution is to do periodic polling until timeout. Tested-on: QCA6174 hw3.2 PCI WLAN.RM.4.4.1-00309-QCARMSWPZ-1 Fixes: e57b7d62a1b2 ("wifi: ath10k: poll service ready message before failing") Reported-by: Paul Menzel Closes: https://lore.kernel.org/all/97a15967-5518-4731-a8ff-d43ff7f437b0@molgen.mpg.de Signed-off-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250811-ath10k-avoid-unnecessary-wait-v1-1-db2deb87c39b@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath10k/wmi.c | 39 +++++++++++++-------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/drivers/net/wireless/ath/ath10k/wmi.c b/drivers/net/wireless/ath/ath10k/wmi.c index cb8ae751eb31..e595b0979a56 100644 --- a/drivers/net/wireless/ath/ath10k/wmi.c +++ b/drivers/net/wireless/ath/ath10k/wmi.c @@ -1764,33 +1764,32 @@ void ath10k_wmi_put_wmi_channel(struct ath10k *ar, struct wmi_channel *ch, int ath10k_wmi_wait_for_service_ready(struct ath10k *ar) { + unsigned long timeout = jiffies + WMI_SERVICE_READY_TIMEOUT_HZ; unsigned long time_left, i; - time_left = wait_for_completion_timeout(&ar->wmi.service_ready, - WMI_SERVICE_READY_TIMEOUT_HZ); - if (!time_left) { - /* Sometimes the PCI HIF doesn't receive interrupt - * for the service ready message even if the buffer - * was completed. PCIe sniffer shows that it's - * because the corresponding CE ring doesn't fires - * it. Workaround here by polling CE rings once. - */ - ath10k_warn(ar, "failed to receive service ready completion, polling..\n"); - + /* Sometimes the PCI HIF doesn't receive interrupt + * for the service ready message even if the buffer + * was completed. PCIe sniffer shows that it's + * because the corresponding CE ring doesn't fires + * it. Workaround here by polling CE rings. Since + * the message could arrive at any time, continue + * polling until timeout. + */ + do { for (i = 0; i < CE_COUNT; i++) ath10k_hif_send_complete_check(ar, i, 1); + /* The 100 ms granularity is a tradeoff considering scheduler + * overhead and response latency + */ time_left = wait_for_completion_timeout(&ar->wmi.service_ready, - WMI_SERVICE_READY_TIMEOUT_HZ); - if (!time_left) { - ath10k_warn(ar, "polling timed out\n"); - return -ETIMEDOUT; - } + msecs_to_jiffies(100)); + if (time_left) + return 0; + } while (time_before(jiffies, timeout)); - ath10k_warn(ar, "service ready completion received, continuing normally\n"); - } - - return 0; + ath10k_warn(ar, "failed to receive service ready completion\n"); + return -ETIMEDOUT; } int ath10k_wmi_wait_for_unified_ready(struct ath10k *ar) From 487e8a8c3421df0af3707e54c7e069f1d89cbda7 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Tue, 2 Sep 2025 16:32:25 +0200 Subject: [PATCH 124/167] wifi: ath10k: Fix connection after GTK rekeying It appears that not all hardware/firmware implementations support group key deletion correctly, which can lead to connection hangs and deauthentication following GTK rekeying (delete and install). To avoid this issue, instead of attempting to delete the key using the special WMI_CIPHER_NONE value, we now replace the key with an invalid (random) value. This behavior has been observed with WCN39xx chipsets. Tested-on: WCN3990 hw1.0 WLAN.HL.3.3.7.c2-00931-QCAHLSWMTPLZ-1 Reported-by: Alexey Klimov Closes: https://lore.kernel.org/all/DAWJQ2NIKY28.1XOG35E4A682G@linaro.org Signed-off-by: Loic Poulain Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Tested-by: Alexey Klimov # QRB2210 RB1 Link: https://patch.msgid.link/20250902143225.837487-1-loic.poulain@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath10k/mac.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath10k/mac.c b/drivers/net/wireless/ath/ath10k/mac.c index 24dd794e31ea..154ac7a70982 100644 --- a/drivers/net/wireless/ath/ath10k/mac.c +++ b/drivers/net/wireless/ath/ath10k/mac.c @@ -16,6 +16,7 @@ #include #include #include +#include #include "hif.h" #include "core.h" @@ -290,8 +291,15 @@ static int ath10k_send_key(struct ath10k_vif *arvif, key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; if (cmd == DISABLE_KEY) { - arg.key_cipher = ar->wmi_key_cipher[WMI_CIPHER_NONE]; - arg.key_data = NULL; + if (flags & WMI_KEY_GROUP) { + /* Not all hardware handles group-key deletion operation + * correctly. Replace the key with a junk value to invalidate it. + */ + get_random_bytes(key->key, key->keylen); + } else { + arg.key_cipher = ar->wmi_key_cipher[WMI_CIPHER_NONE]; + arg.key_data = NULL; + } } return ath10k_wmi_vdev_install_key(arvif->ar, &arg); From 6af5bc381b36282bb90c649c1edcc3396a6cba99 Mon Sep 17 00:00:00 2001 From: Lingbo Kong Date: Tue, 12 Aug 2025 11:00:36 +0800 Subject: [PATCH 125/167] wifi: ath12k: report station mode per-chain signal strength MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, command “iw wlan0 station dump” does not show per-chain signal strength. This is because ath12k does not handle the num_per_chain_rssi and rssi_avg_beacon reported by firmware to ath12k. To address this, update ath12k to send WMI_REQUEST_STATS_CMDID with the flag WMI_REQUEST_RSSI_PER_CHAIN_STAT to the firmware. Then, add logic to handle num_per_chain_rssi and rssi_avg_beacon in the ath12k_wmi_tlv_fw_stats_parse(), and assign the resulting per-chain signal strength to the chain_signal of struct station_info. After that, "iw dev xxx station dump" shows the correct per-chain signal strength. Such as: Station AA:BB:CC:DD:EE:FF (on wlan0) inactive time: 212 ms rx bytes: 10398 rx packets: 64 tx bytes: 4362 tx packets: 33 tx retries: 49 tx failed: 0 beacon loss: 0 beacon rx: 14 rx drop misc: 16 signal: -45 [-51, -46] dBm beacon signal avg: -44 dBm Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0-03427-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.15378.4 Closes: https://bugzilla.kernel.org/show_bug.cgi?id=219751 Signed-off-by: Lingbo Kong Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250812030044.688-1-quic_lingbok@quicinc.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.h | 4 ++ drivers/net/wireless/ath/ath12k/mac.c | 27 +++++++ drivers/net/wireless/ath/ath12k/wmi.c | 98 ++++++++++++++++++++++++++ drivers/net/wireless/ath/ath12k/wmi.h | 18 ++++- 4 files changed, 144 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index da7e99f2ca0b..3d1956966a48 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -72,6 +72,9 @@ #define ATH12K_MAX_MLO_PEERS 256 #define ATH12K_MLO_PEER_ID_INVALID 0xFFFF +#define ATH12K_INVALID_RSSI_FULL -1 +#define ATH12K_INVALID_RSSI_EMPTY -128 + enum ath12k_bdf_search { ATH12K_BDF_SEARCH_DEFAULT, ATH12K_BDF_SEARCH_BUS_AND_BOARD, @@ -560,6 +563,7 @@ struct ath12k_link_sta { u32 bw_prev; u32 peer_nss; s8 rssi_beacon; + s8 chain_signal[IEEE80211_MAX_CHAINS]; /* For now the assoc link will be considered primary */ bool is_assoc_link; diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 7a43f2e8f905..222513cef154 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -12650,6 +12650,27 @@ static int ath12k_mac_op_get_survey(struct ieee80211_hw *hw, int idx, return 0; } +static void ath12k_mac_put_chain_rssi(struct station_info *sinfo, + struct ath12k_link_sta *arsta) +{ + s8 rssi; + int i; + + for (i = 0; i < ARRAY_SIZE(sinfo->chain_signal); i++) { + sinfo->chains &= ~BIT(i); + rssi = arsta->chain_signal[i]; + + if (rssi != ATH12K_DEFAULT_NOISE_FLOOR && + rssi != ATH12K_INVALID_RSSI_FULL && + rssi != ATH12K_INVALID_RSSI_EMPTY && + rssi != 0) { + sinfo->chain_signal[i] = rssi; + sinfo->chains |= BIT(i); + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL); + } + } +} + static void ath12k_mac_op_sta_statistics(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta, @@ -12707,6 +12728,12 @@ static void ath12k_mac_op_sta_statistics(struct ieee80211_hw *hw, !(ath12k_mac_get_fw_stats(ar, ¶ms))) signal = arsta->rssi_beacon; + params.stats_id = WMI_REQUEST_RSSI_PER_CHAIN_STAT; + if (!(sinfo->filled & BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL)) && + ahsta->ahvif->vdev_type == WMI_VDEV_TYPE_STA && + !(ath12k_mac_get_fw_stats(ar, ¶ms))) + ath12k_mac_put_chain_rssi(sinfo, arsta); + spin_lock_bh(&ar->data_lock); noise_floor = ath12k_pdev_get_noise_floor(ar); spin_unlock_bh(&ar->data_lock); diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index 29dadedefdd2..b20b0335e24a 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -30,6 +30,9 @@ struct ath12k_wmi_svc_ready_parse { struct wmi_tlv_fw_stats_parse { const struct wmi_stats_event *ev; struct ath12k_fw_stats *stats; + const struct wmi_per_chain_rssi_stat_params *rssi; + int rssi_num; + bool chain_rssi_done; }; struct ath12k_wmi_dma_ring_caps_parse { @@ -185,6 +188,8 @@ static const struct ath12k_wmi_tlv_policy ath12k_wmi_tlv_policies[] = { .min_len = sizeof(struct wmi_p2p_noa_event) }, [WMI_TAG_11D_NEW_COUNTRY_EVENT] = { .min_len = sizeof(struct wmi_11d_new_cc_event) }, + [WMI_TAG_PER_CHAIN_RSSI_STATS] = { + .min_len = sizeof(struct wmi_per_chain_rssi_stat_params) }, }; __le32 ath12k_wmi_tlv_hdr(u32 cmd, u32 len) @@ -8219,6 +8224,77 @@ exit: return ret; } +static int ath12k_wmi_tlv_rssi_chain_parse(struct ath12k_base *ab, + u16 tag, u16 len, + const void *ptr, void *data) +{ + const struct wmi_rssi_stat_params *stats_rssi = ptr; + struct wmi_tlv_fw_stats_parse *parse = data; + const struct wmi_stats_event *ev = parse->ev; + struct ath12k_fw_stats *stats = parse->stats; + struct ath12k_link_vif *arvif; + struct ath12k_link_sta *arsta; + struct ieee80211_sta *sta; + struct ath12k_sta *ahsta; + struct ath12k *ar; + int vdev_id; + int j; + + if (!ev) { + ath12k_warn(ab, "failed to fetch update stats ev"); + return -EPROTO; + } + + if (tag != WMI_TAG_RSSI_STATS) + return -EPROTO; + + if (!stats) + return -EINVAL; + + stats->pdev_id = le32_to_cpu(ev->pdev_id); + vdev_id = le32_to_cpu(stats_rssi->vdev_id); + guard(rcu)(); + ar = ath12k_mac_get_ar_by_pdev_id(ab, stats->pdev_id); + if (!ar) { + ath12k_warn(ab, "invalid pdev id %d in rssi chain parse\n", + stats->pdev_id); + return -EPROTO; + } + + arvif = ath12k_mac_get_arvif(ar, vdev_id); + if (!arvif) { + ath12k_warn(ab, "not found vif for vdev id %d\n", vdev_id); + return -EPROTO; + } + + ath12k_dbg(ab, ATH12K_DBG_WMI, + "stats bssid %pM vif %p\n", + arvif->bssid, arvif->ahvif->vif); + + sta = ieee80211_find_sta_by_ifaddr(ath12k_ar_to_hw(ar), + arvif->bssid, + NULL); + if (!sta) { + ath12k_dbg(ab, ATH12K_DBG_WMI, + "not found station of bssid %pM for rssi chain\n", + arvif->bssid); + return -EPROTO; + } + + ahsta = ath12k_sta_to_ahsta(sta); + arsta = &ahsta->deflink; + + BUILD_BUG_ON(ARRAY_SIZE(arsta->chain_signal) > + ARRAY_SIZE(stats_rssi->rssi_avg_beacon)); + + for (j = 0; j < ARRAY_SIZE(arsta->chain_signal); j++) + arsta->chain_signal[j] = le32_to_cpu(stats_rssi->rssi_avg_beacon[j]); + + stats->stats_id = WMI_REQUEST_RSSI_PER_CHAIN_STAT; + + return 0; +} + static int ath12k_wmi_tlv_fw_stats_parse(struct ath12k_base *ab, u16 tag, u16 len, const void *ptr, void *data) @@ -8233,6 +8309,22 @@ static int ath12k_wmi_tlv_fw_stats_parse(struct ath12k_base *ab, case WMI_TAG_ARRAY_BYTE: ret = ath12k_wmi_tlv_fw_stats_data_parse(ab, parse, ptr, len); break; + case WMI_TAG_PER_CHAIN_RSSI_STATS: + parse->rssi = ptr; + if (le32_to_cpu(parse->ev->stats_id) & WMI_REQUEST_RSSI_PER_CHAIN_STAT) + parse->rssi_num = le32_to_cpu(parse->rssi->num_per_chain_rssi); + break; + case WMI_TAG_ARRAY_STRUCT: + if (parse->rssi_num && !parse->chain_rssi_done) { + ret = ath12k_wmi_tlv_iter(ab, ptr, len, + ath12k_wmi_tlv_rssi_chain_parse, + parse); + if (ret) + return ret; + + parse->chain_rssi_done = true; + } + break; default: break; } @@ -8346,6 +8438,12 @@ static void ath12k_update_stats_event(struct ath12k_base *ab, struct sk_buff *sk goto complete; } + /* Handle WMI_REQUEST_RSSI_PER_CHAIN_STAT status update */ + if (stats.stats_id == WMI_REQUEST_RSSI_PER_CHAIN_STAT) { + complete(&ar->fw_stats_done); + goto complete; + } + /* Handle WMI_REQUEST_VDEV_STAT and WMI_REQUEST_BCN_STAT updates. */ ath12k_wmi_fw_stats_process(ar, &stats); diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index f3b0a6f57ec2..4ae9a58f944a 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -5875,9 +5875,10 @@ struct wmi_stats_event { } __packed; enum wmi_stats_id { - WMI_REQUEST_PDEV_STAT = BIT(2), - WMI_REQUEST_VDEV_STAT = BIT(3), - WMI_REQUEST_BCN_STAT = BIT(11), + WMI_REQUEST_PDEV_STAT = BIT(2), + WMI_REQUEST_VDEV_STAT = BIT(3), + WMI_REQUEST_RSSI_PER_CHAIN_STAT = BIT(8), + WMI_REQUEST_BCN_STAT = BIT(11), }; struct wmi_request_stats_cmd { @@ -5888,6 +5889,17 @@ struct wmi_request_stats_cmd { __le32 pdev_id; } __packed; +struct wmi_rssi_stat_params { + __le32 vdev_id; + __le32 rssi_avg_beacon[WMI_MAX_CHAINS]; + __le32 rssi_avg_data[WMI_MAX_CHAINS]; + struct ath12k_wmi_mac_addr_params peer_macaddr; +} __packed; + +struct wmi_per_chain_rssi_stat_params { + __le32 num_per_chain_rssi; +} __packed; + #define WLAN_MAX_AC 4 #define MAX_TX_RATE_VALUES 10 From 59a2ef69ec1d0a754f9a229adee64c229f70ed36 Mon Sep 17 00:00:00 2001 From: Maharaja Kennadyrajan Date: Tue, 12 Aug 2025 16:47:06 +0530 Subject: [PATCH 126/167] wifi: ath12k: enhance the WMI_PEER_STA_KICKOUT event with reasons and RSSI reporting Enhance the WMI_PEER_STA_KICKOUT event by adding support for reporting the kickout reason and RSSI value. The reason code will be used in the following patches when the beacon miss handling is added. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ-1 Signed-off-by: Maharaja Kennadyrajan Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250812111708.3686-2-maharaja.kennadyrajan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 7 +++++-- drivers/net/wireless/ath/ath12k/wmi.h | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index b20b0335e24a..95819ca97212 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -6467,6 +6467,8 @@ static int ath12k_pull_peer_sta_kickout_ev(struct ath12k_base *ab, struct sk_buf } arg->mac_addr = ev->peer_macaddr.addr; + arg->reason = le32_to_cpu(ev->reason); + arg->rssi = le32_to_cpu(ev->rssi); kfree(tb); return 0; @@ -7339,8 +7341,9 @@ static void ath12k_peer_sta_kickout_event(struct ath12k_base *ab, struct sk_buff goto exit; } - ath12k_dbg(ab, ATH12K_DBG_WMI, "peer sta kickout event %pM", - arg.mac_addr); + ath12k_dbg(ab, ATH12K_DBG_WMI, + "peer sta kickout event %pM reason: %d rssi: %d\n", + arg.mac_addr, arg.reason, arg.rssi); ieee80211_report_low_ack(sta, 10); diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index 4ae9a58f944a..a8c3190e8ad9 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -4548,12 +4548,27 @@ struct wmi_scan_event { __le32 tsf_timestamp; } __packed; +enum wmi_peer_sta_kickout_reason { + WMI_PEER_STA_KICKOUT_REASON_UNSPECIFIED = 0, + WMI_PEER_STA_KICKOUT_REASON_XRETRY = 1, + WMI_PEER_STA_KICKOUT_REASON_INACTIVITY = 2, + WMI_PEER_STA_KICKOUT_REASON_IBSS_DISCONNECT = 3, + WMI_PEER_STA_KICKOUT_REASON_TDLS_DISCONNECT = 4, + WMI_PEER_STA_KICKOUT_REASON_SA_QUERY_TIMEOUT = 5, + WMI_PEER_STA_KICKOUT_REASON_ROAMING_EVENT = 6, + WMI_PEER_STA_KICKOUT_REASON_PMF_ERROR = 7, +}; + struct wmi_peer_sta_kickout_arg { const u8 *mac_addr; + enum wmi_peer_sta_kickout_reason reason; + u32 rssi; }; struct wmi_peer_sta_kickout_event { struct ath12k_wmi_mac_addr_params peer_macaddr; + __le32 reason; + __le32 rssi; } __packed; #define WMI_ROAM_REASON_MASK GENMASK(3, 0) From 9891fbd9d8ec1710215b3c1ce1a5e9b3f33b5c1f Mon Sep 17 00:00:00 2001 From: Arulanbu Balusamy Date: Tue, 12 Aug 2025 16:47:07 +0530 Subject: [PATCH 127/167] wifi: ath12k: Add support to handle reason inactivity STA kickout event for QCN9274/IPQ5332 Currently, when the non-AP STA connected to the AP STA, and the AP STA goes down or becomes inactive without indication, firmware detects the beacon miss and sends the WMI event WMI_PEER_STA_KICKOUT_EVENTID with reason as INACTIVITY. The host driver handles this event as low ACK and reports it to the mac80211 driver. However, the expectation is that non-AP STA should be disconnected from AP STA instantly once it receives the STA kickout event with reason of inactivity. Trigger a disconnect from AP STA through beacon miss handling upon receiving non-AP STA peer kickout event with reason code inactivity. Replace the helper function ath12k_mac_get_ar_by_vdev_id() with ath12k_mac_get_arvif_by_vdev_id() due to the following reasons. 1. Check the station VIF type for handling the beacon miss. 2. Retrieve the proper ar from the arvif by checking the vdev_id with vdev_map and link_map lookup which is needed for the MLO case in the following patch. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ-1 Co-developed-by: Ramya Gnanasekar Signed-off-by: Ramya Gnanasekar Signed-off-by: Arulanbu Balusamy Co-developed-by: Maharaja Kennadyrajan Signed-off-by: Maharaja Kennadyrajan Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250812111708.3686-3-maharaja.kennadyrajan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index 95819ca97212..918d5b505179 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -7305,6 +7305,7 @@ static void ath12k_scan_event(struct ath12k_base *ab, struct sk_buff *skb) static void ath12k_peer_sta_kickout_event(struct ath12k_base *ab, struct sk_buff *skb) { struct wmi_peer_sta_kickout_arg arg = {}; + struct ath12k_link_vif *arvif; struct ieee80211_sta *sta; struct ath12k_peer *peer; struct ath12k *ar; @@ -7326,13 +7327,15 @@ static void ath12k_peer_sta_kickout_event(struct ath12k_base *ab, struct sk_buff goto exit; } - ar = ath12k_mac_get_ar_by_vdev_id(ab, peer->vdev_id); - if (!ar) { + arvif = ath12k_mac_get_arvif_by_vdev_id(ab, peer->vdev_id); + if (!arvif) { ath12k_warn(ab, "invalid vdev id in peer sta kickout ev %d", peer->vdev_id); goto exit; } + ar = arvif->ar; + sta = ieee80211_find_sta_by_ifaddr(ath12k_ar_to_hw(ar), arg.mac_addr, NULL); if (!sta) { @@ -7345,7 +7348,16 @@ static void ath12k_peer_sta_kickout_event(struct ath12k_base *ab, struct sk_buff "peer sta kickout event %pM reason: %d rssi: %d\n", arg.mac_addr, arg.reason, arg.rssi); - ieee80211_report_low_ack(sta, 10); + switch (arg.reason) { + case WMI_PEER_STA_KICKOUT_REASON_INACTIVITY: + if (arvif->ahvif->vif->type == NL80211_IFTYPE_STATION) { + ath12k_mac_handle_beacon_miss(ar, peer->vdev_id); + break; + } + fallthrough; + default: + ieee80211_report_low_ack(sta, 10); + } exit: spin_unlock_bh(&ab->base_lock); From dcdb05a43df9d2e40116a1ddd1460846bd98a6e0 Mon Sep 17 00:00:00 2001 From: Maharaja Kennadyrajan Date: Tue, 12 Aug 2025 16:47:08 +0530 Subject: [PATCH 128/167] wifi: ath12k: Extend beacon miss handling for MLO non-AP STA Currently, ath12k_mac_handle_beacon_miss() does not handle the beacon miss for the MLO case. In MLO scenarios, the host fails to process the beacon miss because the vdev_id comparison in ath12k_mac_handle_beacon_miss_iter() does not match. This mismatch occurs since arvif always points to ahvif->deflink, which may not correspond to the actual vdev_id associated with the event. Fix this by retrieving arvif from vdev_id instead of ahvif->deflink which will work for both MLO and Non-MLO case. Also refactor the ath12k_mac_handle_beacon_miss(), by passing arvif directly instead of vdev_id and remove ath12k_mac_handle_beacon_miss_iter() which is no longer needed. ath12k_mac_handle_beacon_miss() is called from ath12k_roam_event() for WCN chipsets and ath12k_peer_sta_kickout_event() for QCN chipsets. So, refactor the ath12k_roam_event() to pass arvif instead vdev_id to the ath12k_mac_handle_beacon_miss() function to align with the ath12k_peer_sta_kickout_event() and change the rcu_read_lock() to guard(rcu)() in the same function ath12k_roam_event(). Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00284-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1 Co-developed-by: Aditya Kumar Singh Signed-off-by: Aditya Kumar Singh Signed-off-by: Maharaja Kennadyrajan Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250812111708.3686-4-maharaja.kennadyrajan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 24 ++++------------- drivers/net/wireless/ath/ath12k/mac.h | 3 ++- drivers/net/wireless/ath/ath12k/wmi.c | 37 ++++++++++++++++++--------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 222513cef154..1d7b60aa5cb0 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -1822,22 +1822,16 @@ void ath12k_mac_handle_beacon(struct ath12k *ar, struct sk_buff *skb) skb); } -static void ath12k_mac_handle_beacon_miss_iter(void *data, u8 *mac, - struct ieee80211_vif *vif) +void ath12k_mac_handle_beacon_miss(struct ath12k *ar, + struct ath12k_link_vif *arvif) { - u32 *vdev_id = data; - struct ath12k_vif *ahvif = ath12k_vif_to_ahvif(vif); - struct ath12k_link_vif *arvif = &ahvif->deflink; - struct ieee80211_hw *hw; + struct ieee80211_hw *hw = ath12k_ar_to_hw(ar); + struct ieee80211_vif *vif = ath12k_ahvif_to_vif(arvif->ahvif); - if (!arvif->is_created || arvif->vdev_id != *vdev_id) - return; - - if (!arvif->is_up) + if (!(arvif->is_created && arvif->is_up)) return; ieee80211_beacon_loss(vif); - hw = ath12k_ar_to_hw(arvif->ar); /* Firmware doesn't report beacon loss events repeatedly. If AP probe * (done by mac80211) succeeds but beacons do not resume then it @@ -1848,14 +1842,6 @@ static void ath12k_mac_handle_beacon_miss_iter(void *data, u8 *mac, ATH12K_CONNECTION_LOSS_HZ); } -void ath12k_mac_handle_beacon_miss(struct ath12k *ar, u32 vdev_id) -{ - ieee80211_iterate_active_interfaces_atomic(ath12k_ar_to_hw(ar), - IEEE80211_IFACE_ITER_NORMAL, - ath12k_mac_handle_beacon_miss_iter, - &vdev_id); -} - static void ath12k_mac_vif_sta_connection_loss_work(struct work_struct *work) { struct ath12k_link_vif *arvif = container_of(work, struct ath12k_link_vif, diff --git a/drivers/net/wireless/ath/ath12k/mac.h b/drivers/net/wireless/ath/ath12k/mac.h index 18c79d4002cb..c05af40bd7a2 100644 --- a/drivers/net/wireless/ath/ath12k/mac.h +++ b/drivers/net/wireless/ath/ath12k/mac.h @@ -168,7 +168,8 @@ int ath12k_mac_rfkill_enable_radio(struct ath12k *ar, bool enable); int ath12k_mac_rfkill_config(struct ath12k *ar); int ath12k_mac_wait_tx_complete(struct ath12k *ar); void ath12k_mac_handle_beacon(struct ath12k *ar, struct sk_buff *skb); -void ath12k_mac_handle_beacon_miss(struct ath12k *ar, u32 vdev_id); +void ath12k_mac_handle_beacon_miss(struct ath12k *ar, + struct ath12k_link_vif *arvif); int ath12k_mac_vif_set_keepalive(struct ath12k_link_vif *arvif, enum wmi_sta_keepalive_method method, u32 interval); diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index 918d5b505179..ff6b3d4ea820 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -7308,6 +7308,7 @@ static void ath12k_peer_sta_kickout_event(struct ath12k_base *ab, struct sk_buff struct ath12k_link_vif *arvif; struct ieee80211_sta *sta; struct ath12k_peer *peer; + unsigned int link_id; struct ath12k *ar; if (ath12k_pull_peer_sta_kickout_ev(ab, skb, &arg) != 0) { @@ -7336,11 +7337,23 @@ static void ath12k_peer_sta_kickout_event(struct ath12k_base *ab, struct sk_buff ar = arvif->ar; - sta = ieee80211_find_sta_by_ifaddr(ath12k_ar_to_hw(ar), - arg.mac_addr, NULL); + if (peer->mlo) { + sta = ieee80211_find_sta_by_link_addrs(ath12k_ar_to_hw(ar), + arg.mac_addr, + NULL, &link_id); + if (peer->link_id != link_id) { + ath12k_warn(ab, + "Spurious quick kickout for MLO STA %pM with invalid link_id, peer: %d, sta: %d\n", + arg.mac_addr, peer->link_id, link_id); + goto exit; + } + } else { + sta = ieee80211_find_sta_by_ifaddr(ath12k_ar_to_hw(ar), + arg.mac_addr, NULL); + } if (!sta) { - ath12k_warn(ab, "Spurious quick kickout for STA %pM\n", - arg.mac_addr); + ath12k_warn(ab, "Spurious quick kickout for %sSTA %pM\n", + peer->mlo ? "MLO " : "", arg.mac_addr); goto exit; } @@ -7351,7 +7364,7 @@ static void ath12k_peer_sta_kickout_event(struct ath12k_base *ab, struct sk_buff switch (arg.reason) { case WMI_PEER_STA_KICKOUT_REASON_INACTIVITY: if (arvif->ahvif->vif->type == NL80211_IFTYPE_STATION) { - ath12k_mac_handle_beacon_miss(ar, peer->vdev_id); + ath12k_mac_handle_beacon_miss(ar, arvif); break; } fallthrough; @@ -7366,6 +7379,7 @@ exit: static void ath12k_roam_event(struct ath12k_base *ab, struct sk_buff *skb) { + struct ath12k_link_vif *arvif; struct wmi_roam_event roam_ev = {}; struct ath12k *ar; u32 vdev_id; @@ -7384,21 +7398,22 @@ static void ath12k_roam_event(struct ath12k_base *ab, struct sk_buff *skb) "wmi roam event vdev %u reason %d rssi %d\n", vdev_id, roam_reason, roam_ev.rssi); - rcu_read_lock(); - ar = ath12k_mac_get_ar_by_vdev_id(ab, vdev_id); - if (!ar) { + guard(rcu)(); + arvif = ath12k_mac_get_arvif_by_vdev_id(ab, vdev_id); + if (!arvif) { ath12k_warn(ab, "invalid vdev id in roam ev %d", vdev_id); - rcu_read_unlock(); return; } + ar = arvif->ar; + if (roam_reason >= WMI_ROAM_REASON_MAX) ath12k_warn(ab, "ignoring unknown roam event reason %d on vdev %i\n", roam_reason, vdev_id); switch (roam_reason) { case WMI_ROAM_REASON_BEACON_MISS: - ath12k_mac_handle_beacon_miss(ar, vdev_id); + ath12k_mac_handle_beacon_miss(ar, arvif); break; case WMI_ROAM_REASON_BETTER_AP: case WMI_ROAM_REASON_LOW_RSSI: @@ -7408,8 +7423,6 @@ static void ath12k_roam_event(struct ath12k_base *ab, struct sk_buff *skb) roam_reason, vdev_id); break; } - - rcu_read_unlock(); } static void ath12k_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb) From 01b4a3061b1d4ded108e1a700b4414c00662954c Mon Sep 17 00:00:00 2001 From: Andrei Otcheretianski Date: Mon, 8 Sep 2025 14:12:55 +0300 Subject: [PATCH 129/167] wifi: nl80211: Add more configuration options for NAN commands Current NAN APIs have only basic configuration for master preference and operating bands. Add and parse additional parameters which provide more control over NAN synchronization. The newly added attributes allow to publish additional NAN attributes and vendor elements in NAN beacons, control scan and discovery beacons periodicity, enable/disable DW notifications etc. Signed-off-by: Andrei Otcheretianski tested: Miriam Rachel Korenblit Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250908140015.a4779492bf8e.I375feb919bd72358173766b9fe10010c40796b33@changeid Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 60 +++++++ include/uapi/linux/nl80211.h | 110 ++++++++++++- net/wireless/nl80211.c | 298 +++++++++++++++++++++++++++++++---- 3 files changed, 431 insertions(+), 37 deletions(-) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 4072a67c9cc9..e2f4ca500ea3 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -3912,6 +3912,38 @@ struct cfg80211_qos_map { struct cfg80211_dscp_range up[8]; }; +/** + * struct cfg80211_nan_band_config - NAN band specific configuration + * + * @chan: Pointer to the IEEE 802.11 channel structure. The channel to be used + * for NAN operations on this band. For 2.4 GHz band, this is always + * channel 6. For 5 GHz band, the channel is either 44 or 149, according + * to the regulatory constraints. If chan pointer is NULL the entire band + * configuration entry is considered invalid and should not be used. + * @rssi_close: RSSI close threshold used for NAN state transition algorithm + * as described in chapters 3.3.6 and 3.3.7 "NAN Device Role and State + * Transition" of Wi-Fi Aware Specification v4.0. If not + * specified (set to 0), default device value is used. The value should + * be greater than -60 dBm. + * @rssi_middle: RSSI middle threshold used for NAN state transition algorithm. + * as described in chapters 3.3.6 and 3.3.7 "NAN Device Role and State + * Transition" of Wi-Fi Aware Specification v4.0. If not + * specified (set to 0), default device value is used. The value should be + * greater than -75 dBm and less than rssi_close. + * @awake_dw_interval: Committed DW interval. Valid values range: 0-5. 0 + * indicates no wakeup for DW and can't be used on 2.4GHz band, otherwise + * 2^(n-1). + * @disable_scan: If true, the device will not scan this band for cluster + * merge. Disabling scan on 2.4 GHz band is not allowed. + */ +struct cfg80211_nan_band_config { + struct ieee80211_channel *chan; + s8 rssi_close; + s8 rssi_middle; + u8 awake_dw_interval; + bool disable_scan; +}; + /** * struct cfg80211_nan_conf - NAN configuration * @@ -3921,10 +3953,31 @@ struct cfg80211_qos_map { * @bands: operating bands, a bitmap of &enum nl80211_band values. * For instance, for NL80211_BAND_2GHZ, bit 0 would be set * (i.e. BIT(NL80211_BAND_2GHZ)). + * @cluster_id: cluster ID used for NAN synchronization. This is a MAC address + * that can take a value from 50-6F-9A-01-00-00 to 50-6F-9A-01-FF-FF. + * If NULL, the device will pick a random Cluster ID. + * @scan_period: period (in seconds) between NAN scans. + * @scan_dwell_time: dwell time (in milliseconds) for NAN scans. + * @discovery_beacon_interval: interval (in TUs) for discovery beacons. + * @band_cfgs: array of band specific configurations, indexed by + * &enum nl80211_band values. + * @extra_nan_attrs: pointer to additional NAN attributes. + * @extra_nan_attrs_len: length of the additional NAN attributes. + * @vendor_elems: pointer to vendor-specific elements. + * @vendor_elems_len: length of the vendor-specific elements. */ struct cfg80211_nan_conf { u8 master_pref; u8 bands; + const u8 *cluster_id; + u16 scan_period; + u16 scan_dwell_time; + u8 discovery_beacon_interval; + struct cfg80211_nan_band_config band_cfgs[NUM_NL80211_BANDS]; + const u8 *extra_nan_attrs; + u16 extra_nan_attrs_len; + const u8 *vendor_elems; + u16 vendor_elems_len; }; /** @@ -3933,10 +3986,17 @@ struct cfg80211_nan_conf { * * @CFG80211_NAN_CONF_CHANGED_PREF: master preference * @CFG80211_NAN_CONF_CHANGED_BANDS: operating bands + * @CFG80211_NAN_CONF_CHANGED_CONFIG: changed additional configuration. + * When this flag is set, it indicates that some additional attribute(s) + * (other then master_pref and bands) have been changed. In this case, + * all the unchanged attributes will be properly configured to their + * previous values. The driver doesn't need to store any + * previous configuration besides master_pref and bands. */ enum cfg80211_nan_conf_changes { CFG80211_NAN_CONF_CHANGED_PREF = BIT(0), CFG80211_NAN_CONF_CHANGED_BANDS = BIT(1), + CFG80211_NAN_CONF_CHANGED_CONFIG = BIT(2), }; /** diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index aed0b4c5d5e8..20b8202a3d58 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -1085,8 +1085,9 @@ * %NL80211_ATTR_NAN_MASTER_PREF attribute and optional * %NL80211_ATTR_BANDS attributes. If %NL80211_ATTR_BANDS is * omitted or set to 0, it means don't-care and the device will - * decide what to use. After this command NAN functions can be - * added. + * decide what to use. Additional cluster configuration may be + * optionally provided with %NL80211_ATTR_NAN_CONFIG. + * After this command NAN functions can be added. * @NL80211_CMD_STOP_NAN: Stop the NAN operation, identified by * its %NL80211_ATTR_WDEV interface. * @NL80211_CMD_ADD_NAN_FUNCTION: Add a NAN function. The function is defined @@ -1115,6 +1116,10 @@ * current configuration is not changed. If it is present but * set to zero, the configuration is changed to don't-care * (i.e. the device can decide what to do). + * Additional parameters may be provided with + * %NL80211_ATTR_NAN_CONFIG. User space should provide all previously + * configured nested attributes under %NL80211_ATTR_NAN_CONFIG, even if + * only a subset was changed. * @NL80211_CMD_NAN_MATCH: Notification sent when a match is reported. * This will contain a %NL80211_ATTR_NAN_MATCH nested attribute and * %NL80211_ATTR_COOKIE. @@ -2936,6 +2941,12 @@ enum nl80211_commands { * indicate that it wants strict checking on the BSS parameters to be * modified. * + * @NL80211_ATTR_NAN_CONFIG: Nested attribute for + * extended NAN cluster configuration. This is used with + * %NL80211_CMD_START_NAN and %NL80211_CMD_CHANGE_NAN_CONFIG. + * See &enum nl80211_nan_conf_attributes for details. + * This attribute is optional. + * * @NUM_NL80211_ATTR: total number of nl80211_attrs available * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use @@ -3498,6 +3509,7 @@ enum nl80211_attrs { NL80211_ATTR_S1G_LONG_BEACON_PERIOD, NL80211_ATTR_S1G_SHORT_BEACON, NL80211_ATTR_BSS_PARAM, + NL80211_ATTR_NAN_CONFIG, /* add attributes here, update the policy in nl80211.c */ @@ -7323,6 +7335,100 @@ enum nl80211_nan_match_attributes { NL80211_NAN_MATCH_ATTR_MAX = NUM_NL80211_NAN_MATCH_ATTR - 1 }; +/** + * enum nl80211_nan_band_conf_attributes - NAN band configuration attributes + * @__NL80211_NAN_BAND_CONF_INVALID: Invalid. + * @NL80211_NAN_BAND_CONF_BAND: Band for which the configuration is + * being set. The value is according to &enum nl80211_band (u8). + * @NL80211_NAN_BAND_CONF_FREQ: Discovery frequency. This attribute shall not + * be present on 2.4 GHZ band. On 5 GHz band its presence is optional. + * The allowed values are 5220 (channel 44) or 5745 (channel 149). + * If not present, channel 149 is used if allowed, otherwise channel 44 + * will be selected. The value is in MHz (u16). + * @NL80211_NAN_BAND_CONF_RSSI_CLOSE: RSSI close threshold used for NAN state + * transition algorithm as described in chapters 3.3.6 and 3.3.7 "NAN + * Device Role and State Transition" of Wi-Fi Aware (TM) Specification + * v4.0. If not specified, default device value is used. The value should + * be greater than -60 dBm (s8). + * @NL80211_NAN_BAND_CONF_RSSI_MIDDLE: RSSI middle threshold used for NAN state + * transition algorithm as described in chapters 3.3.6 and 3.3.7 "NAN + * Device Role and State Transition" of Wi-Fi Aware (TM) Specification + * v4.0. If not present, default device value is used. The value should be + * greater than -75 dBm and less than %NL80211_NAN_BAND_CONF_RSSI_CLOSE + * (s8). + * @NL80211_NAN_BAND_CONF_WAKE_DW: Committed DW information (values 0-5). + * Value 0 means that the device will not wake up during the + * discovery window. Values 1-5 mean that the device will wake up + * during each 2^(n - 1) discovery window, where n is the value of + * this attribute. Setting this attribute to 0 is not allowed on + * 2.4 GHz band (u8). This is an optional parameter (default is 1). + * @NL80211_NAN_BAND_CONF_DISABLE_SCAN: Optional flag attribute to disable + * scanning (for cluster merge) on the band. If set, the device will not + * scan on this band anymore. Disabling scanning on 2.4 GHz band is not + * allowed. + * @NUM_NL80211_NAN_BAND_CONF_ATTR: Internal. + * @NL80211_NAN_BAND_CONF_ATTR_MAX: Highest NAN band configuration attribute. + * + * These attributes are used to configure NAN band-specific parameters. Note, + * that both RSSI attributes should be configured (or both left unset). + */ +enum nl80211_nan_band_conf_attributes { + __NL80211_NAN_BAND_CONF_INVALID, + NL80211_NAN_BAND_CONF_BAND, + NL80211_NAN_BAND_CONF_FREQ, + NL80211_NAN_BAND_CONF_RSSI_CLOSE, + NL80211_NAN_BAND_CONF_RSSI_MIDDLE, + NL80211_NAN_BAND_CONF_WAKE_DW, + NL80211_NAN_BAND_CONF_DISABLE_SCAN, + + /* keep last */ + NUM_NL80211_NAN_BAND_CONF_ATTR, + NL80211_NAN_BAND_CONF_ATTR_MAX = NUM_NL80211_NAN_BAND_CONF_ATTR - 1, +}; + +/** + * enum nl80211_nan_conf_attributes - NAN configuration attributes + * @__NL80211_NAN_CONF_INVALID: Invalid attribute, used for validation. + * @NL80211_NAN_CONF_CLUSTER_ID: ID for the NAN cluster. This is a MAC + * address that can take values from 50-6F-9A-01-00-00 to + * 50-6F-9A-01-FF-FF. This attribute is optional. If not present, + * a random Cluster ID will be chosen. + * @NL80211_NAN_CONF_EXTRA_ATTRS: Additional NAN attributes to be + * published in the beacons. This is an optional byte array. + * @NL80211_NAN_CONF_VENDOR_ELEMS: Vendor-specific elements that will + * be published in the beacons. This is an optional byte array. + * @NL80211_NAN_CONF_BAND_CONFIGS: This is a nested array attribute, + * containing multiple entries for each supported band. Each band + * configuration consists of &enum nl80211_nan_band_conf_attributes. + * @NL80211_NAN_CONF_SCAN_PERIOD: Scan period in seconds. If not configured, + * device default is used. Zero value will disable scanning. + * This is u16 (optional). + * @NL80211_NAN_CONF_SCAN_DWELL_TIME: Scan dwell time in TUs per channel. + * Only non-zero values are valid. If not configured the device default + * value is used. This is u16 (optional) + * @NL80211_NAN_CONF_DISCOVERY_BEACON_INTERVAL: Discovery beacon interval + * in TUs. Valid range is 50-200 TUs. If not configured the device default + * value is used. This is u8 (optional) + * @NUM_NL80211_NAN_CONF_ATTR: Internal. + * @NL80211_NAN_CONF_ATTR_MAX: Highest NAN configuration attribute. + * + * These attributes are used to configure NAN-specific parameters. + */ +enum nl80211_nan_conf_attributes { + __NL80211_NAN_CONF_INVALID, + NL80211_NAN_CONF_CLUSTER_ID, + NL80211_NAN_CONF_EXTRA_ATTRS, + NL80211_NAN_CONF_VENDOR_ELEMS, + NL80211_NAN_CONF_BAND_CONFIGS, + NL80211_NAN_CONF_SCAN_PERIOD, + NL80211_NAN_CONF_SCAN_DWELL_TIME, + NL80211_NAN_CONF_DISCOVERY_BEACON_INTERVAL, + + /* keep last */ + NUM_NL80211_NAN_CONF_ATTR, + NL80211_NAN_CONF_ATTR_MAX = NUM_NL80211_NAN_CONF_ATTR - 1, +}; + /** * enum nl80211_external_auth_action - Action to perform with external * authentication request. Used by NL80211_ATTR_EXTERNAL_AUTH_ACTION. diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index b7bc7e5e81dd..04679acc8135 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -312,6 +312,26 @@ static int validate_supported_selectors(const struct nlattr *attr, return 0; } +static int validate_nan_cluster_id(const struct nlattr *attr, + struct netlink_ext_ack *extack) +{ + const u8 *data = nla_data(attr); + unsigned int len = nla_len(attr); + static const u8 cluster_id_prefix[4] = {0x50, 0x6f, 0x9a, 0x1}; + + if (len != ETH_ALEN) { + NL_SET_ERR_MSG_ATTR(extack, attr, "bad cluster id length"); + return -EINVAL; + } + + if (memcmp(data, cluster_id_prefix, sizeof(cluster_id_prefix))) { + NL_SET_ERR_MSG_ATTR(extack, attr, "invalid cluster id prefix"); + return -EINVAL; + } + + return 0; +} + /* policy for the attributes */ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR]; @@ -500,6 +520,35 @@ nl80211_s1g_short_beacon[NL80211_S1G_SHORT_BEACON_ATTR_MAX + 1] = { IEEE80211_MAX_DATA_LEN), }; +static const struct nla_policy +nl80211_nan_band_conf_policy[NL80211_NAN_BAND_CONF_ATTR_MAX + 1] = { + [NL80211_NAN_BAND_CONF_BAND] = NLA_POLICY_MAX(NLA_U8, + NUM_NL80211_BANDS - 1), + [NL80211_NAN_BAND_CONF_FREQ] = { .type = NLA_U16 }, + [NL80211_NAN_BAND_CONF_RSSI_CLOSE] = NLA_POLICY_MIN(NLA_S8, -59), + [NL80211_NAN_BAND_CONF_RSSI_MIDDLE] = NLA_POLICY_MIN(NLA_S8, -74), + [NL80211_NAN_BAND_CONF_WAKE_DW] = NLA_POLICY_MAX(NLA_U8, 5), + [NL80211_NAN_BAND_CONF_DISABLE_SCAN] = { .type = NLA_FLAG }, +}; + +static const struct nla_policy +nl80211_nan_conf_policy[NL80211_NAN_CONF_ATTR_MAX + 1] = { + [NL80211_NAN_CONF_CLUSTER_ID] = + NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_nan_cluster_id, + ETH_ALEN), + [NL80211_NAN_CONF_EXTRA_ATTRS] = { .type = NLA_BINARY, + .len = IEEE80211_MAX_DATA_LEN}, + [NL80211_NAN_CONF_VENDOR_ELEMS] = + NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_ie_attr, + IEEE80211_MAX_DATA_LEN), + [NL80211_NAN_CONF_BAND_CONFIGS] = + NLA_POLICY_NESTED_ARRAY(nl80211_nan_band_conf_policy), + [NL80211_NAN_CONF_SCAN_PERIOD] = { .type = NLA_U16 }, + [NL80211_NAN_CONF_SCAN_DWELL_TIME] = NLA_POLICY_RANGE(NLA_U16, 50, 512), + [NL80211_NAN_CONF_DISCOVERY_BEACON_INTERVAL] = + NLA_POLICY_RANGE(NLA_U8, 50, 200), +}; + static const struct netlink_range_validation nl80211_punct_bitmap_range = { .min = 0, .max = 0xffff, @@ -769,6 +818,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = { [NL80211_ATTR_MU_MIMO_FOLLOW_MAC_ADDR] = NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN), [NL80211_ATTR_NAN_MASTER_PREF] = NLA_POLICY_MIN(NLA_U8, 1), [NL80211_ATTR_BANDS] = { .type = NLA_U32 }, + [NL80211_ATTR_NAN_CONFIG] = NLA_POLICY_NESTED(nl80211_nan_conf_policy), [NL80211_ATTR_NAN_FUNC] = { .type = NLA_NESTED }, [NL80211_ATTR_FILS_KEK] = { .type = NLA_BINARY, .len = FILS_MAX_KEK_LEN }, @@ -15398,6 +15448,211 @@ static int nl80211_stop_p2p_device(struct sk_buff *skb, struct genl_info *info) return 0; } +static struct ieee80211_channel *nl80211_get_nan_channel(struct wiphy *wiphy, + int freq) +{ + struct ieee80211_channel *chan; + struct cfg80211_chan_def def; + + /* Check if the frequency is valid for NAN */ + if (freq != 5220 && freq != 5745 && freq != 2437) + return NULL; + + chan = ieee80211_get_channel(wiphy, freq); + if (!chan) + return NULL; + + cfg80211_chandef_create(&def, chan, NL80211_CHAN_NO_HT); + + /* Check if the channel is allowed */ + if (cfg80211_reg_can_beacon(wiphy, &def, NL80211_IFTYPE_NAN)) + return chan; + + return NULL; +} + +static int nl80211_parse_nan_band_config(struct wiphy *wiphy, + struct nlattr **tb, + struct cfg80211_nan_band_config *cfg, + enum nl80211_band band) +{ + if (BIT(band) & ~(u32)wiphy->nan_supported_bands) + return -EINVAL; + + if (tb[NL80211_NAN_BAND_CONF_FREQ]) { + u16 freq = nla_get_u16(tb[NL80211_NAN_BAND_CONF_FREQ]); + + if (band != NL80211_BAND_5GHZ) + return -EINVAL; + + cfg->chan = nl80211_get_nan_channel(wiphy, freq); + if (!cfg->chan) + return -EINVAL; + } + + if (tb[NL80211_NAN_BAND_CONF_RSSI_CLOSE]) { + cfg->rssi_close = + nla_get_s8(tb[NL80211_NAN_BAND_CONF_RSSI_CLOSE]); + if (!tb[NL80211_NAN_BAND_CONF_RSSI_MIDDLE]) + return -EINVAL; + } + + if (tb[NL80211_NAN_BAND_CONF_RSSI_MIDDLE]) { + cfg->rssi_middle = + nla_get_s8(tb[NL80211_NAN_BAND_CONF_RSSI_MIDDLE]); + if (!cfg->rssi_close || cfg->rssi_middle >= cfg->rssi_close) + return -EINVAL; + } + + if (tb[NL80211_NAN_BAND_CONF_WAKE_DW]) { + cfg->awake_dw_interval = + nla_get_u8(tb[NL80211_NAN_BAND_CONF_WAKE_DW]); + + if (band == NL80211_BAND_2GHZ && cfg->awake_dw_interval == 0) + return -EINVAL; + } + + cfg->disable_scan = + nla_get_flag(tb[NL80211_NAN_BAND_CONF_DISABLE_SCAN]); + return 0; +} + +static int nl80211_parse_nan_conf(struct wiphy *wiphy, + struct genl_info *info, + struct cfg80211_nan_conf *conf, + u32 *changed_flags) +{ + struct nlattr *attrs[NL80211_NAN_CONF_ATTR_MAX + 1]; + int err, rem; + u32 changed = 0; + struct nlattr *band_config; + + if (info->attrs[NL80211_ATTR_NAN_MASTER_PREF]) { + conf->master_pref = + nla_get_u8(info->attrs[NL80211_ATTR_NAN_MASTER_PREF]); + + changed |= CFG80211_NAN_CONF_CHANGED_PREF; + } + + if (info->attrs[NL80211_ATTR_BANDS]) { + u32 bands = nla_get_u32(info->attrs[NL80211_ATTR_BANDS]); + + if (bands & ~(u32)wiphy->nan_supported_bands) + return -EOPNOTSUPP; + + if (bands && !(bands & BIT(NL80211_BAND_2GHZ))) + return -EINVAL; + + conf->bands = bands; + changed |= CFG80211_NAN_CONF_CHANGED_BANDS; + } + + conf->band_cfgs[NL80211_BAND_2GHZ].awake_dw_interval = 1; + if (conf->bands & BIT(NL80211_BAND_5GHZ) || !conf->bands) + conf->band_cfgs[NL80211_BAND_5GHZ].awake_dw_interval = 1; + + /* On 2.4 GHz band use channel 6 */ + conf->band_cfgs[NL80211_BAND_2GHZ].chan = + nl80211_get_nan_channel(wiphy, 2437); + if (!conf->band_cfgs[NL80211_BAND_2GHZ].chan) + return -EINVAL; + + if (!info->attrs[NL80211_ATTR_NAN_CONFIG]) + goto out; + + err = nla_parse_nested(attrs, NL80211_NAN_CONF_ATTR_MAX, + info->attrs[NL80211_ATTR_NAN_CONFIG], NULL, + info->extack); + if (err) + return err; + + changed |= CFG80211_NAN_CONF_CHANGED_CONFIG; + if (attrs[NL80211_NAN_CONF_CLUSTER_ID]) + conf->cluster_id = + nla_data(attrs[NL80211_NAN_CONF_CLUSTER_ID]); + + if (attrs[NL80211_NAN_CONF_EXTRA_ATTRS]) { + conf->extra_nan_attrs = + nla_data(attrs[NL80211_NAN_CONF_EXTRA_ATTRS]); + conf->extra_nan_attrs_len = + nla_len(attrs[NL80211_NAN_CONF_EXTRA_ATTRS]); + } + + if (attrs[NL80211_NAN_CONF_VENDOR_ELEMS]) { + conf->vendor_elems = + nla_data(attrs[NL80211_NAN_CONF_VENDOR_ELEMS]); + conf->vendor_elems_len = + nla_len(attrs[NL80211_NAN_CONF_VENDOR_ELEMS]); + } + + if (attrs[NL80211_NAN_CONF_BAND_CONFIGS]) { + nla_for_each_nested(band_config, + attrs[NL80211_NAN_CONF_BAND_CONFIGS], + rem) { + enum nl80211_band band; + struct cfg80211_nan_band_config *cfg; + struct nlattr *tb[NL80211_NAN_BAND_CONF_ATTR_MAX + 1]; + + err = nla_parse_nested(tb, + NL80211_NAN_BAND_CONF_ATTR_MAX, + band_config, NULL, + info->extack); + if (err) + return err; + + if (!tb[NL80211_NAN_BAND_CONF_BAND]) + return -EINVAL; + + band = nla_get_u8(tb[NL80211_NAN_BAND_CONF_BAND]); + if (conf->bands && !(conf->bands & BIT(band))) + return -EINVAL; + + cfg = &conf->band_cfgs[band]; + + err = nl80211_parse_nan_band_config(wiphy, tb, cfg, + band); + if (err) + return err; + } + } + + if (attrs[NL80211_NAN_CONF_SCAN_PERIOD]) + conf->scan_period = + nla_get_u16(attrs[NL80211_NAN_CONF_SCAN_PERIOD]); + + if (attrs[NL80211_NAN_CONF_SCAN_DWELL_TIME]) + conf->scan_dwell_time = + nla_get_u16(attrs[NL80211_NAN_CONF_SCAN_DWELL_TIME]); + + if (attrs[NL80211_NAN_CONF_DISCOVERY_BEACON_INTERVAL]) + conf->discovery_beacon_interval = + nla_get_u8(attrs[NL80211_NAN_CONF_DISCOVERY_BEACON_INTERVAL]); +out: + if (!conf->band_cfgs[NL80211_BAND_5GHZ].chan && + (!conf->bands || conf->bands & BIT(NL80211_BAND_5GHZ))) { + /* If no 5GHz channel is specified use default, if possible */ + conf->band_cfgs[NL80211_BAND_5GHZ].chan = + nl80211_get_nan_channel(wiphy, 5745); + if (!conf->band_cfgs[NL80211_BAND_5GHZ].chan) + conf->band_cfgs[NL80211_BAND_5GHZ].chan = + nl80211_get_nan_channel(wiphy, 5220); + + /* Return error if user space asked explicitly for 5 GHz */ + if (!conf->band_cfgs[NL80211_BAND_5GHZ].chan && + conf->bands & BIT(NL80211_BAND_5GHZ)) { + NL_SET_ERR_MSG_ATTR(info->extack, + info->attrs[NL80211_ATTR_BANDS], + "5 GHz band operation is not allowed"); + return -EINVAL; + } + } + + if (changed_flags) + *changed_flags = changed; + + return 0; +} + static int nl80211_start_nan(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev = info->user_ptr[0]; @@ -15414,23 +15669,13 @@ static int nl80211_start_nan(struct sk_buff *skb, struct genl_info *info) if (rfkill_blocked(rdev->wiphy.rfkill)) return -ERFKILL; + /* Master preference is mandatory for START_NAN */ if (!info->attrs[NL80211_ATTR_NAN_MASTER_PREF]) return -EINVAL; - conf.master_pref = - nla_get_u8(info->attrs[NL80211_ATTR_NAN_MASTER_PREF]); - - if (info->attrs[NL80211_ATTR_BANDS]) { - u32 bands = nla_get_u32(info->attrs[NL80211_ATTR_BANDS]); - - if (bands & ~(u32)wdev->wiphy->nan_supported_bands) - return -EOPNOTSUPP; - - if (bands && !(bands & BIT(NL80211_BAND_2GHZ))) - return -EINVAL; - - conf.bands = bands; - } + err = nl80211_parse_nan_conf(&rdev->wiphy, info, &conf, NULL); + if (err) + return err; err = rdev_start_nan(rdev, wdev, &conf); if (err) @@ -15786,6 +16031,7 @@ static int nl80211_nan_change_config(struct sk_buff *skb, struct wireless_dev *wdev = info->user_ptr[1]; struct cfg80211_nan_conf conf = {}; u32 changed = 0; + int err; if (wdev->iftype != NL80211_IFTYPE_NAN) return -EOPNOTSUPP; @@ -15793,27 +16039,9 @@ static int nl80211_nan_change_config(struct sk_buff *skb, if (!wdev_running(wdev)) return -ENOTCONN; - if (info->attrs[NL80211_ATTR_NAN_MASTER_PREF]) { - conf.master_pref = - nla_get_u8(info->attrs[NL80211_ATTR_NAN_MASTER_PREF]); - if (conf.master_pref <= 1 || conf.master_pref == 255) - return -EINVAL; - - changed |= CFG80211_NAN_CONF_CHANGED_PREF; - } - - if (info->attrs[NL80211_ATTR_BANDS]) { - u32 bands = nla_get_u32(info->attrs[NL80211_ATTR_BANDS]); - - if (bands & ~(u32)wdev->wiphy->nan_supported_bands) - return -EOPNOTSUPP; - - if (bands && !(bands & BIT(NL80211_BAND_2GHZ))) - return -EINVAL; - - conf.bands = bands; - changed |= CFG80211_NAN_CONF_CHANGED_BANDS; - } + err = nl80211_parse_nan_conf(&rdev->wiphy, info, &conf, &changed); + if (err) + return err; if (!changed) return -EINVAL; From ba9b2ceaa2558a38a5da59fd654b641610a8568e Mon Sep 17 00:00:00 2001 From: Andrei Otcheretianski Date: Mon, 8 Sep 2025 14:12:56 +0300 Subject: [PATCH 130/167] wifi: nl80211: Add NAN Discovery Window (DW) notification This notification will be used by the device to inform user space about upcoming DW. When received, user space will be able to prepare multicast Service Discovery Frames (SDFs) to be transmitted during the next DW using %NL80211_CMD_FRAME command on the NAN management interface. The device/driver will take care to transmit the frames in the correct timing. This allows to implement a synchronized Discovery Engine (DE) in user space, if the device doesn't support DE offload. Note that this notification can be sent before the actual DW starts as long as the driver/device handles the actual timing of the SDF transmission. Signed-off-by: Andrei Otcheretianski Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250908140015.0e1d15031bab.I5b1721e61b63910452b3c5cdcdc1e94cb094d4c9@changeid Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 12 ++++++++++ include/uapi/linux/nl80211.h | 16 +++++++++++++ net/wireless/nl80211.c | 45 ++++++++++++++++++++++++++++++++++++ net/wireless/trace.h | 16 +++++++++++++ 4 files changed, 89 insertions(+) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index e2f4ca500ea3..0c1311d254be 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -3959,6 +3959,8 @@ struct cfg80211_nan_band_config { * @scan_period: period (in seconds) between NAN scans. * @scan_dwell_time: dwell time (in milliseconds) for NAN scans. * @discovery_beacon_interval: interval (in TUs) for discovery beacons. + * @enable_dw_notification: flag to enable/disable discovery window + * notifications. * @band_cfgs: array of band specific configurations, indexed by * &enum nl80211_band values. * @extra_nan_attrs: pointer to additional NAN attributes. @@ -3973,6 +3975,7 @@ struct cfg80211_nan_conf { u16 scan_period; u16 scan_dwell_time; u8 discovery_beacon_interval; + bool enable_dw_notification; struct cfg80211_nan_band_config band_cfgs[NUM_NL80211_BANDS]; const u8 *extra_nan_attrs; u16 extra_nan_attrs_len; @@ -10062,6 +10065,15 @@ void cfg80211_schedule_channels_check(struct wireless_dev *wdev); */ void cfg80211_epcs_changed(struct net_device *netdev, bool enabled); +/** + * cfg80211_next_nan_dw_notif - Notify about the next NAN Discovery Window (DW) + * @wdev: Pointer to the wireless device structure + * @chan: DW channel (6, 44 or 149) + * @gfp: Memory allocation flags + */ +void cfg80211_next_nan_dw_notif(struct wireless_dev *wdev, + struct ieee80211_channel *chan, gfp_t gfp); + #ifdef CONFIG_CFG80211_DEBUGFS /** * wiphy_locked_debugfs_read - do a locked read in debugfs diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 20b8202a3d58..d674608e2635 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -1349,6 +1349,15 @@ * control EPCS configuration. Used to notify userland on the current state * of EPCS. * + * @NL80211_CMD_NAN_NEXT_DW_NOTIFICATION: This command is used to notify + * user space about the next NAN Discovery Window (DW). User space may use + * it to prepare frames to be sent in the next DW. + * %NL80211_ATTR_WIPHY_FREQ is used to indicate the frequency of the next + * DW. SDF transmission should be requested with %NL80211_CMD_FRAME and + * the device/driver shall take care of the actual transmission timing. + * This notification is only sent to the NAN interface owning socket + * (see %NL80211_ATTR_SOCKET_OWNER flag). + * * @NL80211_CMD_MAX: highest used command number * @__NL80211_CMD_AFTER_LAST: internal use */ @@ -1609,6 +1618,8 @@ enum nl80211_commands { NL80211_CMD_ASSOC_MLO_RECONF, NL80211_CMD_EPCS_CFG, + NL80211_CMD_NAN_NEXT_DW_NOTIFICATION, + /* add new commands above here */ /* used to define NL80211_CMD_MAX below */ @@ -7409,6 +7420,10 @@ enum nl80211_nan_band_conf_attributes { * @NL80211_NAN_CONF_DISCOVERY_BEACON_INTERVAL: Discovery beacon interval * in TUs. Valid range is 50-200 TUs. If not configured the device default * value is used. This is u8 (optional) + * @NL80211_NAN_CONF_NOTIFY_DW: If set, the driver will notify userspace about + * the upcoming discovery window with + * %NL80211_CMD_NAN_NEXT_DW_NOTIFICATION. + * This is a flag attribute. * @NUM_NL80211_NAN_CONF_ATTR: Internal. * @NL80211_NAN_CONF_ATTR_MAX: Highest NAN configuration attribute. * @@ -7423,6 +7438,7 @@ enum nl80211_nan_conf_attributes { NL80211_NAN_CONF_SCAN_PERIOD, NL80211_NAN_CONF_SCAN_DWELL_TIME, NL80211_NAN_CONF_DISCOVERY_BEACON_INTERVAL, + NL80211_NAN_CONF_NOTIFY_DW, /* keep last */ NUM_NL80211_NAN_CONF_ATTR, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 04679acc8135..d64145746b65 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -547,6 +547,7 @@ nl80211_nan_conf_policy[NL80211_NAN_CONF_ATTR_MAX + 1] = { [NL80211_NAN_CONF_SCAN_DWELL_TIME] = NLA_POLICY_RANGE(NLA_U16, 50, 512), [NL80211_NAN_CONF_DISCOVERY_BEACON_INTERVAL] = NLA_POLICY_RANGE(NLA_U8, 50, 200), + [NL80211_NAN_CONF_NOTIFY_DW] = { .type = NLA_FLAG }, }; static const struct netlink_range_validation nl80211_punct_bitmap_range = { @@ -15627,6 +15628,11 @@ static int nl80211_parse_nan_conf(struct wiphy *wiphy, if (attrs[NL80211_NAN_CONF_DISCOVERY_BEACON_INTERVAL]) conf->discovery_beacon_interval = nla_get_u8(attrs[NL80211_NAN_CONF_DISCOVERY_BEACON_INTERVAL]); + + if (attrs[NL80211_NAN_CONF_NOTIFY_DW]) + conf->enable_dw_notification = + nla_get_flag(attrs[NL80211_NAN_CONF_NOTIFY_DW]); + out: if (!conf->band_cfgs[NL80211_BAND_5GHZ].chan && (!conf->bands || conf->bands & BIT(NL80211_BAND_5GHZ))) { @@ -21764,6 +21770,45 @@ void cfg80211_epcs_changed(struct net_device *netdev, bool enabled) } EXPORT_SYMBOL(cfg80211_epcs_changed); +void cfg80211_next_nan_dw_notif(struct wireless_dev *wdev, + struct ieee80211_channel *chan, gfp_t gfp) +{ + struct wiphy *wiphy = wdev->wiphy; + struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); + struct sk_buff *msg; + void *hdr; + + trace_cfg80211_next_nan_dw_notif(wdev, chan); + + if (!wdev->owner_nlportid) + return; + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp); + if (!msg) + return; + + hdr = nl80211hdr_put(msg, 0, 0, 0, + NL80211_CMD_NAN_NEXT_DW_NOTIFICATION); + if (!hdr) + goto nla_put_failure; + + if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) || + nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev), + NL80211_ATTR_PAD) || + nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ, chan->center_freq)) + goto nla_put_failure; + + genlmsg_end(msg, hdr); + + genlmsg_unicast(wiphy_net(wiphy), msg, wdev->owner_nlportid); + + return; + + nla_put_failure: + nlmsg_free(msg); +} +EXPORT_SYMBOL(cfg80211_next_nan_dw_notif); + /* initialisation/exit functions */ int __init nl80211_init(void) diff --git a/net/wireless/trace.h b/net/wireless/trace.h index 9b6074155d59..ff47e9bffd4f 100644 --- a/net/wireless/trace.h +++ b/net/wireless/trace.h @@ -4166,6 +4166,22 @@ TRACE_EVENT(cfg80211_epcs_changed, WDEV_PR_ARG, __entry->enabled) ); +TRACE_EVENT(cfg80211_next_nan_dw_notif, + TP_PROTO(struct wireless_dev *wdev, + struct ieee80211_channel *chan), + TP_ARGS(wdev, chan), + TP_STRUCT__entry( + WDEV_ENTRY + CHAN_ENTRY + ), + TP_fast_assign( + WDEV_ASSIGN; + CHAN_ASSIGN(chan); + ), + TP_printk(WDEV_PR_FMT " " CHAN_PR_FMT, + WDEV_PR_ARG, CHAN_PR_ARG) +); + #endif /* !__RDEV_OPS_TRACE || TRACE_HEADER_MULTI_READ */ #undef TRACE_INCLUDE_PATH From 1ccfd8db34fb3b1852284668094d7207499c2415 Mon Sep 17 00:00:00 2001 From: Andrei Otcheretianski Date: Mon, 8 Sep 2025 14:12:57 +0300 Subject: [PATCH 131/167] wifi: cfg80211: Add cluster joined notification APIs The drivers should notify upper layers and user space when a NAN device joins a cluster. This is needed, for example, to set the correct addr3 in SDF frames. Add API to report cluster join event. Signed-off-by: Andrei Otcheretianski Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250908140015.ad27b7b6e4d9.I70b213a2a49f18d1ba2ad325e67e8eff51cc7a1f@changeid Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 14 ++++++++++++ include/uapi/linux/nl80211.h | 8 +++++++ net/wireless/nl80211.c | 41 ++++++++++++++++++++++++++++++++++++ net/wireless/trace.h | 19 +++++++++++++++++ 4 files changed, 82 insertions(+) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 0c1311d254be..1b10bd31bdd6 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -10074,6 +10074,20 @@ void cfg80211_epcs_changed(struct net_device *netdev, bool enabled); void cfg80211_next_nan_dw_notif(struct wireless_dev *wdev, struct ieee80211_channel *chan, gfp_t gfp); +/** + * cfg80211_nan_cluster_joined - Notify about NAN cluster join + * @wdev: Pointer to the wireless device structure + * @cluster_id: Cluster ID of the NAN cluster that was joined or started + * @new_cluster: Indicates if this is a new cluster or an existing one + * @gfp: Memory allocation flags + * + * This function is used to notify user space when a NAN cluster has been + * joined, providing the cluster ID and a flag whether it is a new cluster. + */ +void cfg80211_nan_cluster_joined(struct wireless_dev *wdev, + const u8 *cluster_id, bool new_cluster, + gfp_t gfp); + #ifdef CONFIG_CFG80211_DEBUGFS /** * wiphy_locked_debugfs_read - do a locked read in debugfs diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index d674608e2635..c5a7658b7297 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -1357,6 +1357,9 @@ * the device/driver shall take care of the actual transmission timing. * This notification is only sent to the NAN interface owning socket * (see %NL80211_ATTR_SOCKET_OWNER flag). + * @NL80211_CMD_NAN_CLUSTER_JOINED: This command is used to notify + * user space that the NAN new cluster has been joined. The cluster ID is + * indicated by %NL80211_ATTR_MAC. * * @NL80211_CMD_MAX: highest used command number * @__NL80211_CMD_AFTER_LAST: internal use @@ -1619,6 +1622,7 @@ enum nl80211_commands { NL80211_CMD_EPCS_CFG, NL80211_CMD_NAN_NEXT_DW_NOTIFICATION, + NL80211_CMD_NAN_CLUSTER_JOINED, /* add new commands above here */ @@ -2957,6 +2961,9 @@ enum nl80211_commands { * %NL80211_CMD_START_NAN and %NL80211_CMD_CHANGE_NAN_CONFIG. * See &enum nl80211_nan_conf_attributes for details. * This attribute is optional. + * @NL80211_ATTR_NAN_NEW_CLUSTER: Flag attribute indicating that a new + * NAN cluster has been created. This is used with + * %NL80211_CMD_NAN_CLUSTER_JOINED * * @NUM_NL80211_ATTR: total number of nl80211_attrs available * @NL80211_ATTR_MAX: highest attribute number currently defined @@ -3521,6 +3528,7 @@ enum nl80211_attrs { NL80211_ATTR_S1G_SHORT_BEACON, NL80211_ATTR_BSS_PARAM, NL80211_ATTR_NAN_CONFIG, + NL80211_ATTR_NAN_NEW_CLUSTER, /* add attributes here, update the policy in nl80211.c */ diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index d64145746b65..904a725a4f4a 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -21809,6 +21809,47 @@ void cfg80211_next_nan_dw_notif(struct wireless_dev *wdev, } EXPORT_SYMBOL(cfg80211_next_nan_dw_notif); +void cfg80211_nan_cluster_joined(struct wireless_dev *wdev, + const u8 *cluster_id, bool new_cluster, + gfp_t gfp) +{ + struct wiphy *wiphy = wdev->wiphy; + struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); + struct sk_buff *msg; + void *hdr; + + trace_cfg80211_nan_cluster_joined(wdev, cluster_id, new_cluster); + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp); + if (!msg) + return; + + hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_NAN_CLUSTER_JOINED); + if (!hdr) + goto nla_put_failure; + + if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) || + nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev), + NL80211_ATTR_PAD) || + nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, cluster_id) || + (new_cluster && nla_put_flag(msg, NL80211_ATTR_NAN_NEW_CLUSTER))) + goto nla_put_failure; + + genlmsg_end(msg, hdr); + + if (!wdev->owner_nlportid) + genlmsg_multicast_netns(&nl80211_fam, wiphy_net(wiphy), + msg, 0, NL80211_MCGRP_NAN, gfp); + else + genlmsg_unicast(wiphy_net(wiphy), msg, + wdev->owner_nlportid); + return; + + nla_put_failure: + nlmsg_free(msg); +} +EXPORT_SYMBOL(cfg80211_nan_cluster_joined); + /* initialisation/exit functions */ int __init nl80211_init(void) diff --git a/net/wireless/trace.h b/net/wireless/trace.h index ff47e9bffd4f..8a4c34112eb5 100644 --- a/net/wireless/trace.h +++ b/net/wireless/trace.h @@ -4182,6 +4182,25 @@ TRACE_EVENT(cfg80211_next_nan_dw_notif, WDEV_PR_ARG, CHAN_PR_ARG) ); +TRACE_EVENT(cfg80211_nan_cluster_joined, + TP_PROTO(struct wireless_dev *wdev, + const u8 *cluster_id, + bool new_cluster), + TP_ARGS(wdev, cluster_id, new_cluster), + TP_STRUCT__entry( + WDEV_ENTRY + MAC_ENTRY(cluster_id) + __field(bool, new_cluster) + ), + TP_fast_assign( + WDEV_ASSIGN; + MAC_ASSIGN(cluster_id, cluster_id); + __entry->new_cluster = new_cluster; + ), + TP_printk(WDEV_PR_FMT " cluster_id %pMF%s", + WDEV_PR_ARG, __entry->cluster_id, + __entry->new_cluster ? " [new]" : "") +); #endif /* !__RDEV_OPS_TRACE || TRACE_HEADER_MULTI_READ */ #undef TRACE_INCLUDE_PATH From 3cbadd84f5c4ea792c0df3506639a2cb57ba9b11 Mon Sep 17 00:00:00 2001 From: Andrei Otcheretianski Date: Mon, 8 Sep 2025 14:12:58 +0300 Subject: [PATCH 132/167] wifi: nl80211: Add more NAN capabilities Add better break down for NAN capabilities, as NAN has multiple optional features. This allows to better indicate which features are supported or or offloaded to the device. Signed-off-by: Andrei Otcheretianski Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250908140015.bb02cd8c1596.I01fb2e8dc3662b847f3c27117bc4e199fc96d0a3@changeid Signed-off-by: Johannes Berg --- include/uapi/linux/nl80211.h | 55 ++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index c5a7658b7297..423e258cdbd2 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -2964,6 +2964,10 @@ enum nl80211_commands { * @NL80211_ATTR_NAN_NEW_CLUSTER: Flag attribute indicating that a new * NAN cluster has been created. This is used with * %NL80211_CMD_NAN_CLUSTER_JOINED + * @NL80211_ATTR_NAN_CAPABILITIES: Nested attribute for NAN capabilities. + * This is used with %NL80211_CMD_GET_WIPHY to indicate the NAN + * capabilities supported by the driver. See &enum nl80211_nan_capabilities + * for details. * * @NUM_NL80211_ATTR: total number of nl80211_attrs available * @NL80211_ATTR_MAX: highest attribute number currently defined @@ -3529,6 +3533,7 @@ enum nl80211_attrs { NL80211_ATTR_BSS_PARAM, NL80211_ATTR_NAN_CONFIG, NL80211_ATTR_NAN_NEW_CLUSTER, + NL80211_ATTR_NAN_CAPABILITIES, /* add attributes here, update the policy in nl80211.c */ @@ -8362,4 +8367,54 @@ enum nl80211_s1g_short_beacon_attrs { __NL80211_S1G_SHORT_BEACON_ATTR_LAST - 1 }; +/** + * enum nl80211_nan_capabilities - NAN (Neighbor Aware Networking) + * capabilities. + * + * @__NL80211_NAN_CAPABILITIES_INVALID: Invalid. + * @NL80211_NAN_CAPA_CONFIGURABLE_SYNC: Flag attribute indicating that + * the device supports configurable synchronization. If set, the device + * should be able to handle %NL80211_ATTR_NAN_CONFIG + * attribute in the %NL80211_CMD_START_NAN (and change) command. + * @NL80211_NAN_CAPA_USERSPACE_DE: Flag attribute indicating that + * NAN Discovery Engine (DE) is not offloaded and the driver assumes + * user space DE implementation. When set, %NL80211_CMD_ADD_NAN_FUNCTION, + * %NL80211_CMD_DEL_NAN_FUNCTION and %NL80211_CMD_NAN_MATCH commands + * should not be used. In addition, the device/driver should support + * sending discovery window (DW) notifications using + * %NL80211_CMD_NAN_NEXT_DW_NOTIFICATION and handling transmission and + * reception of NAN SDF frames on NAN device interface during DW windows. + * (%NL80211_CMD_FRAME is used to transmit SDFs) + * @NL80211_NAN_CAPA_OP_MODE: u8 attribute indicating the supported operation + * modes as defined in Wi-Fi Aware (TM) specification Table 81 (Operation + * Mode field format). + * @NL80211_NAN_CAPA_NUM_ANTENNAS: u8 attribute indicating the number of + * TX and RX antennas supported by the device. Lower nibble indicates + * the number of TX antennas and upper nibble indicates the number of RX + * antennas. Value 0 indicates the information is not available. + * See table 79 of Wi-Fi Aware (TM) specification (Number of + * Antennas field). + * @NL80211_NAN_CAPA_MAX_CHANNEL_SWITCH_TIME: u16 attribute indicating the + * maximum time in microseconds that the device requires to switch + * channels. + * @NL80211_NAN_CAPA_CAPABILITIES: u8 attribute containing the + * capabilities of the device as defined in Wi-Fi Aware (TM) + * specification Table 79 (Capabilities field). + * @__NL80211_NAN_CAPABILITIES_LAST: Internal + * @NL80211_NAN_CAPABILITIES_MAX: Highest NAN capability attribute. + */ +enum nl80211_nan_capabilities { + __NL80211_NAN_CAPABILITIES_INVALID, + + NL80211_NAN_CAPA_CONFIGURABLE_SYNC, + NL80211_NAN_CAPA_USERSPACE_DE, + NL80211_NAN_CAPA_OP_MODE, + NL80211_NAN_CAPA_NUM_ANTENNAS, + NL80211_NAN_CAPA_MAX_CHANNEL_SWITCH_TIME, + NL80211_NAN_CAPA_CAPABILITIES, + /* keep last */ + __NL80211_NAN_CAPABILITIES_LAST, + NL80211_NAN_CAPABILITIES_MAX = __NL80211_NAN_CAPABILITIES_LAST - 1, +}; + #endif /* __LINUX_NL80211_H */ From b9c3d426c8a5823b3a1e5078719750c6abb0d2c1 Mon Sep 17 00:00:00 2001 From: Ilan Peer Date: Mon, 8 Sep 2025 14:12:59 +0300 Subject: [PATCH 133/167] wifi: cfg80211: Advertise supported NAN capabilities Allow drivers to specify the supported NAN capabilities and support advertising the NAN capabilities to user space. Signed-off-by: Ilan Peer Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250908140015.2976966556f5.Ic6e43b10049573180c909dad806f279cfb31143e@changeid Signed-off-by: Johannes Berg --- include/linux/ieee80211.h | 17 ++++++++++++++++ include/net/cfg80211.h | 38 ++++++++++++++++++++++++++++++++++++ net/wireless/nl80211.c | 41 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h index d350263f23f3..2110345de8ef 100644 --- a/include/linux/ieee80211.h +++ b/include/linux/ieee80211.h @@ -6065,4 +6065,21 @@ static inline u32 ieee80211_eml_trans_timeout_in_us(u16 eml_cap) _data + ieee80211_mle_common_size(_data),\ _len - ieee80211_mle_common_size(_data)) +/* NAN operation mode, as defined in Wi-Fi Aware (TM) specification Table 81 */ +#define NAN_OP_MODE_PHY_MODE_VHT 0x01 +#define NAN_OP_MODE_PHY_MODE_HE 0x10 +#define NAN_OP_MODE_PHY_MODE_MASK 0x11 +#define NAN_OP_MODE_80P80MHZ 0x02 +#define NAN_OP_MODE_160MHZ 0x04 +#define NAN_OP_MODE_PNDL_SUPPRTED 0x08 + +/* NAN Device capabilities, as defined in Wi-Fi Aware (TM) specification + * Table 79 + */ +#define NAN_DEV_CAPA_DFS_OWNER 0x01 +#define NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED 0x02 +#define NAN_DEV_CAPA_SIM_NDP_RX_SUPPORTED 0x04 +#define NAN_DEV_CAPA_NDPE_SUPPORTED 0x08 +#define NAN_DEV_CAPA_S3_SUPPORTED 0x10 + #endif /* LINUX_IEEE80211_H */ diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 1b10bd31bdd6..e30c1886c530 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -5711,6 +5711,42 @@ struct wiphy_radio { u32 antenna_mask; }; +/** + * enum wiphy_nan_flags - NAN capabilities + * + * @WIPHY_NAN_FLAGS_CONFIGURABLE_SYNC: Device supports NAN configurable + * synchronization. + * @WIPHY_NAN_FLAGS_USERSPACE_DE: Device doesn't support DE offload. + */ +enum wiphy_nan_flags { + WIPHY_NAN_FLAGS_CONFIGURABLE_SYNC = BIT(0), + WIPHY_NAN_FLAGS_USERSPACE_DE = BIT(1), +}; + +/** + * struct wiphy_nan_capa - NAN capabilities + * + * This structure describes the NAN capabilities of a wiphy. + * + * @flags: NAN capabilities flags, see &enum wiphy_nan_flags + * @op_mode: NAN operation mode, as defined in Wi-Fi Aware (TM) specification + * Table 81. + * @n_antennas: number of antennas supported by the device for Tx/Rx. Lower + * nibble indicates the number of TX antennas and upper nibble indicates the + * number of RX antennas. Value 0 indicates the information is not + * available. + * @max_channel_switch_time: maximum channel switch time in milliseconds. + * @dev_capabilities: NAN device capabilities as defined in Wi-Fi Aware (TM) + * specification Table 79 (Capabilities field). + */ +struct wiphy_nan_capa { + u32 flags; + u8 op_mode; + u8 n_antennas; + u16 max_channel_switch_time; + u8 dev_capabilities; +}; + #define CFG80211_HW_TIMESTAMP_ALL_PEERS 0xffff /** @@ -5884,6 +5920,7 @@ struct wiphy_radio { * bitmap of &enum nl80211_band values. For instance, for * NL80211_BAND_2GHZ, bit 0 would be set * (i.e. BIT(NL80211_BAND_2GHZ)). + * @nan_capa: NAN capabilities * * @txq_limit: configuration of internal TX queue frame limit * @txq_memory_limit: configuration internal TX queue memory limit @@ -6065,6 +6102,7 @@ struct wiphy { u32 bss_select_support; u8 nan_supported_bands; + struct wiphy_nan_capa nan_capa; u32 txq_limit; u32 txq_memory_limit; diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 904a725a4f4a..bcd18ae59e84 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -2605,6 +2605,41 @@ fail: return -ENOBUFS; } +static int nl80211_put_nan_capa(struct wiphy *wiphy, struct sk_buff *msg) +{ + struct nlattr *nan_caps; + + nan_caps = nla_nest_start(msg, NL80211_ATTR_NAN_CAPABILITIES); + if (!nan_caps) + return -ENOBUFS; + + if (wiphy->nan_capa.flags & WIPHY_NAN_FLAGS_CONFIGURABLE_SYNC && + nla_put_flag(msg, NL80211_NAN_CAPA_CONFIGURABLE_SYNC)) + goto fail; + + if ((wiphy->nan_capa.flags & WIPHY_NAN_FLAGS_USERSPACE_DE) && + nla_put_flag(msg, NL80211_NAN_CAPA_USERSPACE_DE)) + goto fail; + + if (nla_put_u8(msg, NL80211_NAN_CAPA_OP_MODE, + wiphy->nan_capa.op_mode) || + nla_put_u8(msg, NL80211_NAN_CAPA_NUM_ANTENNAS, + wiphy->nan_capa.n_antennas) || + nla_put_u16(msg, NL80211_NAN_CAPA_MAX_CHANNEL_SWITCH_TIME, + wiphy->nan_capa.max_channel_switch_time) || + nla_put_u8(msg, NL80211_NAN_CAPA_CAPABILITIES, + wiphy->nan_capa.dev_capabilities)) + goto fail; + + nla_nest_end(msg, nan_caps); + + return 0; + +fail: + nla_nest_cancel(msg, nan_caps); + return -ENOBUFS; +} + struct nl80211_dump_wiphy_state { s64 filter_wiphy; long start; @@ -3257,6 +3292,12 @@ static int nl80211_send_wiphy(struct cfg80211_registered_device *rdev, if (nl80211_put_radios(&rdev->wiphy, msg)) goto nla_put_failure; + state->split_start++; + break; + case 18: + if (nl80211_put_nan_capa(&rdev->wiphy, msg)) + goto nla_put_failure; + /* done */ state->split_start = 0; break; From 78e3bd0133f1981755fd0372013a77819e22c825 Mon Sep 17 00:00:00 2001 From: Ilan Peer Date: Mon, 8 Sep 2025 14:13:00 +0300 Subject: [PATCH 134/167] wifi: cfg80211: Support Tx/Rx of action frame for NAN Add support for sending and receiving action frames over a NAN Device interface: - For Synchronized NAN operation NAN Service Discovery Frames (SDFs) and NAN Action Frames (NAFs) transmissions over a NAN Device interface, a channel parameter is not mandatory as the frame can be transmitted based on the NAN Device schedule. - For Unsynchronized NAN Discovery (USD) operation the SDFs and NAFs could be transmitted using NL80211_CMD_FRAME where a specific channel and dwell time are configured. As Synchronized NAN Operation and USD can be done concurrently, both modes need to be supported. Thus, allow sending NAN action frames when user space handles the NAN Discovery Engine (DE) with and without providing a channel as a parameter. To support reception of NAN Action frames and Authentication frames (used for NAN paring and verification) allow to register for management frame reception of NAN Device interface when user space handles the NAN DE. Signed-off-by: Ilan Peer Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250908140015.71da2b062929.I0166d51dcf14393f628cd5da366c21114f518618@changeid Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index bcd18ae59e84..72f68a17c92b 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -13782,7 +13782,9 @@ static int nl80211_register_mgmt(struct sk_buff *skb, struct genl_info *info) break; case NL80211_IFTYPE_NAN: if (!wiphy_ext_feature_isset(wdev->wiphy, - NL80211_EXT_FEATURE_SECURE_NAN)) + NL80211_EXT_FEATURE_SECURE_NAN) && + !(wdev->wiphy->nan_capa.flags & + WIPHY_NAN_FLAGS_USERSPACE_DE)) return -EOPNOTSUPP; break; default: @@ -13843,7 +13845,9 @@ static int nl80211_tx_mgmt(struct sk_buff *skb, struct genl_info *info) break; case NL80211_IFTYPE_NAN: if (!wiphy_ext_feature_isset(wdev->wiphy, - NL80211_EXT_FEATURE_SECURE_NAN)) + NL80211_EXT_FEATURE_SECURE_NAN) && + !(wdev->wiphy->nan_capa.flags & + WIPHY_NAN_FLAGS_USERSPACE_DE)) return -EOPNOTSUPP; break; default: From 1884e2594b084a6b1eb438e5eda586f284d80fee Mon Sep 17 00:00:00 2001 From: Ilan Peer Date: Mon, 8 Sep 2025 14:13:01 +0300 Subject: [PATCH 135/167] wifi: cfg80211: Store the NAN cluster ID When the driver indicates that the device has joined a cluster, store the cluster ID. This is needed for data path operations, e.g., filtering received frames etc. Signed-off-by: Ilan Peer Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250908140015.63e9fef2a3aa.I6c858185c9e71f84bd2c5174d7ee45902b4391c3@changeid Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 3 +++ net/wireless/nl80211.c | 2 ++ 2 files changed, 5 insertions(+) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index e30c1886c530..26fd42e189ce 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -6681,6 +6681,9 @@ struct wireless_dev { struct { struct cfg80211_chan_def chandef; } ocb; + struct { + u8 cluster_id[ETH_ALEN] __aligned(2); + } nan; } u; struct { diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 72f68a17c92b..4e0d40865441 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -21865,6 +21865,8 @@ void cfg80211_nan_cluster_joined(struct wireless_dev *wdev, trace_cfg80211_nan_cluster_joined(wdev, cluster_id, new_cluster); + memcpy(wdev->u.nan.cluster_id, cluster_id, ETH_ALEN); + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp); if (!msg) return; From fc41f4a28ac4d462487903229494eeb266f68a40 Mon Sep 17 00:00:00 2001 From: Ilan Peer Date: Mon, 8 Sep 2025 14:13:02 +0300 Subject: [PATCH 136/167] wifi: mac80211: Support Tx of action frame for NAN Add support for sending management frame over a NAN Device interface: - Declare support for the supported management frames types. - Since action frame transmissions over a NAN Device interface do not necessarily require a channel configuration, e.g., they can be transmitted during DW, modify the Tx path to avoid accessing channel information for NAN Device interface. - In addition modify the points in the Tx path logic to account for cases that a band is not specified in the Tx information. Signed-off-by: Ilan Peer Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250908140015.23b160089228.I65a58af753bcbcfb5c4ad8ef372d546f889725ba@changeid Signed-off-by: Johannes Berg --- include/net/mac80211.h | 4 ++++ net/mac80211/main.c | 5 +++++ net/mac80211/offchannel.c | 5 ++++- net/mac80211/rate.c | 11 ++++++++++- net/mac80211/tx.c | 12 ++++++++++-- 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/include/net/mac80211.h b/include/net/mac80211.h index a45e4bee65d4..a5140ecf334b 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -3192,6 +3192,10 @@ ieee80211_get_tx_rate(const struct ieee80211_hw *hw, { if (WARN_ON_ONCE(c->control.rates[0].idx < 0)) return NULL; + + if (c->band >= NUM_NL80211_BANDS) + return NULL; + return &hw->wiphy->bands[c->band]->bitrates[c->control.rates[0].idx]; } diff --git a/net/mac80211/main.c b/net/mac80211/main.c index 437f1363c982..27b3ec5deabe 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -746,6 +746,11 @@ ieee80211_default_mgmt_stypes[NUM_NL80211_IFTYPES] = { BIT(IEEE80211_STYPE_PROBE_REQ >> 4) | BIT(IEEE80211_STYPE_AUTH >> 4), }, + [NL80211_IFTYPE_NAN] = { + .tx = 0xffff, + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_AUTH >> 4), + }, }; static const struct ieee80211_ht_cap mac80211_ht_capa_mod_mask = { diff --git a/net/mac80211/offchannel.c b/net/mac80211/offchannel.c index 13df6321634d..ae82533e3c02 100644 --- a/net/mac80211/offchannel.c +++ b/net/mac80211/offchannel.c @@ -8,7 +8,7 @@ * Copyright 2006-2007 Jiri Benc * Copyright 2007, Michael Wu * Copyright 2009 Johannes Berg - * Copyright (C) 2019, 2022-2024 Intel Corporation + * Copyright (C) 2019, 2022-2025 Intel Corporation */ #include #include @@ -897,6 +897,7 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, need_offchan = true; break; case NL80211_IFTYPE_NAN: + break; default: return -EOPNOTSUPP; } @@ -910,6 +911,8 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, /* Check if the operating channel is the requested channel */ if (!params->chan && mlo_sta) { need_offchan = false; + } else if (sdata->vif.type == NL80211_IFTYPE_NAN) { + /* Frames can be sent during NAN schedule */ } else if (!need_offchan) { struct ieee80211_chanctx_conf *chanctx_conf = NULL; int i; diff --git a/net/mac80211/rate.c b/net/mac80211/rate.c index 3cb2ad6d0b28..e441f8541603 100644 --- a/net/mac80211/rate.c +++ b/net/mac80211/rate.c @@ -4,7 +4,7 @@ * Copyright 2005-2006, Devicescape Software, Inc. * Copyright (c) 2006 Jiri Benc * Copyright 2017 Intel Deutschland GmbH - * Copyright (C) 2019, 2022-2024 Intel Corporation + * Copyright (C) 2019, 2022-2025 Intel Corporation */ #include @@ -98,6 +98,9 @@ void rate_control_tx_status(struct ieee80211_local *local, if (!ref || !test_sta_flag(sta, WLAN_STA_RATE_CONTROL)) return; + if (st->info->band >= NUM_NL80211_BANDS) + return; + sband = local->hw.wiphy->bands[st->info->band]; spin_lock_bh(&sta->rate_ctrl_lock); @@ -419,6 +422,9 @@ static bool rate_control_send_low(struct ieee80211_sta *pubsta, int mcast_rate; bool use_basicrate = false; + if (!sband) + return false; + if (!pubsta || rc_no_data_or_no_ack_use_min(txrc)) { __rate_control_send_low(txrc->hw, sband, pubsta, info, txrc->rate_idx_mask); @@ -898,6 +904,9 @@ void ieee80211_get_tx_rates(struct ieee80211_vif *vif, return; sdata = vif_to_sdata(vif); + if (info->band >= NUM_NL80211_BANDS) + return; + sband = sdata->local->hw.wiphy->bands[info->band]; if (ieee80211_is_tx_data(skb)) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index a27e2af5d569..ba51198be94a 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -59,6 +59,9 @@ static __le16 ieee80211_duration(struct ieee80211_tx_data *tx, if (WARN_ON_ONCE(tx->rate.idx < 0)) return 0; + if (info->band >= NUM_NL80211_BANDS) + return 0; + sband = local->hw.wiphy->bands[info->band]; txrate = &sband->bitrates[tx->rate.idx]; @@ -683,7 +686,10 @@ ieee80211_tx_h_rate_ctrl(struct ieee80211_tx_data *tx) memset(&txrc, 0, sizeof(txrc)); - sband = tx->local->hw.wiphy->bands[info->band]; + if (info->band < NUM_NL80211_BANDS) + sband = tx->local->hw.wiphy->bands[info->band]; + else + return TX_CONTINUE; len = min_t(u32, tx->skb->len + FCS_LEN, tx->local->hw.wiphy->frag_threshold); @@ -6288,7 +6294,9 @@ void ieee80211_tx_skb_tid(struct ieee80211_sub_if_data *sdata, enum nl80211_band band; rcu_read_lock(); - if (!ieee80211_vif_is_mld(&sdata->vif)) { + if (sdata->vif.type == NL80211_IFTYPE_NAN) { + band = NUM_NL80211_BANDS; + } else if (!ieee80211_vif_is_mld(&sdata->vif)) { WARN_ON(link_id >= 0); chanctx_conf = rcu_dereference(sdata->vif.bss_conf.chanctx_conf); From 488d2e0bba65257cd0e723c413f02a9caf95b27c Mon Sep 17 00:00:00 2001 From: Ilan Peer Date: Mon, 8 Sep 2025 14:13:03 +0300 Subject: [PATCH 137/167] wifi: mac80211: Accept management frames on NAN interface Accept Public Action frames and Authentication frames on NAN Device interface to support flows that require these frames: - SDFs: For user space Discovery Engine (DE) implementation. - NAFs: For user space NAN Data Path (NDP) establishment. - Authentication frames: For NAN Pairing and Verification. Accept only frames from devices that are part of the NAN cluster. Signed-off-by: Ilan Peer Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250908140015.46528d69e881.Ifccd87fb2a49a3af05238f74f52fa6da8de28811@changeid Signed-off-by: Johannes Berg --- net/mac80211/rx.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 4d4ff4d4917a..feb81ffa4f8c 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -4502,8 +4502,16 @@ static bool ieee80211_accept_frame(struct ieee80211_rx_data *rx) (ieee80211_is_auth(hdr->frame_control) && ether_addr_equal(sdata->vif.addr, hdr->addr1)); case NL80211_IFTYPE_NAN: - /* Currently no frames on NAN interface are allowed */ - return false; + /* Accept only frames that are addressed to the NAN cluster + * (based on the Cluster ID). From these frames, accept only + * action frames or authentication frames that are addressed to + * the local NAN interface. + */ + return memcmp(sdata->wdev.u.nan.cluster_id, + hdr->addr3, ETH_ALEN) == 0 && + (ieee80211_is_public_action(hdr, skb->len) || + (ieee80211_is_auth(hdr->frame_control) && + ether_addr_equal(sdata->vif.addr, hdr->addr1))); default: break; } From 8f79d2f13dd3b0af00a5303d4ff913767dd7684e Mon Sep 17 00:00:00 2001 From: Ilan Peer Date: Mon, 8 Sep 2025 14:13:04 +0300 Subject: [PATCH 138/167] wifi: mac80211: Track NAN interface start/stop In case that NAN is started, mark the device as non idle, and set LED triggering similar to scan and ROC. Set the device to idle once NAN is stopped. Signed-off-by: Ilan Peer Reviewed-by: Andrei Otcheretianski Reviewed-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250908140015.2711d62fce22.I9b9f826490e50967a66788d713b0eba985879873@changeid Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 20 +++++++++++++++++--- net/mac80211/ieee80211_i.h | 2 ++ net/mac80211/iface.c | 9 +++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index b26f61f13605..252c0804de2f 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -320,6 +320,9 @@ static int ieee80211_start_nan(struct wiphy *wiphy, lockdep_assert_wiphy(sdata->local->hw.wiphy); + if (sdata->u.nan.started) + return -EALREADY; + ret = ieee80211_check_combinations(sdata, NULL, 0, 0, -1); if (ret < 0) return ret; @@ -329,12 +332,18 @@ static int ieee80211_start_nan(struct wiphy *wiphy, return ret; ret = drv_start_nan(sdata->local, sdata, conf); - if (ret) + if (ret) { ieee80211_sdata_stop(sdata); + return ret; + } - sdata->u.nan.conf = *conf; + sdata->u.nan.started = true; + ieee80211_recalc_idle(sdata->local); - return ret; + sdata->u.nan.conf.master_pref = conf->master_pref; + sdata->u.nan.conf.bands = conf->bands; + + return 0; } static void ieee80211_stop_nan(struct wiphy *wiphy, @@ -342,8 +351,13 @@ static void ieee80211_stop_nan(struct wiphy *wiphy, { struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); + if (!sdata->u.nan.started) + return; + drv_stop_nan(sdata->local, sdata); + sdata->u.nan.started = false; ieee80211_sdata_stop(sdata); + ieee80211_recalc_idle(sdata->local); } static int ieee80211_nan_change_conf(struct wiphy *wiphy, diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 8a666faeb1ec..48e1ba919fba 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -985,11 +985,13 @@ struct ieee80211_if_mntr { * struct ieee80211_if_nan - NAN state * * @conf: current NAN configuration + * @started: true iff NAN is started * @func_lock: lock for @func_inst_ids * @function_inst_ids: a bitmap of available instance_id's */ struct ieee80211_if_nan { struct cfg80211_nan_conf conf; + bool started; /* protects function_inst_ids */ spinlock_t func_lock; diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 07ba68f7cd81..4a9175d9f51c 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -107,6 +107,7 @@ static u32 __ieee80211_recalc_idle(struct ieee80211_local *local, { bool working, scanning, active; unsigned int led_trig_start = 0, led_trig_stop = 0; + struct ieee80211_sub_if_data *iter; lockdep_assert_wiphy(local->hw.wiphy); @@ -117,6 +118,14 @@ static u32 __ieee80211_recalc_idle(struct ieee80211_local *local, working = !local->ops->remain_on_channel && !list_empty(&local->roc_list); + list_for_each_entry(iter, &local->interfaces, list) { + if (iter->vif.type == NL80211_IFTYPE_NAN && + iter->u.nan.started) { + working = true; + break; + } + } + scanning = test_bit(SCAN_SW_SCANNING, &local->scanning) || test_bit(SCAN_ONCHANNEL_SCANNING, &local->scanning); From c7b5355b37a59c927b2374e9f783acd004d00960 Mon Sep 17 00:00:00 2001 From: Ilan Peer Date: Mon, 8 Sep 2025 14:13:05 +0300 Subject: [PATCH 139/167] wifi: mac80211: Get the correct interface for non-netdev skb status The function ieee80211_sdata_from_skb() always returned the P2P Device interface in case the skb was not associated with a netdev and didn't consider the possibility that an NAN Device interface is also enabled. To support configurations where both P2P Device and a NAN Device interface are active, extend the function to match the correct interface based on address 2 in the 802.11 MAC header. Since the 'p2p_sdata' field in struct ieee80211_local is no longer needed, remove it. Signed-off-by: Ilan Peer Reviewed-by: Andrei Otcheretianski Reviewed-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250908140015.5252d2579a49.Id4576531c6b2ad83c9498b708dc0ade6b0214fa8@changeid Signed-off-by: Johannes Berg --- net/mac80211/ieee80211_i.h | 2 -- net/mac80211/iface.c | 16 +--------------- net/mac80211/status.c | 21 +++++++++++++++++++-- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 48e1ba919fba..242cb109b232 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -1676,8 +1676,6 @@ struct ieee80211_local { struct idr ack_status_frames; spinlock_t ack_status_lock; - struct ieee80211_sub_if_data __rcu *p2p_sdata; - /* virtual monitor interface */ struct ieee80211_sub_if_data __rcu *monitor_sdata; struct ieee80211_chan_req monitor_chanreq; diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 4a9175d9f51c..a7873832d4fa 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -620,10 +620,6 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do spin_unlock_bh(&sdata->u.nan.func_lock); break; - case NL80211_IFTYPE_P2P_DEVICE: - /* relies on synchronize_rcu() below */ - RCU_INIT_POINTER(local->p2p_sdata, NULL); - fallthrough; default: wiphy_work_cancel(sdata->local->hw.wiphy, &sdata->work); /* @@ -1414,6 +1410,7 @@ int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up) ieee80211_recalc_idle(local); netif_carrier_on(dev); + list_add_tail_rcu(&sdata->u.mntr.list, &local->mon_list); break; default: if (coming_up) { @@ -1477,17 +1474,6 @@ int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up) sdata->vif.type != NL80211_IFTYPE_STATION); } - switch (sdata->vif.type) { - case NL80211_IFTYPE_P2P_DEVICE: - rcu_assign_pointer(local->p2p_sdata, sdata); - break; - case NL80211_IFTYPE_MONITOR: - list_add_tail_rcu(&sdata->u.mntr.list, &local->mon_list); - break; - default: - break; - } - /* * set_multicast_list will be invoked by the networking core * which will check whether any increments here were done in diff --git a/net/mac80211/status.c b/net/mac80211/status.c index a362254b310c..4b38aa0e902a 100644 --- a/net/mac80211/status.c +++ b/net/mac80211/status.c @@ -5,7 +5,7 @@ * Copyright 2006-2007 Jiri Benc * Copyright 2008-2010 Johannes Berg * Copyright 2013-2014 Intel Mobile Communications GmbH - * Copyright 2021-2024 Intel Corporation + * Copyright 2021-2025 Intel Corporation */ #include @@ -572,6 +572,7 @@ static struct ieee80211_sub_if_data * ieee80211_sdata_from_skb(struct ieee80211_local *local, struct sk_buff *skb) { struct ieee80211_sub_if_data *sdata; + struct ieee80211_hdr *hdr = (void *)skb->data; if (skb->dev) { list_for_each_entry_rcu(sdata, &local->interfaces, list) { @@ -585,7 +586,23 @@ ieee80211_sdata_from_skb(struct ieee80211_local *local, struct sk_buff *skb) return NULL; } - return rcu_dereference(local->p2p_sdata); + list_for_each_entry_rcu(sdata, &local->interfaces, list) { + switch (sdata->vif.type) { + case NL80211_IFTYPE_P2P_DEVICE: + break; + case NL80211_IFTYPE_NAN: + if (sdata->u.nan.started) + break; + fallthrough; + default: + continue; + } + + if (ether_addr_equal(sdata->vif.addr, hdr->addr2)) + return sdata; + } + + return NULL; } static void ieee80211_report_ack_skb(struct ieee80211_local *local, From 04f17cfea2442ef2ed01da7ba1f686a58a50048e Mon Sep 17 00:00:00 2001 From: Ilan Peer Date: Mon, 8 Sep 2025 14:13:06 +0300 Subject: [PATCH 140/167] wifi: mac80211: Export an API to check if NAN is started So it can be used by drivers to check if NAN Device interface is started or not. Signed-off-by: Ilan Peer Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250908140015.c69652f77eb6.Ie4f3d197e0706e742e3d97614fadc11b22adfbc6@changeid Signed-off-by: Johannes Berg --- include/net/mac80211.h | 6 ++++++ net/mac80211/util.c | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/include/net/mac80211.h b/include/net/mac80211.h index a5140ecf334b..a55085cf4ec4 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -7838,4 +7838,10 @@ int ieee80211_emulate_switch_vif_chanctx(struct ieee80211_hw *hw, int n_vifs, enum ieee80211_chanctx_switch_mode mode); +/** + * ieee80211_vif_nan_started - Return whether a NAN vif is started + * @vif: the vif + * Return: %true iff the vif is a NAN interface and NAN is started + */ +bool ieee80211_vif_nan_started(struct ieee80211_vif *vif); #endif /* MAC80211_H */ diff --git a/net/mac80211/util.c b/net/mac80211/util.c index 9eb35e3b9e52..123842b841f2 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -4512,3 +4512,11 @@ void ieee80211_clear_tpe(struct ieee80211_parsed_tpe *tpe) sizeof(tpe->psd_reg_client[i].power)); } } + +bool ieee80211_vif_nan_started(struct ieee80211_vif *vif) +{ + struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif); + + return vif->type == NL80211_IFTYPE_NAN && sdata->u.nan.started; +} +EXPORT_SYMBOL_GPL(ieee80211_vif_nan_started); From 1d04fad3a495062a33940278536c15a29d0f0dbb Mon Sep 17 00:00:00 2001 From: Ilan Peer Date: Mon, 8 Sep 2025 14:13:07 +0300 Subject: [PATCH 141/167] wifi: mac80211: Extend support for changing NAN configuration As 'struct cfg80211_nan_config' was updated, update the relevant logic to accommodate these changes. Signed-off-by: Ilan Peer Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250908140015.92b530ddaedf.I2b6d6f6074e25487303fde573ce764a64f87bdcd@changeid Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 136 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 113 insertions(+), 23 deletions(-) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 252c0804de2f..da15ccfcf4a2 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -311,6 +311,96 @@ static void ieee80211_stop_p2p_device(struct wiphy *wiphy, ieee80211_sdata_stop(IEEE80211_WDEV_TO_SUB_IF(wdev)); } +static void ieee80211_nan_conf_free(struct cfg80211_nan_conf *conf) +{ + kfree(conf->cluster_id); + kfree(conf->extra_nan_attrs); + kfree(conf->vendor_elems); + memset(conf, 0, sizeof(*conf)); +} + +static void ieee80211_stop_nan(struct wiphy *wiphy, + struct wireless_dev *wdev) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); + + if (!sdata->u.nan.started) + return; + + drv_stop_nan(sdata->local, sdata); + sdata->u.nan.started = false; + + ieee80211_nan_conf_free(&sdata->u.nan.conf); + + ieee80211_sdata_stop(sdata); + ieee80211_recalc_idle(sdata->local); +} + +static int ieee80211_nan_conf_copy(struct cfg80211_nan_conf *dst, + struct cfg80211_nan_conf *src, + u32 changes) +{ + if (changes & CFG80211_NAN_CONF_CHANGED_PREF) + dst->master_pref = src->master_pref; + + if (changes & CFG80211_NAN_CONF_CHANGED_BANDS) + dst->bands = src->bands; + + if (changes & CFG80211_NAN_CONF_CHANGED_CONFIG) { + dst->scan_period = src->scan_period; + dst->scan_dwell_time = src->scan_dwell_time; + dst->discovery_beacon_interval = + src->discovery_beacon_interval; + dst->enable_dw_notification = src->enable_dw_notification; + memcpy(&dst->band_cfgs, &src->band_cfgs, + sizeof(dst->band_cfgs)); + + kfree(dst->cluster_id); + dst->cluster_id = NULL; + + kfree(dst->extra_nan_attrs); + dst->extra_nan_attrs = NULL; + dst->extra_nan_attrs_len = 0; + + kfree(dst->vendor_elems); + dst->vendor_elems = NULL; + dst->vendor_elems_len = 0; + + if (src->cluster_id) { + dst->cluster_id = kmemdup(src->cluster_id, ETH_ALEN, + GFP_KERNEL); + if (!dst->cluster_id) + goto no_mem; + } + + if (src->extra_nan_attrs && src->extra_nan_attrs_len) { + dst->extra_nan_attrs = kmemdup(src->extra_nan_attrs, + src->extra_nan_attrs_len, + GFP_KERNEL); + if (!dst->extra_nan_attrs) + goto no_mem; + + dst->extra_nan_attrs_len = src->extra_nan_attrs_len; + } + + if (src->vendor_elems && src->vendor_elems_len) { + dst->vendor_elems = kmemdup(src->vendor_elems, + src->vendor_elems_len, + GFP_KERNEL); + if (!dst->vendor_elems) + goto no_mem; + + dst->vendor_elems_len = src->vendor_elems_len; + } + } + + return 0; + +no_mem: + ieee80211_nan_conf_free(dst); + return -ENOMEM; +} + static int ieee80211_start_nan(struct wiphy *wiphy, struct wireless_dev *wdev, struct cfg80211_nan_conf *conf) @@ -340,33 +430,22 @@ static int ieee80211_start_nan(struct wiphy *wiphy, sdata->u.nan.started = true; ieee80211_recalc_idle(sdata->local); - sdata->u.nan.conf.master_pref = conf->master_pref; - sdata->u.nan.conf.bands = conf->bands; + ret = ieee80211_nan_conf_copy(&sdata->u.nan.conf, conf, 0xFFFFFFFF); + if (ret) { + ieee80211_stop_nan(wiphy, wdev); + return ret; + } return 0; } -static void ieee80211_stop_nan(struct wiphy *wiphy, - struct wireless_dev *wdev) -{ - struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); - - if (!sdata->u.nan.started) - return; - - drv_stop_nan(sdata->local, sdata); - sdata->u.nan.started = false; - ieee80211_sdata_stop(sdata); - ieee80211_recalc_idle(sdata->local); -} - static int ieee80211_nan_change_conf(struct wiphy *wiphy, struct wireless_dev *wdev, struct cfg80211_nan_conf *conf, u32 changes) { struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); - struct cfg80211_nan_conf new_conf; + struct cfg80211_nan_conf new_conf = {}; int ret = 0; if (sdata->vif.type != NL80211_IFTYPE_NAN) @@ -375,17 +454,28 @@ static int ieee80211_nan_change_conf(struct wiphy *wiphy, if (!ieee80211_sdata_running(sdata)) return -ENETDOWN; - new_conf = sdata->u.nan.conf; + if (!changes) + return 0; - if (changes & CFG80211_NAN_CONF_CHANGED_PREF) - new_conf.master_pref = conf->master_pref; + /* First make a full copy of the previous configuration and then apply + * the changes. This might be a little wasteful, but it is simpler. + */ + ret = ieee80211_nan_conf_copy(&new_conf, &sdata->u.nan.conf, + 0xFFFFFFFF); + if (ret < 0) + return ret; - if (changes & CFG80211_NAN_CONF_CHANGED_BANDS) - new_conf.bands = conf->bands; + ret = ieee80211_nan_conf_copy(&new_conf, conf, changes); + if (ret < 0) + return ret; ret = drv_nan_change_conf(sdata->local, sdata, &new_conf, changes); - if (!ret) + if (ret) { + ieee80211_nan_conf_free(&new_conf); + } else { + ieee80211_nan_conf_free(&sdata->u.nan.conf); sdata->u.nan.conf = new_conf; + } return ret; } From a37a6f54439bf82b827a7072415d3a4afa4e12bd Mon Sep 17 00:00:00 2001 From: Ilan Peer Date: Mon, 8 Sep 2025 14:13:08 +0300 Subject: [PATCH 142/167] wifi: mac80211_hwsim: Add simulation support for NAN device Add support for simulating a NAN Device interface: - Update interface limits to include support for NAN Device. - Increase the number of supported HW addresses to allow unique addresses for combination such as: station interface + P2P Device interface + NAN Device interface. - Declare support for NAN capabilities, specifically support for NAN synchronization offload and NAN DE user space support. - Add the relevant callbacks to support start/stop NAN Device operation. - Use a timer to simulate starting a Discovery Window (currently the timer doesn't do much). - Update the Tx path to simulate that the channel used for NAN Device is either channel 6 or channel 149. - Send DW notification when DW starts. - Send cluster join notification when new cluster starts, or when an existing cluster is joined. "Joining" is implemented by reusing the cluster id of any other existing NAN management interface. Signed-off-by: Ilan Peer Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250908140015.2d02d5be6468.I3badfdb80c29e7713bd37373650ccbf099547a59@changeid Signed-off-by: Johannes Berg --- drivers/net/wireless/virtual/mac80211_hwsim.c | 259 +++++++++++++++++- drivers/net/wireless/virtual/mac80211_hwsim.h | 4 +- 2 files changed, 256 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim.c b/drivers/net/wireless/virtual/mac80211_hwsim.c index 3789d46d5614..9f856042a67a 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim.c @@ -645,6 +645,7 @@ static LIST_HEAD(hwsim_radios); static struct rhashtable hwsim_radios_rht; static int hwsim_radio_idx; static int hwsim_radios_generation = 1; +static u8 hwsim_nan_cluster_id[ETH_ALEN]; static struct platform_driver mac80211_hwsim_driver = { .driver = { @@ -670,7 +671,7 @@ struct mac80211_hwsim_data { struct ieee80211_channel channels_s1g[ARRAY_SIZE(hwsim_channels_s1g)]; struct ieee80211_rate rates[ARRAY_SIZE(hwsim_rates)]; struct ieee80211_iface_combination if_combination; - struct ieee80211_iface_limit if_limits[3]; + struct ieee80211_iface_limit if_limits[4]; int n_if_limits; struct ieee80211_iface_combination if_combination_radio; @@ -679,7 +680,7 @@ struct mac80211_hwsim_data { u32 ciphers[ARRAY_SIZE(hwsim_ciphers)]; - struct mac_address addresses[2]; + struct mac_address addresses[3]; int channels, idx; bool use_chanctx; bool destroy_on_close; @@ -752,6 +753,14 @@ struct mac80211_hwsim_data { struct wireless_dev *pmsr_request_wdev; struct mac80211_hwsim_link_data link_data[IEEE80211_MLD_MAX_NUM_LINKS]; + + struct ieee80211_vif *nan_device_vif; + u8 nan_bands; + + enum nl80211_band nan_curr_dw_band; + struct hrtimer nan_timer; + bool notify_dw; + struct ieee80211_vif *nan_vif; }; static const struct rhashtable_params hwsim_rht_params = { @@ -926,6 +935,7 @@ static const struct nla_policy hwsim_genl_policy[HWSIM_ATTR_MAX + 1] = { [HWSIM_ATTR_PMSR_SUPPORT] = NLA_POLICY_NESTED(hwsim_pmsr_capa_policy), [HWSIM_ATTR_PMSR_RESULT] = NLA_POLICY_NESTED(hwsim_pmsr_peers_result_policy), [HWSIM_ATTR_MULTI_RADIO] = { .type = NLA_FLAG }, + [HWSIM_ATTR_SUPPORT_NAN_DEVICE] = { .type = NLA_FLAG }, }; #if IS_REACHABLE(CONFIG_VIRTIO) @@ -1644,6 +1654,16 @@ static void mac80211_hwsim_tx_iter(void *_data, u8 *addr, struct tx_iter_data *data = _data; int i; + /* For NAN Device simulation purposes, assume that NAN is always + * on channel 6 or channel 149. + */ + if (vif->type == NL80211_IFTYPE_NAN) { + data->receive = (data->channel && + (data->channel->center_freq == 2437 || + data->channel->center_freq == 5745)); + return; + } + for (i = 0; i < ARRAY_SIZE(vif->link_conf); i++) { struct ieee80211_bss_conf *conf; struct ieee80211_chanctx_conf *chanctx; @@ -1944,6 +1964,7 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw, struct ieee80211_hdr *hdr = (void *)skb->data; struct ieee80211_chanctx_conf *chanctx_conf; struct ieee80211_channel *channel; + struct ieee80211_vif *vif = txi->control.vif; bool ack; enum nl80211_chan_width confbw = NL80211_CHAN_WIDTH_20_NOHT; u32 _portid, i; @@ -1954,7 +1975,23 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw, return; } - if (!data->use_chanctx) { + if (vif && vif->type == NL80211_IFTYPE_NAN && !data->tmp_chan) { + /* For NAN Device simulation purposes, assume that NAN is always + * on channel 6 or channel 149, unless a ROC is in progress (for + * USD use cases). + */ + if (data->nan_curr_dw_band == NL80211_BAND_2GHZ) + channel = ieee80211_get_channel(hw->wiphy, 2437); + else if (data->nan_curr_dw_band == NL80211_BAND_5GHZ) + channel = ieee80211_get_channel(hw->wiphy, 5745); + else + channel = NULL; + + if (WARN_ON(!channel)) { + ieee80211_free_txskb(hw, skb); + return; + } + } else if (!data->use_chanctx) { channel = data->channel; confbw = data->bw; } else if (txi->hw_queue == 4) { @@ -1962,7 +1999,6 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw, } else { u8 link = u32_get_bits(IEEE80211_SKB_CB(skb)->control.flags, IEEE80211_TX_CTRL_MLO_LINK); - struct ieee80211_vif *vif = txi->control.vif; struct ieee80211_link_sta *link_sta = NULL; struct ieee80211_sta *sta = control->sta; struct ieee80211_bss_conf *bss_conf; @@ -3950,6 +3986,168 @@ out: return err; } +static enum hrtimer_restart +mac80211_hwsim_nan_dw_start(struct hrtimer *timer) +{ + struct mac80211_hwsim_data *data = + container_of(timer, struct mac80211_hwsim_data, + nan_timer); + struct ieee80211_hw *hw = data->hw; + u64 orig_tsf = mac80211_hwsim_get_tsf(hw, NULL), tsf = orig_tsf; + u32 dw_int = 512 * 1024; + u64 until_dw; + + if (!data->nan_device_vif) + return HRTIMER_NORESTART; + + if (data->nan_bands & BIT(NL80211_BAND_5GHZ)) { + if (data->nan_curr_dw_band == NL80211_BAND_2GHZ) { + dw_int = 128 * 1024; + data->nan_curr_dw_band = NL80211_BAND_5GHZ; + } else if (data->nan_curr_dw_band == NL80211_BAND_5GHZ) { + data->nan_curr_dw_band = NL80211_BAND_2GHZ; + } + } + + until_dw = dw_int - do_div(tsf, dw_int); + + /* The timer might fire just before the actual DW, in which case + * update the timeout to the actual next DW + */ + if (until_dw < dw_int / 2) + until_dw += dw_int; + + /* The above do_div() call directly modifies the 'tsf' variable, thus, + * use a copy so that the print below would show the original TSF. + */ + wiphy_debug(hw->wiphy, + "%s: tsf=%llx, curr_dw_band=%u, next_dw=%llu\n", + __func__, orig_tsf, data->nan_curr_dw_band, + until_dw); + + hrtimer_forward_now(&data->nan_timer, + ns_to_ktime(until_dw * NSEC_PER_USEC)); + + if (data->notify_dw) { + struct ieee80211_channel *ch; + struct wireless_dev *wdev = + ieee80211_vif_to_wdev(data->nan_device_vif); + + if (data->nan_curr_dw_band == NL80211_BAND_5GHZ) + ch = ieee80211_get_channel(hw->wiphy, 5475); + else + ch = ieee80211_get_channel(hw->wiphy, 2437); + + cfg80211_next_nan_dw_notif(wdev, ch, GFP_ATOMIC); + } + + return HRTIMER_RESTART; +} + +static int mac80211_hwsim_start_nan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_nan_conf *conf) +{ + struct mac80211_hwsim_data *data = hw->priv; + u64 tsf = mac80211_hwsim_get_tsf(hw, NULL); + u32 dw_int = 512 * 1000; + u64 until_dw = dw_int - do_div(tsf, dw_int); + struct wireless_dev *wdev = ieee80211_vif_to_wdev(vif); + + if (vif->type != NL80211_IFTYPE_NAN) + return -EINVAL; + + if (data->nan_device_vif) + return -EALREADY; + + /* set this before starting the timer, as preemption might occur */ + data->nan_device_vif = vif; + data->nan_bands = conf->bands; + data->nan_curr_dw_band = NL80211_BAND_2GHZ; + + wiphy_debug(hw->wiphy, "nan_started, next_dw=%llu\n", + until_dw); + + hrtimer_start(&data->nan_timer, + ns_to_ktime(until_dw * NSEC_PER_USEC), + HRTIMER_MODE_REL_SOFT); + + if (conf->cluster_id && !is_zero_ether_addr(conf->cluster_id) && + is_zero_ether_addr(hwsim_nan_cluster_id)) { + memcpy(hwsim_nan_cluster_id, conf->cluster_id, ETH_ALEN); + } else if (is_zero_ether_addr(hwsim_nan_cluster_id)) { + hwsim_nan_cluster_id[0] = 0x50; + hwsim_nan_cluster_id[1] = 0x6f; + hwsim_nan_cluster_id[2] = 0x9a; + hwsim_nan_cluster_id[3] = 0x01; + hwsim_nan_cluster_id[4] = get_random_u8(); + hwsim_nan_cluster_id[5] = get_random_u8(); + } + + data->notify_dw = conf->enable_dw_notification; + + cfg80211_nan_cluster_joined(wdev, hwsim_nan_cluster_id, true, + GFP_KERNEL); + + return 0; +} + +static int mac80211_hwsim_stop_nan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mac80211_hwsim_data *data = hw->priv; + struct mac80211_hwsim_data *data2; + bool nan_cluster_running = false; + + if (vif->type != NL80211_IFTYPE_NAN || !data->nan_device_vif || + data->nan_device_vif != vif) + return -EINVAL; + + hrtimer_cancel(&data->nan_timer); + data->nan_device_vif = NULL; + + spin_lock(&hwsim_radio_lock); + list_for_each_entry(data2, &hwsim_radios, list) { + if (data2->nan_device_vif) { + nan_cluster_running = true; + break; + } + } + spin_unlock(&hwsim_radio_lock); + + if (!nan_cluster_running) + memset(hwsim_nan_cluster_id, 0, ETH_ALEN); + + return 0; +} + +static int mac80211_hwsim_change_nan_config(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_nan_conf *conf, + u32 changes) +{ + struct mac80211_hwsim_data *data = hw->priv; + + if (vif->type != NL80211_IFTYPE_NAN) + return -EINVAL; + + if (!data->nan_device_vif) + return -EINVAL; + + wiphy_debug(hw->wiphy, "nan_config_changed: changes=0x%x\n", changes); + + /* Handle only the changes we care about for simulation purposes */ + if (changes & CFG80211_NAN_CONF_CHANGED_BANDS) { + data->nan_bands = conf->bands; + data->nan_curr_dw_band = NL80211_BAND_2GHZ; + } + + if (changes & CFG80211_NAN_CONF_CHANGED_CONFIG) + data->notify_dw = conf->enable_dw_notification; + + return 0; +} + #ifdef CONFIG_MAC80211_DEBUGFS #define HWSIM_DEBUGFS_OPS \ .link_add_debugfs = mac80211_hwsim_link_add_debugfs, @@ -3982,6 +4180,9 @@ out: .get_et_strings = mac80211_hwsim_get_et_strings, \ .start_pmsr = mac80211_hwsim_start_pmsr, \ .abort_pmsr = mac80211_hwsim_abort_pmsr, \ + .start_nan = mac80211_hwsim_start_nan, \ + .stop_nan = mac80211_hwsim_stop_nan, \ + .nan_change_conf = mac80211_hwsim_change_nan_config, \ HWSIM_DEBUGFS_OPS #define HWSIM_NON_MLO_OPS \ @@ -4047,6 +4248,7 @@ struct hwsim_new_radio_params { u8 n_ciphers; bool mlo; const struct cfg80211_pmsr_capabilities *pmsr_capa; + bool nan_device; }; static void hwsim_mcast_config_msg(struct sk_buff *mcast_skb, @@ -4127,6 +4329,11 @@ static int append_radio_msg(struct sk_buff *skb, int id, return ret; } + if (param->nan_device) { + ret = nla_put_flag(skb, HWSIM_ATTR_SUPPORT_NAN_DEVICE); + if (ret < 0) + return ret; + } return 0; } @@ -5239,14 +5446,18 @@ static int mac80211_hwsim_new_radio(struct genl_info *info, /* Why need here second address ? */ memcpy(data->addresses[1].addr, addr, ETH_ALEN); data->addresses[1].addr[0] |= 0x40; - hw->wiphy->n_addresses = 2; + memcpy(data->addresses[2].addr, addr, ETH_ALEN); + data->addresses[2].addr[0] |= 0x50; + + hw->wiphy->n_addresses = 3; hw->wiphy->addresses = data->addresses; /* possible address clash is checked at hash table insertion */ } else { memcpy(data->addresses[0].addr, param->perm_addr, ETH_ALEN); /* compatibility with automatically generated mac addr */ memcpy(data->addresses[1].addr, param->perm_addr, ETH_ALEN); - hw->wiphy->n_addresses = 2; + memcpy(data->addresses[2].addr, param->perm_addr, ETH_ALEN); + hw->wiphy->n_addresses = 3; hw->wiphy->addresses = data->addresses; } @@ -5283,6 +5494,30 @@ static int mac80211_hwsim_new_radio(struct genl_info *info, n_limits++; } + if (param->iftypes & BIT(NL80211_IFTYPE_NAN)) { + data->if_limits[n_limits].max = 1; + data->if_limits[n_limits].types = BIT(NL80211_IFTYPE_NAN); + n_limits++; + + hw->wiphy->nan_supported_bands = BIT(NL80211_BAND_2GHZ) | + BIT(NL80211_BAND_5GHZ); + + hw->wiphy->nan_capa.flags = WIPHY_NAN_FLAGS_CONFIGURABLE_SYNC | + WIPHY_NAN_FLAGS_USERSPACE_DE; + hw->wiphy->nan_capa.op_mode = NAN_OP_MODE_PHY_MODE_MASK | + NAN_OP_MODE_80P80MHZ | + NAN_OP_MODE_160MHZ; + + hw->wiphy->nan_capa.n_antennas = 0x22; + hw->wiphy->nan_capa.max_channel_switch_time = 0; + hw->wiphy->nan_capa.dev_capabilities = + NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED | + NAN_DEV_CAPA_NDPE_SUPPORTED; + + hrtimer_setup(&data->nan_timer, mac80211_hwsim_nan_dw_start, + CLOCK_MONOTONIC, HRTIMER_MODE_ABS_SOFT); + } + data->if_combination.radar_detect_widths = BIT(NL80211_CHAN_WIDTH_5) | BIT(NL80211_CHAN_WIDTH_10) | @@ -5701,6 +5936,8 @@ static int mac80211_hwsim_get_radio(struct sk_buff *skb, REGULATORY_STRICT_REG); param.p2p_device = !!(data->hw->wiphy->interface_modes & BIT(NL80211_IFTYPE_P2P_DEVICE)); + param.nan_device = !!(data->hw->wiphy->interface_modes & + BIT(NL80211_IFTYPE_NAN)); param.use_chanctx = data->use_chanctx; param.regd = data->regd; param.channels = data->channels; @@ -6119,6 +6356,7 @@ static int hwsim_new_radio_nl(struct sk_buff *msg, struct genl_info *info) param.reg_strict = info->attrs[HWSIM_ATTR_REG_STRICT_REG]; param.p2p_device = info->attrs[HWSIM_ATTR_SUPPORT_P2P_DEVICE]; + param.nan_device = info->attrs[HWSIM_ATTR_SUPPORT_NAN_DEVICE]; param.channels = channels; param.destroy_on_close = info->attrs[HWSIM_ATTR_DESTROY_RADIO_ON_CLOSE]; @@ -6190,6 +6428,13 @@ static int hwsim_new_radio_nl(struct sk_buff *msg, struct genl_info *info) param.p2p_device = true; } + /* ensure both flag and iftype support is honored */ + if (param.nan_device || + param.iftypes & BIT(NL80211_IFTYPE_NAN)) { + param.iftypes |= BIT(NL80211_IFTYPE_NAN); + param.nan_device = true; + } + if (info->attrs[HWSIM_ATTR_CIPHER_SUPPORT]) { u32 len = nla_len(info->attrs[HWSIM_ATTR_CIPHER_SUPPORT]); @@ -6910,12 +7155,14 @@ static int __init init_mac80211_hwsim(void) } param.p2p_device = support_p2p_device; + param.nan_device = true; param.mlo = mlo; param.multi_radio = multi_radio; param.use_chanctx = channels > 1 || mlo || multi_radio; param.iftypes = HWSIM_IFTYPE_SUPPORT_MASK; if (param.p2p_device) param.iftypes |= BIT(NL80211_IFTYPE_P2P_DEVICE); + param.iftypes |= BIT(NL80211_IFTYPE_NAN); err = mac80211_hwsim_new_radio(NULL, ¶m); if (err < 0) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim.h b/drivers/net/wireless/virtual/mac80211_hwsim.h index fa157c883f7f..c2d06cf852a5 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim.h +++ b/drivers/net/wireless/virtual/mac80211_hwsim.h @@ -3,7 +3,7 @@ * mac80211_hwsim - software simulator of 802.11 radio(s) for mac80211 * Copyright (c) 2008, Jouni Malinen * Copyright (c) 2011, Javier Lopez - * Copyright (C) 2020, 2022-2024 Intel Corporation + * Copyright (C) 2020, 2022-2025 Intel Corporation */ #ifndef __MAC80211_HWSIM_H @@ -160,6 +160,7 @@ enum hwsim_commands { * @HWSIM_ATTR_MULTI_RADIO: Register multiple wiphy radios (flag). * Adds one radio for each band. Number of supported channels will be set for * each radio instead of for the wiphy. + * @HWSIM_ATTR_SUPPORT_NAN_DEVICE: support NAN Device virtual interface (flag) * @__HWSIM_ATTR_MAX: enum limit */ enum hwsim_attrs { @@ -193,6 +194,7 @@ enum hwsim_attrs { HWSIM_ATTR_PMSR_REQUEST, HWSIM_ATTR_PMSR_RESULT, HWSIM_ATTR_MULTI_RADIO, + HWSIM_ATTR_SUPPORT_NAN_DEVICE, __HWSIM_ATTR_MAX, }; #define HWSIM_ATTR_MAX (__HWSIM_ATTR_MAX - 1) From 872e397d62a67843ea09cf6641819cb1a7e5ee98 Mon Sep 17 00:00:00 2001 From: pengdonglin Date: Tue, 16 Sep 2025 12:47:31 +0800 Subject: [PATCH 143/167] wifi: mac80211: Remove redundant rcu_read_lock/unlock() in spin_lock Since commit a8bb74acd8efe ("rcu: Consolidate RCU-sched update-side function definitions") there is no difference between rcu_read_lock(), rcu_read_lock_bh() and rcu_read_lock_sched() in terms of RCU read section and the relevant grace period. That means that spin_lock(), which implies rcu_read_lock_sched(), also implies rcu_read_lock(). There is no need no explicitly start a RCU read section if one has already been started implicitly by spin_lock(). Simplify the code and remove the inner rcu_read_lock() invocation. Cc: Johannes Berg Signed-off-by: pengdonglin Signed-off-by: pengdonglin Link: https://patch.msgid.link/20250916044735.2316171-11-dolinux.peng@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 2 -- net/mac80211/debugfs.c | 2 -- net/mac80211/debugfs_netdev.c | 2 -- net/mac80211/debugfs_sta.c | 2 -- net/mac80211/sta_info.c | 2 -- 5 files changed, 10 deletions(-) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index da15ccfcf4a2..d9aca1c3c097 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -4935,7 +4935,6 @@ static int ieee80211_get_txq_stats(struct wiphy *wiphy, int ret = 0; spin_lock_bh(&local->fq.lock); - rcu_read_lock(); if (wdev) { sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); @@ -4961,7 +4960,6 @@ static int ieee80211_get_txq_stats(struct wiphy *wiphy, } out: - rcu_read_unlock(); spin_unlock_bh(&local->fq.lock); return ret; diff --git a/net/mac80211/debugfs.c b/net/mac80211/debugfs.c index e8b78ec682da..82099f4cedbe 100644 --- a/net/mac80211/debugfs.c +++ b/net/mac80211/debugfs.c @@ -82,7 +82,6 @@ static ssize_t aqm_read(struct file *file, int len = 0; spin_lock_bh(&local->fq.lock); - rcu_read_lock(); len = scnprintf(buf, sizeof(buf), "access name value\n" @@ -105,7 +104,6 @@ static ssize_t aqm_read(struct file *file, fq->limit, fq->quantum); - rcu_read_unlock(); spin_unlock_bh(&local->fq.lock); return simple_read_from_buffer(user_buf, count, ppos, diff --git a/net/mac80211/debugfs_netdev.c b/net/mac80211/debugfs_netdev.c index 1dac78271045..30a5a978a678 100644 --- a/net/mac80211/debugfs_netdev.c +++ b/net/mac80211/debugfs_netdev.c @@ -625,7 +625,6 @@ static ssize_t ieee80211_if_fmt_aqm( txqi = to_txq_info(sdata->vif.txq); spin_lock_bh(&local->fq.lock); - rcu_read_lock(); len = scnprintf(buf, buflen, @@ -642,7 +641,6 @@ static ssize_t ieee80211_if_fmt_aqm( txqi->tin.tx_bytes, txqi->tin.tx_packets); - rcu_read_unlock(); spin_unlock_bh(&local->fq.lock); return len; diff --git a/net/mac80211/debugfs_sta.c b/net/mac80211/debugfs_sta.c index 49061bd4151b..ef75255d47d5 100644 --- a/net/mac80211/debugfs_sta.c +++ b/net/mac80211/debugfs_sta.c @@ -148,7 +148,6 @@ static ssize_t sta_aqm_read(struct file *file, char __user *userbuf, return -ENOMEM; spin_lock_bh(&local->fq.lock); - rcu_read_lock(); p += scnprintf(p, bufsz + buf - p, @@ -178,7 +177,6 @@ static ssize_t sta_aqm_read(struct file *file, char __user *userbuf, test_bit(IEEE80211_TXQ_DIRTY, &txqi->flags) ? " DIRTY" : ""); } - rcu_read_unlock(); spin_unlock_bh(&local->fq.lock); rv = simple_read_from_buffer(userbuf, count, ppos, buf, p - buf); diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index 1bd75e0375a0..f4d3b67fda06 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -2637,13 +2637,11 @@ static void sta_set_tidstats(struct sta_info *sta, if (link_id < 0 && tid < IEEE80211_NUM_TIDS) { spin_lock_bh(&local->fq.lock); - rcu_read_lock(); tidstats->filled |= BIT(NL80211_TID_STATS_TXQ_STATS); ieee80211_fill_txq_stats(&tidstats->txq_stats, to_txq_info(sta->sta.txq[tid])); - rcu_read_unlock(); spin_unlock_bh(&local->fq.lock); } } From ccdc96fa0ed888e89e617fffdd5c11915568d7a0 Mon Sep 17 00:00:00 2001 From: Sarika Sharma Date: Thu, 18 Sep 2025 09:38:46 +0530 Subject: [PATCH 144/167] wifi: mac80211: remove tx_handlers_drop debugfs stats Commit 906a5a8c7152 ("wifi: mac80211: add tx_handlers_drop statistics to ethtool") added a tx_handlers_drop counter to ethtool stats. During review [1], Johannes noted that the existing debugfs counter is now redundant. Remove the debugfs stat to avoid duplication and streamline statistics reporting. Link: https://lore.kernel.org/linux-wireless/ce5f2bd899caa2de32f36ce554d9cada073979c0.camel@sipsolutions.net/ # [1] Signed-off-by: Sarika Sharma Link: https://patch.msgid.link/20250918040846.4032734-1-quic_sarishar@quicinc.com Signed-off-by: Johannes Berg --- net/mac80211/debugfs.c | 1 - net/mac80211/ieee80211_i.h | 1 - net/mac80211/tx.c | 2 -- 3 files changed, 4 deletions(-) diff --git a/net/mac80211/debugfs.c b/net/mac80211/debugfs.c index 82099f4cedbe..d02f07368c51 100644 --- a/net/mac80211/debugfs.c +++ b/net/mac80211/debugfs.c @@ -715,7 +715,6 @@ void debugfs_hw_add(struct ieee80211_local *local) DEBUGFS_STATS_ADD(dot11ReceivedFragmentCount); DEBUGFS_STATS_ADD(dot11MulticastReceivedFrameCount); DEBUGFS_STATS_ADD(dot11TransmittedFrameCount); - DEBUGFS_STATS_ADD(tx_handlers_drop); DEBUGFS_STATS_ADD(tx_handlers_queued); DEBUGFS_STATS_ADD(tx_handlers_drop_wep); DEBUGFS_STATS_ADD(tx_handlers_drop_not_assoc); diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 242cb109b232..414058bced1a 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -1610,7 +1610,6 @@ struct ieee80211_local { u32 dot11TransmittedFrameCount; /* TX/RX handler statistics */ - unsigned int tx_handlers_drop; unsigned int tx_handlers_queued; unsigned int tx_handlers_drop_wep; unsigned int tx_handlers_drop_not_assoc; diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index ba51198be94a..e7b141c55f7a 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -1821,7 +1821,6 @@ static int invoke_tx_handlers_early(struct ieee80211_tx_data *tx) txh_done: if (unlikely(res == TX_DROP)) { tx->sdata->tx_handlers_drop++; - I802_DEBUG_INC(tx->local->tx_handlers_drop); if (tx->skb) ieee80211_free_txskb(&tx->local->hw, tx->skb); else @@ -1866,7 +1865,6 @@ static int invoke_tx_handlers_late(struct ieee80211_tx_data *tx) txh_done: if (unlikely(res == TX_DROP)) { tx->sdata->tx_handlers_drop++; - I802_DEBUG_INC(tx->local->tx_handlers_drop); if (tx->skb) ieee80211_free_txskb(&tx->local->hw, tx->skb); else From d0688dc2b172d19e20fdb8be8c37930da12aaf88 Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Thu, 18 Sep 2025 15:19:11 +1000 Subject: [PATCH 145/167] wifi: cfg80211: correctly implement and validate S1G chandef Currently, the S1G channelisation implementation differs from that of VHT, which is the PHY that S1G is based on. The major difference between the clock rate is 1/10th of VHT. However how their channelisation is represented within cfg80211 and mac80211 vastly differ. To rectify this, remove the use of IEEE80211_CHAN_1/2/4.. flags that were previously used to indicate the control channel width, however it should be implied that the control channels are 1MHz in the case of S1G. Additionally, introduce the invert - being IEEE80211_CHAN_NO_4/8/16MHz - that imply the control channel may not be used for a certain bandwidth. With these new flags, we can perform regulatory and chandef validation just as we would for VHT. To deal with the notion that S1G PHYs may contain a 2MHz primary channel, introduce a new variable, s1g_primary_2mhz, which indicates whether we are operating on a 2MHz primary channel. In this case, the chandef::chan points to the 1MHz primary channel pointed to by the primary channel location. Alongside this, introduce some new helper routines that can extract the sibling 1MHz channel. The sibling being the alternate 1MHz primary subchannel within the 2MHz primary channel that is not pointed to by chandef::chan. Furthermore, due to unique restrictions imposed on S1G PHYs, introduce a new flag, IEEE80211_CHAN_S1G_NO_PRIMARY, which states that the 1MHz channel cannot be used as a primary channel. This is assumed to be set by vendors as it is hardware and regdom specific, When we validate a 2MHz primary channel, we need to ensure both 1MHz subchannels do not contain this flag. If one or both of the 1MHz subchannels contain this flag then the 2MHz primary is not permitted for use as a primary channel. Properly integrate S1G channel validation such that it is implemented according with other PHY types such as VHT. Additionally, implement a new S1G-specific regulatory flag to allow cfg80211 to understand specific vendor requirements for S1G PHYs. Signed-off-by: Arien Judge Signed-off-by: Andrew Pope Signed-off-by: Lachlan Hodges Link: https://patch.msgid.link/20250918051913.500781-2-lachlan.hodges@morsemicro.com [remove redundant NL80211_ATTR_S1G_PRIMARY_2MHZ check] Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 95 ++++++++++++++++++++++++++++++++ include/uapi/linux/nl80211.h | 15 +++++ net/wireless/chan.c | 103 ++++++++++++++++++++++++----------- net/wireless/nl80211.c | 37 ++++++++----- net/wireless/reg.c | 74 ++++++++----------------- 5 files changed, 224 insertions(+), 100 deletions(-) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 26fd42e189ce..2d612c760dd1 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -129,6 +129,13 @@ struct wiphy; * with very low power (VLP), even if otherwise set to NO_IR. * @IEEE80211_CHAN_ALLOW_20MHZ_ACTIVITY: Allow activity on a 20 MHz channel, * even if otherwise set to NO_IR. + * @IEEE80211_CHAN_S1G_NO_PRIMARY: Prevents the channel for use as an S1G + * primary channel. Does not prevent the wider operating channel + * described by the chandef from being used. In order for a 2MHz primary + * to be used, both 1MHz subchannels shall not contain this flag. + * @IEEE80211_CHAN_NO_4MHZ: 4 MHz bandwidth is not permitted on this channel. + * @IEEE80211_CHAN_NO_8MHZ: 8 MHz bandwidth is not permitted on this channel. + * @IEEE80211_CHAN_NO_16MHZ: 16 MHz bandwidth is not permitted on this channel. */ enum ieee80211_channel_flags { IEEE80211_CHAN_DISABLED = BIT(0), @@ -158,6 +165,10 @@ enum ieee80211_channel_flags { IEEE80211_CHAN_CAN_MONITOR = BIT(24), IEEE80211_CHAN_ALLOW_6GHZ_VLP_AP = BIT(25), IEEE80211_CHAN_ALLOW_20MHZ_ACTIVITY = BIT(26), + IEEE80211_CHAN_S1G_NO_PRIMARY = BIT(27), + IEEE80211_CHAN_NO_4MHZ = BIT(28), + IEEE80211_CHAN_NO_8MHZ = BIT(29), + IEEE80211_CHAN_NO_16MHZ = BIT(30), }; #define IEEE80211_CHAN_NO_HT40 \ @@ -821,6 +832,9 @@ struct key_params { * @punctured: mask of the punctured 20 MHz subchannels, with * bits turned on being disabled (punctured); numbered * from lower to higher frequency (like in the spec) + * @s1g_primary_2mhz: Indicates if the control channel pointed to + * by 'chan' exists as a 1MHz primary subchannel within an + * S1G 2MHz primary channel. */ struct cfg80211_chan_def { struct ieee80211_channel *chan; @@ -830,6 +844,7 @@ struct cfg80211_chan_def { struct ieee80211_edmg edmg; u16 freq1_offset; u16 punctured; + bool s1g_primary_2mhz; }; /* @@ -990,6 +1005,18 @@ cfg80211_chandef_is_edmg(const struct cfg80211_chan_def *chandef) return chandef->edmg.channels || chandef->edmg.bw_config; } +/** + * cfg80211_chandef_is_s1g - check if chandef represents an S1G channel + * @chandef: the channel definition + * + * Return: %true if S1G. + */ +static inline bool +cfg80211_chandef_is_s1g(const struct cfg80211_chan_def *chandef) +{ + return chandef->chan->band == NL80211_BAND_S1GHZ; +} + /** * cfg80211_chandef_compatible - check if two channel definitions are compatible * @chandef1: first channel definition @@ -10179,4 +10206,72 @@ ssize_t wiphy_locked_debugfs_write(struct wiphy *wiphy, struct file *file, void *data); #endif +/** + * cfg80211_s1g_get_start_freq_khz - get S1G chandef start frequency + * @chandef: the chandef to use + * + * Return: the chandefs starting frequency in KHz + */ +static inline u32 +cfg80211_s1g_get_start_freq_khz(const struct cfg80211_chan_def *chandef) +{ + u32 bw_mhz = cfg80211_chandef_get_width(chandef); + u32 center_khz = + MHZ_TO_KHZ(chandef->center_freq1) + chandef->freq1_offset; + return center_khz - bw_mhz * 500 + 500; +} + +/** + * cfg80211_s1g_get_end_freq_khz - get S1G chandef end frequency + * @chandef: the chandef to use + * + * Return: the chandefs ending frequency in KHz + */ +static inline u32 +cfg80211_s1g_get_end_freq_khz(const struct cfg80211_chan_def *chandef) +{ + u32 bw_mhz = cfg80211_chandef_get_width(chandef); + u32 center_khz = + MHZ_TO_KHZ(chandef->center_freq1) + chandef->freq1_offset; + return center_khz + bw_mhz * 500 - 500; +} + +/** + * cfg80211_s1g_get_primary_sibling - retrieve the sibling 1MHz subchannel + * for an S1G chandef using a 2MHz primary channel. + * @wiphy: wiphy the channel belongs to + * @chandef: the chandef to use + * + * When chandef::s1g_primary_2mhz is set to true, we are operating on a 2MHz + * primary channel. The 1MHz subchannel designated by the primary channel + * location exists within chandef::chan, whilst the 'sibling' is denoted as + * being the other 1MHz subchannel that make up the 2MHz primary channel. + * + * Returns: the sibling 1MHz &struct ieee80211_channel, or %NULL on failure. + */ +static inline struct ieee80211_channel * +cfg80211_s1g_get_primary_sibling(struct wiphy *wiphy, + const struct cfg80211_chan_def *chandef) +{ + int width_mhz = cfg80211_chandef_get_width(chandef); + u32 pri_1mhz_khz, sibling_1mhz_khz, op_low_1mhz_khz, pri_index; + + if (!chandef->s1g_primary_2mhz || width_mhz < 2) + return NULL; + + pri_1mhz_khz = ieee80211_channel_to_khz(chandef->chan); + op_low_1mhz_khz = cfg80211_s1g_get_start_freq_khz(chandef); + + /* + * Compute the index of the primary 1 MHz subchannel within the + * operating channel, relative to the lowest 1 MHz center frequency. + * Flip the least significant bit to select the even/odd sibling, + * then translate that index back into a channel frequency. + */ + pri_index = (pri_1mhz_khz - op_low_1mhz_khz) / 1000; + sibling_1mhz_khz = op_low_1mhz_khz + ((pri_index ^ 1) * 1000); + + return ieee80211_get_channel_khz(wiphy, sibling_1mhz_khz); +} + #endif /* __NET_CFG80211_H */ diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 423e258cdbd2..8134f10e4e6c 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -2969,6 +2969,10 @@ enum nl80211_commands { * capabilities supported by the driver. See &enum nl80211_nan_capabilities * for details. * + * @NL80211_ATTR_S1G_PRIMARY_2MHZ: flag attribute indicating that the S1G + * primary channel is 2 MHz wide, and the control channel designates + * the 1 MHz primary subchannel within that 2 MHz primary. + * * @NUM_NL80211_ATTR: total number of nl80211_attrs available * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use @@ -3535,6 +3539,8 @@ enum nl80211_attrs { NL80211_ATTR_NAN_NEW_CLUSTER, NL80211_ATTR_NAN_CAPABILITIES, + NL80211_ATTR_S1G_PRIMARY_2MHZ, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, @@ -4432,6 +4438,12 @@ enum nl80211_wmm_rule { * very low power (VLP) AP, despite being NO_IR. * @NL80211_FREQUENCY_ATTR_ALLOW_20MHZ_ACTIVITY: This channel can be active in * 20 MHz bandwidth, despite being NO_IR. + * @NL80211_FREQUENCY_ATTR_NO_4MHZ: 4 MHz operation is not allowed on this + * channel in current regulatory domain. + * @NL80211_FREQUENCY_ATTR_NO_8MHZ: 8 MHz operation is not allowed on this + * channel in current regulatory domain. + * @NL80211_FREQUENCY_ATTR_NO_16MHZ: 16 MHz operation is not allowed on this + * channel in current regulatory domain. * @NL80211_FREQUENCY_ATTR_MAX: highest frequency attribute number * currently defined * @__NL80211_FREQUENCY_ATTR_AFTER_LAST: internal use @@ -4477,6 +4489,9 @@ enum nl80211_frequency_attr { NL80211_FREQUENCY_ATTR_CAN_MONITOR, NL80211_FREQUENCY_ATTR_ALLOW_6GHZ_VLP_AP, NL80211_FREQUENCY_ATTR_ALLOW_20MHZ_ACTIVITY, + NL80211_FREQUENCY_ATTR_NO_4MHZ, + NL80211_FREQUENCY_ATTR_NO_8MHZ, + NL80211_FREQUENCY_ATTR_NO_16MHZ, /* keep last */ __NL80211_FREQUENCY_ATTR_AFTER_LAST, diff --git a/net/wireless/chan.c b/net/wireless/chan.c index 193734b7f9dc..68221b1ab45e 100644 --- a/net/wireless/chan.c +++ b/net/wireless/chan.c @@ -100,6 +100,11 @@ static u32 cfg80211_get_end_freq(const struct cfg80211_chan_def *chandef, punctured = 0) : (punctured >>= 1))) \ if (!(punctured & 1)) +#define for_each_s1g_subchan(chandef, freq_khz) \ + for (freq_khz = cfg80211_s1g_get_start_freq_khz(chandef); \ + freq_khz <= cfg80211_s1g_get_end_freq_khz(chandef); \ + freq_khz += MHZ_TO_KHZ(1)) + struct cfg80211_per_bw_puncturing_values { u8 len; const u16 *valid_values; @@ -336,8 +341,7 @@ static bool cfg80211_valid_center_freq(u32 center, bool cfg80211_chandef_valid(const struct cfg80211_chan_def *chandef) { - u32 control_freq, oper_freq; - int oper_width, control_width; + u32 control_freq, control_freq_khz, start_khz, end_khz; if (!chandef->chan) return false; @@ -363,27 +367,16 @@ bool cfg80211_chandef_valid(const struct cfg80211_chan_def *chandef) case NL80211_CHAN_WIDTH_4: case NL80211_CHAN_WIDTH_8: case NL80211_CHAN_WIDTH_16: - if (chandef->chan->band != NL80211_BAND_S1GHZ) - return false; - - control_freq = ieee80211_channel_to_khz(chandef->chan); - oper_freq = ieee80211_chandef_to_khz(chandef); - control_width = nl80211_chan_width_to_mhz( - ieee80211_s1g_channel_width( - chandef->chan)); - oper_width = cfg80211_chandef_get_width(chandef); - - if (oper_width < 0 || control_width < 0) + if (!cfg80211_chandef_is_s1g(chandef)) return false; if (chandef->center_freq2) return false; - if (control_freq + MHZ_TO_KHZ(control_width) / 2 > - oper_freq + MHZ_TO_KHZ(oper_width) / 2) - return false; + control_freq_khz = ieee80211_channel_to_khz(chandef->chan); + start_khz = cfg80211_s1g_get_start_freq_khz(chandef); + end_khz = cfg80211_s1g_get_end_freq_khz(chandef); - if (control_freq - MHZ_TO_KHZ(control_width) / 2 < - oper_freq - MHZ_TO_KHZ(oper_width) / 2) + if (control_freq_khz < start_khz || control_freq_khz > end_khz) return false; break; case NL80211_CHAN_WIDTH_80P80: @@ -461,6 +454,9 @@ bool cfg80211_chandef_valid(const struct cfg80211_chan_def *chandef) !cfg80211_edmg_chandef_valid(chandef)) return false; + if (!cfg80211_chandef_is_s1g(chandef) && chandef->s1g_primary_2mhz) + return false; + return valid_puncturing_bitmap(chandef); } EXPORT_SYMBOL(cfg80211_chandef_valid); @@ -725,6 +721,10 @@ static int cfg80211_get_chans_dfs_required(struct wiphy *wiphy, { struct ieee80211_channel *c; + /* DFS is not required for S1G */ + if (cfg80211_chandef_is_s1g(chandef)) + return 0; + for_each_subchan(chandef, freq, cf) { c = ieee80211_get_channel_khz(wiphy, freq); if (!c) @@ -1130,6 +1130,55 @@ static bool cfg80211_edmg_usable(struct wiphy *wiphy, u8 edmg_channels, return true; } +static bool cfg80211_s1g_usable(struct wiphy *wiphy, + const struct cfg80211_chan_def *chandef) +{ + u32 freq_khz; + const struct ieee80211_channel *chan; + u32 pri_khz = ieee80211_channel_to_khz(chandef->chan); + u32 end_khz = cfg80211_s1g_get_end_freq_khz(chandef); + u32 start_khz = cfg80211_s1g_get_start_freq_khz(chandef); + int width_mhz = cfg80211_chandef_get_width(chandef); + u32 prohibited_flags = IEEE80211_CHAN_DISABLED; + + if (width_mhz >= 16) + prohibited_flags |= IEEE80211_CHAN_NO_16MHZ; + if (width_mhz >= 8) + prohibited_flags |= IEEE80211_CHAN_NO_8MHZ; + if (width_mhz >= 4) + prohibited_flags |= IEEE80211_CHAN_NO_4MHZ; + + if (chandef->chan->flags & IEEE80211_CHAN_S1G_NO_PRIMARY) + return false; + + if (pri_khz < start_khz || pri_khz > end_khz) + return false; + + for_each_s1g_subchan(chandef, freq_khz) { + chan = ieee80211_get_channel_khz(wiphy, freq_khz); + if (!chan || (chan->flags & prohibited_flags)) + return false; + } + + if (chandef->s1g_primary_2mhz) { + u32 sib_khz; + const struct ieee80211_channel *sibling; + + sibling = cfg80211_s1g_get_primary_sibling(wiphy, chandef); + if (!sibling) + return false; + + if (sibling->flags & IEEE80211_CHAN_S1G_NO_PRIMARY) + return false; + + sib_khz = ieee80211_channel_to_khz(sibling); + if (sib_khz < start_khz || sib_khz > end_khz) + return false; + } + + return true; +} + bool _cfg80211_chandef_usable(struct wiphy *wiphy, const struct cfg80211_chan_def *chandef, u32 prohibited_flags, @@ -1154,6 +1203,9 @@ bool _cfg80211_chandef_usable(struct wiphy *wiphy, ext_nss_cap = __le16_to_cpu(vht_cap->vht_mcs.tx_highest) & IEEE80211_VHT_EXT_NSS_BW_CAPABLE; + if (cfg80211_chandef_is_s1g(chandef)) + return cfg80211_s1g_usable(wiphy, chandef); + if (edmg_cap->channels && !cfg80211_edmg_usable(wiphy, chandef->edmg.channels, @@ -1165,21 +1217,6 @@ bool _cfg80211_chandef_usable(struct wiphy *wiphy, control_freq = chandef->chan->center_freq; switch (chandef->width) { - case NL80211_CHAN_WIDTH_1: - width = 1; - break; - case NL80211_CHAN_WIDTH_2: - width = 2; - break; - case NL80211_CHAN_WIDTH_4: - width = 4; - break; - case NL80211_CHAN_WIDTH_8: - width = 8; - break; - case NL80211_CHAN_WIDTH_16: - width = 16; - break; case NL80211_CHAN_WIDTH_5: width = 5; break; diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 4e0d40865441..de34a1d14073 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -931,6 +931,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = { [NL80211_ATTR_S1G_SHORT_BEACON] = NLA_POLICY_NESTED(nl80211_s1g_short_beacon), [NL80211_ATTR_BSS_PARAM] = { .type = NLA_FLAG }, + [NL80211_ATTR_S1G_PRIMARY_2MHZ] = { .type = NLA_FLAG }, }; /* policy for the key attributes */ @@ -1319,6 +1320,15 @@ static int nl80211_msg_put_channel(struct sk_buff *msg, struct wiphy *wiphy, nla_put_flag(msg, NL80211_FREQUENCY_ATTR_ALLOW_20MHZ_ACTIVITY)) goto nla_put_failure; + if ((chan->flags & IEEE80211_CHAN_NO_4MHZ) && + nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_4MHZ)) + goto nla_put_failure; + if ((chan->flags & IEEE80211_CHAN_NO_8MHZ) && + nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_8MHZ)) + goto nla_put_failure; + if ((chan->flags & IEEE80211_CHAN_NO_16MHZ) && + nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_16MHZ)) + goto nla_put_failure; } if (nla_put_u32(msg, NL80211_FREQUENCY_ATTR_MAX_TX_POWER, @@ -3541,6 +3551,7 @@ static int _nl80211_parse_chandef(struct cfg80211_registered_device *rdev, chandef->center_freq1 = KHZ_TO_MHZ(control_freq); chandef->freq1_offset = control_freq % 1000; chandef->center_freq2 = 0; + chandef->s1g_primary_2mhz = false; if (!chandef->chan) { NL_SET_ERR_MSG_ATTR(extack, attrs[NL80211_ATTR_WIPHY_FREQ], @@ -3584,27 +3595,20 @@ static int _nl80211_parse_chandef(struct cfg80211_registered_device *rdev, return -EINVAL; } } else if (attrs[NL80211_ATTR_CHANNEL_WIDTH]) { - chandef->width = - nla_get_u32(attrs[NL80211_ATTR_CHANNEL_WIDTH]); - if (chandef->chan->band == NL80211_BAND_S1GHZ) { - /* User input error for channel width doesn't match channel */ - if (chandef->width != ieee80211_s1g_channel_width(chandef->chan)) { - NL_SET_ERR_MSG_ATTR(extack, - attrs[NL80211_ATTR_CHANNEL_WIDTH], - "bad channel width"); - return -EINVAL; - } - } + chandef->width = nla_get_u32(attrs[NL80211_ATTR_CHANNEL_WIDTH]); if (attrs[NL80211_ATTR_CENTER_FREQ1]) { chandef->center_freq1 = nla_get_u32(attrs[NL80211_ATTR_CENTER_FREQ1]); - chandef->freq1_offset = - nla_get_u32_default(attrs[NL80211_ATTR_CENTER_FREQ1_OFFSET], - 0); + chandef->freq1_offset = nla_get_u32_default( + attrs[NL80211_ATTR_CENTER_FREQ1_OFFSET], 0); } + if (attrs[NL80211_ATTR_CENTER_FREQ2]) chandef->center_freq2 = nla_get_u32(attrs[NL80211_ATTR_CENTER_FREQ2]); + + chandef->s1g_primary_2mhz = nla_get_flag( + attrs[NL80211_ATTR_S1G_PRIMARY_2MHZ]); } if (info->attrs[NL80211_ATTR_WIPHY_EDMG_CHANNELS]) { @@ -10455,8 +10459,9 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info) goto out_free; } - /* ignore disabled channels */ + /* Ignore disabled / no primary channels */ if (chan->flags & IEEE80211_CHAN_DISABLED || + chan->flags & IEEE80211_CHAN_S1G_NO_PRIMARY || !cfg80211_wdev_channel_allowed(wdev, chan)) continue; @@ -10478,6 +10483,8 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info) chan = &wiphy->bands[band]->channels[j]; if (chan->flags & IEEE80211_CHAN_DISABLED || + chan->flags & + IEEE80211_CHAN_S1G_NO_PRIMARY || !cfg80211_wdev_channel_allowed(wdev, chan)) continue; diff --git a/net/wireless/reg.c b/net/wireless/reg.c index 3b0ac3437f81..73cab51f6379 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -1707,6 +1707,16 @@ static uint32_t reg_rule_to_chan_bw_flags(const struct ieee80211_regdomain *regd if (reg_rule->flags & NL80211_RRF_AUTO_BW) max_bandwidth_khz = reg_get_max_bandwidth(regd, reg_rule); + if (is_s1g) { + if (max_bandwidth_khz < MHZ_TO_KHZ(16)) + bw_flags |= IEEE80211_CHAN_NO_16MHZ; + if (max_bandwidth_khz < MHZ_TO_KHZ(8)) + bw_flags |= IEEE80211_CHAN_NO_8MHZ; + if (max_bandwidth_khz < MHZ_TO_KHZ(4)) + bw_flags |= IEEE80211_CHAN_NO_4MHZ; + return bw_flags; + } + /* If we get a reg_rule we can assume that at least 5Mhz fit */ if (!cfg80211_does_bw_fit_range(freq_range, center_freq_khz, @@ -1717,59 +1727,19 @@ static uint32_t reg_rule_to_chan_bw_flags(const struct ieee80211_regdomain *regd MHZ_TO_KHZ(20))) bw_flags |= IEEE80211_CHAN_NO_20MHZ; - if (is_s1g) { - /* S1G is strict about non overlapping channels. We can - * calculate which bandwidth is allowed per channel by finding - * the largest bandwidth which cleanly divides the freq_range. - */ - int edge_offset; - int ch_bw = max_bandwidth_khz; + if (max_bandwidth_khz < MHZ_TO_KHZ(10)) + bw_flags |= IEEE80211_CHAN_NO_10MHZ; + if (max_bandwidth_khz < MHZ_TO_KHZ(20)) + bw_flags |= IEEE80211_CHAN_NO_20MHZ; + if (max_bandwidth_khz < MHZ_TO_KHZ(40)) + bw_flags |= IEEE80211_CHAN_NO_HT40; + if (max_bandwidth_khz < MHZ_TO_KHZ(80)) + bw_flags |= IEEE80211_CHAN_NO_80MHZ; + if (max_bandwidth_khz < MHZ_TO_KHZ(160)) + bw_flags |= IEEE80211_CHAN_NO_160MHZ; + if (max_bandwidth_khz < MHZ_TO_KHZ(320)) + bw_flags |= IEEE80211_CHAN_NO_320MHZ; - while (ch_bw) { - edge_offset = (center_freq_khz - ch_bw / 2) - - freq_range->start_freq_khz; - if (edge_offset % ch_bw == 0) { - switch (KHZ_TO_MHZ(ch_bw)) { - case 1: - bw_flags |= IEEE80211_CHAN_1MHZ; - break; - case 2: - bw_flags |= IEEE80211_CHAN_2MHZ; - break; - case 4: - bw_flags |= IEEE80211_CHAN_4MHZ; - break; - case 8: - bw_flags |= IEEE80211_CHAN_8MHZ; - break; - case 16: - bw_flags |= IEEE80211_CHAN_16MHZ; - break; - default: - /* If we got here, no bandwidths fit on - * this frequency, ie. band edge. - */ - bw_flags |= IEEE80211_CHAN_DISABLED; - break; - } - break; - } - ch_bw /= 2; - } - } else { - if (max_bandwidth_khz < MHZ_TO_KHZ(10)) - bw_flags |= IEEE80211_CHAN_NO_10MHZ; - if (max_bandwidth_khz < MHZ_TO_KHZ(20)) - bw_flags |= IEEE80211_CHAN_NO_20MHZ; - if (max_bandwidth_khz < MHZ_TO_KHZ(40)) - bw_flags |= IEEE80211_CHAN_NO_HT40; - if (max_bandwidth_khz < MHZ_TO_KHZ(80)) - bw_flags |= IEEE80211_CHAN_NO_80MHZ; - if (max_bandwidth_khz < MHZ_TO_KHZ(160)) - bw_flags |= IEEE80211_CHAN_NO_160MHZ; - if (max_bandwidth_khz < MHZ_TO_KHZ(320)) - bw_flags |= IEEE80211_CHAN_NO_320MHZ; - } return bw_flags; } From 31e7681da78d7e8d2d83185c0e640012a018f229 Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Thu, 18 Sep 2025 15:19:12 +1000 Subject: [PATCH 146/167] wifi: mac80211: correctly initialise S1G chandef for STA When moving to the APs channel, ensure we correctly initialise the chandef and perform the required validation. Additionally, if the AP is beaconing on a 2MHz primary, calculate the 2MHz primary center frequency by extracting the sibling 1MHz primary and averaging the frequencies to find the 2MHz primary center frequency. Signed-off-by: Lachlan Hodges Link: https://patch.msgid.link/20250918051913.500781-3-lachlan.hodges@morsemicro.com Signed-off-by: Johannes Berg --- include/linux/ieee80211.h | 18 ++++++++++++- net/mac80211/ieee80211_i.h | 3 ++- net/mac80211/main.c | 6 +++-- net/mac80211/mlme.c | 53 +++++++++++++++++++++++++++++++++----- net/mac80211/scan.c | 13 +++++----- net/mac80211/util.c | 39 +++++++++++++++++++++++----- 6 files changed, 109 insertions(+), 23 deletions(-) diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h index 2110345de8ef..ddff9102f633 100644 --- a/include/linux/ieee80211.h +++ b/include/linux/ieee80211.h @@ -1182,6 +1182,18 @@ enum ieee80211_s1g_chanwidth { IEEE80211_S1G_CHANWIDTH_16MHZ = 15, }; +/** + * enum ieee80211_s1g_pri_chanwidth - S1G primary channel widths + * described in IEEE80211-2024 Table 10-39. + * + * @IEEE80211_S1G_PRI_CHANWIDTH_2MHZ: 2MHz primary channel + * @IEEE80211_S1G_PRI_CHANWIDTH_1MHZ: 1MHz primary channel + */ +enum ieee80211_s1g_pri_chanwidth { + IEEE80211_S1G_PRI_CHANWIDTH_2MHZ = 0, + IEEE80211_S1G_PRI_CHANWIDTH_1MHZ = 1, +}; + #define WLAN_SA_QUERY_TR_ID_LEN 2 #define WLAN_MEMBERSHIP_LEN 8 #define WLAN_USER_POSITION_LEN 16 @@ -3170,8 +3182,12 @@ ieee80211_he_spr_size(const u8 *he_spr_ie) #define S1G_CAP9_LINK_ADAPT_PER_CONTROL_RESPONSE BIT(0) -#define S1G_OPER_CH_WIDTH_PRIMARY_1MHZ BIT(0) +#define S1G_OPER_CH_WIDTH_PRIMARY BIT(0) #define S1G_OPER_CH_WIDTH_OPER GENMASK(4, 1) +#define S1G_OPER_CH_PRIMARY_LOCATION BIT(5) + +#define S1G_2M_PRIMARY_LOCATION_LOWER 0 +#define S1G_2M_PRIMARY_LOCATION_UPPER 1 /* EHT MAC capabilities as defined in P802.11be_D2.0 section 9.4.2.313.2 */ #define IEEE80211_EHT_MAC_CAP0_EPCS_PRIO_ACCESS 0x01 diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 414058bced1a..73fd86ec1bce 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -2710,7 +2710,8 @@ bool ieee80211_chandef_he_6ghz_oper(struct ieee80211_local *local, const struct ieee80211_he_operation *he_oper, const struct ieee80211_eht_operation *eht_oper, struct cfg80211_chan_def *chandef); -bool ieee80211_chandef_s1g_oper(const struct ieee80211_s1g_oper_ie *oper, +bool ieee80211_chandef_s1g_oper(struct ieee80211_local *local, + const struct ieee80211_s1g_oper_ie *oper, struct cfg80211_chan_def *chandef); void ieee80211_chandef_downgrade(struct cfg80211_chan_def *chandef, struct ieee80211_conn_settings *conn); diff --git a/net/mac80211/main.c b/net/mac80211/main.c index 27b3ec5deabe..eefa6f7e899b 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -1249,11 +1249,13 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) if (!dflt_chandef.chan) { /* * Assign the first enabled channel to dflt_chandef - * from the list of channels + * from the list of channels. For S1G interfaces + * ensure it can be used as a primary. */ for (i = 0; i < sband->n_channels; i++) if (!(sband->channels[i].flags & - IEEE80211_CHAN_DISABLED)) + (IEEE80211_CHAN_DISABLED | + IEEE80211_CHAN_S1G_NO_PRIMARY))) break; /* if none found then use the first anyway */ if (i == sband->n_channels) diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 0e12309accbe..3b5827ea438e 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -180,10 +180,11 @@ ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata, /* get special S1G case out of the way */ if (sband->band == NL80211_BAND_S1GHZ) { - if (!ieee80211_chandef_s1g_oper(elems->s1g_oper, chandef)) { - sdata_info(sdata, - "Missing S1G Operation Element? Trying operating == primary\n"); - chandef->width = ieee80211_s1g_channel_width(channel); + if (!ieee80211_chandef_s1g_oper(sdata->local, elems->s1g_oper, + chandef)) { + /* Fallback to default 1MHz */ + chandef->width = NL80211_CHAN_WIDTH_1; + chandef->s1g_primary_2mhz = false; } return IEEE80211_CONN_MODE_S1G; @@ -1046,6 +1047,14 @@ again: ret = -EINVAL; goto free; } + + chanreq->oper = *ap_chandef; + if (!cfg80211_chandef_usable(sdata->wdev.wiphy, &chanreq->oper, + IEEE80211_CHAN_DISABLED)) { + ret = -EINVAL; + goto free; + } + return elems; case NL80211_BAND_6GHZ: if (ap_mode < IEEE80211_CONN_MODE_HE) { @@ -7292,6 +7301,38 @@ static bool ieee80211_mgd_ssid_mismatch(struct ieee80211_sub_if_data *sdata, return memcmp(elems->ssid, cfg->ssid, cfg->ssid_len); } +static bool +ieee80211_rx_beacon_freq_valid(struct ieee80211_local *local, + struct ieee80211_mgmt *mgmt, + struct ieee80211_rx_status *rx_status, + struct ieee80211_chanctx_conf *chanctx) +{ + u32 pri_2mhz_khz; + struct ieee80211_channel *s1g_sibling_1mhz; + u32 pri_khz = ieee80211_channel_to_khz(chanctx->def.chan); + u32 rx_khz = ieee80211_rx_status_to_khz(rx_status); + + if (rx_khz == pri_khz) + return true; + + if (!chanctx->def.s1g_primary_2mhz) + return false; + + /* + * If we have an S1G interface with a 2MHz primary, beacons are + * sent on the center frequency of the 2MHz primary. Find the sibling + * 1MHz channel and calculate the 2MHz primary center frequency. + */ + s1g_sibling_1mhz = cfg80211_s1g_get_primary_sibling(local->hw.wiphy, + &chanctx->def); + if (!s1g_sibling_1mhz) + return false; + + pri_2mhz_khz = + (pri_khz + ieee80211_channel_to_khz(s1g_sibling_1mhz)) / 2; + return rx_khz == pri_2mhz_khz; +} + static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link, struct ieee80211_hdr *hdr, size_t len, struct ieee80211_rx_status *rx_status) @@ -7346,8 +7387,8 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link, return; } - if (ieee80211_rx_status_to_khz(rx_status) != - ieee80211_channel_to_khz(chanctx_conf->def.chan)) { + if (!ieee80211_rx_beacon_freq_valid(local, mgmt, rx_status, + chanctx_conf)) { rcu_read_unlock(); return; } diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index dbf98aa4cd67..bb9563f50e7b 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c @@ -996,15 +996,15 @@ static void ieee80211_scan_state_set_channel(struct ieee80211_local *local, local->scan_chandef.freq1_offset = chan->freq_offset; local->scan_chandef.center_freq2 = 0; - /* For scanning on the S1G band, detect the channel width according to - * the channel being scanned. - */ + /* For S1G, only scan the 1MHz primaries. */ if (chan->band == NL80211_BAND_S1GHZ) { - local->scan_chandef.width = ieee80211_s1g_channel_width(chan); + local->scan_chandef.width = NL80211_CHAN_WIDTH_1; + local->scan_chandef.s1g_primary_2mhz = false; goto set_channel; } - /* If scanning on oper channel, use whatever channel-type + /* + * If scanning on oper channel, use whatever channel-type * is currently in use. */ if (chan == local->hw.conf.chandef.chan) @@ -1213,7 +1213,8 @@ int ieee80211_request_ibss_scan(struct ieee80211_sub_if_data *sdata, for (band = 0; band < NUM_NL80211_BANDS; band++) { if (!local->hw.wiphy->bands[band] || - band == NL80211_BAND_6GHZ) + band == NL80211_BAND_6GHZ || + band == NL80211_BAND_S1GHZ) continue; max_n = local->hw.wiphy->bands[band]->n_channels; diff --git a/net/mac80211/util.c b/net/mac80211/util.c index 123842b841f2..c9931537f9d2 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -3199,10 +3199,11 @@ bool ieee80211_chandef_he_6ghz_oper(struct ieee80211_local *local, return true; } -bool ieee80211_chandef_s1g_oper(const struct ieee80211_s1g_oper_ie *oper, +bool ieee80211_chandef_s1g_oper(struct ieee80211_local *local, + const struct ieee80211_s1g_oper_ie *oper, struct cfg80211_chan_def *chandef) { - u32 oper_freq; + u32 oper_khz, pri_1mhz_khz, pri_2mhz_khz; if (!oper) return false; @@ -3227,12 +3228,36 @@ bool ieee80211_chandef_s1g_oper(const struct ieee80211_s1g_oper_ie *oper, return false; } - oper_freq = ieee80211_channel_to_freq_khz(oper->oper_ch, - NL80211_BAND_S1GHZ); - chandef->center_freq1 = KHZ_TO_MHZ(oper_freq); - chandef->freq1_offset = oper_freq % 1000; + chandef->s1g_primary_2mhz = false; - return true; + switch (u8_get_bits(oper->ch_width, S1G_OPER_CH_WIDTH_PRIMARY)) { + case IEEE80211_S1G_PRI_CHANWIDTH_1MHZ: + pri_1mhz_khz = ieee80211_channel_to_freq_khz( + oper->primary_ch, NL80211_BAND_S1GHZ); + break; + case IEEE80211_S1G_PRI_CHANWIDTH_2MHZ: + chandef->s1g_primary_2mhz = true; + pri_2mhz_khz = ieee80211_channel_to_freq_khz( + oper->primary_ch, NL80211_BAND_S1GHZ); + + if (u8_get_bits(oper->ch_width, S1G_OPER_CH_PRIMARY_LOCATION) == + S1G_2M_PRIMARY_LOCATION_LOWER) + pri_1mhz_khz = pri_2mhz_khz - 500; + else + pri_1mhz_khz = pri_2mhz_khz + 500; + break; + default: + return false; + } + + oper_khz = ieee80211_channel_to_freq_khz(oper->oper_ch, + NL80211_BAND_S1GHZ); + chandef->center_freq1 = KHZ_TO_MHZ(oper_khz); + chandef->freq1_offset = oper_khz % 1000; + chandef->chan = + ieee80211_get_channel_khz(local->hw.wiphy, pri_1mhz_khz); + + return chandef->chan; } int ieee80211_put_srates_elem(struct sk_buff *skb, From cbcd507f01deb983d5cad0a25b6495930ab59593 Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Thu, 18 Sep 2025 15:19:13 +1000 Subject: [PATCH 147/167] wifi: cfg80211: remove ieee80211_s1g_channel_width With the introduction of proper S1G channel flags, this function is no longer used. Remove it. Signed-off-by: Lachlan Hodges Link: https://patch.msgid.link/20250918051913.500781-4-lachlan.hodges@morsemicro.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 10 ---------- net/wireless/util.c | 27 --------------------------- 2 files changed, 37 deletions(-) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 2d612c760dd1..1c041ce7a03b 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -6818,16 +6818,6 @@ ieee80211_channel_to_khz(const struct ieee80211_channel *chan) return MHZ_TO_KHZ(chan->center_freq) + chan->freq_offset; } -/** - * ieee80211_s1g_channel_width - get allowed channel width from @chan - * - * Only allowed for band NL80211_BAND_S1GHZ - * @chan: channel - * Return: The allowed channel width for this center_freq - */ -enum nl80211_chan_width -ieee80211_s1g_channel_width(const struct ieee80211_channel *chan); - /** * ieee80211_channel_to_freq_khz - convert channel number to frequency * @chan: channel number diff --git a/net/wireless/util.c b/net/wireless/util.c index d12d49134c88..f26440d18ad3 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -106,33 +106,6 @@ u32 ieee80211_channel_to_freq_khz(int chan, enum nl80211_band band) } EXPORT_SYMBOL(ieee80211_channel_to_freq_khz); -enum nl80211_chan_width -ieee80211_s1g_channel_width(const struct ieee80211_channel *chan) -{ - if (WARN_ON(!chan || chan->band != NL80211_BAND_S1GHZ)) - return NL80211_CHAN_WIDTH_20_NOHT; - - /*S1G defines a single allowed channel width per channel. - * Extract that width here. - */ - if (chan->flags & IEEE80211_CHAN_1MHZ) - return NL80211_CHAN_WIDTH_1; - else if (chan->flags & IEEE80211_CHAN_2MHZ) - return NL80211_CHAN_WIDTH_2; - else if (chan->flags & IEEE80211_CHAN_4MHZ) - return NL80211_CHAN_WIDTH_4; - else if (chan->flags & IEEE80211_CHAN_8MHZ) - return NL80211_CHAN_WIDTH_8; - else if (chan->flags & IEEE80211_CHAN_16MHZ) - return NL80211_CHAN_WIDTH_16; - - pr_err("unknown channel width for channel at %dKHz?\n", - ieee80211_channel_to_khz(chan)); - - return NL80211_CHAN_WIDTH_1; -} -EXPORT_SYMBOL(ieee80211_s1g_channel_width); - int ieee80211_freq_khz_to_channel(u32 freq) { /* TODO: just handle MHz for now */ From 32d340ae675800672e1219444a17940a8efe5cca Mon Sep 17 00:00:00 2001 From: Aditya Kumar Singh Date: Wed, 17 Sep 2025 12:42:03 +0530 Subject: [PATCH 148/167] wifi: mac80211: fix Rx packet handling when pubsta information is not available In ieee80211_rx_handle_packet(), if the caller does not provide pubsta information, an attempt is made to find the station using the address 2 (source address) field in the header. Since pubsta is missing, link information such as link_valid and link_id is also unavailable. Now if such a situation comes, and if a matching ML station entry is found based on the source address, currently the packet is dropped due to missing link ID in the status field which is not correct. Hence, to fix this issue, if link_valid is not set and the station is an ML station, make an attempt to find a link station entry using the source address. If a valid link station is found, derive the link ID and proceed with packet processing. Otherwise, drop the packet as per the existing flow. Fixes: ea9d807b5642 ("wifi: mac80211: add link information in ieee80211_rx_status") Suggested-by: Vasanthakumar Thiagarajan Signed-off-by: Aditya Kumar Singh Link: https://patch.msgid.link/20250917-fix_data_packet_rx_with_mlo_and_no_pubsta-v1-1-8cf971a958ac@oss.qualcomm.com Signed-off-by: Johannes Berg --- net/mac80211/rx.c | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index feb81ffa4f8c..6af43dfefdd6 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -5238,12 +5238,20 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, } rx.sdata = prev_sta->sdata; + if (!status->link_valid && prev_sta->sta.mlo) { + struct link_sta_info *link_sta; + + link_sta = link_sta_info_get_bss(rx.sdata, + hdr->addr2); + if (!link_sta) + continue; + + link_id = link_sta->link_id; + } + if (!ieee80211_rx_data_set_sta(&rx, prev_sta, link_id)) goto out; - if (!status->link_valid && prev_sta->sta.mlo) - continue; - ieee80211_prepare_and_rx_handle(&rx, skb, false); prev_sta = sta; @@ -5251,10 +5259,18 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, if (prev_sta) { rx.sdata = prev_sta->sdata; - if (!ieee80211_rx_data_set_sta(&rx, prev_sta, link_id)) - goto out; + if (!status->link_valid && prev_sta->sta.mlo) { + struct link_sta_info *link_sta; - if (!status->link_valid && prev_sta->sta.mlo) + link_sta = link_sta_info_get_bss(rx.sdata, + hdr->addr2); + if (!link_sta) + goto out; + + link_id = link_sta->link_id; + } + + if (!ieee80211_rx_data_set_sta(&rx, prev_sta, link_id)) goto out; if (ieee80211_prepare_and_rx_handle(&rx, skb, true)) From 7ca61ed8b3f3fc9a7decd68039cb1d7d1238c566 Mon Sep 17 00:00:00 2001 From: Hari Chandrakanthan Date: Thu, 24 Jul 2025 09:35:52 +0530 Subject: [PATCH 149/167] wifi: ath12k: Fix peer lookup in ath12k_dp_mon_rx_deliver_msdu() In ath12k_dp_mon_rx_deliver_msdu(), peer lookup fails because rxcb->peer_id is not updated with a valid value. This is expected in monitor mode, where RX frames bypass the regular RX descriptor path that typically sets rxcb->peer_id. As a result, the peer is NULL, and link_id and link_valid fields in the RX status are not populated. This leads to a WARN_ON in mac80211 when it receives data frame from an associated station with invalid link_id. Fix this potential issue by using ppduinfo->peer_id, which holds the correct peer id for the received frame. This ensures that the peer is correctly found and the associated link metadata is updated accordingly. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ-1 Fixes: bd00cc7e8a4c ("wifi: ath12k: replace the usage of rx desc with rx_info") Signed-off-by: Hari Chandrakanthan Signed-off-by: Aishwarya R Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250724040552.1170642-1-aishwarya.r@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp_mon.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/dp_mon.c b/drivers/net/wireless/ath/ath12k/dp_mon.c index abd611ac37f0..009c49502148 100644 --- a/drivers/net/wireless/ath/ath12k/dp_mon.c +++ b/drivers/net/wireless/ath/ath12k/dp_mon.c @@ -2273,6 +2273,7 @@ static void ath12k_dp_mon_update_radiotap(struct ath12k *ar, static void ath12k_dp_mon_rx_deliver_msdu(struct ath12k *ar, struct napi_struct *napi, struct sk_buff *msdu, + const struct hal_rx_mon_ppdu_info *ppduinfo, struct ieee80211_rx_status *status, u8 decap) { @@ -2286,7 +2287,6 @@ static void ath12k_dp_mon_rx_deliver_msdu(struct ath12k *ar, struct napi_struct struct ieee80211_sta *pubsta = NULL; struct ath12k_peer *peer; struct ath12k_skb_rxcb *rxcb = ATH12K_SKB_RXCB(msdu); - struct ath12k_dp_rx_info rx_info; bool is_mcbc = rxcb->is_mcbc; bool is_eapol_tkip = rxcb->is_eapol; @@ -2300,8 +2300,7 @@ static void ath12k_dp_mon_rx_deliver_msdu(struct ath12k *ar, struct napi_struct } spin_lock_bh(&ar->ab->base_lock); - rx_info.addr2_present = false; - peer = ath12k_dp_rx_h_find_peer(ar->ab, msdu, &rx_info); + peer = ath12k_peer_find_by_id(ar->ab, ppduinfo->peer_id); if (peer && peer->sta) { pubsta = peer->sta; if (pubsta->valid_links) { @@ -2394,7 +2393,7 @@ static int ath12k_dp_mon_rx_deliver(struct ath12k *ar, decap = mon_mpdu->decap_format; ath12k_dp_mon_update_radiotap(ar, ppduinfo, mon_skb, rxs); - ath12k_dp_mon_rx_deliver_msdu(ar, napi, mon_skb, rxs, decap); + ath12k_dp_mon_rx_deliver_msdu(ar, napi, mon_skb, ppduinfo, rxs, decap); mon_skb = skb_next; } while (mon_skb); rxs->flag = 0; From 3e31a6bc07312b448fad3b45de578471f86f0e77 Mon Sep 17 00:00:00 2001 From: Fedor Pchelkin Date: Sat, 20 Sep 2025 00:08:47 +0300 Subject: [PATCH 150/167] wifi: rtw89: fix use-after-free in rtw89_core_tx_kick_off_and_wait() There is a bug observed when rtw89_core_tx_kick_off_and_wait() tries to access already freed skb_data: BUG: KFENCE: use-after-free write in rtw89_core_tx_kick_off_and_wait drivers/net/wireless/realtek/rtw89/core.c:1110 CPU: 6 UID: 0 PID: 41377 Comm: kworker/u64:24 Not tainted 6.17.0-rc1+ #1 PREEMPT(lazy) Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS edk2-20250523-14.fc42 05/23/2025 Workqueue: events_unbound cfg80211_wiphy_work [cfg80211] Use-after-free write at 0x0000000020309d9d (in kfence-#251): rtw89_core_tx_kick_off_and_wait drivers/net/wireless/realtek/rtw89/core.c:1110 rtw89_core_scan_complete drivers/net/wireless/realtek/rtw89/core.c:5338 rtw89_hw_scan_complete_cb drivers/net/wireless/realtek/rtw89/fw.c:7979 rtw89_chanctx_proceed_cb drivers/net/wireless/realtek/rtw89/chan.c:3165 rtw89_chanctx_proceed drivers/net/wireless/realtek/rtw89/chan.h:141 rtw89_hw_scan_complete drivers/net/wireless/realtek/rtw89/fw.c:8012 rtw89_mac_c2h_scanofld_rsp drivers/net/wireless/realtek/rtw89/mac.c:5059 rtw89_fw_c2h_work drivers/net/wireless/realtek/rtw89/fw.c:6758 process_one_work kernel/workqueue.c:3241 worker_thread kernel/workqueue.c:3400 kthread kernel/kthread.c:463 ret_from_fork arch/x86/kernel/process.c:154 ret_from_fork_asm arch/x86/entry/entry_64.S:258 kfence-#251: 0x0000000056e2393d-0x000000009943cb62, size=232, cache=skbuff_head_cache allocated by task 41377 on cpu 6 at 77869.159548s (0.009551s ago): __alloc_skb net/core/skbuff.c:659 __netdev_alloc_skb net/core/skbuff.c:734 ieee80211_nullfunc_get net/mac80211/tx.c:5844 rtw89_core_send_nullfunc drivers/net/wireless/realtek/rtw89/core.c:3431 rtw89_core_scan_complete drivers/net/wireless/realtek/rtw89/core.c:5338 rtw89_hw_scan_complete_cb drivers/net/wireless/realtek/rtw89/fw.c:7979 rtw89_chanctx_proceed_cb drivers/net/wireless/realtek/rtw89/chan.c:3165 rtw89_chanctx_proceed drivers/net/wireless/realtek/rtw89/chan.c:3194 rtw89_hw_scan_complete drivers/net/wireless/realtek/rtw89/fw.c:8012 rtw89_mac_c2h_scanofld_rsp drivers/net/wireless/realtek/rtw89/mac.c:5059 rtw89_fw_c2h_work drivers/net/wireless/realtek/rtw89/fw.c:6758 process_one_work kernel/workqueue.c:3241 worker_thread kernel/workqueue.c:3400 kthread kernel/kthread.c:463 ret_from_fork arch/x86/kernel/process.c:154 ret_from_fork_asm arch/x86/entry/entry_64.S:258 freed by task 1045 on cpu 9 at 77869.168393s (0.001557s ago): ieee80211_tx_status_skb net/mac80211/status.c:1117 rtw89_pci_release_txwd_skb drivers/net/wireless/realtek/rtw89/pci.c:564 rtw89_pci_release_tx_skbs.isra.0 drivers/net/wireless/realtek/rtw89/pci.c:651 rtw89_pci_release_tx drivers/net/wireless/realtek/rtw89/pci.c:676 rtw89_pci_napi_poll drivers/net/wireless/realtek/rtw89/pci.c:4238 __napi_poll net/core/dev.c:7495 net_rx_action net/core/dev.c:7557 net/core/dev.c:7684 handle_softirqs kernel/softirq.c:580 do_softirq.part.0 kernel/softirq.c:480 __local_bh_enable_ip kernel/softirq.c:407 rtw89_pci_interrupt_threadfn drivers/net/wireless/realtek/rtw89/pci.c:927 irq_thread_fn kernel/irq/manage.c:1133 irq_thread kernel/irq/manage.c:1257 kthread kernel/kthread.c:463 ret_from_fork arch/x86/kernel/process.c:154 ret_from_fork_asm arch/x86/entry/entry_64.S:258 It is a consequence of a race between the waiting and the signaling side of the completion: Waiting thread Completing thread rtw89_core_tx_kick_off_and_wait() rcu_assign_pointer(skb_data->wait, wait) /* start waiting */ wait_for_completion_timeout() rtw89_pci_tx_status() rtw89_core_tx_wait_complete() rcu_read_lock() /* signals completion and * proceeds further */ complete(&wait->completion) rcu_read_unlock() ... /* frees skb_data */ ieee80211_tx_status_ni() /* returns (exit status doesn't matter) */ wait_for_completion_timeout() ... /* accesses the already freed skb_data */ rcu_assign_pointer(skb_data->wait, NULL) The completing side might proceed and free the underlying skb even before the waiting side is fully awoken and run to execution. Actually the race happens regardless of wait_for_completion_timeout() exit status, e.g. the waiting side may hit a timeout and the concurrent completing side is still able to free the skb. Skbs which are sent by rtw89_core_tx_kick_off_and_wait() are owned by the driver. They don't come from core ieee80211 stack so no need to pass them to ieee80211_tx_status_ni() on completing side. Introduce a work function which will act as a garbage collector for rtw89_tx_wait_info objects and the associated skbs. Thus no potentially heavy locks are required on the completing side. Found by Linux Verification Center (linuxtesting.org). Fixes: 1ae5ca615285 ("wifi: rtw89: add function to wait for completion of TX skbs") Cc: stable@vger.kernel.org Suggested-by: Zong-Zhe Yang Signed-off-by: Fedor Pchelkin Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250919210852.823912-2-pchelkin@ispras.ru --- drivers/net/wireless/realtek/rtw89/core.c | 30 +++++++++++++++---- drivers/net/wireless/realtek/rtw89/core.h | 35 +++++++++++++++++++++-- drivers/net/wireless/realtek/rtw89/pci.c | 3 +- drivers/net/wireless/realtek/rtw89/ser.c | 2 ++ 4 files changed, 61 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index fe059c4a6b55..ec467ae0e9e6 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -1135,6 +1135,14 @@ rtw89_core_tx_update_desc_info(struct rtw89_dev *rtwdev, } } +static void rtw89_tx_wait_work(struct wiphy *wiphy, struct wiphy_work *work) +{ + struct rtw89_dev *rtwdev = container_of(work, struct rtw89_dev, + tx_wait_work.work); + + rtw89_tx_wait_list_clear(rtwdev); +} + void rtw89_core_tx_kick_off(struct rtw89_dev *rtwdev, u8 qsel) { u8 ch_dma; @@ -1152,6 +1160,8 @@ int rtw89_core_tx_kick_off_and_wait(struct rtw89_dev *rtwdev, struct sk_buff *sk unsigned long time_left; int ret = 0; + lockdep_assert_wiphy(rtwdev->hw->wiphy); + wait = kzalloc(sizeof(*wait), GFP_KERNEL); if (!wait) { rtw89_core_tx_kick_off(rtwdev, qsel); @@ -1159,18 +1169,23 @@ int rtw89_core_tx_kick_off_and_wait(struct rtw89_dev *rtwdev, struct sk_buff *sk } init_completion(&wait->completion); + wait->skb = skb; rcu_assign_pointer(skb_data->wait, wait); rtw89_core_tx_kick_off(rtwdev, qsel); time_left = wait_for_completion_timeout(&wait->completion, msecs_to_jiffies(timeout)); - if (time_left == 0) - ret = -ETIMEDOUT; - else if (!wait->tx_done) - ret = -EAGAIN; - rcu_assign_pointer(skb_data->wait, NULL); - kfree_rcu(wait, rcu_head); + if (time_left == 0) { + ret = -ETIMEDOUT; + list_add_tail(&wait->list, &rtwdev->tx_waits); + wiphy_delayed_work_queue(rtwdev->hw->wiphy, &rtwdev->tx_wait_work, + RTW89_TX_WAIT_WORK_TIMEOUT); + } else { + if (!wait->tx_done) + ret = -EAGAIN; + rtw89_tx_wait_release(wait); + } return ret; } @@ -5548,6 +5563,7 @@ void rtw89_core_stop(struct rtw89_dev *rtwdev) wiphy_work_cancel(wiphy, &btc->dhcp_notify_work); wiphy_work_cancel(wiphy, &btc->icmp_notify_work); cancel_delayed_work_sync(&rtwdev->txq_reinvoke_work); + wiphy_delayed_work_cancel(wiphy, &rtwdev->tx_wait_work); wiphy_delayed_work_cancel(wiphy, &rtwdev->track_work); wiphy_delayed_work_cancel(wiphy, &rtwdev->track_ps_work); wiphy_delayed_work_cancel(wiphy, &rtwdev->chanctx_work); @@ -5773,6 +5789,7 @@ int rtw89_core_init(struct rtw89_dev *rtwdev) INIT_LIST_HEAD(&rtwdev->scan_info.pkt_list[band]); } INIT_LIST_HEAD(&rtwdev->scan_info.chan_list); + INIT_LIST_HEAD(&rtwdev->tx_waits); INIT_WORK(&rtwdev->ba_work, rtw89_core_ba_work); INIT_WORK(&rtwdev->txq_work, rtw89_core_txq_work); INIT_DELAYED_WORK(&rtwdev->txq_reinvoke_work, rtw89_core_txq_reinvoke_work); @@ -5784,6 +5801,7 @@ int rtw89_core_init(struct rtw89_dev *rtwdev) wiphy_delayed_work_init(&rtwdev->coex_rfk_chk_work, rtw89_coex_rfk_chk_work); wiphy_delayed_work_init(&rtwdev->cfo_track_work, rtw89_phy_cfo_track_work); wiphy_delayed_work_init(&rtwdev->mcc_prepare_done_work, rtw89_mcc_prepare_done_work); + wiphy_delayed_work_init(&rtwdev->tx_wait_work, rtw89_tx_wait_work); INIT_DELAYED_WORK(&rtwdev->forbid_ba_work, rtw89_forbid_ba_work); wiphy_delayed_work_init(&rtwdev->antdiv_work, rtw89_phy_antdiv_work); rtwdev->txq_wq = alloc_workqueue("rtw89_tx_wq", WQ_UNBOUND | WQ_HIGHPRI, 0); diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 3d4ad2ffb75c..d15fa70eb4dc 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -3507,9 +3507,12 @@ struct rtw89_phy_rate_pattern { bool enable; }; +#define RTW89_TX_WAIT_WORK_TIMEOUT msecs_to_jiffies(500) struct rtw89_tx_wait_info { struct rcu_head rcu_head; + struct list_head list; struct completion completion; + struct sk_buff *skb; bool tx_done; }; @@ -6000,6 +6003,9 @@ struct rtw89_dev { /* used to protect rpwm */ spinlock_t rpwm_lock; + struct list_head tx_waits; + struct wiphy_delayed_work tx_wait_work; + struct rtw89_cam_info cam_info; struct sk_buff_head c2h_queue; @@ -6256,6 +6262,26 @@ rtw89_assoc_link_rcu_dereference(struct rtw89_dev *rtwdev, u8 macid) list_first_entry_or_null(&p->dlink_pool, typeof(*p->links_inst), dlink_schd); \ }) +static inline void rtw89_tx_wait_release(struct rtw89_tx_wait_info *wait) +{ + dev_kfree_skb_any(wait->skb); + kfree_rcu(wait, rcu_head); +} + +static inline void rtw89_tx_wait_list_clear(struct rtw89_dev *rtwdev) +{ + struct rtw89_tx_wait_info *wait, *tmp; + + lockdep_assert_wiphy(rtwdev->hw->wiphy); + + list_for_each_entry_safe(wait, tmp, &rtwdev->tx_waits, list) { + if (!completion_done(&wait->completion)) + continue; + list_del(&wait->list); + rtw89_tx_wait_release(wait); + } +} + static inline int rtw89_hci_tx_write(struct rtw89_dev *rtwdev, struct rtw89_core_tx_request *tx_req) { @@ -6265,6 +6291,7 @@ static inline int rtw89_hci_tx_write(struct rtw89_dev *rtwdev, static inline void rtw89_hci_reset(struct rtw89_dev *rtwdev) { rtwdev->hci.ops->reset(rtwdev); + rtw89_tx_wait_list_clear(rtwdev); } static inline int rtw89_hci_start(struct rtw89_dev *rtwdev) @@ -7345,11 +7372,12 @@ static inline struct sk_buff *rtw89_alloc_skb_for_rx(struct rtw89_dev *rtwdev, return dev_alloc_skb(length); } -static inline void rtw89_core_tx_wait_complete(struct rtw89_dev *rtwdev, +static inline bool rtw89_core_tx_wait_complete(struct rtw89_dev *rtwdev, struct rtw89_tx_skb_data *skb_data, bool tx_done) { struct rtw89_tx_wait_info *wait; + bool ret = false; rcu_read_lock(); @@ -7357,11 +7385,14 @@ static inline void rtw89_core_tx_wait_complete(struct rtw89_dev *rtwdev, if (!wait) goto out; + ret = true; wait->tx_done = tx_done; - complete(&wait->completion); + /* Don't access skb anymore after completion */ + complete_all(&wait->completion); out: rcu_read_unlock(); + return ret; } static inline bool rtw89_is_mlo_1_1(struct rtw89_dev *rtwdev) diff --git a/drivers/net/wireless/realtek/rtw89/pci.c b/drivers/net/wireless/realtek/rtw89/pci.c index c8286eb84276..8dd91d867ea6 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.c +++ b/drivers/net/wireless/realtek/rtw89/pci.c @@ -464,7 +464,8 @@ static void rtw89_pci_tx_status(struct rtw89_dev *rtwdev, struct rtw89_tx_skb_data *skb_data = RTW89_TX_SKB_CB(skb); struct ieee80211_tx_info *info; - rtw89_core_tx_wait_complete(rtwdev, skb_data, tx_status == RTW89_TX_DONE); + if (rtw89_core_tx_wait_complete(rtwdev, skb_data, tx_status == RTW89_TX_DONE)) + return; info = IEEE80211_SKB_CB(skb); ieee80211_tx_info_clear_status(info); diff --git a/drivers/net/wireless/realtek/rtw89/ser.c b/drivers/net/wireless/realtek/rtw89/ser.c index bb39fdbcba0d..fe7beff8c424 100644 --- a/drivers/net/wireless/realtek/rtw89/ser.c +++ b/drivers/net/wireless/realtek/rtw89/ser.c @@ -502,7 +502,9 @@ static void ser_reset_trx_st_hdl(struct rtw89_ser *ser, u8 evt) } drv_stop_rx(ser); + wiphy_lock(wiphy); drv_trx_reset(ser); + wiphy_unlock(wiphy); /* wait m3 */ hal_send_m2_event(ser); From c24248ed78f33ea299ea61d105355ba47157d49f Mon Sep 17 00:00:00 2001 From: Fedor Pchelkin Date: Sat, 20 Sep 2025 00:08:48 +0300 Subject: [PATCH 151/167] wifi: rtw89: avoid possible TX wait initialization race The value of skb_data->wait indicates whether skb is passed on to the core mac80211 stack or released by the driver itself. Make sure that by the time skb is added to txwd queue and becomes visible to the completing side, it has already allocated and initialized TX wait related data (in case it's needed). This is found by code review and addresses a possible race scenario described below: Waiting thread Completing thread rtw89_core_send_nullfunc() rtw89_core_tx_write_link() ... rtw89_pci_txwd_submit() skb_data->wait = NULL /* add skb to the queue */ skb_queue_tail(&txwd->queue, skb) /* another thread (e.g. rtw89_ops_tx) performs TX kick off for the same queue */ rtw89_pci_napi_poll() ... rtw89_pci_release_txwd_skb() /* get skb from the queue */ skb_unlink(skb, &txwd->queue) rtw89_pci_tx_status() rtw89_core_tx_wait_complete() /* use incorrect skb_data->wait */ rtw89_core_tx_kick_off_and_wait() /* assign skb_data->wait but too late */ Found by Linux Verification Center (linuxtesting.org). Fixes: 1ae5ca615285 ("wifi: rtw89: add function to wait for completion of TX skbs") Cc: stable@vger.kernel.org Signed-off-by: Fedor Pchelkin Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250919210852.823912-3-pchelkin@ispras.ru --- drivers/net/wireless/realtek/rtw89/core.c | 39 +++++++++++++---------- drivers/net/wireless/realtek/rtw89/core.h | 3 +- drivers/net/wireless/realtek/rtw89/pci.c | 2 -- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index ec467ae0e9e6..1f44c7fc1c5e 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -1153,25 +1153,14 @@ void rtw89_core_tx_kick_off(struct rtw89_dev *rtwdev, u8 qsel) } int rtw89_core_tx_kick_off_and_wait(struct rtw89_dev *rtwdev, struct sk_buff *skb, - int qsel, unsigned int timeout) + struct rtw89_tx_wait_info *wait, int qsel, + unsigned int timeout) { - struct rtw89_tx_skb_data *skb_data = RTW89_TX_SKB_CB(skb); - struct rtw89_tx_wait_info *wait; unsigned long time_left; int ret = 0; lockdep_assert_wiphy(rtwdev->hw->wiphy); - wait = kzalloc(sizeof(*wait), GFP_KERNEL); - if (!wait) { - rtw89_core_tx_kick_off(rtwdev, qsel); - return 0; - } - - init_completion(&wait->completion); - wait->skb = skb; - rcu_assign_pointer(skb_data->wait, wait); - rtw89_core_tx_kick_off(rtwdev, qsel); time_left = wait_for_completion_timeout(&wait->completion, msecs_to_jiffies(timeout)); @@ -1234,10 +1223,12 @@ int rtw89_h2c_tx(struct rtw89_dev *rtwdev, static int rtw89_core_tx_write_link(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwvif_link, struct rtw89_sta_link *rtwsta_link, - struct sk_buff *skb, int *qsel, bool sw_mld) + struct sk_buff *skb, int *qsel, bool sw_mld, + struct rtw89_tx_wait_info *wait) { struct ieee80211_sta *sta = rtwsta_link_to_sta_safe(rtwsta_link); struct ieee80211_vif *vif = rtwvif_link_to_vif(rtwvif_link); + struct rtw89_tx_skb_data *skb_data = RTW89_TX_SKB_CB(skb); struct rtw89_vif *rtwvif = rtwvif_link->rtwvif; struct rtw89_core_tx_request tx_req = {}; int ret; @@ -1254,6 +1245,8 @@ static int rtw89_core_tx_write_link(struct rtw89_dev *rtwdev, rtw89_core_tx_update_desc_info(rtwdev, &tx_req); rtw89_core_tx_wake(rtwdev, &tx_req); + rcu_assign_pointer(skb_data->wait, wait); + ret = rtw89_hci_tx_write(rtwdev, &tx_req); if (ret) { rtw89_err(rtwdev, "failed to transmit skb to HCI\n"); @@ -1290,7 +1283,8 @@ int rtw89_core_tx_write(struct rtw89_dev *rtwdev, struct ieee80211_vif *vif, } } - return rtw89_core_tx_write_link(rtwdev, rtwvif_link, rtwsta_link, skb, qsel, false); + return rtw89_core_tx_write_link(rtwdev, rtwvif_link, rtwsta_link, skb, qsel, false, + NULL); } static __le32 rtw89_build_txwd_body0(struct rtw89_tx_desc_info *desc_info) @@ -3928,6 +3922,7 @@ int rtw89_core_send_nullfunc(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rt struct ieee80211_vif *vif = rtwvif_link_to_vif(rtwvif_link); int link_id = ieee80211_vif_is_mld(vif) ? rtwvif_link->link_id : -1; struct rtw89_sta_link *rtwsta_link; + struct rtw89_tx_wait_info *wait; struct ieee80211_sta *sta; struct ieee80211_hdr *hdr; struct rtw89_sta *rtwsta; @@ -3937,6 +3932,12 @@ int rtw89_core_send_nullfunc(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rt if (vif->type != NL80211_IFTYPE_STATION || !vif->cfg.assoc) return 0; + wait = kzalloc(sizeof(*wait), GFP_KERNEL); + if (!wait) + return -ENOMEM; + + init_completion(&wait->completion); + rcu_read_lock(); sta = ieee80211_find_sta(vif, vif->cfg.ap_addr); if (!sta) { @@ -3951,6 +3952,8 @@ int rtw89_core_send_nullfunc(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rt goto out; } + wait->skb = skb; + hdr = (struct ieee80211_hdr *)skb->data; if (ps) hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_PM); @@ -3961,7 +3964,8 @@ int rtw89_core_send_nullfunc(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rt goto out; } - ret = rtw89_core_tx_write_link(rtwdev, rtwvif_link, rtwsta_link, skb, &qsel, true); + ret = rtw89_core_tx_write_link(rtwdev, rtwvif_link, rtwsta_link, skb, &qsel, true, + wait); if (ret) { rtw89_warn(rtwdev, "nullfunc transmit failed: %d\n", ret); dev_kfree_skb_any(skb); @@ -3970,10 +3974,11 @@ int rtw89_core_send_nullfunc(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rt rcu_read_unlock(); - return rtw89_core_tx_kick_off_and_wait(rtwdev, skb, qsel, + return rtw89_core_tx_kick_off_and_wait(rtwdev, skb, wait, qsel, timeout); out: rcu_read_unlock(); + kfree(wait); return ret; } diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index d15fa70eb4dc..928c8c84c964 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -7476,7 +7476,8 @@ int rtw89_h2c_tx(struct rtw89_dev *rtwdev, struct sk_buff *skb, bool fwdl); void rtw89_core_tx_kick_off(struct rtw89_dev *rtwdev, u8 qsel); int rtw89_core_tx_kick_off_and_wait(struct rtw89_dev *rtwdev, struct sk_buff *skb, - int qsel, unsigned int timeout); + struct rtw89_tx_wait_info *wait, int qsel, + unsigned int timeout); void rtw89_core_fill_txdesc(struct rtw89_dev *rtwdev, struct rtw89_tx_desc_info *desc_info, void *txdesc); diff --git a/drivers/net/wireless/realtek/rtw89/pci.c b/drivers/net/wireless/realtek/rtw89/pci.c index 8dd91d867ea6..0ee5f8579447 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.c +++ b/drivers/net/wireless/realtek/rtw89/pci.c @@ -1494,7 +1494,6 @@ static int rtw89_pci_txwd_submit(struct rtw89_dev *rtwdev, struct pci_dev *pdev = rtwpci->pdev; struct sk_buff *skb = tx_req->skb; struct rtw89_pci_tx_data *tx_data = RTW89_PCI_TX_SKB_CB(skb); - struct rtw89_tx_skb_data *skb_data = RTW89_TX_SKB_CB(skb); bool en_wd_info = desc_info->en_wd_info; u32 txwd_len; u32 txwp_len; @@ -1510,7 +1509,6 @@ static int rtw89_pci_txwd_submit(struct rtw89_dev *rtwdev, } tx_data->dma = dma; - rcu_assign_pointer(skb_data->wait, NULL); txwp_len = sizeof(*txwp_info); txwd_len = chip->txwd_body_size; From a9f0064f4716b0fd97085015ea1dd398bdfdc946 Mon Sep 17 00:00:00 2001 From: Fedor Pchelkin Date: Sat, 20 Sep 2025 00:08:49 +0300 Subject: [PATCH 152/167] wifi: rtw89: fix leak in rtw89_core_send_nullfunc() If there is no rtwsta_link found in rtw89_core_send_nullfunc(), allocated skb is leaked. Free it on the error handling path. Found by Linux Verification Center (linuxtesting.org). Fixes: a8ba4acab7db ("wifi: rtw89: send nullfunc based on the given link") Signed-off-by: Fedor Pchelkin Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250919210852.823912-4-pchelkin@ispras.ru --- drivers/net/wireless/realtek/rtw89/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index 1f44c7fc1c5e..917b2adede61 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -3961,6 +3961,7 @@ int rtw89_core_send_nullfunc(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rt rtwsta_link = rtwsta->links[rtwvif_link->link_id]; if (unlikely(!rtwsta_link)) { ret = -ENOLINK; + dev_kfree_skb_any(skb); goto out; } From 570f94511766f9236d3462dfb8a3c719c2b54c23 Mon Sep 17 00:00:00 2001 From: Fedor Pchelkin Date: Sat, 20 Sep 2025 00:08:50 +0300 Subject: [PATCH 153/167] wifi: rtw89: avoid circular locking dependency in ser_state_run() Lockdep gives a splat [1] when ser_hdl_work item is executed. It is scheduled at mac80211 workqueue via ieee80211_queue_work() and takes a wiphy lock inside. However, this workqueue can be flushed when e.g. closing the interface and wiphy lock is already taken in that case. Choosing wiphy_work_queue() for SER is likely not suitable. Back on to the global workqueue. [1]: WARNING: possible circular locking dependency detected 6.17.0-rc2 #17 Not tainted ------------------------------------------------------ kworker/u32:1/61 is trying to acquire lock: ffff88811bc00768 (&rdev->wiphy.mtx){+.+.}-{4:4}, at: ser_state_run+0x5e/0x180 [rtw89_core] but task is already holding lock: ffffc9000048fd30 ((work_completion)(&ser->ser_hdl_work)){+.+.}-{0:0}, at: process_one_work+0x7b5/0x1450 which lock already depends on the new lock. the existing dependency chain (in reverse order) is: -> #2 ((work_completion)(&ser->ser_hdl_work)){+.+.}-{0:0}: process_one_work+0x7c6/0x1450 worker_thread+0x49e/0xd00 kthread+0x313/0x640 ret_from_fork+0x221/0x300 ret_from_fork_asm+0x1a/0x30 -> #1 ((wq_completion)phy0){+.+.}-{0:0}: touch_wq_lockdep_map+0x8e/0x180 __flush_workqueue+0x129/0x10d0 ieee80211_stop_device+0xa8/0x110 ieee80211_do_stop+0x14ce/0x2880 ieee80211_stop+0x13a/0x2c0 __dev_close_many+0x18f/0x510 __dev_change_flags+0x25f/0x670 netif_change_flags+0x7b/0x160 do_setlink.isra.0+0x1640/0x35d0 rtnl_newlink+0xd8c/0x1d30 rtnetlink_rcv_msg+0x700/0xb80 netlink_rcv_skb+0x11d/0x350 netlink_unicast+0x49a/0x7a0 netlink_sendmsg+0x759/0xc20 ____sys_sendmsg+0x812/0xa00 ___sys_sendmsg+0xf7/0x180 __sys_sendmsg+0x11f/0x1b0 do_syscall_64+0xbb/0x360 entry_SYSCALL_64_after_hwframe+0x77/0x7f -> #0 (&rdev->wiphy.mtx){+.+.}-{4:4}: __lock_acquire+0x124c/0x1d20 lock_acquire+0x154/0x2e0 __mutex_lock+0x17b/0x12f0 ser_state_run+0x5e/0x180 [rtw89_core] rtw89_ser_hdl_work+0x119/0x220 [rtw89_core] process_one_work+0x82d/0x1450 worker_thread+0x49e/0xd00 kthread+0x313/0x640 ret_from_fork+0x221/0x300 ret_from_fork_asm+0x1a/0x30 other info that might help us debug this: Chain exists of: &rdev->wiphy.mtx --> (wq_completion)phy0 --> (work_completion)(&ser->ser_hdl_work) Possible unsafe locking scenario: CPU0 CPU1 ---- ---- lock((work_completion)(&ser->ser_hdl_work)); lock((wq_completion)phy0); lock((work_completion)(&ser->ser_hdl_work)); lock(&rdev->wiphy.mtx); *** DEADLOCK *** 2 locks held by kworker/u32:1/61: #0: ffff888103835148 ((wq_completion)phy0){+.+.}-{0:0}, at: process_one_work+0xefa/0x1450 #1: ffffc9000048fd30 ((work_completion)(&ser->ser_hdl_work)){+.+.}-{0:0}, at: process_one_work+0x7b5/0x1450 stack backtrace: CPU: 0 UID: 0 PID: 61 Comm: kworker/u32:1 Not tainted 6.17.0-rc2 #17 PREEMPT(voluntary) Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS edk2-20250523-14.fc42 05/23/2025 Workqueue: phy0 rtw89_ser_hdl_work [rtw89_core] Call Trace: dump_stack_lvl+0x5d/0x80 print_circular_bug.cold+0x178/0x1be check_noncircular+0x14c/0x170 __lock_acquire+0x124c/0x1d20 lock_acquire+0x154/0x2e0 __mutex_lock+0x17b/0x12f0 ser_state_run+0x5e/0x180 [rtw89_core] rtw89_ser_hdl_work+0x119/0x220 [rtw89_core] process_one_work+0x82d/0x1450 worker_thread+0x49e/0xd00 kthread+0x313/0x640 ret_from_fork+0x221/0x300 ret_from_fork_asm+0x1a/0x30 Found by Linux Verification Center (linuxtesting.org). Fixes: ebfc9199df05 ("wifi: rtw89: add wiphy_lock() to work that isn't held wiphy_lock() yet") Signed-off-by: Fedor Pchelkin Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250919210852.823912-5-pchelkin@ispras.ru --- drivers/net/wireless/realtek/rtw89/ser.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/ser.c b/drivers/net/wireless/realtek/rtw89/ser.c index fe7beff8c424..f99e179f7ff9 100644 --- a/drivers/net/wireless/realtek/rtw89/ser.c +++ b/drivers/net/wireless/realtek/rtw89/ser.c @@ -205,7 +205,6 @@ static void rtw89_ser_hdl_work(struct work_struct *work) static int ser_send_msg(struct rtw89_ser *ser, u8 event) { - struct rtw89_dev *rtwdev = container_of(ser, struct rtw89_dev, ser); struct ser_msg *msg = NULL; if (test_bit(RTW89_SER_DRV_STOP_RUN, ser->flags)) @@ -221,7 +220,7 @@ static int ser_send_msg(struct rtw89_ser *ser, u8 event) list_add(&msg->list, &ser->msg_q); spin_unlock_irq(&ser->msg_q_lock); - ieee80211_queue_work(rtwdev->hw, &ser->ser_hdl_work); + schedule_work(&ser->ser_hdl_work); return 0; } From e0d3bba84ff8b82d4e8820856a7850afb17c14f9 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 19 Sep 2025 12:23:25 +0200 Subject: [PATCH 154/167] wifi: cfg80211: remove IEEE80211_CHAN_{1,2,4,8,16}MHZ flags These were used by S1G for older chandef representation, but are no longer needed. Clean them up, even if we can't drop them from the userspace API entirely. Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 16 +--------------- net/wireless/nl80211.c | 15 --------------- 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 1c041ce7a03b..781624f5913a 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -101,16 +101,6 @@ struct wiphy; * @IEEE80211_CHAN_NO_10MHZ: 10 MHz bandwidth is not permitted * on this channel. * @IEEE80211_CHAN_NO_HE: HE operation is not permitted on this channel. - * @IEEE80211_CHAN_1MHZ: 1 MHz bandwidth is permitted - * on this channel. - * @IEEE80211_CHAN_2MHZ: 2 MHz bandwidth is permitted - * on this channel. - * @IEEE80211_CHAN_4MHZ: 4 MHz bandwidth is permitted - * on this channel. - * @IEEE80211_CHAN_8MHZ: 8 MHz bandwidth is permitted - * on this channel. - * @IEEE80211_CHAN_16MHZ: 16 MHz bandwidth is permitted - * on this channel. * @IEEE80211_CHAN_NO_320MHZ: If the driver supports 320 MHz on the band, * this flag indicates that a 320 MHz channel cannot use this * channel as the control or any of the secondary channels. @@ -152,11 +142,7 @@ enum ieee80211_channel_flags { IEEE80211_CHAN_NO_20MHZ = BIT(11), IEEE80211_CHAN_NO_10MHZ = BIT(12), IEEE80211_CHAN_NO_HE = BIT(13), - IEEE80211_CHAN_1MHZ = BIT(14), - IEEE80211_CHAN_2MHZ = BIT(15), - IEEE80211_CHAN_4MHZ = BIT(16), - IEEE80211_CHAN_8MHZ = BIT(17), - IEEE80211_CHAN_16MHZ = BIT(18), + /* can use free bits here */ IEEE80211_CHAN_NO_320MHZ = BIT(19), IEEE80211_CHAN_NO_EHT = BIT(20), IEEE80211_CHAN_DFS_CONCURRENT = BIT(21), diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index de34a1d14073..346dfd2bd987 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -1280,21 +1280,6 @@ static int nl80211_msg_put_channel(struct sk_buff *msg, struct wiphy *wiphy, if ((chan->flags & IEEE80211_CHAN_NO_HE) && nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_HE)) goto nla_put_failure; - if ((chan->flags & IEEE80211_CHAN_1MHZ) && - nla_put_flag(msg, NL80211_FREQUENCY_ATTR_1MHZ)) - goto nla_put_failure; - if ((chan->flags & IEEE80211_CHAN_2MHZ) && - nla_put_flag(msg, NL80211_FREQUENCY_ATTR_2MHZ)) - goto nla_put_failure; - if ((chan->flags & IEEE80211_CHAN_4MHZ) && - nla_put_flag(msg, NL80211_FREQUENCY_ATTR_4MHZ)) - goto nla_put_failure; - if ((chan->flags & IEEE80211_CHAN_8MHZ) && - nla_put_flag(msg, NL80211_FREQUENCY_ATTR_8MHZ)) - goto nla_put_failure; - if ((chan->flags & IEEE80211_CHAN_16MHZ) && - nla_put_flag(msg, NL80211_FREQUENCY_ATTR_16MHZ)) - goto nla_put_failure; if ((chan->flags & IEEE80211_CHAN_NO_320MHZ) && nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_320MHZ)) goto nla_put_failure; From 82993345aef6987a916337ebd2fca3ff4a6250a7 Mon Sep 17 00:00:00 2001 From: Nithyanantham Paramasivam Date: Wed, 6 Aug 2025 16:47:44 +0530 Subject: [PATCH 155/167] wifi: ath12k: Increase DP_REO_CMD_RING_SIZE to 256 Increase DP_REO_CMD_RING_SIZE from 128 to 256 to avoid queuing failures observed during stress test scenarios. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.3.1-00173-QCAHKSWPL_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3 Signed-off-by: Nithyanantham Paramasivam Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250806111750.3214584-2-nithyanantham.paramasivam@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/dp.h b/drivers/net/wireless/ath/ath12k/dp.h index 7baa48b86f7a..10093b451588 100644 --- a/drivers/net/wireless/ath/ath12k/dp.h +++ b/drivers/net/wireless/ath/ath12k/dp.h @@ -184,7 +184,7 @@ struct ath12k_pdev_dp { #define DP_REO_REINJECT_RING_SIZE 32 #define DP_RX_RELEASE_RING_SIZE 1024 #define DP_REO_EXCEPTION_RING_SIZE 128 -#define DP_REO_CMD_RING_SIZE 128 +#define DP_REO_CMD_RING_SIZE 256 #define DP_REO_STATUS_RING_SIZE 2048 #define DP_RXDMA_BUF_RING_SIZE 4096 #define DP_RX_MAC_BUF_RING_SIZE 2048 From 7c32476253f11210ac24c7818ca07e19bc032521 Mon Sep 17 00:00:00 2001 From: Nithyanantham Paramasivam Date: Wed, 6 Aug 2025 16:47:45 +0530 Subject: [PATCH 156/167] wifi: ath12k: Refactor RX TID deletion handling into helper function Refactor RX TID deletion handling by moving the REO command setup and send sequence into a new helper function: ath12k_dp_rx_tid_delete_handler(). This improves code readability and modularity, and prepares the codebase for potential reuse of the REO command logic in other contexts where RX TID deletion is required. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.3.1-00173-QCAHKSWPL_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3 Signed-off-by: Nithyanantham Paramasivam Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250806111750.3214584-3-nithyanantham.paramasivam@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp_rx.c | 27 +++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/dp_rx.c b/drivers/net/wireless/ath/ath12k/dp_rx.c index adb0cfe109e6..8c61c7f3bbdc 100644 --- a/drivers/net/wireless/ath/ath12k/dp_rx.c +++ b/drivers/net/wireless/ath/ath12k/dp_rx.c @@ -21,6 +21,9 @@ #define ATH12K_DP_RX_FRAGMENT_TIMEOUT_MS (2 * HZ) +static int ath12k_dp_rx_tid_delete_handler(struct ath12k_base *ab, + struct ath12k_dp_rx_tid *rx_tid); + static enum hal_encrypt_type ath12k_dp_rx_h_enctype(struct ath12k_base *ab, struct hal_rx_desc *desc) { @@ -769,6 +772,21 @@ free_desc: rx_tid->qbuf.vaddr = NULL; } +static int ath12k_dp_rx_tid_delete_handler(struct ath12k_base *ab, + struct ath12k_dp_rx_tid *rx_tid) +{ + struct ath12k_hal_reo_cmd cmd = {}; + + cmd.flag = HAL_REO_CMD_FLG_NEED_STATUS; + cmd.addr_lo = lower_32_bits(rx_tid->qbuf.paddr_aligned); + cmd.addr_hi = upper_32_bits(rx_tid->qbuf.paddr_aligned); + cmd.upd0 |= HAL_REO_CMD_UPD0_VLD; + + return ath12k_dp_reo_cmd_send(ab, rx_tid, + HAL_REO_CMD_UPDATE_RX_QUEUE, &cmd, + ath12k_dp_rx_tid_del_func); +} + static void ath12k_peer_rx_tid_qref_setup(struct ath12k_base *ab, u16 peer_id, u16 tid, dma_addr_t paddr) { @@ -828,20 +846,13 @@ static void ath12k_peer_rx_tid_qref_reset(struct ath12k_base *ab, u16 peer_id, u void ath12k_dp_rx_peer_tid_delete(struct ath12k *ar, struct ath12k_peer *peer, u8 tid) { - struct ath12k_hal_reo_cmd cmd = {}; struct ath12k_dp_rx_tid *rx_tid = &peer->rx_tid[tid]; int ret; if (!rx_tid->active) return; - cmd.flag = HAL_REO_CMD_FLG_NEED_STATUS; - cmd.addr_lo = lower_32_bits(rx_tid->qbuf.paddr_aligned); - cmd.addr_hi = upper_32_bits(rx_tid->qbuf.paddr_aligned); - cmd.upd0 = HAL_REO_CMD_UPD0_VLD; - ret = ath12k_dp_reo_cmd_send(ar->ab, rx_tid, - HAL_REO_CMD_UPDATE_RX_QUEUE, &cmd, - ath12k_dp_rx_tid_del_func); + ret = ath12k_dp_rx_tid_delete_handler(ar->ab, rx_tid); if (ret) { ath12k_err(ar->ab, "failed to send HAL_REO_CMD_UPDATE_RX_QUEUE cmd, tid %d (%d)\n", tid, ret); From f829a1f8f27555742c9759870895f22c4ab7febb Mon Sep 17 00:00:00 2001 From: Nithyanantham Paramasivam Date: Wed, 6 Aug 2025 16:47:46 +0530 Subject: [PATCH 157/167] wifi: ath12k: Refactor RX TID buffer cleanup into helper function Introduce ath12k_dp_rx_tid_cleanup() to handle RX TID buffer unmapping and freeing. This replaces duplicated cleanup logic across multiple code paths. This improves code maintainability and avoids redundancy in buffer cleanup operations. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.3.1-00173-QCAHKSWPL_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3 Signed-off-by: Nithyanantham Paramasivam Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250806111750.3214584-4-nithyanantham.paramasivam@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp_rx.c | 34 ++++++++++++------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/dp_rx.c b/drivers/net/wireless/ath/ath12k/dp_rx.c index 8c61c7f3bbdc..d1022c7938db 100644 --- a/drivers/net/wireless/ath/ath12k/dp_rx.c +++ b/drivers/net/wireless/ath/ath12k/dp_rx.c @@ -584,6 +584,17 @@ static int ath12k_dp_rx_pdev_srng_alloc(struct ath12k *ar) return 0; } +static void ath12k_dp_rx_tid_cleanup(struct ath12k_base *ab, + struct ath12k_reoq_buf *tid_qbuf) +{ + if (tid_qbuf->vaddr) { + dma_unmap_single(ab->dev, tid_qbuf->paddr_aligned, + tid_qbuf->size, DMA_BIDIRECTIONAL); + kfree(tid_qbuf->vaddr); + tid_qbuf->vaddr = NULL; + } +} + void ath12k_dp_rx_reo_cmd_list_cleanup(struct ath12k_base *ab) { struct ath12k_dp *dp = &ab->dp; @@ -593,9 +604,7 @@ void ath12k_dp_rx_reo_cmd_list_cleanup(struct ath12k_base *ab) spin_lock_bh(&dp->reo_cmd_lock); list_for_each_entry_safe(cmd, tmp, &dp->reo_cmd_list, list) { list_del(&cmd->list); - dma_unmap_single(ab->dev, cmd->data.qbuf.paddr_aligned, - cmd->data.qbuf.size, DMA_BIDIRECTIONAL); - kfree(cmd->data.qbuf.vaddr); + ath12k_dp_rx_tid_cleanup(ab, &cmd->data.qbuf); kfree(cmd); } @@ -603,9 +612,7 @@ void ath12k_dp_rx_reo_cmd_list_cleanup(struct ath12k_base *ab) &dp->reo_cmd_cache_flush_list, list) { list_del(&cmd_cache->list); dp->reo_cmd_cache_flush_count--; - dma_unmap_single(ab->dev, cmd_cache->data.qbuf.paddr_aligned, - cmd_cache->data.qbuf.size, DMA_BIDIRECTIONAL); - kfree(cmd_cache->data.qbuf.vaddr); + ath12k_dp_rx_tid_cleanup(ab, &cmd_cache->data.qbuf); kfree(cmd_cache); } spin_unlock_bh(&dp->reo_cmd_lock); @@ -620,10 +627,7 @@ static void ath12k_dp_reo_cmd_free(struct ath12k_dp *dp, void *ctx, ath12k_warn(dp->ab, "failed to flush rx tid hw desc, tid %d status %d\n", rx_tid->tid, status); - dma_unmap_single(dp->ab->dev, rx_tid->qbuf.paddr_aligned, rx_tid->qbuf.size, - DMA_BIDIRECTIONAL); - kfree(rx_tid->qbuf.vaddr); - rx_tid->qbuf.vaddr = NULL; + ath12k_dp_rx_tid_cleanup(dp->ab, &rx_tid->qbuf); } static int ath12k_dp_reo_cmd_send(struct ath12k_base *ab, struct ath12k_dp_rx_tid *rx_tid, @@ -766,10 +770,7 @@ static void ath12k_dp_rx_tid_del_func(struct ath12k_dp *dp, void *ctx, return; free_desc: - dma_unmap_single(ab->dev, rx_tid->qbuf.paddr_aligned, rx_tid->qbuf.size, - DMA_BIDIRECTIONAL); - kfree(rx_tid->qbuf.vaddr); - rx_tid->qbuf.vaddr = NULL; + ath12k_dp_rx_tid_cleanup(ab, &rx_tid->qbuf); } static int ath12k_dp_rx_tid_delete_handler(struct ath12k_base *ab, @@ -856,10 +857,7 @@ void ath12k_dp_rx_peer_tid_delete(struct ath12k *ar, if (ret) { ath12k_err(ar->ab, "failed to send HAL_REO_CMD_UPDATE_RX_QUEUE cmd, tid %d (%d)\n", tid, ret); - dma_unmap_single(ar->ab->dev, rx_tid->qbuf.paddr_aligned, - rx_tid->qbuf.size, DMA_BIDIRECTIONAL); - kfree(rx_tid->qbuf.vaddr); - rx_tid->qbuf.vaddr = NULL; + ath12k_dp_rx_tid_cleanup(ar->ab, &rx_tid->qbuf); } if (peer->mlo) From 6a01985105843e8c043c2bffe25c54b71bc45002 Mon Sep 17 00:00:00 2001 From: Nithyanantham Paramasivam Date: Wed, 6 Aug 2025 16:47:47 +0530 Subject: [PATCH 158/167] wifi: ath12k: Refactor REO command to use ath12k_dp_rx_tid_rxq Introduce ath12k_dp_rx_tid_rxq as a lightweight structure to represent only the necessary fields for REO command construction. Replace direct usage of ath12k_dp_rx_tid in REO command paths with this new structure. This decouples REO command logic from internal TID state representation, improves modularity, and reduces unnecessary data dependencies. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.3.1-00173-QCAHKSWPL_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3 Signed-off-by: Nithyanantham Paramasivam Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250806111750.3214584-5-nithyanantham.paramasivam@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp_rx.c | 46 +++++++++++++++++-------- drivers/net/wireless/ath/ath12k/dp_rx.h | 10 ++++-- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/dp_rx.c b/drivers/net/wireless/ath/ath12k/dp_rx.c index d1022c7938db..98d89dc9b64f 100644 --- a/drivers/net/wireless/ath/ath12k/dp_rx.c +++ b/drivers/net/wireless/ath/ath12k/dp_rx.c @@ -22,7 +22,7 @@ #define ATH12K_DP_RX_FRAGMENT_TIMEOUT_MS (2 * HZ) static int ath12k_dp_rx_tid_delete_handler(struct ath12k_base *ab, - struct ath12k_dp_rx_tid *rx_tid); + struct ath12k_dp_rx_tid_rxq *rx_tid); static enum hal_encrypt_type ath12k_dp_rx_h_enctype(struct ath12k_base *ab, struct hal_rx_desc *desc) @@ -584,6 +584,14 @@ static int ath12k_dp_rx_pdev_srng_alloc(struct ath12k *ar) return 0; } +static void ath12k_dp_init_rx_tid_rxq(struct ath12k_dp_rx_tid_rxq *rx_tid_rxq, + struct ath12k_dp_rx_tid *rx_tid) +{ + rx_tid_rxq->tid = rx_tid->tid; + rx_tid_rxq->active = rx_tid->active; + rx_tid_rxq->qbuf = rx_tid->qbuf; +} + static void ath12k_dp_rx_tid_cleanup(struct ath12k_base *ab, struct ath12k_reoq_buf *tid_qbuf) { @@ -621,7 +629,7 @@ void ath12k_dp_rx_reo_cmd_list_cleanup(struct ath12k_base *ab) static void ath12k_dp_reo_cmd_free(struct ath12k_dp *dp, void *ctx, enum hal_reo_cmd_status status) { - struct ath12k_dp_rx_tid *rx_tid = ctx; + struct ath12k_dp_rx_tid_rxq *rx_tid = ctx; if (status != HAL_REO_CMD_SUCCESS) ath12k_warn(dp->ab, "failed to flush rx tid hw desc, tid %d status %d\n", @@ -630,7 +638,8 @@ static void ath12k_dp_reo_cmd_free(struct ath12k_dp *dp, void *ctx, ath12k_dp_rx_tid_cleanup(dp->ab, &rx_tid->qbuf); } -static int ath12k_dp_reo_cmd_send(struct ath12k_base *ab, struct ath12k_dp_rx_tid *rx_tid, +static int ath12k_dp_reo_cmd_send(struct ath12k_base *ab, + struct ath12k_dp_rx_tid_rxq *rx_tid, enum hal_reo_cmd_type type, struct ath12k_hal_reo_cmd *cmd, void (*cb)(struct ath12k_dp *dp, void *ctx, @@ -676,7 +685,7 @@ static int ath12k_dp_reo_cmd_send(struct ath12k_base *ab, struct ath12k_dp_rx_ti } static void ath12k_dp_reo_cache_flush(struct ath12k_base *ab, - struct ath12k_dp_rx_tid *rx_tid) + struct ath12k_dp_rx_tid_rxq *rx_tid) { struct ath12k_hal_reo_cmd cmd = {}; unsigned long tot_desc_sz, desc_sz; @@ -719,7 +728,7 @@ static void ath12k_dp_rx_tid_del_func(struct ath12k_dp *dp, void *ctx, enum hal_reo_cmd_status status) { struct ath12k_base *ab = dp->ab; - struct ath12k_dp_rx_tid *rx_tid = ctx; + struct ath12k_dp_rx_tid_rxq *rx_tid = ctx; struct ath12k_dp_rx_reo_cache_flush_elem *elem, *tmp; if (status == HAL_REO_CMD_DRAIN) { @@ -774,7 +783,7 @@ free_desc: } static int ath12k_dp_rx_tid_delete_handler(struct ath12k_base *ab, - struct ath12k_dp_rx_tid *rx_tid) + struct ath12k_dp_rx_tid_rxq *rx_tid) { struct ath12k_hal_reo_cmd cmd = {}; @@ -849,11 +858,14 @@ void ath12k_dp_rx_peer_tid_delete(struct ath12k *ar, { struct ath12k_dp_rx_tid *rx_tid = &peer->rx_tid[tid]; int ret; + struct ath12k_dp_rx_tid_rxq rx_tid_rxq; if (!rx_tid->active) return; - ret = ath12k_dp_rx_tid_delete_handler(ar->ab, rx_tid); + ath12k_dp_init_rx_tid_rxq(&rx_tid_rxq, rx_tid); + + ret = ath12k_dp_rx_tid_delete_handler(ar->ab, &rx_tid_rxq); if (ret) { ath12k_err(ar->ab, "failed to send HAL_REO_CMD_UPDATE_RX_QUEUE cmd, tid %d (%d)\n", tid, ret); @@ -950,9 +962,12 @@ static int ath12k_peer_rx_tid_reo_update(struct ath12k *ar, { struct ath12k_hal_reo_cmd cmd = {}; int ret; + struct ath12k_dp_rx_tid_rxq rx_tid_rxq; - cmd.addr_lo = lower_32_bits(rx_tid->qbuf.paddr_aligned); - cmd.addr_hi = upper_32_bits(rx_tid->qbuf.paddr_aligned); + ath12k_dp_init_rx_tid_rxq(&rx_tid_rxq, rx_tid); + + cmd.addr_lo = lower_32_bits(rx_tid_rxq.qbuf.paddr_aligned); + cmd.addr_hi = upper_32_bits(rx_tid_rxq.qbuf.paddr_aligned); cmd.flag = HAL_REO_CMD_FLG_NEED_STATUS; cmd.upd0 = HAL_REO_CMD_UPD0_BA_WINDOW_SIZE; cmd.ba_window_size = ba_win_sz; @@ -962,12 +977,12 @@ static int ath12k_peer_rx_tid_reo_update(struct ath12k *ar, cmd.upd2 = u32_encode_bits(ssn, HAL_REO_CMD_UPD2_SSN); } - ret = ath12k_dp_reo_cmd_send(ar->ab, rx_tid, + ret = ath12k_dp_reo_cmd_send(ar->ab, &rx_tid_rxq, HAL_REO_CMD_UPDATE_RX_QUEUE, &cmd, NULL); if (ret) { ath12k_warn(ar->ab, "failed to update rx tid queue, tid %d (%d)\n", - rx_tid->tid, ret); + rx_tid_rxq.tid, ret); return ret; } @@ -1216,6 +1231,7 @@ int ath12k_dp_rx_peer_pn_replay_config(struct ath12k_link_vif *arvif, struct ath12k_hal_reo_cmd cmd = {}; struct ath12k_peer *peer; struct ath12k_dp_rx_tid *rx_tid; + struct ath12k_dp_rx_tid_rxq rx_tid_rxq; u8 tid; int ret = 0; @@ -1262,9 +1278,11 @@ int ath12k_dp_rx_peer_pn_replay_config(struct ath12k_link_vif *arvif, rx_tid = &peer->rx_tid[tid]; if (!rx_tid->active) continue; - cmd.addr_lo = lower_32_bits(rx_tid->qbuf.paddr_aligned); - cmd.addr_hi = upper_32_bits(rx_tid->qbuf.paddr_aligned); - ret = ath12k_dp_reo_cmd_send(ab, rx_tid, + + ath12k_dp_init_rx_tid_rxq(&rx_tid_rxq, rx_tid); + cmd.addr_lo = lower_32_bits(rx_tid_rxq.qbuf.paddr_aligned); + cmd.addr_hi = upper_32_bits(rx_tid_rxq.qbuf.paddr_aligned); + ret = ath12k_dp_reo_cmd_send(ab, &rx_tid_rxq, HAL_REO_CMD_UPDATE_RX_QUEUE, &cmd, NULL); if (ret) { diff --git a/drivers/net/wireless/ath/ath12k/dp_rx.h b/drivers/net/wireless/ath/ath12k/dp_rx.h index e971a314bd2d..da2332236b77 100644 --- a/drivers/net/wireless/ath/ath12k/dp_rx.h +++ b/drivers/net/wireless/ath/ath12k/dp_rx.h @@ -31,15 +31,21 @@ struct ath12k_dp_rx_tid { struct ath12k_base *ab; }; +struct ath12k_dp_rx_tid_rxq { + u8 tid; + bool active; + struct ath12k_reoq_buf qbuf; +}; + struct ath12k_dp_rx_reo_cache_flush_elem { struct list_head list; - struct ath12k_dp_rx_tid data; + struct ath12k_dp_rx_tid_rxq data; unsigned long ts; }; struct ath12k_dp_rx_reo_cmd { struct list_head list; - struct ath12k_dp_rx_tid data; + struct ath12k_dp_rx_tid_rxq data; int cmd_num; void (*handler)(struct ath12k_dp *dp, void *ctx, enum hal_reo_cmd_status status); From 3bf2e57e7d6cd3e0896c2aa5e86f11aeb7bc3702 Mon Sep 17 00:00:00 2001 From: Manish Dharanenthiran Date: Wed, 6 Aug 2025 16:47:48 +0530 Subject: [PATCH 159/167] wifi: ath12k: Add Retry Mechanism for REO RX Queue Update Failures During stress test scenarios, when the REO command ring becomes full, the RX queue update command issued during peer deletion fails due to insufficient space. In response, the host performs a dma_unmap and frees the associated memory. However, the hardware still retains a reference to the same memory address. If the kernel later reallocates this address, unaware that the hardware is still using it, it can lead to memory corruption-since the host might access or modify memory that is still actively referenced by the hardware. Implement a retry mechanism for the HAL_REO_CMD_UPDATE_RX_QUEUE command during TID deletion to prevent memory corruption. Introduce a new list, reo_cmd_update_rx_queue_list, in the struct ath12k_dp to track pending RX queue updates. Protect this list with reo_rxq_flush_lock, which also ensures synchronized access to reo_cmd_cache_flush_list. Defer memory release until hardware confirms the virtual address is no longer in use, avoiding immediate deallocation on command failure. Release memory for pending RX queue updates via ath12k_dp_rx_reo_cmd_list_cleanup() on system reset if hardware confirmation is not received. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.3.1-00173-QCAHKSWPL_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3 Signed-off-by: Manish Dharanenthiran Co-developed-by: Nithyanantham Paramasivam Signed-off-by: Nithyanantham Paramasivam Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250806111750.3214584-6-nithyanantham.paramasivam@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp.c | 2 + drivers/net/wireless/ath/ath12k/dp.h | 10 +- drivers/net/wireless/ath/ath12k/dp_rx.c | 190 +++++++++++++++++------- drivers/net/wireless/ath/ath12k/dp_rx.h | 8 + 4 files changed, 150 insertions(+), 60 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/dp.c b/drivers/net/wireless/ath/ath12k/dp.c index f893fce6d9bd..4a54b8c35311 100644 --- a/drivers/net/wireless/ath/ath12k/dp.c +++ b/drivers/net/wireless/ath/ath12k/dp.c @@ -1745,7 +1745,9 @@ int ath12k_dp_alloc(struct ath12k_base *ab) INIT_LIST_HEAD(&dp->reo_cmd_list); INIT_LIST_HEAD(&dp->reo_cmd_cache_flush_list); + INIT_LIST_HEAD(&dp->reo_cmd_update_rx_queue_list); spin_lock_init(&dp->reo_cmd_lock); + spin_lock_init(&dp->reo_rxq_flush_lock); dp->reo_cmd_cache_flush_count = 0; dp->idle_link_rbm = ath12k_dp_get_idle_link_rbm(ab); diff --git a/drivers/net/wireless/ath/ath12k/dp.h b/drivers/net/wireless/ath/ath12k/dp.h index 10093b451588..4ffec6ad7d8d 100644 --- a/drivers/net/wireless/ath/ath12k/dp.h +++ b/drivers/net/wireless/ath/ath12k/dp.h @@ -389,15 +389,19 @@ struct ath12k_dp { struct dp_srng reo_dst_ring[DP_REO_DST_RING_MAX]; struct dp_tx_ring tx_ring[DP_TCL_NUM_RING_MAX]; struct hal_wbm_idle_scatter_list scatter_list[DP_IDLE_SCATTER_BUFS_MAX]; - struct list_head reo_cmd_list; + struct list_head reo_cmd_update_rx_queue_list; struct list_head reo_cmd_cache_flush_list; u32 reo_cmd_cache_flush_count; - /* protects access to below fields, - * - reo_cmd_list + * - reo_cmd_update_rx_queue_list * - reo_cmd_cache_flush_list * - reo_cmd_cache_flush_count */ + spinlock_t reo_rxq_flush_lock; + struct list_head reo_cmd_list; + /* protects access to below fields, + * - reo_cmd_list + */ spinlock_t reo_cmd_lock; struct ath12k_hp_update_timer reo_cmd_timer; struct ath12k_hp_update_timer tx_ring_timer[DP_TCL_NUM_RING_MAX]; diff --git a/drivers/net/wireless/ath/ath12k/dp_rx.c b/drivers/net/wireless/ath/ath12k/dp_rx.c index 98d89dc9b64f..331dafcecc89 100644 --- a/drivers/net/wireless/ath/ath12k/dp_rx.c +++ b/drivers/net/wireless/ath/ath12k/dp_rx.c @@ -608,14 +608,15 @@ void ath12k_dp_rx_reo_cmd_list_cleanup(struct ath12k_base *ab) struct ath12k_dp *dp = &ab->dp; struct ath12k_dp_rx_reo_cmd *cmd, *tmp; struct ath12k_dp_rx_reo_cache_flush_elem *cmd_cache, *tmp_cache; + struct dp_reo_update_rx_queue_elem *cmd_queue, *tmp_queue; - spin_lock_bh(&dp->reo_cmd_lock); - list_for_each_entry_safe(cmd, tmp, &dp->reo_cmd_list, list) { - list_del(&cmd->list); - ath12k_dp_rx_tid_cleanup(ab, &cmd->data.qbuf); - kfree(cmd); + spin_lock_bh(&dp->reo_rxq_flush_lock); + list_for_each_entry_safe(cmd_queue, tmp_queue, &dp->reo_cmd_update_rx_queue_list, + list) { + list_del(&cmd_queue->list); + ath12k_dp_rx_tid_cleanup(ab, &cmd_queue->rx_tid.qbuf); + kfree(cmd_queue); } - list_for_each_entry_safe(cmd_cache, tmp_cache, &dp->reo_cmd_cache_flush_list, list) { list_del(&cmd_cache->list); @@ -623,6 +624,14 @@ void ath12k_dp_rx_reo_cmd_list_cleanup(struct ath12k_base *ab) ath12k_dp_rx_tid_cleanup(ab, &cmd_cache->data.qbuf); kfree(cmd_cache); } + spin_unlock_bh(&dp->reo_rxq_flush_lock); + + spin_lock_bh(&dp->reo_cmd_lock); + list_for_each_entry_safe(cmd, tmp, &dp->reo_cmd_list, list) { + list_del(&cmd->list); + ath12k_dp_rx_tid_cleanup(ab, &cmd->data.qbuf); + kfree(cmd); + } spin_unlock_bh(&dp->reo_cmd_lock); } @@ -724,6 +733,61 @@ static void ath12k_dp_reo_cache_flush(struct ath12k_base *ab, } } +static void ath12k_peer_rx_tid_qref_reset(struct ath12k_base *ab, u16 peer_id, u16 tid) +{ + struct ath12k_reo_queue_ref *qref; + struct ath12k_dp *dp = &ab->dp; + bool ml_peer = false; + + if (!ab->hw_params->reoq_lut_support) + return; + + if (peer_id & ATH12K_PEER_ML_ID_VALID) { + peer_id &= ~ATH12K_PEER_ML_ID_VALID; + ml_peer = true; + } + + if (ml_peer) + qref = (struct ath12k_reo_queue_ref *)dp->ml_reoq_lut.vaddr + + (peer_id * (IEEE80211_NUM_TIDS + 1) + tid); + else + qref = (struct ath12k_reo_queue_ref *)dp->reoq_lut.vaddr + + (peer_id * (IEEE80211_NUM_TIDS + 1) + tid); + + qref->info0 = u32_encode_bits(0, BUFFER_ADDR_INFO0_ADDR); + qref->info1 = u32_encode_bits(0, BUFFER_ADDR_INFO1_ADDR) | + u32_encode_bits(tid, DP_REO_QREF_NUM); +} + +static void ath12k_dp_rx_process_reo_cmd_update_rx_queue_list(struct ath12k_dp *dp) +{ + struct ath12k_base *ab = dp->ab; + struct dp_reo_update_rx_queue_elem *elem, *tmp; + + spin_lock_bh(&dp->reo_rxq_flush_lock); + + list_for_each_entry_safe(elem, tmp, &dp->reo_cmd_update_rx_queue_list, list) { + if (elem->rx_tid.active) + continue; + + if (ath12k_dp_rx_tid_delete_handler(ab, &elem->rx_tid)) + break; + + ath12k_peer_rx_tid_qref_reset(ab, + elem->is_ml_peer ? elem->ml_peer_id : + elem->peer_id, + elem->rx_tid.tid); + + if (ab->hw_params->reoq_lut_support) + ath12k_hal_reo_shared_qaddr_cache_clear(ab); + + list_del(&elem->list); + kfree(elem); + } + + spin_unlock_bh(&dp->reo_rxq_flush_lock); +} + static void ath12k_dp_rx_tid_del_func(struct ath12k_dp *dp, void *ctx, enum hal_reo_cmd_status status) { @@ -740,6 +804,13 @@ static void ath12k_dp_rx_tid_del_func(struct ath12k_dp *dp, void *ctx, return; } + /* Retry the HAL_REO_CMD_UPDATE_RX_QUEUE command for entries + * in the pending queue list marked TID as inactive + */ + spin_lock_bh(&dp->ab->base_lock); + ath12k_dp_rx_process_reo_cmd_update_rx_queue_list(dp); + spin_unlock_bh(&dp->ab->base_lock); + elem = kzalloc(sizeof(*elem), GFP_ATOMIC); if (!elem) goto free_desc; @@ -747,7 +818,7 @@ static void ath12k_dp_rx_tid_del_func(struct ath12k_dp *dp, void *ctx, elem->ts = jiffies; memcpy(&elem->data, rx_tid, sizeof(*rx_tid)); - spin_lock_bh(&dp->reo_cmd_lock); + spin_lock_bh(&dp->reo_rxq_flush_lock); list_add_tail(&elem->list, &dp->reo_cmd_cache_flush_list); dp->reo_cmd_cache_flush_count++; @@ -759,23 +830,11 @@ static void ath12k_dp_rx_tid_del_func(struct ath12k_dp *dp, void *ctx, msecs_to_jiffies(ATH12K_DP_RX_REO_DESC_FREE_TIMEOUT_MS))) { list_del(&elem->list); dp->reo_cmd_cache_flush_count--; - - /* Unlock the reo_cmd_lock before using ath12k_dp_reo_cmd_send() - * within ath12k_dp_reo_cache_flush. The reo_cmd_cache_flush_list - * is used in only two contexts, one is in this function called - * from napi and the other in ath12k_dp_free during core destroy. - * Before dp_free, the irqs would be disabled and would wait to - * synchronize. Hence there wouldn’t be any race against add or - * delete to this list. Hence unlock-lock is safe here. - */ - spin_unlock_bh(&dp->reo_cmd_lock); - ath12k_dp_reo_cache_flush(ab, &elem->data); kfree(elem); - spin_lock_bh(&dp->reo_cmd_lock); } } - spin_unlock_bh(&dp->reo_cmd_lock); + spin_unlock_bh(&dp->reo_rxq_flush_lock); return; free_desc: @@ -827,57 +886,38 @@ static void ath12k_peer_rx_tid_qref_setup(struct ath12k_base *ab, u16 peer_id, u ath12k_hal_reo_shared_qaddr_cache_clear(ab); } -static void ath12k_peer_rx_tid_qref_reset(struct ath12k_base *ab, u16 peer_id, u16 tid) +static void ath12k_dp_mark_tid_as_inactive(struct ath12k_dp *dp, int peer_id, u8 tid) { - struct ath12k_reo_queue_ref *qref; - struct ath12k_dp *dp = &ab->dp; - bool ml_peer = false; + struct dp_reo_update_rx_queue_elem *elem; + struct ath12k_dp_rx_tid_rxq *rx_tid; - if (!ab->hw_params->reoq_lut_support) - return; - - if (peer_id & ATH12K_PEER_ML_ID_VALID) { - peer_id &= ~ATH12K_PEER_ML_ID_VALID; - ml_peer = true; + spin_lock_bh(&dp->reo_rxq_flush_lock); + list_for_each_entry(elem, &dp->reo_cmd_update_rx_queue_list, list) { + if (elem->peer_id == peer_id) { + rx_tid = &elem->rx_tid; + if (rx_tid->tid == tid) { + rx_tid->active = false; + break; + } + } } - - if (ml_peer) - qref = (struct ath12k_reo_queue_ref *)dp->ml_reoq_lut.vaddr + - (peer_id * (IEEE80211_NUM_TIDS + 1) + tid); - else - qref = (struct ath12k_reo_queue_ref *)dp->reoq_lut.vaddr + - (peer_id * (IEEE80211_NUM_TIDS + 1) + tid); - - qref->info0 = u32_encode_bits(0, BUFFER_ADDR_INFO0_ADDR); - qref->info1 = u32_encode_bits(0, BUFFER_ADDR_INFO1_ADDR) | - u32_encode_bits(tid, DP_REO_QREF_NUM); + spin_unlock_bh(&dp->reo_rxq_flush_lock); } void ath12k_dp_rx_peer_tid_delete(struct ath12k *ar, struct ath12k_peer *peer, u8 tid) { struct ath12k_dp_rx_tid *rx_tid = &peer->rx_tid[tid]; - int ret; - struct ath12k_dp_rx_tid_rxq rx_tid_rxq; + struct ath12k_base *ab = ar->ab; + struct ath12k_dp *dp = &ab->dp; if (!rx_tid->active) return; - ath12k_dp_init_rx_tid_rxq(&rx_tid_rxq, rx_tid); - - ret = ath12k_dp_rx_tid_delete_handler(ar->ab, &rx_tid_rxq); - if (ret) { - ath12k_err(ar->ab, "failed to send HAL_REO_CMD_UPDATE_RX_QUEUE cmd, tid %d (%d)\n", - tid, ret); - ath12k_dp_rx_tid_cleanup(ar->ab, &rx_tid->qbuf); - } - - if (peer->mlo) - ath12k_peer_rx_tid_qref_reset(ar->ab, peer->ml_id, tid); - else - ath12k_peer_rx_tid_qref_reset(ar->ab, peer->peer_id, tid); - rx_tid->active = false; + + ath12k_dp_mark_tid_as_inactive(dp, peer->peer_id, tid); + ath12k_dp_rx_process_reo_cmd_update_rx_queue_list(dp); } int ath12k_dp_rx_link_desc_return(struct ath12k_base *ab, @@ -1042,6 +1082,29 @@ static int ath12k_dp_rx_assign_reoq(struct ath12k_base *ab, return 0; } +static int ath12k_dp_prepare_reo_update_elem(struct ath12k_dp *dp, + struct ath12k_peer *peer, + struct ath12k_dp_rx_tid *rx_tid) +{ + struct dp_reo_update_rx_queue_elem *elem; + + elem = kzalloc(sizeof(*elem), GFP_ATOMIC); + if (!elem) + return -ENOMEM; + + elem->peer_id = peer->peer_id; + elem->is_ml_peer = peer->mlo; + elem->ml_peer_id = peer->ml_id; + + ath12k_dp_init_rx_tid_rxq(&elem->rx_tid, rx_tid); + + spin_lock_bh(&dp->reo_rxq_flush_lock); + list_add_tail(&elem->list, &dp->reo_cmd_update_rx_queue_list); + spin_unlock_bh(&dp->reo_rxq_flush_lock); + + return 0; +} + int ath12k_dp_rx_peer_tid_setup(struct ath12k *ar, const u8 *peer_mac, int vdev_id, u8 tid, u32 ba_win_sz, u16 ssn, enum hal_pn_type pn_type) @@ -1122,6 +1185,19 @@ int ath12k_dp_rx_peer_tid_setup(struct ath12k *ar, const u8 *peer_mac, int vdev_ return ret; } + /* Pre-allocate the update_rxq_list for the corresponding tid + * This will be used during the tid delete. The reason we are not + * allocating during tid delete is that, if any alloc fail in update_rxq_list + * we may not be able to delete the tid vaddr/paddr and may lead to leak + */ + ret = ath12k_dp_prepare_reo_update_elem(dp, peer, rx_tid); + if (ret) { + ath12k_warn(ab, "failed to alloc update_rxq_list for rx tid %u\n", tid); + ath12k_dp_rx_tid_cleanup(ab, &rx_tid->qbuf); + spin_unlock_bh(&ab->base_lock); + return ret; + } + paddr_aligned = rx_tid->qbuf.paddr_aligned; if (ab->hw_params->reoq_lut_support) { /* Update the REO queue LUT at the corresponding peer id diff --git a/drivers/net/wireless/ath/ath12k/dp_rx.h b/drivers/net/wireless/ath/ath12k/dp_rx.h index da2332236b77..69d0a36a91d8 100644 --- a/drivers/net/wireless/ath/ath12k/dp_rx.h +++ b/drivers/net/wireless/ath/ath12k/dp_rx.h @@ -43,6 +43,14 @@ struct ath12k_dp_rx_reo_cache_flush_elem { unsigned long ts; }; +struct dp_reo_update_rx_queue_elem { + struct list_head list; + struct ath12k_dp_rx_tid_rxq rx_tid; + int peer_id; + bool is_ml_peer; + u16 ml_peer_id; +}; + struct ath12k_dp_rx_reo_cmd { struct list_head list; struct ath12k_dp_rx_tid_rxq data; From 5e32edc6942570429d9c64d0641fc2addbf92be1 Mon Sep 17 00:00:00 2001 From: Nithyanantham Paramasivam Date: Wed, 6 Aug 2025 16:47:49 +0530 Subject: [PATCH 160/167] wifi: ath12k: Fix flush cache failure during RX queue update Flush cache failures were observed after RX queue update for TID delete. This occurred because the queue was invalid during flush. Set the VLD bit in the RX queue update command for TID delete. This ensures the queue remains valid during the flush cache process. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.3.1-00173-QCAHKSWPL_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3 Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Signed-off-by: Nithyanantham Paramasivam Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250806111750.3214584-7-nithyanantham.paramasivam@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp_rx.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/dp_rx.c b/drivers/net/wireless/ath/ath12k/dp_rx.c index 331dafcecc89..1ed42207a637 100644 --- a/drivers/net/wireless/ath/ath12k/dp_rx.c +++ b/drivers/net/wireless/ath/ath12k/dp_rx.c @@ -850,6 +850,8 @@ static int ath12k_dp_rx_tid_delete_handler(struct ath12k_base *ab, cmd.addr_lo = lower_32_bits(rx_tid->qbuf.paddr_aligned); cmd.addr_hi = upper_32_bits(rx_tid->qbuf.paddr_aligned); cmd.upd0 |= HAL_REO_CMD_UPD0_VLD; + /* Observed flush cache failure, to avoid that set vld bit during delete */ + cmd.upd1 |= HAL_REO_CMD_UPD1_VLD; return ath12k_dp_reo_cmd_send(ab, rx_tid, HAL_REO_CMD_UPDATE_RX_QUEUE, &cmd, From b706fb4e580ba66b2c05be93809807c9312008e2 Mon Sep 17 00:00:00 2001 From: Manish Dharanenthiran Date: Wed, 6 Aug 2025 16:47:50 +0530 Subject: [PATCH 161/167] wifi: ath12k: Use 1KB Cache Flush Command for QoS TID Descriptors Currently, if the descriptor size exceeds 128 bytes, the total descriptor is split into multiple 128-byte segments, each requiring a separate flush cache queue command. This results in multiple commands being issued to flush a single TID, which negatively impacts performance. To optimize this, use the _FLUSH_QUEUE_1K_DESC REO command to flush a 1KB descriptor in a single operation to optimize performance. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.3.1-00173-QCAHKSWPL_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3 Signed-off-by: Manish Dharanenthiran Signed-off-by: Nithyanantham Paramasivam Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250806111750.3214584-8-nithyanantham.paramasivam@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp_rx.c | 59 +++++++++++----------- drivers/net/wireless/ath/ath12k/hal.h | 1 + drivers/net/wireless/ath/ath12k/hal_desc.h | 1 + drivers/net/wireless/ath/ath12k/hal_rx.c | 3 ++ 4 files changed, 34 insertions(+), 30 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/dp_rx.c b/drivers/net/wireless/ath/ath12k/dp_rx.c index 1ed42207a637..5e5c14a70316 100644 --- a/drivers/net/wireless/ath/ath12k/dp_rx.c +++ b/drivers/net/wireless/ath/ath12k/dp_rx.c @@ -693,44 +693,33 @@ static int ath12k_dp_reo_cmd_send(struct ath12k_base *ab, return 0; } -static void ath12k_dp_reo_cache_flush(struct ath12k_base *ab, - struct ath12k_dp_rx_tid_rxq *rx_tid) +static int ath12k_dp_reo_cache_flush(struct ath12k_base *ab, + struct ath12k_dp_rx_tid_rxq *rx_tid) { struct ath12k_hal_reo_cmd cmd = {}; - unsigned long tot_desc_sz, desc_sz; int ret; - tot_desc_sz = rx_tid->qbuf.size; - desc_sz = ath12k_hal_reo_qdesc_size(0, HAL_DESC_REO_NON_QOS_TID); - - while (tot_desc_sz > desc_sz) { - tot_desc_sz -= desc_sz; - cmd.addr_lo = lower_32_bits(rx_tid->qbuf.paddr_aligned + tot_desc_sz); - cmd.addr_hi = upper_32_bits(rx_tid->qbuf.paddr_aligned); - ret = ath12k_dp_reo_cmd_send(ab, rx_tid, - HAL_REO_CMD_FLUSH_CACHE, &cmd, - NULL); - if (ret) - ath12k_warn(ab, - "failed to send HAL_REO_CMD_FLUSH_CACHE, tid %d (%d)\n", - rx_tid->tid, ret); - } - - memset(&cmd, 0, sizeof(cmd)); cmd.addr_lo = lower_32_bits(rx_tid->qbuf.paddr_aligned); cmd.addr_hi = upper_32_bits(rx_tid->qbuf.paddr_aligned); - cmd.flag = HAL_REO_CMD_FLG_NEED_STATUS; + /* HAL_REO_CMD_FLG_FLUSH_FWD_ALL_MPDUS - all pending MPDUs + *in the bitmap will be forwarded/flushed to REO output rings + */ + cmd.flag = HAL_REO_CMD_FLG_NEED_STATUS | + HAL_REO_CMD_FLG_FLUSH_FWD_ALL_MPDUS; + + /* For all QoS TIDs (except NON_QOS), the driver allocates a maximum + * window size of 1024. In such cases, the driver can issue a single + * 1KB descriptor flush command instead of sending multiple 128-byte + * flush commands for each QoS TID, improving efficiency. + */ + + if (rx_tid->tid != HAL_DESC_REO_NON_QOS_TID) + cmd.flag |= HAL_REO_CMD_FLG_FLUSH_QUEUE_1K_DESC; + ret = ath12k_dp_reo_cmd_send(ab, rx_tid, HAL_REO_CMD_FLUSH_CACHE, &cmd, ath12k_dp_reo_cmd_free); - if (ret) { - ath12k_err(ab, "failed to send HAL_REO_CMD_FLUSH_CACHE cmd, tid %d (%d)\n", - rx_tid->tid, ret); - dma_unmap_single(ab->dev, rx_tid->qbuf.paddr_aligned, rx_tid->qbuf.size, - DMA_BIDIRECTIONAL); - kfree(rx_tid->qbuf.vaddr); - rx_tid->qbuf.vaddr = NULL; - } + return ret; } static void ath12k_peer_rx_tid_qref_reset(struct ath12k_base *ab, u16 peer_id, u16 tid) @@ -828,9 +817,19 @@ static void ath12k_dp_rx_tid_del_func(struct ath12k_dp *dp, void *ctx, if (dp->reo_cmd_cache_flush_count > ATH12K_DP_RX_REO_DESC_FREE_THRES || time_after(jiffies, elem->ts + msecs_to_jiffies(ATH12K_DP_RX_REO_DESC_FREE_TIMEOUT_MS))) { + /* The reo_cmd_cache_flush_list is used in only two contexts, + * one is in this function called from napi and the + * other in ath12k_dp_free during core destroy. + * If cache command sent is success, delete the element in + * the cache list. ath12k_dp_rx_reo_cmd_list_cleanup + * will be called during core destroy. + */ + + if (ath12k_dp_reo_cache_flush(ab, &elem->data)) + break; + list_del(&elem->list); dp->reo_cmd_cache_flush_count--; - ath12k_dp_reo_cache_flush(ab, &elem->data); kfree(elem); } } diff --git a/drivers/net/wireless/ath/ath12k/hal.h b/drivers/net/wireless/ath/ath12k/hal.h index c1750b5dc03c..efe00e167998 100644 --- a/drivers/net/wireless/ath/ath12k/hal.h +++ b/drivers/net/wireless/ath/ath12k/hal.h @@ -832,6 +832,7 @@ enum hal_rx_buf_return_buf_manager { #define HAL_REO_CMD_FLG_FLUSH_ALL BIT(6) #define HAL_REO_CMD_FLG_UNBLK_RESOURCE BIT(7) #define HAL_REO_CMD_FLG_UNBLK_CACHE BIT(8) +#define HAL_REO_CMD_FLG_FLUSH_QUEUE_1K_DESC BIT(9) /* Should be matching with HAL_REO_UPD_RX_QUEUE_INFO0_UPD_* fields */ #define HAL_REO_CMD_UPD0_RX_QUEUE_NUM BIT(8) diff --git a/drivers/net/wireless/ath/ath12k/hal_desc.h b/drivers/net/wireless/ath/ath12k/hal_desc.h index 0173f731bfef..13ddac4a9412 100644 --- a/drivers/net/wireless/ath/ath12k/hal_desc.h +++ b/drivers/net/wireless/ath/ath12k/hal_desc.h @@ -1225,6 +1225,7 @@ struct hal_reo_flush_queue { #define HAL_REO_FLUSH_CACHE_INFO0_FLUSH_WO_INVALIDATE BIT(12) #define HAL_REO_FLUSH_CACHE_INFO0_BLOCK_CACHE_USAGE BIT(13) #define HAL_REO_FLUSH_CACHE_INFO0_FLUSH_ALL BIT(14) +#define HAL_REO_FLUSH_CACHE_INFO0_FLUSH_QUEUE_1K_DESC BIT(15) struct hal_reo_flush_cache { struct hal_reo_cmd_hdr cmd; diff --git a/drivers/net/wireless/ath/ath12k/hal_rx.c b/drivers/net/wireless/ath/ath12k/hal_rx.c index 48aa48c48606..669096278fdd 100644 --- a/drivers/net/wireless/ath/ath12k/hal_rx.c +++ b/drivers/net/wireless/ath/ath12k/hal_rx.c @@ -89,6 +89,9 @@ static int ath12k_hal_reo_cmd_flush_cache(struct ath12k_hal *hal, if (cmd->flag & HAL_REO_CMD_FLG_FLUSH_ALL) desc->info0 |= cpu_to_le32(HAL_REO_FLUSH_CACHE_INFO0_FLUSH_ALL); + if (cmd->flag & HAL_REO_CMD_FLG_FLUSH_QUEUE_1K_DESC) + desc->info0 |= cpu_to_le32(HAL_REO_FLUSH_CACHE_INFO0_FLUSH_QUEUE_1K_DESC); + return le32_get_bits(desc->cmd.info0, HAL_REO_CMD_HDR_INFO0_CMD_NUMBER); } From 9eb6f553026e1268a62aa352af38f70fe7d42a46 Mon Sep 17 00:00:00 2001 From: Alexander Wilhelm Date: Mon, 22 Sep 2025 08:16:06 +0200 Subject: [PATCH 162/167] wifi: ath12k: enforce CPU endian format for all QMI data Currently, the QMI interface only works on little endian systems due to how it encodes and decodes data. Most QMI related data structures do not use endian specific types and are already defined in CPU native order. The ath12k specific QMI structs are an exception: they use partially endian specific types, which prevents the QMI interface from being extended to support big endian systems. Update the two affected ath12k QMI structs to use CPU order types instead. This is required because the QMI interface is being extended to support big endian system, and that support depends on QMI data structures being defined in CPU native order. This change: * preserves compatibility with existing kernels, which only support little endian system * enables future support for big endian systems * aligns ath12k QMI handling with the general QMI design Signed-off-by: Alexander Wilhelm Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250922061607.11543-1-alexander.wilhelm@westermo.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/qmi.c | 24 ++++++++++++++++-------- drivers/net/wireless/ath/ath12k/qmi.h | 16 ++++++++-------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index 7c611a1fd6d0..36325e62aa24 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -3307,20 +3307,28 @@ static int ath12k_qmi_wlanfw_wlan_cfg_send(struct ath12k_base *ab) /* This is number of CE configs */ req->tgt_cfg_len = ab->qmi.ce_cfg.tgt_ce_len; for (pipe_num = 0; pipe_num < req->tgt_cfg_len ; pipe_num++) { - req->tgt_cfg[pipe_num].pipe_num = ce_cfg[pipe_num].pipenum; - req->tgt_cfg[pipe_num].pipe_dir = ce_cfg[pipe_num].pipedir; - req->tgt_cfg[pipe_num].nentries = ce_cfg[pipe_num].nentries; - req->tgt_cfg[pipe_num].nbytes_max = ce_cfg[pipe_num].nbytes_max; - req->tgt_cfg[pipe_num].flags = ce_cfg[pipe_num].flags; + req->tgt_cfg[pipe_num].pipe_num = + __le32_to_cpu(ce_cfg[pipe_num].pipenum); + req->tgt_cfg[pipe_num].pipe_dir = + __le32_to_cpu(ce_cfg[pipe_num].pipedir); + req->tgt_cfg[pipe_num].nentries = + __le32_to_cpu(ce_cfg[pipe_num].nentries); + req->tgt_cfg[pipe_num].nbytes_max = + __le32_to_cpu(ce_cfg[pipe_num].nbytes_max); + req->tgt_cfg[pipe_num].flags = + __le32_to_cpu(ce_cfg[pipe_num].flags); } req->svc_cfg_valid = 1; /* This is number of Service/CE configs */ req->svc_cfg_len = ab->qmi.ce_cfg.svc_to_ce_map_len; for (pipe_num = 0; pipe_num < req->svc_cfg_len; pipe_num++) { - req->svc_cfg[pipe_num].service_id = svc_cfg[pipe_num].service_id; - req->svc_cfg[pipe_num].pipe_dir = svc_cfg[pipe_num].pipedir; - req->svc_cfg[pipe_num].pipe_num = svc_cfg[pipe_num].pipenum; + req->svc_cfg[pipe_num].service_id = + __le32_to_cpu(svc_cfg[pipe_num].service_id); + req->svc_cfg[pipe_num].pipe_dir = + __le32_to_cpu(svc_cfg[pipe_num].pipedir); + req->svc_cfg[pipe_num].pipe_num = + __le32_to_cpu(svc_cfg[pipe_num].pipenum); } /* set shadow v3 configuration */ diff --git a/drivers/net/wireless/ath/ath12k/qmi.h b/drivers/net/wireless/ath/ath12k/qmi.h index abdaade3b542..4767d9a2e309 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.h +++ b/drivers/net/wireless/ath/ath12k/qmi.h @@ -392,17 +392,17 @@ enum qmi_wlanfw_pipedir_enum_v01 { }; struct qmi_wlanfw_ce_tgt_pipe_cfg_s_v01 { - __le32 pipe_num; - __le32 pipe_dir; - __le32 nentries; - __le32 nbytes_max; - __le32 flags; + u32 pipe_num; + u32 pipe_dir; + u32 nentries; + u32 nbytes_max; + u32 flags; }; struct qmi_wlanfw_ce_svc_pipe_cfg_s_v01 { - __le32 service_id; - __le32 pipe_dir; - __le32 pipe_num; + u32 service_id; + u32 pipe_dir; + u32 pipe_num; }; struct qmi_wlanfw_shadow_reg_cfg_s_v01 { From 32be3ca4cf78b309dfe7ba52fe2d7cc3c23c5634 Mon Sep 17 00:00:00 2001 From: Muhammad Usama Anjum Date: Tue, 22 Jul 2025 10:31:21 +0500 Subject: [PATCH 163/167] wifi: ath11k: HAL SRNG: don't deinitialize and re-initialize again Don't deinitialize and reinitialize the HAL helpers. The dma memory is deallocated and there is high possibility that we'll not be able to get the same memory allocated from dma when there is high memory pressure. Tested-on: WCN6855 hw2.0 PCI WLAN.HSP.1.1-03926.13-QCAHSPSWPL_V2_SILICONZ_CE-2.52297.6 Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices") Cc: stable@vger.kernel.org Cc: Baochen Qiang Reviewed-by: Baochen Qiang Signed-off-by: Muhammad Usama Anjum Link: https://patch.msgid.link/20250722053121.1145001-1-usama.anjum@collabora.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/core.c | 6 +----- drivers/net/wireless/ath/ath11k/hal.c | 16 ++++++++++++++++ drivers/net/wireless/ath/ath11k/hal.h | 1 + 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/core.c b/drivers/net/wireless/ath/ath11k/core.c index d49353b6b2e7..2810752260f2 100644 --- a/drivers/net/wireless/ath/ath11k/core.c +++ b/drivers/net/wireless/ath/ath11k/core.c @@ -2215,14 +2215,10 @@ static int ath11k_core_reconfigure_on_crash(struct ath11k_base *ab) mutex_unlock(&ab->core_lock); ath11k_dp_free(ab); - ath11k_hal_srng_deinit(ab); + ath11k_hal_srng_clear(ab); ab->free_vdev_map = (1LL << (ab->num_radios * TARGET_NUM_VDEVS(ab))) - 1; - ret = ath11k_hal_srng_init(ab); - if (ret) - return ret; - clear_bit(ATH11K_FLAG_CRASH_FLUSH, &ab->dev_flags); ret = ath11k_core_qmi_firmware_ready(ab); diff --git a/drivers/net/wireless/ath/ath11k/hal.c b/drivers/net/wireless/ath/ath11k/hal.c index 0c3ce7509ab8..0c797b8d0a27 100644 --- a/drivers/net/wireless/ath/ath11k/hal.c +++ b/drivers/net/wireless/ath/ath11k/hal.c @@ -1386,6 +1386,22 @@ void ath11k_hal_srng_deinit(struct ath11k_base *ab) } EXPORT_SYMBOL(ath11k_hal_srng_deinit); +void ath11k_hal_srng_clear(struct ath11k_base *ab) +{ + /* No need to memset rdp and wrp memory since each individual + * segment would get cleared in ath11k_hal_srng_src_hw_init() + * and ath11k_hal_srng_dst_hw_init(). + */ + memset(ab->hal.srng_list, 0, + sizeof(ab->hal.srng_list)); + memset(ab->hal.shadow_reg_addr, 0, + sizeof(ab->hal.shadow_reg_addr)); + ab->hal.avail_blk_resource = 0; + ab->hal.current_blk_index = 0; + ab->hal.num_shadow_reg_configured = 0; +} +EXPORT_SYMBOL(ath11k_hal_srng_clear); + void ath11k_hal_dump_srng_stats(struct ath11k_base *ab) { struct hal_srng *srng; diff --git a/drivers/net/wireless/ath/ath11k/hal.h b/drivers/net/wireless/ath/ath11k/hal.h index 601542410c75..839095af9267 100644 --- a/drivers/net/wireless/ath/ath11k/hal.h +++ b/drivers/net/wireless/ath/ath11k/hal.h @@ -965,6 +965,7 @@ int ath11k_hal_srng_setup(struct ath11k_base *ab, enum hal_ring_type type, struct hal_srng_params *params); int ath11k_hal_srng_init(struct ath11k_base *ath11k); void ath11k_hal_srng_deinit(struct ath11k_base *ath11k); +void ath11k_hal_srng_clear(struct ath11k_base *ab); void ath11k_hal_dump_srng_stats(struct ath11k_base *ab); void ath11k_hal_srng_get_shadow_config(struct ath11k_base *ab, u32 **cfg, u32 *len); From 17f34ab55a8518ecbd5dcacec48e6ee903f7c1d0 Mon Sep 17 00:00:00 2001 From: Ryder Lee Date: Mon, 22 Sep 2025 22:19:08 +0000 Subject: [PATCH 164/167] wifi: cfg80211: fix width unit in cfg80211_radio_chandef_valid() The original code used nl80211_chan_width_to_mhz(), which returns the width in MHz. However, the expected unit is KHz. Fixes: 510dba80ed66 ("wifi: cfg80211: add helper for checking if a chandef is valid on a radio") Signed-off-by: Ryder Lee Link: https://patch.msgid.link/df54294e6c4ed0f3ceff6e818b710478ddfc62c0.1758579480.git.Ryder%20Lee%20ryder.lee@mediatek.com/ Signed-off-by: Johannes Berg --- net/wireless/util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/wireless/util.c b/net/wireless/util.c index f26440d18ad3..56724b33af04 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -2965,7 +2965,7 @@ bool cfg80211_radio_chandef_valid(const struct wiphy_radio *radio, u32 freq, width; freq = ieee80211_chandef_to_khz(chandef); - width = cfg80211_chandef_get_width(chandef); + width = MHZ_TO_KHZ(cfg80211_chandef_get_width(chandef)); if (!ieee80211_radio_freq_range_valid(radio, freq, width)) return false; From 1e06a137513dff9835e57f76cff177cb1e2e5475 Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Mon, 22 Sep 2025 12:24:07 +0200 Subject: [PATCH 165/167] wifi: libertas: WQ_PERCPU added to alloc_workqueue users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently if a user enqueue a work item using schedule_delayed_work() the used wq is "system_wq" (per-cpu wq) while queue_delayed_work() use WORK_CPU_UNBOUND (used when a cpu is not specified). The same applies to schedule_work() that is using system_wq and queue_work(), that makes use again of WORK_CPU_UNBOUND. This lack of consistentcy cannot be addressed without refactoring the API. alloc_workqueue() treats all queues as per-CPU by default, while unbound workqueues must opt-in via WQ_UNBOUND. This default is suboptimal: most workloads benefit from unbound queues, allowing the scheduler to place worker threads where they’re needed and reducing noise when CPUs are isolated. This change add a new WQ_PERCPU flag, to explicitly request the use of the per-CPU behavior. Both flags coexist for one release cycle to allow callers to transition their calls. Once migration is complete, WQ_UNBOUND can be removed and unbound will become the implicit default. With the introduction of the WQ_PERCPU flag (equivalent to !WQ_UNBOUND), any alloc_workqueue() caller that doesn’t explicitly specify WQ_UNBOUND must now use WQ_PERCPU. All existing users have been updated accordingly. Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Link: https://patch.msgid.link/20250922102407.186660-2-marco.crivellari@suse.com Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/libertas/if_sdio.c | 3 ++- drivers/net/wireless/marvell/libertas/if_spi.c | 3 ++- drivers/net/wireless/marvell/libertas_tf/main.c | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/marvell/libertas/if_sdio.c b/drivers/net/wireless/marvell/libertas/if_sdio.c index 524034699972..1e29e80cad61 100644 --- a/drivers/net/wireless/marvell/libertas/if_sdio.c +++ b/drivers/net/wireless/marvell/libertas/if_sdio.c @@ -1181,7 +1181,8 @@ static int if_sdio_probe(struct sdio_func *func, spin_lock_init(&card->lock); INIT_LIST_HEAD(&card->packets); - card->workqueue = alloc_workqueue("libertas_sdio", WQ_MEM_RECLAIM, 0); + card->workqueue = alloc_workqueue("libertas_sdio", + WQ_MEM_RECLAIM | WQ_PERCPU, 0); if (unlikely(!card->workqueue)) { ret = -ENOMEM; goto err_queue; diff --git a/drivers/net/wireless/marvell/libertas/if_spi.c b/drivers/net/wireless/marvell/libertas/if_spi.c index b722a6587fd3..699bae8971f8 100644 --- a/drivers/net/wireless/marvell/libertas/if_spi.c +++ b/drivers/net/wireless/marvell/libertas/if_spi.c @@ -1153,7 +1153,8 @@ static int if_spi_probe(struct spi_device *spi) priv->fw_ready = 1; /* Initialize interrupt handling stuff. */ - card->workqueue = alloc_workqueue("libertas_spi", WQ_MEM_RECLAIM, 0); + card->workqueue = alloc_workqueue("libertas_spi", + WQ_MEM_RECLAIM | WQ_PERCPU, 0); if (!card->workqueue) { err = -ENOMEM; goto remove_card; diff --git a/drivers/net/wireless/marvell/libertas_tf/main.c b/drivers/net/wireless/marvell/libertas_tf/main.c index d1067874428f..c51091e7c6c6 100644 --- a/drivers/net/wireless/marvell/libertas_tf/main.c +++ b/drivers/net/wireless/marvell/libertas_tf/main.c @@ -708,7 +708,7 @@ EXPORT_SYMBOL_GPL(lbtf_bcn_sent); static int __init lbtf_init_module(void) { lbtf_deb_enter(LBTF_DEB_MAIN); - lbtf_wq = alloc_workqueue("libertastf", WQ_MEM_RECLAIM, 0); + lbtf_wq = alloc_workqueue("libertastf", WQ_MEM_RECLAIM | WQ_PERCPU, 0); if (lbtf_wq == NULL) { printk(KERN_ERR "libertastf: couldn't create workqueue\n"); return -ENOMEM; From 04ffa809728f106e1f8824ba0a0ee32054e393bd Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 24 Sep 2025 10:47:48 +0200 Subject: [PATCH 166/167] Revert "wifi: libertas: WQ_PERCPU added to alloc_workqueue users" This reverts commit 1e06a137513d ("wifi: libertas: WQ_PERCPU added to alloc_workqueue users") since there's really no reason to use per-CPU here. Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/libertas/if_sdio.c | 3 +-- drivers/net/wireless/marvell/libertas/if_spi.c | 3 +-- drivers/net/wireless/marvell/libertas_tf/main.c | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/marvell/libertas/if_sdio.c b/drivers/net/wireless/marvell/libertas/if_sdio.c index 1e29e80cad61..524034699972 100644 --- a/drivers/net/wireless/marvell/libertas/if_sdio.c +++ b/drivers/net/wireless/marvell/libertas/if_sdio.c @@ -1181,8 +1181,7 @@ static int if_sdio_probe(struct sdio_func *func, spin_lock_init(&card->lock); INIT_LIST_HEAD(&card->packets); - card->workqueue = alloc_workqueue("libertas_sdio", - WQ_MEM_RECLAIM | WQ_PERCPU, 0); + card->workqueue = alloc_workqueue("libertas_sdio", WQ_MEM_RECLAIM, 0); if (unlikely(!card->workqueue)) { ret = -ENOMEM; goto err_queue; diff --git a/drivers/net/wireless/marvell/libertas/if_spi.c b/drivers/net/wireless/marvell/libertas/if_spi.c index 699bae8971f8..b722a6587fd3 100644 --- a/drivers/net/wireless/marvell/libertas/if_spi.c +++ b/drivers/net/wireless/marvell/libertas/if_spi.c @@ -1153,8 +1153,7 @@ static int if_spi_probe(struct spi_device *spi) priv->fw_ready = 1; /* Initialize interrupt handling stuff. */ - card->workqueue = alloc_workqueue("libertas_spi", - WQ_MEM_RECLAIM | WQ_PERCPU, 0); + card->workqueue = alloc_workqueue("libertas_spi", WQ_MEM_RECLAIM, 0); if (!card->workqueue) { err = -ENOMEM; goto remove_card; diff --git a/drivers/net/wireless/marvell/libertas_tf/main.c b/drivers/net/wireless/marvell/libertas_tf/main.c index c51091e7c6c6..d1067874428f 100644 --- a/drivers/net/wireless/marvell/libertas_tf/main.c +++ b/drivers/net/wireless/marvell/libertas_tf/main.c @@ -708,7 +708,7 @@ EXPORT_SYMBOL_GPL(lbtf_bcn_sent); static int __init lbtf_init_module(void) { lbtf_deb_enter(LBTF_DEB_MAIN); - lbtf_wq = alloc_workqueue("libertastf", WQ_MEM_RECLAIM | WQ_PERCPU, 0); + lbtf_wq = alloc_workqueue("libertastf", WQ_MEM_RECLAIM, 0); if (lbtf_wq == NULL) { printk(KERN_ERR "libertastf: couldn't create workqueue\n"); return -ENOMEM; From 56d9de46715245c9cc46dbe16830e431056abbc3 Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Tue, 23 Sep 2025 16:59:05 +0200 Subject: [PATCH 167/167] wifi: libertas: add WQ_UNBOUND to alloc_workqueue users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently if a user enqueues a work item using schedule_delayed_work() the used wq is "system_wq" (per-cpu wq) while queue_delayed_work() uses WORK_CPU_UNBOUND (used when a cpu is not specified). The same applies to schedule_work() that is using system_wq and queue_work(), that makes use again of WORK_CPU_UNBOUND. This lack of consistency cannot be addressed without refactoring the API. alloc_workqueue() treats all queues as per-CPU by default, while unbound workqueues must opt-in via WQ_UNBOUND. This default is suboptimal: most workloads benefit from unbound queues, allowing the scheduler to place worker threads where they’re needed and reducing noise when CPUs are isolated. Explicitly add the WQ_UNBOUND flag to alloc_workqueue() users, marking the workqueue unbound. Once migration is complete, WQ_UNBOUND can be removed and unbound will become the implicit default. With the introduction of the WQ_PERCPU flag (equivalent to !WQ_UNBOUND), any alloc_workqueue() caller that doesn’t explicitly specify WQ_UNBOUND must now use WQ_PERCPU. Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Link: https://patch.msgid.link/20250923145905.327269-2-marco.crivellari@suse.com [use imperative voice in subject, fix typos] Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/libertas/if_sdio.c | 3 ++- drivers/net/wireless/marvell/libertas/if_spi.c | 3 ++- drivers/net/wireless/marvell/libertas_tf/main.c | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/marvell/libertas/if_sdio.c b/drivers/net/wireless/marvell/libertas/if_sdio.c index 524034699972..fc5318035822 100644 --- a/drivers/net/wireless/marvell/libertas/if_sdio.c +++ b/drivers/net/wireless/marvell/libertas/if_sdio.c @@ -1181,7 +1181,8 @@ static int if_sdio_probe(struct sdio_func *func, spin_lock_init(&card->lock); INIT_LIST_HEAD(&card->packets); - card->workqueue = alloc_workqueue("libertas_sdio", WQ_MEM_RECLAIM, 0); + card->workqueue = alloc_workqueue("libertas_sdio", + WQ_MEM_RECLAIM | WQ_UNBOUND, 0); if (unlikely(!card->workqueue)) { ret = -ENOMEM; goto err_queue; diff --git a/drivers/net/wireless/marvell/libertas/if_spi.c b/drivers/net/wireless/marvell/libertas/if_spi.c index b722a6587fd3..8a2504a62840 100644 --- a/drivers/net/wireless/marvell/libertas/if_spi.c +++ b/drivers/net/wireless/marvell/libertas/if_spi.c @@ -1153,7 +1153,8 @@ static int if_spi_probe(struct spi_device *spi) priv->fw_ready = 1; /* Initialize interrupt handling stuff. */ - card->workqueue = alloc_workqueue("libertas_spi", WQ_MEM_RECLAIM, 0); + card->workqueue = alloc_workqueue("libertas_spi", + WQ_MEM_RECLAIM | WQ_UNBOUND, 0); if (!card->workqueue) { err = -ENOMEM; goto remove_card; diff --git a/drivers/net/wireless/marvell/libertas_tf/main.c b/drivers/net/wireless/marvell/libertas_tf/main.c index d1067874428f..fb20fe31cd36 100644 --- a/drivers/net/wireless/marvell/libertas_tf/main.c +++ b/drivers/net/wireless/marvell/libertas_tf/main.c @@ -708,7 +708,7 @@ EXPORT_SYMBOL_GPL(lbtf_bcn_sent); static int __init lbtf_init_module(void) { lbtf_deb_enter(LBTF_DEB_MAIN); - lbtf_wq = alloc_workqueue("libertastf", WQ_MEM_RECLAIM, 0); + lbtf_wq = alloc_workqueue("libertastf", WQ_MEM_RECLAIM | WQ_UNBOUND, 0); if (lbtf_wq == NULL) { printk(KERN_ERR "libertastf: couldn't create workqueue\n"); return -ENOMEM;