From 44bc17f7f5b3b2cc4084eba6307ba750078a8a73 Mon Sep 17 00:00:00 2001 From: Chin-Yen Lee Date: Thu, 19 Dec 2019 16:58:14 +0800 Subject: rtw88: support wowlan feature for 8822c Wake on WLAN(wowlan) is a feature which allows devices to be woken up from suspend state through wlan events. When user enables wowlan feature and then let the device enter suspend state, wowlan firmware will be loaded by the driver and periodically monitors wifi packets. Power consumption of wifi chip will be reduced in this state. If wowlan firmware detects that specific wlan event happens, it will issue wakeup signal to trigger resume process. Driver will load normal firmware and let wifi chip return to the original state. Currently supported wlan events include receiving magic packet, rekey packet and deauth packet, and disconnecting from AP. Signed-off-by: Chin-Yen Lee Signed-off-by: Yan-Hsuan Chuang Signed-off-by: Kalle Valo --- drivers/net/wireless/realtek/rtw88/fw.h | 69 +++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) (limited to 'drivers/net/wireless/realtek/rtw88/fw.h') diff --git a/drivers/net/wireless/realtek/rtw88/fw.h b/drivers/net/wireless/realtek/rtw88/fw.h index 73d1b9ca8efc..9ffc1b1ea895 100644 --- a/drivers/net/wireless/realtek/rtw88/fw.h +++ b/drivers/net/wireless/realtek/rtw88/fw.h @@ -100,6 +100,23 @@ struct rtw_rsvd_page { bool add_txdesc; }; +enum rtw_keep_alive_pkt_type { + KEEP_ALIVE_NULL_PKT = 0, + KEEP_ALIVE_ARP_RSP = 1, +}; + +struct rtw_fw_wow_keep_alive_para { + bool adopt; + u8 pkt_type; + u8 period; /* unit: sec */ +}; + +struct rtw_fw_wow_disconnect_para { + bool adopt; + u8 period; /* unit: sec */ + u8 retry_count; +}; + struct rtw_fw_hdr { __le16 signature; u8 category; @@ -198,6 +215,11 @@ static inline void rtw_h2c_pkt_set_header(u8 *h2c_pkt, u8 sub_id) #define H2C_CMD_QUERY_BT_MP_INFO 0x67 #define H2C_CMD_BT_WIFI_CONTROL 0x69 +#define H2C_CMD_KEEP_ALIVE 0x03 +#define H2C_CMD_DISCONNECT_DECISION 0x04 +#define H2C_CMD_WOWLAN 0x80 +#define H2C_CMD_REMOTE_WAKE_CTRL 0x81 +#define H2C_CMD_AOAC_GLOBAL_INFO 0x82 #define SET_H2C_CMD_ID_CLASS(h2c_pkt, value) \ le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, GENMASK(7, 0)) @@ -301,6 +323,45 @@ static inline void rtw_h2c_pkt_set_header(u8 *h2c_pkt, u8 sub_id) #define SET_BT_WIFI_CONTROL_DATA5(h2c_pkt, value) \ le32p_replace_bits((__le32 *)(h2c_pkt) + 0x01, value, GENMASK(23, 16)) +#define SET_KEEP_ALIVE_ENABLE(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(8)) +#define SET_KEEP_ALIVE_ADOPT(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(9)) +#define SET_KEEP_ALIVE_PKT_TYPE(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(10)) +#define SET_KEEP_ALIVE_CHECK_PERIOD(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, GENMASK(23, 16)) + +#define SET_DISCONNECT_DECISION_ENABLE(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(8)) +#define SET_DISCONNECT_DECISION_ADOPT(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(9)) +#define SET_DISCONNECT_DECISION_CHECK_PERIOD(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, GENMASK(23, 16)) +#define SET_DISCONNECT_DECISION_TRY_PKT_NUM(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, GENMASK(31, 24)) + +#define SET_WOWLAN_FUNC_ENABLE(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(8)) +#define SET_WOWLAN_MAGIC_PKT_ENABLE(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(10)) +#define SET_WOWLAN_UNICAST_PKT_ENABLE(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(11)) +#define SET_WOWLAN_REKEY_WAKEUP_ENABLE(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(14)) +#define SET_WOWLAN_DEAUTH_WAKEUP_ENABLE(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(15)) + +#define SET_REMOTE_WAKECTRL_ENABLE(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(8)) +#define SET_REMOTE_WAKE_CTRL_NLO_OFFLOAD_EN(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(12)) + +#define SET_AOAC_GLOBAL_INFO_PAIRWISE_ENC_ALG(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, GENMASK(15, 8)) +#define SET_AOAC_GLOBAL_INFO_GROUP_ENC_ALG(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, GENMASK(23, 16)) + static inline struct rtw_c2h_cmd *get_c2h_from_skb(struct sk_buff *skb) { u32 pkt_offset; @@ -340,4 +401,12 @@ int rtw_fw_download_rsvd_page(struct rtw_dev *rtwdev, void rtw_send_rsvd_page_h2c(struct rtw_dev *rtwdev); int rtw_dump_drv_rsvd_page(struct rtw_dev *rtwdev, u32 offset, u32 size, u32 *buf); +void rtw_fw_set_remote_wake_ctrl_cmd(struct rtw_dev *rtwdev, bool enable); +void rtw_fw_set_wowlan_ctrl_cmd(struct rtw_dev *rtwdev, bool enable); +void rtw_fw_set_keep_alive_cmd(struct rtw_dev *rtwdev, bool enable); +void rtw_fw_set_disconnect_decision_cmd(struct rtw_dev *rtwdev, bool enable); +void rtw_fw_set_aoac_global_info_cmd(struct rtw_dev *rtwdev, + u8 pairwise_key_enc, + u8 group_key_enc); + #endif -- cgit v1.2.3-59-g8ed1b From e3e400df30078830871b927ca24826667d97d1f6 Mon Sep 17 00:00:00 2001 From: Chin-Yen Lee Date: Thu, 19 Dec 2019 16:58:15 +0800 Subject: rtw88: Add wowlan pattern match support Pattern match is an option of wowlan to allow the device to be woken up from suspend mode when receiving packets matched user-designed patterns. The patterns are written into hardware cam in suspend flow if users have set up them. If packets matched designed pattern are received, wowlan firmware will get an interrupt and then wake up the device. Signed-off-by: Chin-Yen Lee Signed-off-by: Yan-Hsuan Chuang Signed-off-by: Kalle Valo --- drivers/net/wireless/realtek/rtw88/fw.c | 6 + drivers/net/wireless/realtek/rtw88/fw.h | 4 + drivers/net/wireless/realtek/rtw88/main.h | 14 ++ drivers/net/wireless/realtek/rtw88/reg.h | 14 ++ drivers/net/wireless/realtek/rtw88/rtw8822c.c | 3 + drivers/net/wireless/realtek/rtw88/wow.c | 224 ++++++++++++++++++++++++++ drivers/net/wireless/realtek/rtw88/wow.h | 9 ++ 7 files changed, 274 insertions(+) (limited to 'drivers/net/wireless/realtek/rtw88/fw.h') diff --git a/drivers/net/wireless/realtek/rtw88/fw.c b/drivers/net/wireless/realtek/rtw88/fw.c index 16ff59576154..0523851cf98d 100644 --- a/drivers/net/wireless/realtek/rtw88/fw.c +++ b/drivers/net/wireless/realtek/rtw88/fw.c @@ -538,6 +538,8 @@ void rtw_fw_set_wowlan_ctrl_cmd(struct rtw_dev *rtwdev, bool enable) SET_WOWLAN_DEAUTH_WAKEUP_ENABLE(h2c_pkt, enable); if (test_bit(RTW_WOW_FLAG_EN_REKEY_PKT, rtw_wow->flags)) SET_WOWLAN_REKEY_WAKEUP_ENABLE(h2c_pkt, enable); + if (rtw_wow->pattern_cnt) + SET_WOWLAN_PATTERN_MATCH_ENABLE(h2c_pkt, enable); } rtw_fw_send_h2c_command(rtwdev, h2c_pkt); @@ -596,6 +598,7 @@ void rtw_fw_set_pg_info(struct rtw_dev *rtwdev) LPS_PG_INFO_LOC(h2c_pkt, loc_pg); LPS_PG_DPK_LOC(h2c_pkt, loc_dpk); LPS_PG_SEC_CAM_EN(h2c_pkt, conf->sec_cam_backup); + LPS_PG_PATTERN_CAM_EN(h2c_pkt, conf->pattern_cam_backup); rtw_fw_send_h2c_command(rtwdev, h2c_pkt); } @@ -677,6 +680,7 @@ static struct sk_buff *rtw_lps_pg_info_get(struct ieee80211_hw *hw, struct rtw_chip_info *chip = rtwdev->chip; struct rtw_lps_conf *conf = &rtwdev->lps_conf; struct rtw_lps_pg_info_hdr *pg_info_hdr; + struct rtw_wow_param *rtw_wow = &rtwdev->wow; struct sk_buff *skb; u32 size; @@ -691,8 +695,10 @@ static struct sk_buff *rtw_lps_pg_info_get(struct ieee80211_hw *hw, pg_info_hdr->macid = find_first_bit(rtwdev->mac_id_map, RTW_MAX_MAC_ID_NUM); pg_info_hdr->sec_cam_count = rtw_sec_cam_pg_backup(rtwdev, pg_info_hdr->sec_cam); + pg_info_hdr->pattern_count = rtw_wow->pattern_cnt; conf->sec_cam_backup = pg_info_hdr->sec_cam_count != 0; + conf->pattern_cam_backup = rtw_wow->pattern_cnt != 0; return skb; } diff --git a/drivers/net/wireless/realtek/rtw88/fw.h b/drivers/net/wireless/realtek/rtw88/fw.h index 9ffc1b1ea895..9ea27cca11ac 100644 --- a/drivers/net/wireless/realtek/rtw88/fw.h +++ b/drivers/net/wireless/realtek/rtw88/fw.h @@ -246,6 +246,8 @@ static inline void rtw_h2c_pkt_set_header(u8 *h2c_pkt, u8 sub_id) le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, GENMASK(31, 24)) #define LPS_PG_SEC_CAM_EN(h2c_pkt, value) \ le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(8)) +#define LPS_PG_PATTERN_CAM_EN(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(10)) #define SET_RSSI_INFO_MACID(h2c_pkt, value) \ le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, GENMASK(15, 8)) #define SET_RSSI_INFO_RSSI(h2c_pkt, value) \ @@ -343,6 +345,8 @@ static inline void rtw_h2c_pkt_set_header(u8 *h2c_pkt, u8 sub_id) #define SET_WOWLAN_FUNC_ENABLE(h2c_pkt, value) \ le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(8)) +#define SET_WOWLAN_PATTERN_MATCH_ENABLE(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(9)) #define SET_WOWLAN_MAGIC_PKT_ENABLE(h2c_pkt, value) \ le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(10)) #define SET_WOWLAN_UNICAST_PKT_ENABLE(h2c_pkt, value) \ diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h index e403d24adbcf..74368ef954fa 100644 --- a/drivers/net/wireless/realtek/rtw88/main.h +++ b/drivers/net/wireless/realtek/rtw88/main.h @@ -19,6 +19,10 @@ #define RTW_MAX_SEC_CAM_NUM 32 #define MAX_PG_CAM_BACKUP_NUM 8 +#define RTW_MAX_PATTERN_NUM 12 +#define RTW_MAX_PATTERN_MASK_SIZE 16 +#define RTW_MAX_PATTERN_SIZE 128 + #define RTW_WATCH_DOG_DELAY_TIME round_jiffies_relative(HZ * 2) #define RFREG_MASK 0xfffff @@ -623,6 +627,7 @@ struct rtw_lps_conf { u8 smart_ps; u8 port_id; bool sec_cam_backup; + bool pattern_cam_backup; }; enum rtw_hw_key_type { @@ -917,10 +922,19 @@ struct rtw_intf_phy_para { u16 platform; }; +struct rtw_wow_pattern { + u16 crc; + u8 type; + u8 valid; + u8 mask[RTW_MAX_PATTERN_MASK_SIZE]; +}; + struct rtw_wow_param { struct ieee80211_vif *wow_vif; DECLARE_BITMAP(flags, RTW_WOW_FLAG_MAX); u8 txpause; + u8 pattern_cnt; + struct rtw_wow_pattern patterns[RTW_MAX_PATTERN_NUM]; }; struct rtw_intf_phy_para_table { diff --git a/drivers/net/wireless/realtek/rtw88/reg.h b/drivers/net/wireless/realtek/rtw88/reg.h index 77f31420599f..9d94534c9674 100644 --- a/drivers/net/wireless/realtek/rtw88/reg.h +++ b/drivers/net/wireless/realtek/rtw88/reg.h @@ -338,6 +338,20 @@ #define BIT_RFMOD_80M BIT(8) #define BIT_RFMOD_40M BIT(7) #define REG_WMAC_TRXPTCL_CTL_H 0x066C +#define REG_WKFMCAM_CMD 0x0698 +#define BIT_WKFCAM_POLLING_V1 BIT(31) +#define BIT_WKFCAM_CLR_V1 BIT(30) +#define BIT_WKFCAM_WE BIT(16) +#define BIT_SHIFT_WKFCAM_ADDR_V2 8 +#define BIT_MASK_WKFCAM_ADDR_V2 0xff +#define BIT_WKFCAM_ADDR_V2(x) \ + (((x) & BIT_MASK_WKFCAM_ADDR_V2) << BIT_SHIFT_WKFCAM_ADDR_V2) +#define REG_WKFMCAM_RWD 0x069C +#define BIT_WKFMCAM_VALID BIT(31) +#define BIT_WKFMCAM_BC BIT(26) +#define BIT_WKFMCAM_MC BIT(25) +#define BIT_WKFMCAM_UC BIT(24) + #define REG_RXFLTMAP0 0x06A0 #define REG_RXFLTMAP1 0x06A2 #define REG_RXFLTMAP2 0x06A4 diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822c.c b/drivers/net/wireless/realtek/rtw88/rtw8822c.c index e10fe672f79c..4836c0044b05 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8822c.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8822c.c @@ -4064,6 +4064,9 @@ static const struct rtw_pwr_track_tbl rtw8822c_rtw_pwr_track_tbl = { static const struct wiphy_wowlan_support rtw_wowlan_stub_8822c = { .flags = WIPHY_WOWLAN_MAGIC_PKT | WIPHY_WOWLAN_GTK_REKEY_FAILURE | WIPHY_WOWLAN_DISCONNECT | WIPHY_WOWLAN_SUPPORTS_GTK_REKEY, + .n_patterns = RTW_MAX_PATTERN_NUM, + .pattern_max_len = RTW_MAX_PATTERN_SIZE, + .pattern_min_len = 1, }; #endif diff --git a/drivers/net/wireless/realtek/rtw88/wow.c b/drivers/net/wireless/realtek/rtw88/wow.c index 70289bccd5e4..f4645f3c98e0 100644 --- a/drivers/net/wireless/realtek/rtw88/wow.c +++ b/drivers/net/wireless/realtek/rtw88/wow.c @@ -26,10 +26,220 @@ static void rtw_wow_show_wakeup_reason(struct rtw_dev *rtwdev) rtw_dbg(rtwdev, RTW_DBG_WOW, "WOW: Rx gtk rekey\n"); else if (reason == RTW_WOW_RSN_RX_PTK_REKEY) rtw_dbg(rtwdev, RTW_DBG_WOW, "WOW: Rx ptk rekey\n"); + else if (reason == RTW_WOW_RSN_RX_PATTERN_MATCH) + rtw_dbg(rtwdev, RTW_DBG_WOW, "WOW: Rx pattern match packet\n"); else rtw_warn(rtwdev, "Unknown wakeup reason %x\n", reason); } +static void rtw_wow_pattern_write_cam(struct rtw_dev *rtwdev, u8 addr, + u32 wdata) +{ + rtw_write32(rtwdev, REG_WKFMCAM_RWD, wdata); + rtw_write32(rtwdev, REG_WKFMCAM_CMD, BIT_WKFCAM_POLLING_V1 | + BIT_WKFCAM_WE | BIT_WKFCAM_ADDR_V2(addr)); + + if (!check_hw_ready(rtwdev, REG_WKFMCAM_CMD, BIT_WKFCAM_POLLING_V1, 0)) + rtw_err(rtwdev, "failed to write pattern cam\n"); +} + +static void rtw_wow_pattern_write_cam_ent(struct rtw_dev *rtwdev, u8 id, + struct rtw_wow_pattern *rtw_pattern) +{ + int i; + u8 addr; + u32 wdata; + + for (i = 0; i < RTW_MAX_PATTERN_MASK_SIZE / 4; i++) { + addr = (id << 3) + i; + wdata = rtw_pattern->mask[i * 4]; + wdata |= rtw_pattern->mask[i * 4 + 1] << 8; + wdata |= rtw_pattern->mask[i * 4 + 2] << 16; + wdata |= rtw_pattern->mask[i * 4 + 3] << 24; + rtw_wow_pattern_write_cam(rtwdev, addr, wdata); + } + + wdata = rtw_pattern->crc; + addr = (id << 3) + RTW_MAX_PATTERN_MASK_SIZE / 4; + + switch (rtw_pattern->type) { + case RTW_PATTERN_BROADCAST: + wdata |= BIT_WKFMCAM_BC | BIT_WKFMCAM_VALID; + break; + case RTW_PATTERN_MULTICAST: + wdata |= BIT_WKFMCAM_MC | BIT_WKFMCAM_VALID; + break; + case RTW_PATTERN_UNICAST: + wdata |= BIT_WKFMCAM_UC | BIT_WKFMCAM_VALID; + break; + default: + break; + } + rtw_wow_pattern_write_cam(rtwdev, addr, wdata); +} + +/* RTK internal CRC16 for Pattern Cam */ +static u16 __rtw_cal_crc16(u8 data, u16 crc) +{ + u8 shift_in, data_bit; + u8 crc_bit4, crc_bit11, crc_bit15; + u16 crc_result; + int index; + + for (index = 0; index < 8; index++) { + crc_bit15 = ((crc & BIT(15)) ? 1 : 0); + data_bit = (data & (BIT(0) << index) ? 1 : 0); + shift_in = crc_bit15 ^ data_bit; + + crc_result = crc << 1; + + if (shift_in == 0) + crc_result &= (~BIT(0)); + else + crc_result |= BIT(0); + + crc_bit11 = ((crc & BIT(11)) ? 1 : 0) ^ shift_in; + + if (crc_bit11 == 0) + crc_result &= (~BIT(12)); + else + crc_result |= BIT(12); + + crc_bit4 = ((crc & BIT(4)) ? 1 : 0) ^ shift_in; + + if (crc_bit4 == 0) + crc_result &= (~BIT(5)); + else + crc_result |= BIT(5); + + crc = crc_result; + } + return crc; +} + +static u16 rtw_calc_crc(u8 *pdata, int length) +{ + u16 crc = 0xffff; + int i; + + for (i = 0; i < length; i++) + crc = __rtw_cal_crc16(pdata[i], crc); + + /* get 1' complement */ + return ~crc; +} + +static void rtw_wow_pattern_generate(struct rtw_dev *rtwdev, + struct rtw_vif *rtwvif, + const struct cfg80211_pkt_pattern *pkt_pattern, + struct rtw_wow_pattern *rtw_pattern) +{ + const u8 *mask; + const u8 *pattern; + u8 mask_hw[RTW_MAX_PATTERN_MASK_SIZE] = {0}; + u8 content[RTW_MAX_PATTERN_SIZE] = {0}; + u8 mac_addr[ETH_ALEN] = {0}; + u8 mask_len; + u16 count; + int len; + int i; + + pattern = pkt_pattern->pattern; + len = pkt_pattern->pattern_len; + mask = pkt_pattern->mask; + + ether_addr_copy(mac_addr, rtwvif->mac_addr); + memset(rtw_pattern, 0, sizeof(*rtw_pattern)); + + mask_len = DIV_ROUND_UP(len, 8); + + if (is_broadcast_ether_addr(pattern)) + rtw_pattern->type = RTW_PATTERN_BROADCAST; + else if (is_multicast_ether_addr(pattern)) + rtw_pattern->type = RTW_PATTERN_MULTICAST; + else if (ether_addr_equal(pattern, mac_addr)) + rtw_pattern->type = RTW_PATTERN_UNICAST; + else + rtw_pattern->type = RTW_PATTERN_INVALID; + + /* translate mask from os to mask for hw + * pattern from OS uses 'ethenet frame', like this: + * | 6 | 6 | 2 | 20 | Variable | 4 | + * |--------+--------+------+-----------+------------+-----| + * | 802.3 Mac Header | IP Header | TCP Packet | FCS | + * | DA | SA | Type | + * + * BUT, packet catched by our HW is in '802.11 frame', begin from LLC + * | 24 or 30 | 6 | 2 | 20 | Variable | 4 | + * |-------------------+--------+------+-----------+------------+-----| + * | 802.11 MAC Header | LLC | IP Header | TCP Packet | FCS | + * | Others | Tpye | + * + * Therefore, we need translate mask_from_OS to mask_to_hw. + * We should left-shift mask by 6 bits, then set the new bit[0~5] = 0, + * because new mask[0~5] means 'SA', but our HW packet begins from LLC, + * bit[0~5] corresponds to first 6 Bytes in LLC, they just don't match. + */ + + /* Shift 6 bits */ + for (i = 0; i < mask_len - 1; i++) { + mask_hw[i] = u8_get_bits(mask[i], GENMASK(7, 6)); + mask_hw[i] |= u8_get_bits(mask[i + 1], GENMASK(5, 0)) << 2; + } + mask_hw[i] = u8_get_bits(mask[i], GENMASK(7, 6)); + + /* Set bit 0-5 to zero */ + mask_hw[0] &= (~GENMASK(5, 0)); + + memcpy(rtw_pattern->mask, mask_hw, RTW_MAX_PATTERN_MASK_SIZE); + + /* To get the wake up pattern from the mask. + * We do not count first 12 bits which means + * DA[6] and SA[6] in the pattern to match HW design. + */ + count = 0; + for (i = 12; i < len; i++) { + if ((mask[i / 8] >> (i % 8)) & 0x01) { + content[count] = pattern[i]; + count++; + } + } + + rtw_pattern->crc = rtw_calc_crc(content, count); +} + +static void rtw_wow_pattern_clear_cam(struct rtw_dev *rtwdev) +{ + bool ret; + + rtw_write32(rtwdev, REG_WKFMCAM_CMD, BIT_WKFCAM_POLLING_V1 | + BIT_WKFCAM_CLR_V1); + + ret = check_hw_ready(rtwdev, REG_WKFMCAM_CMD, BIT_WKFCAM_POLLING_V1, 0); + if (!ret) + rtw_err(rtwdev, "failed to clean pattern cam\n"); +} + +static void rtw_wow_pattern_write(struct rtw_dev *rtwdev) +{ + struct rtw_wow_param *rtw_wow = &rtwdev->wow; + struct rtw_wow_pattern *rtw_pattern = rtw_wow->patterns; + int i = 0; + + for (i = 0; i < rtw_wow->pattern_cnt; i++) + rtw_wow_pattern_write_cam_ent(rtwdev, i, rtw_pattern + i); +} + +static void rtw_wow_pattern_clear(struct rtw_dev *rtwdev) +{ + struct rtw_wow_param *rtw_wow = &rtwdev->wow; + + rtw_wow_pattern_clear_cam(rtwdev); + + rtw_wow->pattern_cnt = 0; + memset(rtw_wow->patterns, 0, sizeof(rtw_wow->patterns)); +} + static void rtw_wow_bb_stop(struct rtw_dev *rtwdev) { struct rtw_wow_param *rtw_wow = &rtwdev->wow; @@ -148,6 +358,7 @@ static int rtw_wow_fw_start(struct rtw_dev *rtwdev) { if (rtw_wow_mgd_linked(rtwdev)) { rtw_send_rsvd_page_h2c(rtwdev); + rtw_wow_pattern_write(rtwdev); rtw_wow_fw_security_type(rtwdev); rtw_fw_set_disconnect_decision_cmd(rtwdev, true); rtw_fw_set_keep_alive_cmd(rtwdev, true); @@ -164,6 +375,7 @@ static int rtw_wow_fw_stop(struct rtw_dev *rtwdev) if (rtw_wow_mgd_linked(rtwdev)) { rtw_fw_set_disconnect_decision_cmd(rtwdev, false); rtw_fw_set_keep_alive_cmd(rtwdev, false); + rtw_wow_pattern_clear(rtwdev); } rtw_fw_set_wowlan_ctrl_cmd(rtwdev, false); @@ -450,6 +662,9 @@ static int rtw_wow_set_wakeups(struct rtw_dev *rtwdev, struct cfg80211_wowlan *wowlan) { struct rtw_wow_param *rtw_wow = &rtwdev->wow; + struct rtw_wow_pattern *rtw_patterns = rtw_wow->patterns; + struct rtw_vif *rtwvif; + int i; if (wowlan->disconnect) set_bit(RTW_WOW_FLAG_EN_DISCONNECT, rtw_wow->flags); @@ -462,6 +677,15 @@ static int rtw_wow_set_wakeups(struct rtw_dev *rtwdev, if (!rtw_wow->wow_vif) return -EPERM; + rtwvif = (struct rtw_vif *)rtw_wow->wow_vif->drv_priv; + if (wowlan->n_patterns && wowlan->patterns) { + rtw_wow->pattern_cnt = wowlan->n_patterns; + for (i = 0; i < wowlan->n_patterns; i++) + rtw_wow_pattern_generate(rtwdev, rtwvif, + wowlan->patterns + i, + rtw_patterns + i); + } + return 0; } diff --git a/drivers/net/wireless/realtek/rtw88/wow.h b/drivers/net/wireless/realtek/rtw88/wow.h index 617806a2f28d..54f742f4c955 100644 --- a/drivers/net/wireless/realtek/rtw88/wow.h +++ b/drivers/net/wireless/realtek/rtw88/wow.h @@ -5,12 +5,21 @@ #ifndef __RTW_WOW_H__ #define __RTW_WOW_H__ +enum rtw_wow_pattern_type { + RTW_PATTERN_BROADCAST = 0, + RTW_PATTERN_MULTICAST, + RTW_PATTERN_UNICAST, + RTW_PATTERN_VALID, + RTW_PATTERN_INVALID, +}; + enum rtw_wake_reason { RTW_WOW_RSN_RX_PTK_REKEY = 0x1, RTW_WOW_RSN_RX_GTK_REKEY = 0x2, RTW_WOW_RSN_RX_DEAUTH = 0x8, RTW_WOW_RSN_DISCONNECT = 0x10, RTW_WOW_RSN_RX_MAGIC_PKT = 0x21, + RTW_WOW_RSN_RX_PATTERN_MATCH = 0x23, }; struct rtw_fw_media_status_iter_data { -- cgit v1.2.3-59-g8ed1b From b6c12908a33e4e413523e73464cd5957690f1a76 Mon Sep 17 00:00:00 2001 From: Chin-Yen Lee Date: Thu, 19 Dec 2019 16:58:16 +0800 Subject: rtw88: Add wowlan net-detect support Net-detect is an option of wowlan to allow the device to be woken up from suspend mode when configured network is detected. When user enables net-detect and lets the device enter suspend state, wowlan firmware will periodically scan until beacon or probe response of configured networks are received. Between two scans, wowlan firmware keeps wifi chip in idle mode to reduce power consumption. If configured networks are detected, wowlan firmware will trigger resume process. Signed-off-by: Chin-Yen Lee Signed-off-by: Yan-Hsuan Chuang Signed-off-by: Kalle Valo --- drivers/net/wireless/realtek/rtw88/fw.c | 292 ++++++++++++++++++++++++-- drivers/net/wireless/realtek/rtw88/fw.h | 113 ++++++++++ drivers/net/wireless/realtek/rtw88/main.c | 1 + drivers/net/wireless/realtek/rtw88/main.h | 25 +++ drivers/net/wireless/realtek/rtw88/rtw8822c.c | 5 +- drivers/net/wireless/realtek/rtw88/wow.c | 139 +++++++++++- drivers/net/wireless/realtek/rtw88/wow.h | 11 + 7 files changed, 571 insertions(+), 15 deletions(-) (limited to 'drivers/net/wireless/realtek/rtw88/fw.h') diff --git a/drivers/net/wireless/realtek/rtw88/fw.c b/drivers/net/wireless/realtek/rtw88/fw.c index 0523851cf98d..e3e91ae0d3d2 100644 --- a/drivers/net/wireless/realtek/rtw88/fw.c +++ b/drivers/net/wireless/realtek/rtw88/fw.c @@ -567,6 +567,9 @@ void rtw_fw_set_remote_wake_ctrl_cmd(struct rtw_dev *rtwdev, bool enable) SET_REMOTE_WAKECTRL_ENABLE(h2c_pkt, enable); + if (rtw_wow_no_link(rtwdev)) + SET_REMOTE_WAKE_CTRL_NLO_OFFLOAD_EN(h2c_pkt, enable); + rtw_fw_send_h2c_command(rtwdev, h2c_pkt); } @@ -584,6 +587,26 @@ static u8 rtw_get_rsvd_page_location(struct rtw_dev *rtwdev, return location; } +void rtw_fw_set_nlo_info(struct rtw_dev *rtwdev, bool enable) +{ + u8 h2c_pkt[H2C_PKT_SIZE] = {0}; + u8 loc_nlo; + + loc_nlo = rtw_get_rsvd_page_location(rtwdev, RSVD_NLO_INFO); + + SET_H2C_CMD_ID_CLASS(h2c_pkt, H2C_CMD_NLO_INFO); + + SET_NLO_FUN_EN(h2c_pkt, enable); + if (enable) { + if (rtw_fw_lps_deep_mode) + SET_NLO_PS_32K(h2c_pkt, enable); + SET_NLO_IGNORE_SECURITY(h2c_pkt, enable); + SET_NLO_LOC_NLO_INFO(h2c_pkt, loc_nlo); + } + + rtw_fw_send_h2c_command(rtwdev, h2c_pkt); +} + void rtw_fw_set_pg_info(struct rtw_dev *rtwdev) { struct rtw_lps_conf *conf = &rtwdev->lps_conf; @@ -603,6 +626,40 @@ void rtw_fw_set_pg_info(struct rtw_dev *rtwdev) rtw_fw_send_h2c_command(rtwdev, h2c_pkt); } +u8 rtw_get_rsvd_page_probe_req_location(struct rtw_dev *rtwdev, + struct cfg80211_ssid *ssid) +{ + struct rtw_rsvd_page *rsvd_pkt; + u8 location = 0; + + list_for_each_entry(rsvd_pkt, &rtwdev->rsvd_page_list, list) { + if (rsvd_pkt->type != RSVD_PROBE_REQ) + continue; + if ((!ssid && !rsvd_pkt->ssid) || + rtw_ssid_equal(rsvd_pkt->ssid, ssid)) + location = rsvd_pkt->page; + } + + return location; +} + +u16 rtw_get_rsvd_page_probe_req_size(struct rtw_dev *rtwdev, + struct cfg80211_ssid *ssid) +{ + struct rtw_rsvd_page *rsvd_pkt; + u16 size = 0; + + list_for_each_entry(rsvd_pkt, &rtwdev->rsvd_page_list, list) { + if (rsvd_pkt->type != RSVD_PROBE_REQ) + continue; + if ((!ssid && !rsvd_pkt->ssid) || + rtw_ssid_equal(rsvd_pkt->ssid, ssid)) + size = rsvd_pkt->skb->len; + } + + return size; +} + void rtw_send_rsvd_page_h2c(struct rtw_dev *rtwdev) { u8 h2c_pkt[H2C_PKT_SIZE] = {0}; @@ -648,6 +705,95 @@ rtw_beacon_get(struct ieee80211_hw *hw, struct ieee80211_vif *vif) return skb_new; } +static struct sk_buff *rtw_nlo_info_get(struct ieee80211_hw *hw) +{ + struct rtw_dev *rtwdev = hw->priv; + struct rtw_chip_info *chip = rtwdev->chip; + struct rtw_pno_request *pno_req = &rtwdev->wow.pno_req; + struct rtw_nlo_info_hdr *nlo_hdr; + struct cfg80211_ssid *ssid; + struct sk_buff *skb; + u8 *pos, loc; + u32 size; + int i; + + if (!pno_req->inited || !pno_req->match_set_cnt) + return NULL; + + size = sizeof(struct rtw_nlo_info_hdr) + pno_req->match_set_cnt * + IEEE80211_MAX_SSID_LEN + chip->tx_pkt_desc_sz; + + skb = alloc_skb(size, GFP_KERNEL); + if (!skb) + return NULL; + + skb_reserve(skb, chip->tx_pkt_desc_sz); + + nlo_hdr = skb_put_zero(skb, sizeof(struct rtw_nlo_info_hdr)); + + nlo_hdr->nlo_count = pno_req->match_set_cnt; + nlo_hdr->hidden_ap_count = pno_req->match_set_cnt; + + /* pattern check for firmware */ + memset(nlo_hdr->pattern_check, 0xA5, FW_NLO_INFO_CHECK_SIZE); + + for (i = 0; i < pno_req->match_set_cnt; i++) + nlo_hdr->ssid_len[i] = pno_req->match_sets[i].ssid.ssid_len; + + for (i = 0; i < pno_req->match_set_cnt; i++) { + ssid = &pno_req->match_sets[i].ssid; + loc = rtw_get_rsvd_page_probe_req_location(rtwdev, ssid); + if (!loc) { + rtw_err(rtwdev, "failed to get probe req rsvd loc\n"); + kfree(skb); + return NULL; + } + nlo_hdr->location[i] = loc; + } + + for (i = 0; i < pno_req->match_set_cnt; i++) { + pos = skb_put_zero(skb, IEEE80211_MAX_SSID_LEN); + memcpy(pos, pno_req->match_sets[i].ssid.ssid, + pno_req->match_sets[i].ssid.ssid_len); + } + + return skb; +} + +static struct sk_buff *rtw_cs_channel_info_get(struct ieee80211_hw *hw) +{ + struct rtw_dev *rtwdev = hw->priv; + struct rtw_chip_info *chip = rtwdev->chip; + struct rtw_pno_request *pno_req = &rtwdev->wow.pno_req; + struct ieee80211_channel *channels = pno_req->channels; + struct sk_buff *skb; + int count = pno_req->channel_cnt; + u8 *pos; + int i = 0; + + skb = alloc_skb(4 * count + chip->tx_pkt_desc_sz, GFP_KERNEL); + if (!skb) + return NULL; + + skb_reserve(skb, chip->tx_pkt_desc_sz); + + for (i = 0; i < count; i++) { + pos = skb_put_zero(skb, 4); + + CHSW_INFO_SET_CH(pos, channels[i].hw_value); + + if (channels[i].flags & IEEE80211_CHAN_RADAR) + CHSW_INFO_SET_ACTION_ID(pos, 0); + else + CHSW_INFO_SET_ACTION_ID(pos, 1); + CHSW_INFO_SET_TIMEOUT(pos, 1); + CHSW_INFO_SET_PRI_CH_IDX(pos, 1); + CHSW_INFO_SET_BW(pos, 0); + } + + return skb; +} + static struct sk_buff *rtw_lps_pg_dpk_get(struct ieee80211_hw *hw) { struct rtw_dev *rtwdev = hw->priv; @@ -705,11 +851,12 @@ static struct sk_buff *rtw_lps_pg_info_get(struct ieee80211_hw *hw, static struct sk_buff *rtw_get_rsvd_page_skb(struct ieee80211_hw *hw, struct ieee80211_vif *vif, - enum rtw_rsvd_packet_type type) + struct rtw_rsvd_page *rsvd_pkt) { struct sk_buff *skb_new; + struct cfg80211_ssid *ssid; - switch (type) { + switch (rsvd_pkt->type) { case RSVD_BEACON: skb_new = rtw_beacon_get(hw, vif); break; @@ -731,6 +878,21 @@ static struct sk_buff *rtw_get_rsvd_page_skb(struct ieee80211_hw *hw, case RSVD_LPS_PG_INFO: skb_new = rtw_lps_pg_info_get(hw, vif); break; + case RSVD_PROBE_REQ: + ssid = (struct cfg80211_ssid *)rsvd_pkt->ssid; + if (ssid) + skb_new = ieee80211_probereq_get(hw, vif->addr, + ssid->ssid, + ssid->ssid_len, 0); + else + skb_new = ieee80211_probereq_get(hw, vif->addr, NULL, 0, 0); + break; + case RSVD_NLO_INFO: + skb_new = rtw_nlo_info_get(hw); + break; + case RSVD_CH_INFO: + skb_new = rtw_cs_channel_info_get(hw); + break; default: return NULL; } @@ -772,25 +934,53 @@ static void rtw_rsvd_page_list_to_buf(struct rtw_dev *rtwdev, u8 page_size, memcpy(buf, skb->data, skb->len); } +static struct rtw_rsvd_page *rtw_alloc_rsvd_page(struct rtw_dev *rtwdev, + enum rtw_rsvd_packet_type type, + bool txdesc) +{ + struct rtw_rsvd_page *rsvd_pkt = NULL; + + rsvd_pkt = kzalloc(sizeof(*rsvd_pkt), GFP_KERNEL); + + if (!rsvd_pkt) + return NULL; + + rsvd_pkt->type = type; + rsvd_pkt->add_txdesc = txdesc; + + return rsvd_pkt; +} + +static void rtw_insert_rsvd_page(struct rtw_dev *rtwdev, + struct rtw_rsvd_page *rsvd_pkt) +{ + lockdep_assert_held(&rtwdev->mutex); + list_add_tail(&rsvd_pkt->list, &rtwdev->rsvd_page_list); +} + void rtw_add_rsvd_page(struct rtw_dev *rtwdev, enum rtw_rsvd_packet_type type, bool txdesc) { struct rtw_rsvd_page *rsvd_pkt; - lockdep_assert_held(&rtwdev->mutex); + rsvd_pkt = rtw_alloc_rsvd_page(rtwdev, type, txdesc); + if (!rsvd_pkt) + return; - list_for_each_entry(rsvd_pkt, &rtwdev->rsvd_page_list, list) { - if (rsvd_pkt->type == type) - return; - } + rtw_insert_rsvd_page(rtwdev, rsvd_pkt); +} - rsvd_pkt = kmalloc(sizeof(*rsvd_pkt), GFP_KERNEL); +void rtw_add_rsvd_page_probe_req(struct rtw_dev *rtwdev, + struct cfg80211_ssid *ssid) +{ + struct rtw_rsvd_page *rsvd_pkt; + + rsvd_pkt = rtw_alloc_rsvd_page(rtwdev, RSVD_PROBE_REQ, true); if (!rsvd_pkt) return; - rsvd_pkt->type = type; - rsvd_pkt->add_txdesc = txdesc; - list_add_tail(&rsvd_pkt->list, &rtwdev->rsvd_page_list); + rsvd_pkt->ssid = ssid; + rtw_insert_rsvd_page(rtwdev, rsvd_pkt); } void rtw_reset_rsvd_page(struct rtw_dev *rtwdev) @@ -887,7 +1077,7 @@ static u8 *rtw_build_rsvd_page(struct rtw_dev *rtwdev, page_margin = page_size - tx_desc_sz; list_for_each_entry(rsvd_pkt, &rtwdev->rsvd_page_list, list) { - iter = rtw_get_rsvd_page_skb(hw, vif, rsvd_pkt->type); + iter = rtw_get_rsvd_page_skb(hw, vif, rsvd_pkt); if (!iter) { rtw_err(rtwdev, "failed to build rsvd packet\n"); goto release_skb; @@ -1065,3 +1255,81 @@ out: rtw_write8(rtwdev, REG_RCR + 2, rcr); return 0; } + +static void __rtw_fw_update_pkt(struct rtw_dev *rtwdev, u8 pkt_id, u16 size, + u8 location) +{ + u8 h2c_pkt[H2C_PKT_SIZE] = {0}; + u16 total_size = H2C_PKT_HDR_SIZE + H2C_PKT_UPDATE_PKT_LEN; + + rtw_h2c_pkt_set_header(h2c_pkt, H2C_PKT_UPDATE_PKT); + + SET_PKT_H2C_TOTAL_LEN(h2c_pkt, total_size); + UPDATE_PKT_SET_PKT_ID(h2c_pkt, pkt_id); + UPDATE_PKT_SET_LOCATION(h2c_pkt, location); + + /* include txdesc size */ + UPDATE_PKT_SET_SIZE(h2c_pkt, size); + + rtw_fw_send_h2c_packet(rtwdev, h2c_pkt); +} + +void rtw_fw_update_pkt_probe_req(struct rtw_dev *rtwdev, + struct cfg80211_ssid *ssid) +{ + u8 loc; + u32 size; + + loc = rtw_get_rsvd_page_probe_req_location(rtwdev, ssid); + if (!loc) { + rtw_err(rtwdev, "failed to get probe_req rsvd loc\n"); + return; + } + + size = rtw_get_rsvd_page_probe_req_size(rtwdev, ssid); + if (!size) { + rtw_err(rtwdev, "failed to get probe_req rsvd size\n"); + return; + } + + __rtw_fw_update_pkt(rtwdev, RTW_PACKET_PROBE_REQ, size, loc); +} + +void rtw_fw_channel_switch(struct rtw_dev *rtwdev, bool enable) +{ + struct rtw_pno_request *rtw_pno_req = &rtwdev->wow.pno_req; + u8 h2c_pkt[H2C_PKT_SIZE] = {0}; + u16 total_size = H2C_PKT_HDR_SIZE + H2C_PKT_CH_SWITCH_LEN; + u8 loc_ch_info; + const struct rtw_ch_switch_option cs_option = { + .dest_ch_en = 1, + .dest_ch = 1, + .periodic_option = 2, + .normal_period = 5, + .normal_period_sel = 0, + .normal_cycle = 10, + .slow_period = 1, + .slow_period_sel = 1, + }; + + rtw_h2c_pkt_set_header(h2c_pkt, H2C_PKT_CH_SWITCH); + SET_PKT_H2C_TOTAL_LEN(h2c_pkt, total_size); + + CH_SWITCH_SET_START(h2c_pkt, enable); + CH_SWITCH_SET_DEST_CH_EN(h2c_pkt, cs_option.dest_ch_en); + CH_SWITCH_SET_DEST_CH(h2c_pkt, cs_option.dest_ch); + CH_SWITCH_SET_NORMAL_PERIOD(h2c_pkt, cs_option.normal_period); + CH_SWITCH_SET_NORMAL_PERIOD_SEL(h2c_pkt, cs_option.normal_period_sel); + CH_SWITCH_SET_SLOW_PERIOD(h2c_pkt, cs_option.slow_period); + CH_SWITCH_SET_SLOW_PERIOD_SEL(h2c_pkt, cs_option.slow_period_sel); + CH_SWITCH_SET_NORMAL_CYCLE(h2c_pkt, cs_option.normal_cycle); + CH_SWITCH_SET_PERIODIC_OPT(h2c_pkt, cs_option.periodic_option); + + CH_SWITCH_SET_CH_NUM(h2c_pkt, rtw_pno_req->channel_cnt); + CH_SWITCH_SET_INFO_SIZE(h2c_pkt, rtw_pno_req->channel_cnt * 4); + + loc_ch_info = rtw_get_rsvd_page_location(rtwdev, RSVD_CH_INFO); + CH_SWITCH_SET_INFO_LOC(h2c_pkt, loc_ch_info); + + rtw_fw_send_h2c_packet(rtwdev, h2c_pkt); +} diff --git a/drivers/net/wireless/realtek/rtw88/fw.h b/drivers/net/wireless/realtek/rtw88/fw.h index 9ea27cca11ac..ccd27bd45775 100644 --- a/drivers/net/wireless/realtek/rtw88/fw.h +++ b/drivers/net/wireless/realtek/rtw88/fw.h @@ -12,6 +12,8 @@ #define FW_HDR_SIZE 64 #define FW_HDR_CHKSUM_SIZE 8 +#define FW_NLO_INFO_CHECK_SIZE 4 + #define FIFO_PAGE_SIZE_SHIFT 12 #define FIFO_PAGE_SIZE 4096 #define RSVD_PAGE_START_ADDR 0x780 @@ -45,6 +47,9 @@ enum rtw_rsvd_packet_type { RSVD_QOS_NULL, RSVD_LPS_PG_DPK, RSVD_LPS_PG_INFO, + RSVD_PROBE_REQ, + RSVD_NLO_INFO, + RSVD_CH_INFO, }; enum rtw_fw_rf_type { @@ -98,6 +103,7 @@ struct rtw_rsvd_page { enum rtw_rsvd_packet_type type; u8 page; bool add_txdesc; + struct cfg80211_ssid *ssid; }; enum rtw_keep_alive_pkt_type { @@ -105,6 +111,24 @@ enum rtw_keep_alive_pkt_type { KEEP_ALIVE_ARP_RSP = 1, }; +struct rtw_nlo_info_hdr { + u8 nlo_count; + u8 hidden_ap_count; + u8 rsvd1[2]; + u8 pattern_check[FW_NLO_INFO_CHECK_SIZE]; + u8 rsvd2[8]; + u8 ssid_len[16]; + u8 chiper[16]; + u8 rsvd3[16]; + u8 location[8]; +} __packed; + +enum rtw_packet_type { + RTW_PACKET_PROBE_REQ = 0x00, + + RTW_PACKET_UNDEFINE = 0x7FFFFFFF, +}; + struct rtw_fw_wow_keep_alive_para { bool adopt; u8 pkt_type; @@ -117,6 +141,21 @@ struct rtw_fw_wow_disconnect_para { u8 retry_count; }; +struct rtw_ch_switch_option { + u8 periodic_option; + u32 tsf_high; + u32 tsf_low; + u8 dest_ch_en; + u8 absolute_time_en; + u8 dest_ch; + u8 normal_period; + u8 normal_period_sel; + u8 normal_cycle; + u8 slow_period; + u8 slow_period_sel; + u8 nlo_en; +}; + struct rtw_fw_hdr { __le16 signature; u8 category; @@ -163,6 +202,12 @@ struct rtw_fw_hdr { #define H2C_PKT_PHYDM_INFO 0x11 #define H2C_PKT_IQK 0x0E +#define H2C_PKT_CH_SWITCH 0x02 +#define H2C_PKT_UPDATE_PKT 0x0C + +#define H2C_PKT_CH_SWITCH_LEN 0x20 +#define H2C_PKT_UPDATE_PKT_LEN 0x4 + #define SET_PKT_H2C_CATEGORY(h2c_pkt, value) \ le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, GENMASK(6, 0)) #define SET_PKT_H2C_CMD_ID(h2c_pkt, value) \ @@ -199,6 +244,57 @@ static inline void rtw_h2c_pkt_set_header(u8 *h2c_pkt, u8 sub_id) #define IQK_SET_SEGMENT_IQK(h2c_pkt, value) \ le32p_replace_bits((__le32 *)(h2c_pkt) + 0x02, value, BIT(1)) +#define CHSW_INFO_SET_CH(pkt, value) \ + le32p_replace_bits((__le32 *)(pkt) + 0x00, value, GENMASK(7, 0)) +#define CHSW_INFO_SET_PRI_CH_IDX(pkt, value) \ + le32p_replace_bits((__le32 *)(pkt) + 0x00, value, GENMASK(11, 8)) +#define CHSW_INFO_SET_BW(pkt, value) \ + le32p_replace_bits((__le32 *)(pkt) + 0x00, value, GENMASK(15, 12)) +#define CHSW_INFO_SET_TIMEOUT(pkt, value) \ + le32p_replace_bits((__le32 *)(pkt) + 0x00, value, GENMASK(23, 16)) +#define CHSW_INFO_SET_ACTION_ID(pkt, value) \ + le32p_replace_bits((__le32 *)(pkt) + 0x00, value, GENMASK(30, 24)) + +#define UPDATE_PKT_SET_SIZE(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x02, value, GENMASK(15, 0)) +#define UPDATE_PKT_SET_PKT_ID(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x02, value, GENMASK(23, 16)) +#define UPDATE_PKT_SET_LOCATION(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x02, value, GENMASK(31, 24)) + +#define CH_SWITCH_SET_START(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x02, value, BIT(0)) +#define CH_SWITCH_SET_DEST_CH_EN(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x02, value, BIT(1)) +#define CH_SWITCH_SET_ABSOLUTE_TIME(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x02, value, BIT(2)) +#define CH_SWITCH_SET_PERIODIC_OPT(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x02, value, GENMASK(4, 3)) +#define CH_SWITCH_SET_INFO_LOC(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x02, value, GENMASK(15, 8)) +#define CH_SWITCH_SET_CH_NUM(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x02, value, GENMASK(23, 16)) +#define CH_SWITCH_SET_PRI_CH_IDX(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x02, value, GENMASK(27, 24)) +#define CH_SWITCH_SET_DEST_CH(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x03, value, GENMASK(7, 0)) +#define CH_SWITCH_SET_NORMAL_PERIOD(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x03, value, GENMASK(13, 8)) +#define CH_SWITCH_SET_NORMAL_PERIOD_SEL(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x03, value, GENMASK(15, 14)) +#define CH_SWITCH_SET_SLOW_PERIOD(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x03, value, GENMASK(21, 16)) +#define CH_SWITCH_SET_SLOW_PERIOD_SEL(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x03, value, GENMASK(23, 22)) +#define CH_SWITCH_SET_NORMAL_CYCLE(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x03, value, GENMASK(31, 24)) +#define CH_SWITCH_SET_TSF_HIGH(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x04, value, GENMASK(31, 0)) +#define CH_SWITCH_SET_TSF_LOW(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x05, value, GENMASK(31, 0)) +#define CH_SWITCH_SET_INFO_SIZE(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x06, value, GENMASK(15, 0)) + /* Command H2C */ #define H2C_CMD_RSVD_PAGE 0x0 #define H2C_CMD_MEDIA_STATUS_RPT 0x01 @@ -220,6 +316,8 @@ static inline void rtw_h2c_pkt_set_header(u8 *h2c_pkt, u8 sub_id) #define H2C_CMD_WOWLAN 0x80 #define H2C_CMD_REMOTE_WAKE_CTRL 0x81 #define H2C_CMD_AOAC_GLOBAL_INFO 0x82 +#define H2C_CMD_NLO_INFO 0x8C + #define SET_H2C_CMD_ID_CLASS(h2c_pkt, value) \ le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, GENMASK(7, 0)) @@ -366,6 +464,15 @@ static inline void rtw_h2c_pkt_set_header(u8 *h2c_pkt, u8 sub_id) #define SET_AOAC_GLOBAL_INFO_GROUP_ENC_ALG(h2c_pkt, value) \ le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, GENMASK(23, 16)) +#define SET_NLO_FUN_EN(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(8)) +#define SET_NLO_PS_32K(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(9)) +#define SET_NLO_IGNORE_SECURITY(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(10)) +#define SET_NLO_LOC_NLO_INFO(h2c_pkt, value) \ + le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, GENMASK(23, 16)) + static inline struct rtw_c2h_cmd *get_c2h_from_skb(struct sk_buff *skb) { u32 pkt_offset; @@ -397,6 +504,8 @@ void rtw_fw_send_ra_info(struct rtw_dev *rtwdev, struct rtw_sta_info *si); void rtw_fw_media_status_report(struct rtw_dev *rtwdev, u8 mac_id, bool conn); void rtw_add_rsvd_page(struct rtw_dev *rtwdev, enum rtw_rsvd_packet_type type, bool txdesc); +void rtw_add_rsvd_page_probe_req(struct rtw_dev *rtwdev, + struct cfg80211_ssid *ssid); int rtw_fw_write_data_rsvd_page(struct rtw_dev *rtwdev, u16 pg_addr, u8 *buf, u32 size); void rtw_reset_rsvd_page(struct rtw_dev *rtwdev); @@ -413,4 +522,8 @@ void rtw_fw_set_aoac_global_info_cmd(struct rtw_dev *rtwdev, u8 pairwise_key_enc, u8 group_key_enc); +void rtw_fw_set_nlo_info(struct rtw_dev *rtwdev, bool enable); +void rtw_fw_update_pkt_probe_req(struct rtw_dev *rtwdev, + struct cfg80211_ssid *ssid); +void rtw_fw_channel_switch(struct rtw_dev *rtwdev, bool enable); #endif diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c index 1ac48ccdb3c8..3d26c4a6e8da 100644 --- a/drivers/net/wireless/realtek/rtw88/main.c +++ b/drivers/net/wireless/realtek/rtw88/main.c @@ -1495,6 +1495,7 @@ int rtw_register_hw(struct rtw_dev *rtwdev, struct ieee80211_hw *hw) #ifdef CONFIG_PM hw->wiphy->wowlan = rtwdev->chip->wowlan_stub; + hw->wiphy->max_sched_scan_ssids = rtwdev->chip->max_sched_scan_ssids; #endif rtw_set_supported_band(hw, rtwdev->chip); SET_IEEE80211_PERM_ADDR(hw, rtwdev->efuse.addr); diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h index 74368ef954fa..4fa17aab3838 100644 --- a/drivers/net/wireless/realtek/rtw88/main.h +++ b/drivers/net/wireless/realtek/rtw88/main.h @@ -929,12 +929,24 @@ struct rtw_wow_pattern { u8 mask[RTW_MAX_PATTERN_MASK_SIZE]; }; +struct rtw_pno_request { + bool inited; + u32 match_set_cnt; + struct cfg80211_match_set *match_sets; + u8 channel_cnt; + struct ieee80211_channel *channels; + struct cfg80211_sched_scan_plan scan_plan; +}; + struct rtw_wow_param { struct ieee80211_vif *wow_vif; DECLARE_BITMAP(flags, RTW_WOW_FLAG_MAX); u8 txpause; u8 pattern_cnt; struct rtw_wow_pattern patterns[RTW_MAX_PATTERN_NUM]; + + bool ips_enabled; + struct rtw_pno_request pno_req; }; struct rtw_intf_phy_para_table { @@ -1067,6 +1079,7 @@ struct rtw_chip_info { const char *wow_fw_name; const struct wiphy_wowlan_support *wowlan_stub; + const u8 max_sched_scan_ssids; /* coex paras */ u32 coex_para_ver; @@ -1647,6 +1660,18 @@ static inline struct ieee80211_vif *rtwvif_to_vif(struct rtw_vif *rtwvif) return container_of(p, struct ieee80211_vif, drv_priv); } +static inline bool rtw_ssid_equal(struct cfg80211_ssid *a, + struct cfg80211_ssid *b) +{ + if (!a || !b || a->ssid_len != b->ssid_len) + return false; + + if (memcmp(a->ssid, b->ssid, a->ssid_len)) + return false; + + return true; +} + void rtw_get_channel_params(struct cfg80211_chan_def *chandef, struct rtw_channel_params *ch_param); bool check_hw_ready(struct rtw_dev *rtwdev, u32 addr, u32 mask, u32 target); diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822c.c b/drivers/net/wireless/realtek/rtw88/rtw8822c.c index 4836c0044b05..655ca9265f68 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8822c.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8822c.c @@ -4063,10 +4063,12 @@ static const struct rtw_pwr_track_tbl rtw8822c_rtw_pwr_track_tbl = { #ifdef CONFIG_PM static const struct wiphy_wowlan_support rtw_wowlan_stub_8822c = { .flags = WIPHY_WOWLAN_MAGIC_PKT | WIPHY_WOWLAN_GTK_REKEY_FAILURE | - WIPHY_WOWLAN_DISCONNECT | WIPHY_WOWLAN_SUPPORTS_GTK_REKEY, + WIPHY_WOWLAN_DISCONNECT | WIPHY_WOWLAN_SUPPORTS_GTK_REKEY | + WIPHY_WOWLAN_NET_DETECT, .n_patterns = RTW_MAX_PATTERN_NUM, .pattern_max_len = RTW_MAX_PATTERN_SIZE, .pattern_min_len = 1, + .max_nd_match_sets = 4, }; #endif @@ -4119,6 +4121,7 @@ struct rtw_chip_info rtw8822c_hw_spec = { #ifdef CONFIG_PM .wow_fw_name = "rtw88/rtw8822c_wow_fw.bin", .wowlan_stub = &rtw_wowlan_stub_8822c, + .max_sched_scan_ssids = 4, #endif .coex_para_ver = 0x19062706, .bt_desired_ver = 0x6, diff --git a/drivers/net/wireless/realtek/rtw88/wow.c b/drivers/net/wireless/realtek/rtw88/wow.c index f4645f3c98e0..af5c27e1bb07 100644 --- a/drivers/net/wireless/realtek/rtw88/wow.c +++ b/drivers/net/wireless/realtek/rtw88/wow.c @@ -28,6 +28,8 @@ static void rtw_wow_show_wakeup_reason(struct rtw_dev *rtwdev) rtw_dbg(rtwdev, RTW_DBG_WOW, "WOW: Rx ptk rekey\n"); else if (reason == RTW_WOW_RSN_RX_PATTERN_MATCH) rtw_dbg(rtwdev, RTW_DBG_WOW, "WOW: Rx pattern match packet\n"); + else if (reason == RTW_WOW_RSN_RX_NLO) + rtw_dbg(rtwdev, RTW_DBG_WOW, "Rx NLO\n"); else rtw_warn(rtwdev, "Unknown wakeup reason %x\n", reason); } @@ -362,6 +364,10 @@ static int rtw_wow_fw_start(struct rtw_dev *rtwdev) rtw_wow_fw_security_type(rtwdev); rtw_fw_set_disconnect_decision_cmd(rtwdev, true); rtw_fw_set_keep_alive_cmd(rtwdev, true); + } else if (rtw_wow_no_link(rtwdev)) { + rtw_fw_set_nlo_info(rtwdev, true); + rtw_fw_update_pkt_probe_req(rtwdev, NULL); + rtw_fw_channel_switch(rtwdev, true); } rtw_fw_set_wowlan_ctrl_cmd(rtwdev, true); @@ -376,6 +382,9 @@ static int rtw_wow_fw_stop(struct rtw_dev *rtwdev) rtw_fw_set_disconnect_decision_cmd(rtwdev, false); rtw_fw_set_keep_alive_cmd(rtwdev, false); rtw_wow_pattern_clear(rtwdev); + } else if (rtw_wow_no_link(rtwdev)) { + rtw_fw_channel_switch(rtwdev, false); + rtw_fw_set_nlo_info(rtwdev, false); } rtw_fw_set_wowlan_ctrl_cmd(rtwdev, false); @@ -423,6 +432,22 @@ static void rtw_wow_fw_media_status(struct rtw_dev *rtwdev, bool connect) rtw_iterate_stas_atomic(rtwdev, rtw_wow_fw_media_status_iter, &data); } +static void rtw_wow_config_pno_rsvd_page(struct rtw_dev *rtwdev) +{ + struct rtw_wow_param *rtw_wow = &rtwdev->wow; + struct rtw_pno_request *rtw_pno_req = &rtw_wow->pno_req; + struct cfg80211_ssid *ssid; + int i; + + for (i = 0 ; i < rtw_pno_req->match_set_cnt; i++) { + ssid = &rtw_pno_req->match_sets[i].ssid; + rtw_add_rsvd_page_probe_req(rtwdev, ssid); + } + rtw_add_rsvd_page_probe_req(rtwdev, NULL); + rtw_add_rsvd_page(rtwdev, RSVD_NLO_INFO, false); + rtw_add_rsvd_page(rtwdev, RSVD_CH_INFO, true); +} + static void rtw_wow_config_linked_rsvd_page(struct rtw_dev *rtwdev) { rtw_add_rsvd_page(rtwdev, RSVD_PS_POLL, true); @@ -436,8 +461,12 @@ static void rtw_wow_config_rsvd_page(struct rtw_dev *rtwdev) { rtw_reset_rsvd_page(rtwdev); - if (rtw_wow_mgd_linked(rtwdev)) + if (rtw_wow_mgd_linked(rtwdev)) { rtw_wow_config_linked_rsvd_page(rtwdev); + } else if (test_bit(RTW_FLAG_WOWLAN, rtwdev->flags) && + rtw_wow_no_link(rtwdev)) { + rtw_wow_config_pno_rsvd_page(rtwdev); + } } static int rtw_wow_dl_fw_rsvd_page(struct rtw_dev *rtwdev) @@ -480,11 +509,75 @@ out: return ret; } +static void rtw_wow_check_pno(struct rtw_dev *rtwdev, + struct cfg80211_sched_scan_request *nd_config) +{ + struct rtw_wow_param *rtw_wow = &rtwdev->wow; + struct rtw_pno_request *pno_req = &rtw_wow->pno_req; + struct ieee80211_channel *channel; + int i, size; + + if (!nd_config->n_match_sets || !nd_config->n_channels) + goto err; + + pno_req->match_set_cnt = nd_config->n_match_sets; + size = sizeof(*pno_req->match_sets) * pno_req->match_set_cnt; + pno_req->match_sets = kmemdup(nd_config->match_sets, size, GFP_KERNEL); + if (!pno_req->match_sets) + goto err; + + pno_req->channel_cnt = nd_config->n_channels; + size = sizeof(*nd_config->channels[0]) * nd_config->n_channels; + pno_req->channels = kmalloc(size, GFP_KERNEL); + if (!pno_req->channels) + goto channel_err; + + for (i = 0 ; i < pno_req->channel_cnt; i++) { + channel = pno_req->channels + i; + memcpy(channel, nd_config->channels[i], sizeof(*channel)); + } + + pno_req->scan_plan = *nd_config->scan_plans; + pno_req->inited = true; + + rtw_dbg(rtwdev, RTW_DBG_WOW, "WOW: net-detect is enabled\n"); + + return; + +channel_err: + kfree(pno_req->match_sets); + +err: + rtw_dbg(rtwdev, RTW_DBG_WOW, "WOW: net-detect is disabled\n"); +} + static int rtw_wow_leave_linked_ps(struct rtw_dev *rtwdev) { if (!test_bit(RTW_FLAG_WOWLAN, rtwdev->flags)) cancel_delayed_work_sync(&rtwdev->watch_dog_work); + rtw_leave_lps(rtwdev); + + return 0; +} + +static int rtw_wow_leave_no_link_ps(struct rtw_dev *rtwdev) +{ + struct rtw_wow_param *rtw_wow = &rtwdev->wow; + int ret = 0; + + if (test_bit(RTW_FLAG_WOWLAN, rtwdev->flags)) { + if (rtw_fw_lps_deep_mode) + rtw_leave_lps_deep(rtwdev); + } else { + if (test_bit(RTW_FLAG_INACTIVE_PS, rtwdev->flags)) { + rtw_wow->ips_enabled = true; + ret = rtw_leave_ips(rtwdev); + if (ret) + return ret; + } + } + return 0; } @@ -494,6 +587,18 @@ static int rtw_wow_leave_ps(struct rtw_dev *rtwdev) if (rtw_wow_mgd_linked(rtwdev)) ret = rtw_wow_leave_linked_ps(rtwdev); + else if (rtw_wow_no_link(rtwdev)) + ret = rtw_wow_leave_no_link_ps(rtwdev); + + return ret; +} + +static int rtw_wow_restore_ps(struct rtw_dev *rtwdev) +{ + int ret = 0; + + if (rtw_wow_no_link(rtwdev) && rtwdev->wow.ips_enabled) + ret = rtw_enter_ips(rtwdev); return ret; } @@ -509,12 +614,22 @@ static int rtw_wow_enter_linked_ps(struct rtw_dev *rtwdev) return 0; } +static int rtw_wow_enter_no_link_ps(struct rtw_dev *rtwdev) +{ + /* firmware enters deep ps by itself if supported */ + set_bit(RTW_FLAG_LEISURE_PS_DEEP, rtwdev->flags); + + return 0; +} + static int rtw_wow_enter_ps(struct rtw_dev *rtwdev) { int ret = 0; if (rtw_wow_mgd_linked(rtwdev)) ret = rtw_wow_enter_linked_ps(rtwdev); + else if (rtw_wow_no_link(rtwdev) && rtw_fw_lps_deep_mode) + ret = rtw_wow_enter_no_link_ps(rtwdev); return ret; } @@ -653,6 +768,10 @@ static void rtw_wow_vif_iter(void *data, u8 *mac, struct ieee80211_vif *vif) case RTW_NET_MGD_LINKED: rtw_wow->wow_vif = vif; break; + case RTW_NET_NO_LINK: + if (rtw_wow->pno_req.inited) + rtwdev->wow.wow_vif = vif; + break; default: break; } @@ -673,6 +792,9 @@ static int rtw_wow_set_wakeups(struct rtw_dev *rtwdev, if (wowlan->gtk_rekey_failure) set_bit(RTW_WOW_FLAG_EN_REKEY_PKT, rtw_wow->flags); + if (wowlan->nd_config) + rtw_wow_check_pno(rtwdev, wowlan->nd_config); + rtw_iterate_vifs_atomic(rtwdev, rtw_wow_vif_iter, rtwdev); if (!rtw_wow->wow_vif) return -EPERM; @@ -692,6 +814,12 @@ static int rtw_wow_set_wakeups(struct rtw_dev *rtwdev, static void rtw_wow_clear_wakeups(struct rtw_dev *rtwdev) { struct rtw_wow_param *rtw_wow = &rtwdev->wow; + struct rtw_pno_request *pno_req = &rtw_wow->pno_req; + + if (pno_req->inited) { + kfree(pno_req->channels); + kfree(pno_req->match_sets); + } memset(rtw_wow, 0, sizeof(rtwdev->wow)); } @@ -715,6 +843,7 @@ int rtw_wow_suspend(struct rtw_dev *rtwdev, struct cfg80211_wowlan *wowlan) ret = rtw_wow_enable(rtwdev); if (ret) { rtw_err(rtwdev, "failed to enable wow\n"); + rtw_wow_restore_ps(rtwdev); goto out; } @@ -746,8 +875,14 @@ int rtw_wow_resume(struct rtw_dev *rtwdev) rtw_wow_show_wakeup_reason(rtwdev); ret = rtw_wow_disable(rtwdev); - if (ret) + if (ret) { rtw_err(rtwdev, "failed to disable wow\n"); + goto out; + } + + ret = rtw_wow_restore_ps(rtwdev); + if (ret) + rtw_err(rtwdev, "failed to restore ps to normal mode\n"); out: rtw_wow_clear_wakeups(rtwdev); diff --git a/drivers/net/wireless/realtek/rtw88/wow.h b/drivers/net/wireless/realtek/rtw88/wow.h index 54f742f4c955..289368a2cba4 100644 --- a/drivers/net/wireless/realtek/rtw88/wow.h +++ b/drivers/net/wireless/realtek/rtw88/wow.h @@ -5,6 +5,8 @@ #ifndef __RTW_WOW_H__ #define __RTW_WOW_H__ +#define PNO_CHECK_BYTE 4 + enum rtw_wow_pattern_type { RTW_PATTERN_BROADCAST = 0, RTW_PATTERN_MULTICAST, @@ -20,6 +22,7 @@ enum rtw_wake_reason { RTW_WOW_RSN_DISCONNECT = 0x10, RTW_WOW_RSN_RX_MAGIC_PKT = 0x21, RTW_WOW_RSN_RX_PATTERN_MATCH = 0x23, + RTW_WOW_RSN_RX_NLO = 0x55, }; struct rtw_fw_media_status_iter_data { @@ -41,6 +44,14 @@ static inline bool rtw_wow_mgd_linked(struct rtw_dev *rtwdev) return (rtwvif->net_type == RTW_NET_MGD_LINKED); } +static inline bool rtw_wow_no_link(struct rtw_dev *rtwdev) +{ + struct ieee80211_vif *wow_vif = rtwdev->wow.wow_vif; + struct rtw_vif *rtwvif = (struct rtw_vif *)wow_vif->drv_priv; + + return (rtwvif->net_type == RTW_NET_NO_LINK); +} + int rtw_wow_suspend(struct rtw_dev *rtwdev, struct cfg80211_wowlan *wowlan); int rtw_wow_resume(struct rtw_dev *rtwdev); -- cgit v1.2.3-59-g8ed1b