A single timeout in rtw_fw_leave_lps_check() does not always indicate a frozen firmware; in noisy RF environments or during beacon loss, the 802.11 null data frame ACK can be delayed or dropped. Immediately triggering firmware recovery and a full hardware restart on a single timeout causes unnecessary disconnections and kernel warning traces on idle links. Follow rtw89 by introducing a consecutive failure counter (ps_hang_cnt) and only triggering rtw_fw_recovery() after RTW_PS_HANG_MAX_CNT (3) consecutive failures. Reset the counter to 0 on every successful LPS leave and in rtw_core_start() so that cold starts and hardware restarts always begin with a clean slate. On chipsets without FW_FEATURE_LPS_C2H (such as RTL8822B and RTL8723D), rtw_fw_leave_lps_check() falls back to polling REG_TCR for BIT_PWRMGT_HWDATA_EN. On timeout, the driver forcefully clears this bit so subsequent packets can be transmitted. Because stuck firmware never re-asserts the bit, subsequent register checks immediately pass, clearing ps_hang_cnt back to 0 and preventing the counter from ever accumulating. Therefore, only count consecutive failures on devices with FW_FEATURE_LPS_C2H, where C2H completion signaling reliably detects consecutive timeouts. Additionally, rtw_fw_recovery() clears keys via rtw_iterate_keys_atomic() under a spinlock. On USB and SDIO, register writes in key clearing sleep (via usb_control_msg() and sdio_claim_host()), which would trigger a BUG: scheduling while atomic if executed. Because USB and SDIO do not support firmware recovery, restrict firmware recovery invocation to PCI devices via rtw_hci_type(). Signed-off-by: Kalpesh Solanki --- drivers/net/wireless/realtek/rtw88/main.c | 2 ++ drivers/net/wireless/realtek/rtw88/main.h | 1 + drivers/net/wireless/realtek/rtw88/ps.c | 9 +++++++++ drivers/net/wireless/realtek/rtw88/ps.h | 1 + 4 files changed, 13 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c index a74f199a93a..d6acf33c3c9 100644 --- a/drivers/net/wireless/realtek/rtw88/main.c +++ b/drivers/net/wireless/realtek/rtw88/main.c @@ -1536,6 +1536,8 @@ int rtw_core_start(struct rtw_dev *rtwdev) set_bit(RTW_FLAG_RUNNING, rtwdev->flags); + rtwdev->ps_hang_cnt = 0; + return 0; } diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h index 6ecbeb4ffe4..525f17590ad 100644 --- a/drivers/net/wireless/realtek/rtw88/main.h +++ b/drivers/net/wireless/realtek/rtw88/main.h @@ -2113,6 +2113,7 @@ struct rtw_dev { /* lps power state & handler work */ struct rtw_lps_conf lps_conf; bool ps_enabled; + u8 ps_hang_cnt; bool beacon_loss; struct completion lps_leave_check; diff --git a/drivers/net/wireless/realtek/rtw88/ps.c b/drivers/net/wireless/realtek/rtw88/ps.c index add5a20b843..908d2ea3572 100644 --- a/drivers/net/wireless/realtek/rtw88/ps.c +++ b/drivers/net/wireless/realtek/rtw88/ps.c @@ -166,6 +166,15 @@ static void rtw_fw_leave_lps_check(struct rtw_dev *rtwdev) rtw_write32_clr(rtwdev, REG_TCR, BIT_PWRMGT_HWDATA_EN); rtw_warn(rtwdev, "firmware failed to leave lps state\n"); rtw_fw_dump_dbg_info(rtwdev); + + if (rtw_fw_feature_check(fw, FW_FEATURE_LPS_C2H) && + rtw_hci_type(rtwdev) == RTW_HCI_TYPE_PCIE) { + rtwdev->ps_hang_cnt++; + if (rtwdev->ps_hang_cnt >= RTW_PS_HANG_MAX_CNT) + rtw_fw_recovery(rtwdev); + } + } else { + rtwdev->ps_hang_cnt = 0; } } diff --git a/drivers/net/wireless/realtek/rtw88/ps.h b/drivers/net/wireless/realtek/rtw88/ps.h index 5ae83d2526c..b088fad7cd8 100644 --- a/drivers/net/wireless/realtek/rtw88/ps.h +++ b/drivers/net/wireless/realtek/rtw88/ps.h @@ -14,6 +14,7 @@ #define LEAVE_LPS_TRY_CNT 5 #define LEAVE_LPS_TIMEOUT msecs_to_jiffies(100) +#define RTW_PS_HANG_MAX_CNT 3 int rtw_enter_ips(struct rtw_dev *rtwdev); int rtw_leave_ips(struct rtw_dev *rtwdev); -- 2.53.0