/****************************************************************************** * * Copyright(c) 2019 - 2021 Realtek Corporation. * * This program is free software; you can redistribute it and/or modify it * under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * *****************************************************************************/ #define _RTW_CSA_C_ #include #if CONFIG_DFS void reset_csa_param(struct rf_ctl_t *rfctl) { rfctl->csa_mode = 0; rfctl->csa_switch_cnt = 0; rfctl->csa_ch_width = 0; _rtw_memset(&(rfctl->csa_chandef), 0, sizeof(struct rtw_chan_def)); } /* Get ch/bw/offset of CSA from rfctl, and check these parameters is valid or not */ bool rtw_get_csa_setting(struct dvobj_priv *d, s16 *req_ch, u8 *req_bw, u8 *req_offset) { struct rf_ctl_t *rfctl = dvobj_to_rfctl(d); struct _ADAPTER *a = dvobj_get_primary_adapter(d); u8 ifbmp_m = rtw_mi_get_ap_mesh_ifbmp(a); bool valid = _TRUE; *req_ch = REQ_CH_NONE; *req_bw = CHANNEL_WIDTH_20; *req_offset = CHAN_OFFSET_NO_EXT; if (rtw_chset_search_ch(rfctl->channel_set, rfctl->csa_chandef.chan) >= 0 && !rtw_chset_is_ch_non_ocp(rfctl->channel_set, rfctl->csa_chandef.chan) ) { /* CSA channel available and valid */ *req_ch = rfctl->csa_chandef.chan; RTW_INFO("CSA : "FUNC_ADPT_FMT" valid CSA ch%u\n", FUNC_ADPT_ARG(a), rfctl->csa_chandef.chan); } else if (ifbmp_m) { /* no available or valid CSA channel, having AP/MESH ifaces */ *req_ch = REQ_CH_NONE; valid = _FALSE; RTW_INFO("CSA : "FUNC_ADPT_FMT" ch sel by AP/MESH ifaces\n", FUNC_ADPT_ARG(a)); goto exit; } else { /* no available or valid CSA channel and no AP/MESH ifaces */ if (!is_supported_24g(dvobj_to_regsty(d)->band_type)) *req_ch = 36; else *req_ch = 1; valid = _FALSE; RTW_INFO("CSA : "FUNC_ADPT_FMT" switch to ch%d, then disconnect with AP\n", FUNC_ADPT_ARG(a), *req_ch); goto exit; } if (rfctl->csa_ch_width == 1) { *req_bw = CHANNEL_WIDTH_80; *req_offset = rfctl->csa_chandef.offset; } else if (rfctl->csa_ch_width == 0 && rfctl->csa_chandef.offset != CHAN_OFFSET_NO_EXT) { *req_bw = CHANNEL_WIDTH_40; *req_offset = rfctl->csa_chandef.offset; } else { *req_bw = CHANNEL_WIDTH_20; *req_offset = CHAN_OFFSET_NO_EXT; } /* get correct offset and check ch/bw/offset is valid or not */ if (!rtw_get_offset_by_chbw(*req_ch, *req_bw, req_offset)) { *req_bw = CHANNEL_WIDTH_20; *req_offset = CHAN_OFFSET_NO_EXT; } exit: return valid; } #ifdef CONFIG_ECSA_PHL static void reset_ecsa_param(struct _ADAPTER *a) { struct core_ecsa_info *ecsa_info = &(a->ecsa_info); struct rtw_phl_ecsa_param *ecsa_param = &(ecsa_info->phl_ecsa_param); SET_ECSA_STATE(a, ECSA_ST_NONE); ecsa_info->ecsa_allow_case = 0; ecsa_info->ecsa_delay_time = 0; ecsa_info->channel_width = 0; ecsa_info->bss_param = NULL; _rtw_memset(ecsa_param, 0, sizeof(struct rtw_phl_ecsa_param)); } bool rtw_mr_is_ecsa_running(struct _ADAPTER *a) { struct dvobj_priv *d = adapter_to_dvobj(a); struct _ADAPTER *iface; struct core_ecsa_info *ecsa_info = &(a->ecsa_info); u8 i; for (i = 0; i < d->iface_nums; i++) { iface = d->padapters[i]; if (!iface) continue; if (!CHK_ECSA_STATE(iface, ECSA_ST_NONE)) return _TRUE; } return _FALSE; } static void rtw_ecsa_update_sta_chan_info(struct _ADAPTER *a, struct rtw_chan_def new_chan_def) { struct mlme_ext_priv *pmlmeext = &a->mlmeextpriv; struct mlme_priv *pmlmepriv = &a->mlmepriv; struct dvobj_priv *d = adapter_to_dvobj(a); struct rf_ctl_t *rfctl = dvobj_to_rfctl(d); u8 new_ch = new_chan_def.chan; u8 new_bw = (u8)new_chan_def.bw; u8 new_offset = (u8)new_chan_def.offset; bool is_chctx_add = _FALSE; pmlmeext->chandef.chan= new_ch; pmlmeext->chandef.bw = new_bw; pmlmeext->chandef.offset = new_offset; pmlmepriv->cur_network.network.Configuration.DSConfig = new_ch; /* update wifi role chandef */ rtw_hw_update_chan_def(a); /* update chanctx */ rtw_phl_chanctx_del(d->phl, a->phl_role, NULL); is_chctx_add = rtw_phl_chanctx_add(d->phl, a->phl_role, &new_chan_def.chan, &new_chan_def.bw, &new_chan_def.offset); if (is_chctx_add == _FALSE) RTW_ERR("CSA : "FUNC_ADPT_FMT" chan_ctx add fail!", FUNC_ADPT_ARG(a)); set_fwstate(pmlmepriv, WIFI_CSA_UPDATE_BEACON); /* STA wait 70 seconds for receiving beacons in DFS channel */ if (rtw_chset_is_dfs_chbw(rfctl->channel_set, new_ch, new_bw, new_offset)) { RTW_INFO("CSA : set csa_timer to 70 seconds\n"); _set_timer(&pmlmeext->csa_timer, CAC_TIME_MS + 10000); } #ifdef CONFIG_DFS_MASTER rtw_dfs_rd_en_decision(a, MLME_OPCH_SWITCH, 0); #endif } static void rtw_ecsa_update_ap_chan_info(struct _ADAPTER *a, struct rtw_chan_def new_chan_def) { struct core_ecsa_info *ecsa_info = &(a->ecsa_info); struct createbss_parm *parm; ecsa_info->bss_param = (struct createbss_parm *)rtw_zmalloc(sizeof(struct createbss_parm)); if (ecsa_info->bss_param) { parm = ecsa_info->bss_param; parm->adhoc = 0; parm->ifbmp = BIT(a->iface_id); parm->excl_ifbmp = 0; parm->req_ch = new_chan_def.chan; parm->req_bw = new_chan_def.bw; parm->req_offset = new_chan_def.offset; parm->ifbmp_ch_changed = 0; parm->ch_to_set = 0; parm->bw_to_set = 0; parm->offset_to_set = 0; parm->do_rfk = _FALSE; start_bss_network(a, parm); } else { RTW_ERR("CSA : can't allocate memory for bss_param\n"); } } void rtw_ecsa_update_probe_resp(struct xmit_frame *xframe) { struct _ADAPTER *a = xframe->padapter; struct core_ecsa_info *ecsa_info = &(a->ecsa_info); struct rtw_phl_ecsa_param *ecsa_param = &(ecsa_info->phl_ecsa_param); struct pkt_attrib *pattrib = &xframe->attrib; u8 hdr_len = sizeof(struct rtw_ieee80211_hdr_3addr); u8 *ies; sint ies_len; u8 *csa_ie; sint csa_ie_len; ies = xframe->buf_addr + TXDESC_OFFSET + hdr_len + _BEACON_IE_OFFSET_; ies_len = pattrib->pktlen - hdr_len - _BEACON_IE_OFFSET_; csa_ie = rtw_get_ie(ies, WLAN_EID_CHANNEL_SWITCH, &csa_ie_len, ies_len); if (csa_ie == NULL) return; csa_ie[2 + CSA_SWITCH_COUNT] = ecsa_param->count; #ifdef DBG_CSA RTW_INFO("CSA : update csa count of probe response = %u\n", csa_ie[2 + CSA_SWITCH_COUNT]); #endif } void rtw_ecsa_update_beacon(void *priv, struct rtw_wifi_role_t *role) { #ifdef CONFIG_AP_MODE struct dvobj_priv *d = (struct dvobj_priv *)priv; struct _ADAPTER *a = d->padapters[role->id]; _update_beacon(a, WLAN_EID_CHANNEL_SWITCH, NULL, _TRUE, 0, "update CSA count"); #endif } /* PHL MR module check core layer if AP mode can switch channel now */ bool rtw_ap_check_ecsa_allow( void *priv, struct rtw_wifi_role_t *role, struct rtw_chan_def chan_def, enum phl_ecsa_start_reason reason, u32 *delay_start_ms ) { struct dvobj_priv *d = (struct dvobj_priv *)priv; struct _ADAPTER *a = d->padapters[role->id]; struct core_ecsa_info *ecsa_info = &(a->ecsa_info); bool ecsa_allow = _TRUE; u8 i; /* TODO : need to check MCC-ECSA case */ /* if (!(ecsa_info->ecsa_allow_case & BIT(reason))) { */ if (0) { RTW_INFO("CSA : "FUNC_ADPT_FMT" : Case %u not support!\n", FUNC_ADPT_ARG(a), reason); ecsa_allow = _FALSE; goto exit; } /* Check DFS channel */ if (!rtw_phl_regulation_valid_channel(GET_PHL_INFO(d), chan_def.band, chan_def.chan, (CH_PASSIVE | CH_DFS))) { RTW_ERR("CSA : "FUNC_ADPT_FMT" : DFS channel (%u) not support!\n", FUNC_ADPT_ARG(a), chan_def.chan); ecsa_allow = _FALSE; goto exit; } if (ecsa_info->ecsa_delay_time != 0) *delay_start_ms = ecsa_info->ecsa_delay_time; else if (reason <= ECSA_START_MCC_5G_TO_24G) *delay_start_ms = MCC_ECSA_DELAY_START_TIME; RTW_INFO("CSA : %s : ECSA will delay %u ms\n", __func__, *delay_start_ms); SET_ECSA_STATE(a, ECSA_ST_SW_START); exit: return ecsa_allow; } void rtw_ecsa_mr_update_chan_info_by_role( void *priv, struct rtw_wifi_role_t *role, struct rtw_chan_def new_chan_def ) { struct dvobj_priv *d = (struct dvobj_priv *)priv; struct _ADAPTER *a = d->padapters[role->id]; struct mlme_ext_priv *pmlmeext = &a->mlmeextpriv; struct mlme_priv *pmlmepriv = &a->mlmepriv; RTW_INFO("CSA : "FUNC_ADPT_FMT", new ch/bw/offset = %u,%u,%u\n", FUNC_ADPT_ARG(a), \ new_chan_def.chan, new_chan_def.bw, new_chan_def.offset); if (role->type == PHL_RTYPE_STATION || role->type == PHL_RTYPE_P2P_GC) rtw_ecsa_update_sta_chan_info(a, new_chan_def); else if (role->type == PHL_RTYPE_AP ||role->type == PHL_RTYPE_P2P_GO) rtw_ecsa_update_ap_chan_info(a, new_chan_def); } bool rtw_ecsa_check_tx_resume_allow(void *priv, struct rtw_wifi_role_t *role) { /* TODO */ /* Is DFS slave still monitoring channel ? If Yes, return False to PHL; If no, return True to PHL */ struct dvobj_priv *d = (struct dvobj_priv *)priv; struct _ADAPTER *a = d->padapters[role->id]; RTW_INFO("CSA : "FUNC_ADPT_FMT"\n", FUNC_ADPT_ARG(a)); return 1; } void rtw_ecsa_complete(void *priv, struct rtw_wifi_role_t *role) { struct dvobj_priv *d = (struct dvobj_priv *)priv; struct _ADAPTER *a = d->padapters[role->id]; struct rf_ctl_t *rfctl = dvobj_to_rfctl(d); struct core_ecsa_info *ecsa_info = &(a->ecsa_info); struct createbss_parm *parm = ecsa_info->bss_param; u8 i, ht_option = 0; RTW_INFO("CSA : "FUNC_ADPT_FMT"\n", FUNC_ADPT_ARG(a)); rtw_phl_mr_dump_cur_chandef(d->phl, role); if (role->type == PHL_RTYPE_STATION || role->type == PHL_RTYPE_P2P_GC) { /* * TODO * STA mode need to update RA if it receive CHANNEL_SWITCH_WRAPPER IE * STA mode update its RA at rtw_check_bcn_info() now */ rtw_rfctl_update_op_mode(rfctl, 0, 0); } else if (role->type == PHL_RTYPE_AP ||role->type == PHL_RTYPE_P2P_GO) { #if defined(CONFIG_IOCTL_CFG80211) && (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)) for (i = 0; i < d->iface_nums; i++) { if (!(parm->ifbmp_ch_changed & BIT(i)) || !d->padapters[i]) continue; #ifdef CONFIG_80211N_HT ht_option = d->padapters[i]->mlmepriv.htpriv.ht_option; #endif rtw_cfg80211_ch_switch_notify(d->padapters[i] , d->padapters[i]->mlmeextpriv.chandef.chan , d->padapters[i]->mlmeextpriv.chandef.bw , d->padapters[i]->mlmeextpriv.chandef.offset , ht_option, 0); } #endif rtw_rfctl_update_op_mode(adapter_to_rfctl(a), parm->ifbmp, 1); rtw_core_ap_start(a, parm); rtw_ap_update_clients_rainfo(a, PHL_CMD_DIRECTLY); rtw_mfree((u8 *)parm, sizeof(struct createbss_parm)); } rtw_mi_os_xmit_schedule(a); reset_ecsa_param(a); } /* Get ch/bw/offset of CSA from adapter, and check these parameters is valid or not */ static bool rtw_sta_get_ecsa_setting(struct _ADAPTER *a, s16 *req_ch, u8 *req_bw, u8 *req_offset) { struct rf_ctl_t *rfctl = adapter_to_rfctl(a); struct core_ecsa_info *ecsa_info = &(a->ecsa_info); struct rtw_phl_ecsa_param *ecsa_param = &(ecsa_info->phl_ecsa_param); u8 ifbmp_m = rtw_mi_get_ap_mesh_ifbmp(a); u8 csa_chan = ecsa_param->new_chan_def.chan; u8 csa_offset = ecsa_param->new_chan_def.offset; bool valid = _TRUE; *req_ch = REQ_CH_NONE; *req_bw = CHANNEL_WIDTH_20; *req_offset = CHAN_OFFSET_NO_EXT; if (rtw_chset_search_ch(rfctl->channel_set, csa_chan) >= 0 && !rtw_chset_is_ch_non_ocp(rfctl->channel_set, csa_chan) ) { /* CSA channel available and valid */ *req_ch = csa_chan; RTW_INFO("CSA : "FUNC_ADPT_FMT" valid CSA ch%u\n", FUNC_ADPT_ARG(a), csa_chan); } else if (ifbmp_m) { /* no available or valid CSA channel, having AP/MESH ifaces */ *req_ch = REQ_CH_NONE; valid = _FALSE; RTW_INFO("CSA : "FUNC_ADPT_FMT" ch sel by AP/MESH ifaces\n", FUNC_ADPT_ARG(a)); goto exit; } else { /* no available or valid CSA channel and no AP/MESH ifaces */ /* TODO : DFS slave may need to switch channel as soon as possible before disconnect */ #if 0 if (!is_supported_24g(adapter_to_regsty(a)->band_type)) *req_ch = 36; else *req_ch = 1; #endif valid = _FALSE; RTW_INFO("CSA : "FUNC_ADPT_FMT" switch to ch %d, then disconnect with AP\n", FUNC_ADPT_ARG(a), *req_ch); goto exit; } /* Transform channel_width to bandwidth 20/40/80M */ if (ecsa_info->channel_width == 1) { *req_bw = CHANNEL_WIDTH_80; *req_offset = csa_offset; } else if (ecsa_info->channel_width == 0 && csa_offset != CHAN_OFFSET_NO_EXT) { *req_bw = CHANNEL_WIDTH_40; *req_offset = csa_offset; } else { *req_bw = CHANNEL_WIDTH_20; *req_offset = CHAN_OFFSET_NO_EXT; } /* Get correct offset and check ch/bw/offset is valid or not */ if (!rtw_get_offset_by_chbw(*req_ch, *req_bw, req_offset)) { *req_bw = CHANNEL_WIDTH_20; *req_offset = CHAN_OFFSET_NO_EXT; } /* Update result to ecsa_param */ ecsa_param->new_chan_def.chan = *req_ch; ecsa_param->new_chan_def.bw = *req_bw; ecsa_param->new_chan_def.offset = *req_offset; exit: return valid; } static void rtw_sta_ecsa_invalid_hdl(struct _ADAPTER *a, s16 req_ch, u8 req_bw, u8 req_offset) { struct dvobj_priv *d = adapter_to_dvobj(a); struct rf_ctl_t *rfctl = dvobj_to_rfctl(d); struct mlme_ext_priv *pmlmeext = &a->mlmeextpriv; struct mlme_ext_info *pmlmeinfo = &pmlmeext->mlmext_info; u8 ifbmp_s = rtw_mi_get_ld_sta_ifbmp(a); struct rtw_chan_def mr_chdef = {0}; if (!ifbmp_s) return; set_fwstate(&a->mlmepriv, WIFI_OP_CH_SWITCHING); issue_deauth(a, get_bssid(&a->mlmepriv), WLAN_REASON_DEAUTH_LEAVING); /* Decide whether enable DFS slave radar detection or not */ #ifdef CONFIG_DFS_MASTER rtw_dfs_rd_en_decision(a, MLME_OPCH_SWITCH, ifbmp_s); #endif /* TODO : DFS slave may need to switch channel as soon as possible before disconnect */ /* This context can't I/O, so use RTW_CMDF_DIRECTLY */ rtw_disassoc_cmd(a, 0, RTW_CMDF_DIRECTLY); rtw_indicate_disconnect(a, 0, _FALSE); #ifndef CONFIG_STA_CMD_DISPR rtw_free_assoc_resources(a, _TRUE); #endif rtw_free_network_queue(a, _TRUE); RTW_INFO("CSA : "FUNC_ADPT_FMT" disconnect with AP\n", FUNC_ADPT_ARG(a)); pmlmeinfo->disconnect_occurred_time = rtw_systime_to_ms(rtw_get_current_time()); pmlmeinfo->disconnect_code = DISCONNECTION_BY_DRIVER_DUE_TO_RECEIVE_INVALID_CSA; pmlmeinfo->wifi_reason_code = WLAN_REASON_DEAUTH_LEAVING; reset_ecsa_param(a); rtw_mi_os_xmit_schedule(a); } void rtw_trigger_phl_ecsa_start(struct _ADAPTER *a) { struct dvobj_priv *d = adapter_to_dvobj(a); struct rtw_wifi_role_t *role = a->phl_role; struct core_ecsa_info *ecsa_info = &(a->ecsa_info); struct rtw_phl_ecsa_param *ecsa_param = &(ecsa_info->phl_ecsa_param); /* STA need to check ecsa setting */ if (ecsa_param->ecsa_type == ECSA_TYPE_STA) { s16 req_ch; u8 req_bw, req_offset; if (!rtw_sta_get_ecsa_setting(a, &req_ch, &req_bw, &req_offset)) { /* we should handle error case by core layer self */ rtw_sta_ecsa_invalid_hdl(a, req_ch, req_bw, req_offset); return; } } if (rtw_phl_ecsa_start(GET_PHL_INFO(d), role, ecsa_param) != RTW_PHL_STATUS_SUCCESS) RTW_ERR("CSA : Start PHL ECSA fail\n"); #if 0 /* TODO : ECSA */ if(0) { if(!rtw_phl_get_chandef_from_operating_class(param.ch, param.op_class, &(param.new_chan_def))){ RTW_INFO("[CSA] Get chandef fail!Use 20 MHz to switch\n"); param.new_chan_def.band = rtw_phl_get_band_type(param.ch); param.new_chan_def.chan = param.ch; param.new_chan_def.bw = CHANNEL_WIDTH_20; param.new_chan_def.offset = CHAN_OFFSET_NO_EXT; } } #endif } #endif /* CONFIG_ECSA_PHL */ #endif /* CONFIG_DFS */