/****************************************************************************** * * Copyright(c) 2007 - 2017 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_MBO_C #include #ifdef CONFIG_RTW_MBO #ifndef RTW_MBO_DBG #define RTW_MBO_DBG 0 #endif #if RTW_MBO_DBG #define RTW_MBO_INFO(fmt, arg...) \ RTW_INFO(fmt, arg) #define RTW_MBO_DUMP(str, data, len) \ RTW_INFO_DUMP(str, data, len) #else #define RTW_MBO_INFO(fmt, arg...) do {} while (0) #define RTW_MBO_DUMP(str, data, len) do {} while (0) #endif /* Cellular Data Connectivity field * 1 : Cellular data connection available * 2 : Cellular data connection not available * 3 : Not Cellular data capable * otherwise : Reserved */ int rtw_mbo_cell_data_conn = 2; module_param(rtw_mbo_cell_data_conn, int, 0644); static u8 wfa_mbo_oui[] = {0x50, 0x6F, 0x9A, 0x16}; #define rtw_mbo_get_oui(p) ((u8 *)(p) + 2) #define rtw_mbo_get_attr_id(p) ((u8 *)(p)) #define rtw_mbo_get_disallow_res(p) ((u8 *)(p) + 3) #define rtw_mbo_set_1byte_ie(p, v, l) \ rtw_set_fixed_ie((p), 1, (v), (l)) #define rtw_mbo_set_2byte_ie(p, v, l) \ rtw_set_fixed_ie((p), 2, (v), (l)) #define rtw_mbo_set_4byte_ie(p, v, l) \ rtw_set_fixed_ie((p), 4, (v), (l)) #define rtw_mbo_set_nbyte_ie(p, sz, v, l) \ rtw_set_fixed_ie((p), (sz), (v), (l)) #define rtw_mbo_subfield_set(p, offset, val) (*(p + offset) = val) #define rtw_mbo_subfields_set(p, offset, buf, len) \ do { \ u32 _offset = 0; \ u8 *_p = p + offset; \ while(_offset < len) { \ *(_p + _offset) = *(buf + _offset); \ _offset++; \ } \ } while(0) void rtw_mbo_ie_init(_adapter *padapter, struct mbo_priv *mbopriv) { if(!mbopriv) return; mbopriv->assoc_disallow = 0; mbopriv->cellular_aware = 0; mbopriv->ch_list_num = 0; mbopriv->mbo_oce_element_len = 6; mbopriv->mbo_oce_element[0] = 0xdd; mbopriv->mbo_oce_element[1] = mbopriv->mbo_oce_element_len; mbopriv->mbo_oce_element[2] = 0x50; mbopriv->mbo_oce_element[3] = 0x6f; mbopriv->mbo_oce_element[4] = 0x9a; mbopriv->mbo_oce_element[5] = 0x16; } void rtw_mbo_fill_non_prefer_channel_list(_adapter *padapter, struct mbo_priv *mbopriv, const u8 *pbuf, u8 len) { u8 op_class = 0; u8 preference = 0; int i; /* invalid length */ if(len != 0 && len < 3) return; /* reset non-prefer channel list */ mbopriv->ch_list_num = 0; op_class = *pbuf; preference = *(pbuf + len - 2); if (len == 3 && mbopriv->ch_list_num < MBO_CH_LIST_MAX_NUM) { mbopriv->ch_list[mbopriv->ch_list_num].op_class = op_class; mbopriv->ch_list[mbopriv->ch_list_num].preference = preference; mbopriv->ch_list[mbopriv->ch_list_num].channel = 0; mbopriv->ch_list_num += 1; RTW_INFO("[%s:%d]channel = %d, preference = %d\n", __func__, __LINE__, 0, preference); } else { for (i = 0; i < len - 3; i++) { if(mbopriv->ch_list_num >= MBO_CH_LIST_MAX_NUM) break; mbopriv->ch_list[mbopriv->ch_list_num].op_class = op_class; mbopriv->ch_list[mbopriv->ch_list_num].preference = preference; mbopriv->ch_list[mbopriv->ch_list_num].channel = *(pbuf + 1 + i); mbopriv->ch_list_num += 1; RTW_INFO("[%s:%d]channel = %d, preference = %d\n", __func__, __LINE__, *(pbuf + 1 + i), preference); } } } void rtw_mbo_ie_handler(_adapter *padapter, struct mbo_priv *mbopriv, const u8 *pbuf, uint limit_len) { struct mlme_priv *pmlmepriv = &(padapter->mlmepriv); uint total_len = 0; u8 attribute_id = 0; u8 attribute_len = 0; const u8 *p = pbuf; if(!mbopriv) return; rtw_mbo_ie_init(padapter, mbopriv); _rtw_memcpy(mbopriv->mbo_oce_element + 6, pbuf, limit_len); mbopriv->mbo_oce_element[1] = limit_len + 4; while (total_len <= limit_len) { attribute_id = *p; attribute_len = *(p + 1); total_len += attribute_len; if(total_len > limit_len) break; switch (attribute_id) { case MBO_AP_CAPABILITY: if(attribute_len == 1){ RTW_INFO("[%s]Find attribute MBO_AP_CAPABILITY\n", __func__); if(*(p+2) & 0x40) mbopriv->cellular_aware = 1; } break; case ASSOCIATION_DISALLOW: if(attribute_len == 1){ RTW_INFO("[%s]Find attribute ASSOCIATION_DISALLOW\n", __func__); mbopriv->assoc_disallow = *(p+2); } break; case NON_PREFER_CHANNEL_RPT: RTW_INFO("[%s]Find attribute NON_PREFER_CHANNEL_RPT\n", __func__); rtw_mbo_fill_non_prefer_channel_list(padapter, mbopriv, p + 2, attribute_len); break; case CELLULAR_DATA_CAPABILITY: case CELLULAR_DATA_CONNECT_PREFER: case TRANS_REASON_CODE: case TRANS_REJECT_REASON_CODE: case ASSOCIATION_RETRY_DELAY: break; default: RTW_ERR("[%s]Unknown MBO attribute %d\n", __func__, attribute_id); } p += (attribute_len + 2); } } void rtw_ap_parse_sta_mbo_element(_adapter *padapter, struct sta_info *psta, u8 *ies_buf, u16 ies_len) { uint ie_len = 0; u8 *p; u8 WIFI_ALLIANCE_OUI[] = {0x50, 0x6f, 0x9a}; ie_len = 0; for (p = ies_buf; ; p += (ie_len + 2)) { p = rtw_get_ie(p, _SSN_IE_1_, &ie_len, (ies_len - (ie_len + 2))); if ((p) && (_rtw_memcmp(p + 2, WIFI_ALLIANCE_OUI, 3)) && (*(p+5) == MBO_OUI_TYPE)) { /* find MBO-OCE information element */ psta->mbopriv.enable = _TRUE; rtw_mbo_ie_handler(padapter, &psta->mbopriv, p + 6, ie_len - 4); break; } if ((p == NULL) || (ie_len == 0)) break; } } static u8 *rtw_mbo_ie_get(u8 *pie, u32 *plen, u32 limit) { const u8 *p = pie; u32 tmp, i; if (limit <= 1) return NULL; i = 0; *plen = 0; while (1) { if ((*p == _VENDOR_SPECIFIC_IE_) && (_rtw_memcmp(rtw_mbo_get_oui(p), wfa_mbo_oui, 4))) { *plen = *(p + 1); /* RTW_MBO_DUMP("VENDOR_SPECIFIC_IE MBO: ", p, *(p + 1)); */ return (u8 *)p; } else { tmp = *(p + 1); p += (tmp + 2); i += (tmp + 2); } if (i >= limit) break; } return NULL; } static u8 *rtw_mbo_attrs_get(u8 *pie, u32 limit, u8 attr_id,u32 *attr_len, u8 dbg) { u8 *p = NULL; u32 offset, plen = 0; if ((pie == NULL) || (limit <= 1)) goto exit; if ((p = rtw_mbo_ie_get(pie, &plen, limit)) == NULL) goto exit; /* shift 2 + OUI size and move to attributes content */ p = p + 2 + sizeof(wfa_mbo_oui); plen = plen - 4; if (dbg) RTW_MBO_DUMP("Attributes contents: ", p, plen); if ((p = rtw_get_ie(p, attr_id, attr_len, plen)) == NULL) goto exit; if (dbg) { RTW_MBO_INFO("%s : id=%u(len=%u)\n", __func__, attr_id, *attr_len); RTW_MBO_DUMP("contents : ", (p + 2), *attr_len); } exit: return p; } static u32 rtw_mbo_attr_sz_get( _adapter *padapter, u8 id) { u32 len = 0; switch (id) { case RTW_MBO_ATTR_NPREF_CH_RPT_ID: { struct rf_ctl_t *prfctl = \ adapter_to_rfctl(padapter); struct npref_ch_rtp *prpt = \ &(prfctl->ch_rtp); struct npref_ch* pch; u32 i, attr_len, offset; for (i=0; i < prpt->nm_of_rpt; i++) { pch = &prpt->ch_rpt[i]; /*attr_len = ch list + op class + preference + reason */ attr_len = pch->nm_of_ch + 3; /* offset = id + len field + attr_len */ offset = attr_len + 2; len += offset; } } break; case RTW_MBO_ATTR_ASSOC_RETRY_DELAY_ID: len = 4; break; case RTW_MBO_ATTR_AP_CAP_ID: case RTW_MBO_ATTR_CELL_DATA_CAP_ID: case RTW_MBO_ATTR_ASSOC_DISABLED_ID: case RTW_MBO_ATTR_TRANS_RES_ID: case RTW_MBO_ATTR_TRANS_REJ_ID: len = 3; break; default: break; } return len; } static void rtw_mbo_build_mbo_ie_hdr( u8 **pframe, struct pkt_attrib *pattrib, u8 payload_len) { u8 eid = RTW_MBO_EID; u8 len = payload_len + 4; *pframe = rtw_mbo_set_1byte_ie(*pframe, &eid, &(pattrib->pktlen)); *pframe = rtw_mbo_set_1byte_ie(*pframe, &len, &(pattrib->pktlen)); *pframe = rtw_mbo_set_4byte_ie(*pframe, wfa_mbo_oui, &(pattrib->pktlen)); } void rtw_mbo_build_cell_data_cap_attr( _adapter *padapter, u8 **pframe, struct pkt_attrib *pattrib) { struct mlme_priv *pmlmepriv = &(padapter->mlmepriv); u8 attr_id = RTW_MBO_ATTR_CELL_DATA_CAP_ID; u8 attr_len = 1; u8 cell_data_con = rtw_mbo_cell_data_conn; /* used Cellular Data Capabilities from supplicant */ if (!rtw_mbo_wifi_logo_test(padapter) && pmlmepriv->pcell_data_cap_ie && pmlmepriv->cell_data_cap_len == 1) { cell_data_con = *pmlmepriv->pcell_data_cap_ie; } *pframe = rtw_mbo_set_1byte_ie(*pframe, &attr_id, &(pattrib->pktlen)); *pframe = rtw_mbo_set_1byte_ie(*pframe, &attr_len, &(pattrib->pktlen)); *pframe = rtw_mbo_set_1byte_ie(*pframe, &cell_data_con, &(pattrib->pktlen)); } static void rtw_mbo_build_ap_cap_Indication_attr( _adapter *padapter, u8 **pframe, struct pkt_attrib *pattrib, u8 cap_ind) { struct mlme_priv *pmlmepriv = &(padapter->mlmepriv); u8 attr_id = RTW_MBO_ATTR_AP_CAP_ID; u8 attr_len = 1; u8 ap_cap_ind = cap_ind; *pframe = rtw_mbo_set_1byte_ie(*pframe, &attr_id, &(pattrib->pktlen)); *pframe = rtw_mbo_set_1byte_ie(*pframe, &attr_len, &(pattrib->pktlen)); *pframe = rtw_mbo_set_1byte_ie(*pframe, &ap_cap_ind, &(pattrib->pktlen)); } static void rtw_mbo_build_ap_disallowed_attr( _adapter *padapter, u8 **pframe, struct pkt_attrib *pattrib, u8 code) { struct mlme_priv *pmlmepriv = &(padapter->mlmepriv); u8 attr_id = RTW_MBO_ATTR_ASSOC_DISABLED_ID; u8 attr_len = 1; u8 reason = 0; if (code > 0) { reason = code; *pframe = rtw_mbo_set_1byte_ie(*pframe, &attr_id, &(pattrib->pktlen)); *pframe = rtw_mbo_set_1byte_ie(*pframe, &attr_len, &(pattrib->pktlen)); *pframe = rtw_mbo_set_1byte_ie(*pframe, &reason, &(pattrib->pktlen)); } } static void rtw_mbo_build_ap_trans_reason_attr( _adapter *padapter, u8 **pframe, struct pkt_attrib *pattrib, u8 code) { struct mlme_priv *pmlmepriv = &(padapter->mlmepriv); u8 attr_id = RTW_MBO_ATTR_TRANS_RES_ID; u8 attr_len = 1; u8 reason = 0; reason = code; *pframe = rtw_mbo_set_1byte_ie(*pframe, &attr_id, &(pattrib->pktlen)); *pframe = rtw_mbo_set_1byte_ie(*pframe, &attr_len, &(pattrib->pktlen)); *pframe = rtw_mbo_set_1byte_ie(*pframe, &reason, &(pattrib->pktlen)); } static void rtw_mbo_build_ap_assoc_retry_delay_attr( _adapter *padapter, u8 **pframe, struct pkt_attrib *pattrib, u16 code) { struct mlme_priv *pmlmepriv = &(padapter->mlmepriv); u8 attr_id = RTW_MBO_ATTR_ASSOC_RETRY_DELAY_ID; u8 attr_len = 2; u16 delay = 0; delay = code; *pframe = rtw_mbo_set_1byte_ie(*pframe, &attr_id, &(pattrib->pktlen)); *pframe = rtw_mbo_set_1byte_ie(*pframe, &attr_len, &(pattrib->pktlen)); *pframe = rtw_mbo_set_2byte_ie(*pframe, (u8 *)&delay, &(pattrib->pktlen)); } static void rtw_mbo_update_cell_data_cap( _adapter *padapter, u8 *pie, u32 ie_len) { struct mlme_priv *pmlmepriv = &(padapter->mlmepriv); u8 *mbo_attr; u32 mbo_attrlen; if ((pie == NULL) || (ie_len == 0)) return; mbo_attr = rtw_mbo_attrs_get(pie, ie_len, RTW_MBO_ATTR_CELL_DATA_CAP_ID, &mbo_attrlen, 0); if ((mbo_attr == NULL) || (mbo_attrlen == 0) ) { RTW_INFO("MBO : Cellular Data Capabilities not found!\n"); return; } rtw_buf_update(&pmlmepriv->pcell_data_cap_ie, &pmlmepriv->cell_data_cap_len, (mbo_attr + 2), mbo_attrlen); RTW_MBO_DUMP("rtw_mbo_update_cell_data_cap : ", pmlmepriv->pcell_data_cap_ie, pmlmepriv->cell_data_cap_len); } void rtw_mbo_update_ie_data( _adapter *padapter, u8 *pie, u32 ie_len) { rtw_mbo_update_cell_data_cap(padapter, pie, ie_len); } static u8 rtw_mbo_current_op_class_get(_adapter *padapter) { struct rf_ctl_t *prfctl = adapter_to_rfctl(padapter); struct p2p_channels *pch_list = &(prfctl->channel_list); struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv); struct p2p_reg_class *preg_class; int class_idx, ch_idx; u8 cur_op_class = 0; for(class_idx =0; class_idx < pch_list->reg_classes; class_idx++) { preg_class = &pch_list->reg_class[class_idx]; for (ch_idx = 0; ch_idx <= preg_class->channels; ch_idx++) { if (pmlmeext->chandef.chan == \ preg_class->channel[ch_idx]) { cur_op_class = preg_class->reg_class; RTW_MBO_INFO("%s : current ch : %d," " op class : %d\n", __func__, pmlmeext->chandef.chan, cur_op_class); break; } } } return cur_op_class; } static void rtw_mbo_supp_op_classes_get(_adapter *padapter, u8 *pclasses) { struct rf_ctl_t *prfctl = adapter_to_rfctl(padapter); struct p2p_channels *pch_list = &(prfctl->channel_list); int class_idx; if (pclasses == NULL) return; RTW_MBO_INFO("%s : support op class \n", __func__); for(class_idx = 0; class_idx < pch_list->reg_classes; class_idx++) { *(pclasses + class_idx) = \ pch_list->reg_class[class_idx].reg_class; RTW_MBO_INFO("%u ,", *(pclasses + class_idx)); } RTW_MBO_INFO("%s : \n", __func__); } void rtw_mbo_build_supp_op_class_elem( _adapter *padapter, u8 **pframe, struct pkt_attrib *pattrib) { struct rf_ctl_t *prfctl = adapter_to_rfctl(padapter); u8 payload[32] = {0}; u8 delimiter_130 = 130; /*0x82*/ u8 reg_class_nm, len; if ((reg_class_nm = prfctl->channel_list.reg_classes) == 0) return; payload[0] = rtw_mbo_current_op_class_get(padapter); rtw_mbo_supp_op_classes_get(padapter, &payload[1]); /* IEEE 802.11 Std Current Operating Class Extension Sequence */ payload[reg_class_nm + 1] = delimiter_130; payload[reg_class_nm + 2] = 0x00; RTW_MBO_DUMP("op class :", payload, reg_class_nm); /* Current Operating Class field + Operating Class field + OneHundredAndThirty Delimiter field */ len = reg_class_nm + 3; *pframe = rtw_set_ie(*pframe, EID_SupRegulatory, len , payload, &(pattrib->pktlen)); } static u8 rtw_mbo_construct_npref_ch_rpt_attr( _adapter *padapter, u8 *pbuf, u32 buf_len, u32 *plen) { struct rf_ctl_t *prfctl = adapter_to_rfctl(padapter); struct npref_ch_rtp *prpt = &(prfctl->ch_rtp); struct npref_ch* pch; u32 attr_len, offset; int i; u8 *p = pbuf; if (prpt->nm_of_rpt == 0) { *plen = 0; return _FALSE; } for (i=0; i < prpt->nm_of_rpt; i++) { pch = &prpt->ch_rpt[i]; /* attr_len = ch list + op class + preference + reason */ attr_len = pch->nm_of_ch + 3; /* offset = id + len field + attr_len */ offset = attr_len + 2; rtw_mbo_subfield_set(p, 0, RTW_MBO_ATTR_NPREF_CH_RPT_ID); rtw_mbo_subfield_set(p, 1, attr_len); rtw_mbo_subfield_set(p, 2, pch->op_class); rtw_mbo_subfields_set(p, 3, pch->chs, pch->nm_of_ch); rtw_mbo_subfield_set(p, (offset - 2), pch->preference); rtw_mbo_subfield_set(p, (offset - 1), pch->reason); p += offset; *plen += offset; if (*plen >= buf_len) { RTW_ERR("MBO : construct non-preferred-ch rpt fail!\n"); return _FALSE; } } return _TRUE; } void rtw_mbo_build_npref_ch_rpt_attr( _adapter *padapter, u8 **pframe, struct pkt_attrib *pattrib) { struct rf_ctl_t *prfctl = adapter_to_rfctl(padapter); struct npref_ch_rtp *prpt = &(prfctl->ch_rtp); u32 tmp_sz = 0, body_len = 0; u8 *ptmp; tmp_sz = prpt->nm_of_rpt * sizeof(struct npref_ch); ptmp = rtw_zmalloc(tmp_sz); if (ptmp == NULL) return; if (rtw_mbo_construct_npref_ch_rpt_attr( padapter, ptmp, tmp_sz, &body_len) == _FALSE) { rtw_mfree(ptmp, tmp_sz); return; } RTW_MBO_DUMP("Non-preferred Channel Report :", ptmp, body_len); *pframe = rtw_mbo_set_nbyte_ie(*pframe, body_len, ptmp, &(pattrib->pktlen)); rtw_mfree(ptmp, tmp_sz); } void rtw_mbo_build_trans_reject_reason_attr( _adapter *padapter, u8 **pframe, struct pkt_attrib *pattrib, u8 *pres) { u8 attr_id = RTW_MBO_ATTR_TRANS_REJ_ID; u8 attr_len = 1; u32 len = 0; len = rtw_mbo_attr_sz_get(padapter, RTW_MBO_ATTR_TRANS_REJ_ID); if ((len == 0) || (len > 3)) { RTW_ERR("MBO : build Transition Rejection Reason" " attribute fail(len=%u)\n", len); return; } rtw_mbo_build_mbo_ie_hdr(pframe, pattrib, len); *pframe = rtw_mbo_set_1byte_ie(*pframe, &attr_id, &(pattrib->pktlen)); *pframe = rtw_mbo_set_1byte_ie(*pframe, &attr_len, &(pattrib->pktlen)); *pframe = rtw_mbo_set_1byte_ie(*pframe, pres, &(pattrib->pktlen)); } u8 rtw_mbo_disallowed_network(struct wlan_network *pnetwork) { u8 *p, *attr_id, *res; u32 attr_len = 0; u8 disallow = _FALSE; if ((pnetwork == NULL) || \ ((p = rtw_mbo_attrs_get( pnetwork->network.IEs, pnetwork->network.IELength, RTW_MBO_ATTR_ASSOC_DISABLED_ID, &attr_len, 0)) == NULL)) { goto exit; } RTW_MBO_DUMP("Association Disallowed attribute :",p , attr_len + 2); RTW_INFO("MBO : block "MAC_FMT" assoc disallowed reason %d\n", MAC_ARG(pnetwork->network.MacAddress), *(rtw_mbo_get_disallow_res(p))); disallow = _TRUE; exit: return disallow; } void rtw_mbo_build_extended_cap( _adapter *padapter, u8 **pframe, struct pkt_attrib *pattrib) { struct mlme_priv *pmlmepriv = &(padapter->mlmepriv); if (!rtw_mbo_wifi_logo_test(padapter)) return; rtw_wnm_add_btm_ext_cap(pmlmepriv->ext_capab_ie_data, &(pmlmepriv->ext_capab_ie_len)); rtw_mbo_add_internw_ext_cap(pmlmepriv->ext_capab_ie_data, &(pmlmepriv->ext_capab_ie_len)); *pframe = rtw_set_ie(*pframe, WLAN_EID_EXT_CAP, pmlmepriv->ext_capab_ie_len, pmlmepriv->ext_capab_ie_data, &(pattrib->pktlen)); } static void rtw_mbo_non_pref_chans_dump(struct npref_ch* pch) { int i; u8 buf[128] = {0}; for (i=0; i < pch->nm_of_ch; i++) rtw_sprintf(buf, 128, "%s,%d", buf, pch->chs[i]); RTW_MBO_INFO("%s : op_class=%01x, ch=%s, preference=%d, reason=%d\n", __func__, pch->op_class, buf, pch->preference, pch->reason); } static u8 rtw_mbo_non_pref_chan_exist(struct npref_ch* pch, u8 ch) { u32 i; u8 found = _FALSE; for (i=0; i < pch->nm_of_ch; i++) { if (pch->chs[i] == ch) { found = _TRUE; break; } } return found; } static struct npref_ch* rtw_mbo_non_pref_chan_get( _adapter *padapter, u8 op_class, u8 prefe, u8 res) { struct rf_ctl_t *prfctl = adapter_to_rfctl(padapter); struct npref_ch_rtp *prpt = &(prfctl->ch_rtp); struct npref_ch* pch = NULL; int i; if (prpt->nm_of_rpt == 0) return pch; for (i=0; i < prpt->nm_of_rpt; i++) { if ((prpt->ch_rpt[i].op_class == op_class) && (prpt->ch_rpt[i].preference == prefe) && (prpt->ch_rpt[i].reason == res)) { pch = &prpt->ch_rpt[i]; break; } } return pch; } static void rtw_mbo_non_pref_chan_set( struct npref_ch* pch, u8 op_class, u8 ch, u8 prefe, u8 res, u8 update) { u32 offset = pch->nm_of_ch; if (update) { if (rtw_mbo_non_pref_chan_exist(pch, ch) == _FALSE) { pch->chs[offset] = ch; pch->nm_of_ch++; } } else { pch->op_class = op_class; pch->chs[0] = ch; pch->preference = prefe; pch->reason = res; pch->nm_of_ch = 1; } } static void rtw_mbo_non_pref_chans_update( _adapter *padapter, u8 op_class, u8 ch, u8 prefe, u8 res) { struct rf_ctl_t *prfctl = adapter_to_rfctl(padapter); struct npref_ch_rtp *pch_rpt = &(prfctl->ch_rtp); struct npref_ch* pch; if (pch_rpt->nm_of_rpt >= RTW_MBO_MAX_CH_RPT_NUM) { RTW_ERR("MBO : %d non_pref_chan entries supported!", RTW_MBO_MAX_CH_RPT_NUM); return; } if (pch_rpt->nm_of_rpt == 0) { pch = &pch_rpt->ch_rpt[0]; rtw_mbo_non_pref_chan_set(pch, op_class, ch, prefe, res, _FALSE); pch_rpt->nm_of_rpt = 1; return; } pch = rtw_mbo_non_pref_chan_get(padapter, op_class, prefe, res); if (pch == NULL) { pch = &pch_rpt->ch_rpt[pch_rpt->nm_of_rpt]; rtw_mbo_non_pref_chan_set(pch, op_class, ch, prefe, res, _FALSE); pch_rpt->nm_of_rpt++; } else { rtw_mbo_non_pref_chan_set(pch, op_class, ch, prefe, res, _TRUE); } rtw_mbo_non_pref_chans_dump(pch); } static void rtw_mbo_non_pref_chans_set( _adapter *padapter, char *param, ssize_t sz) { char *pnext; u32 op_class, ch, prefe, res; int i = 0; do { pnext = strsep(¶m, " "); if (pnext == NULL) break; sscanf(pnext, "%d:%d:%d:%d", &op_class, &ch, &prefe, &res); rtw_mbo_non_pref_chans_update(padapter, op_class, ch, prefe, res); if ((i++) > 10) { RTW_ERR("MBO : overflow %d \n", i); break; } } while (param != (char*)'\0'); } static void rtw_mbo_non_pref_chans_del( _adapter *padapter, char *param, ssize_t sz) { struct rf_ctl_t *prfctl = adapter_to_rfctl(padapter); struct npref_ch_rtp *prpt = &(prfctl->ch_rtp); RTW_INFO("%s : delete non_pref_chan %s\n", __func__, param); _rtw_memset(prpt, 0, sizeof(struct npref_ch_rtp)); } ssize_t rtw_mbo_proc_non_pref_chans_set( struct file *pfile, const char __user *buffer, size_t count, loff_t *pos, void *pdata) { struct net_device *dev = pdata; _adapter *padapter = (_adapter *)rtw_netdev_priv(dev); struct mlme_priv *pmlmepriv = &padapter->mlmepriv; u8 tmp[128] = {0}; if (count < 1) return -EFAULT; if (count > sizeof(tmp)) { rtw_warn_on(1); return -EFAULT; } if (buffer && !copy_from_user(tmp, buffer, count)) { if (strncmp(tmp, "add", 3) == 0) { rtw_mbo_non_pref_chans_set(padapter, &tmp[4], (count - 4)); } else if (strncmp(tmp, "delete", 6) == 0) { rtw_mbo_non_pref_chans_del(padapter, &tmp[7], (count - 7)); } else { RTW_ERR("MBO : Invalid format : echo [add|delete]" " :::\n"); return -EFAULT; } } #ifdef CONFIG_RTW_WNM if (check_fwstate(pmlmepriv, WIFI_ASOC_STATE) && check_fwstate(pmlmepriv, WIFI_STATION_STATE)) rtw_wnm_issue_action(padapter, RTW_WLAN_ACTION_WNM_NOTIF_REQ, 0, 0); #endif return count; } int rtw_mbo_proc_non_pref_chans_get( struct seq_file *m, void *v) { struct net_device *dev = m->private; _adapter *padapter = (_adapter *)rtw_netdev_priv(dev); struct rf_ctl_t *prfctl = adapter_to_rfctl(padapter); struct npref_ch_rtp *prpt = &(prfctl->ch_rtp); struct npref_ch* pch; int i,j; u8 buf[32] = {0}; RTW_PRINT_SEL(m, "op_class ch preference reason \n"); RTW_PRINT_SEL(m, "=======================================================\n"); if (prpt->nm_of_rpt == 0) { RTW_PRINT_SEL(m, " empty table \n"); return 0; } for (i=0; i < prpt->nm_of_rpt; i++) { pch = &prpt->ch_rpt[i]; buf[0]='\0'; for (j=0; j < pch->nm_of_ch; j++) { if (j == 0) rtw_sprintf(buf, 32, "%02u", pch->chs[j]); else rtw_sprintf(buf, 32, "%s,%02u", buf, pch->chs[j]); } RTW_PRINT_SEL(m, " %04u %20s %02u %02u\n", pch->op_class, buf, pch->preference, pch->reason); } return 0; } ssize_t rtw_mbo_proc_cell_data_set( struct file *pfile, const char __user *buffer, size_t count, loff_t *pos, void *pdata) { struct net_device *dev = pdata; _adapter *padapter = (_adapter *)rtw_netdev_priv(dev); struct mlme_priv *pmlmepriv = &padapter->mlmepriv; int mbo_cell_data = 0; u8 tmp[8] = {0}; if (count < 1) return -EFAULT; if (count > sizeof(tmp)) return -EFAULT; if (buffer && !copy_from_user(tmp, buffer, count)) { int num = sscanf(tmp, "%d", &mbo_cell_data); if (num == 1) { rtw_mbo_cell_data_conn = mbo_cell_data; #ifdef CONFIG_RTW_WNM if (check_fwstate(pmlmepriv, WIFI_ASOC_STATE) && check_fwstate(pmlmepriv, WIFI_STATION_STATE)) { rtw_wnm_issue_action(padapter, RTW_WLAN_ACTION_WNM_NOTIF_REQ, 0, 0); } #endif } } return count; } int rtw_mbo_proc_cell_data_get( struct seq_file *m, void *v) { #if 0 struct net_device *dev = m->private; _adapter *adapter = (_adapter *)rtw_netdev_priv(dev); #endif RTW_PRINT_SEL(m, "Cellular Data Connectivity : %d\n", rtw_mbo_cell_data_conn); return 0; } static void rtw_mbo_disassoc(_adapter *padapter, u8 *da, u8 reason, u8 wait_ack) { struct xmit_frame *pmgntframe; struct pkt_attrib *pattrib; struct rtw_ieee80211_hdr *pwlanhdr; struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv); struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info); u8 *pframe; u16 *fctrl; int ret = _FAIL; if (rtw_rfctl_is_tx_blocked_by_ch_waiting(adapter_to_rfctl(padapter))) return; pmgntframe = alloc_mgtxmitframe(pxmitpriv); if (pmgntframe == NULL) return; /* update attribute */ pattrib = &pmgntframe->attrib; update_mgntframe_attrib(padapter, pattrib); pattrib->retry_ctrl = _FALSE; pattrib->key_type = IEEE80211W_RIGHT_KEY; _rtw_memset(pmgntframe->buf_addr, 0, WLANHDR_OFFSET + TXDESC_OFFSET); pframe = (u8 *)(pmgntframe->buf_addr) + TXDESC_OFFSET; pwlanhdr = (struct rtw_ieee80211_hdr *)pframe; fctrl = &(pwlanhdr->frame_ctl); *(fctrl) = 0; _rtw_memcpy(pwlanhdr->addr1, da, ETH_ALEN); _rtw_memcpy(pwlanhdr->addr2, adapter_mac_addr(padapter), ETH_ALEN); _rtw_memcpy(pwlanhdr->addr3, get_my_bssid(&(pmlmeinfo->network)), ETH_ALEN); SetSeqNum(pwlanhdr, pmlmeext->mgnt_seq); pmlmeext->mgnt_seq++; set_frame_sub_type(pframe, WIFI_DISASSOC); pframe += sizeof(struct rtw_ieee80211_hdr_3addr); pattrib->pktlen = sizeof(struct rtw_ieee80211_hdr_3addr); reason = cpu_to_le16(reason); pframe = rtw_set_fixed_ie(pframe, _RSON_CODE_ , (unsigned char *)&reason, &(pattrib->pktlen)); pattrib->last_txcmdsz = pattrib->pktlen; if (wait_ack) dump_mgntframe_and_wait_ack(padapter, pmgntframe); else dump_mgntframe(padapter, pmgntframe); RTW_MBO_INFO("%s : reason %u\n", __func__, reason); } static void rtw_mbo_construct_user_btm_req( _adapter *padapter, struct btm_req_hdr *phdr, u8 *purl, u32 url_len, struct wnm_btm_cant *pbtm_cant) { struct mlme_priv *pmlmepriv = &(padapter->mlmepriv); struct mbo_attr_info *pmbo_attr = &(pmlmepriv->mbo_attr); struct mbo_user_btm_req_pkt *puser = &(pmbo_attr->user_raw); struct wnm_btm_cant *puser_cant = NULL; if (phdr) { puser->hdr.req_mode = phdr->req_mode; puser->hdr.disassoc_timer = phdr->disassoc_timer; puser->hdr.validity_interval = phdr->validity_interval; puser->hdr.term_duration.tsf = phdr->term_duration.tsf; puser->hdr.term_duration.duration = \ phdr->term_duration.duration; puser->hdr.term_duration.id = 0x4; puser->hdr.term_duration.len = 0xa; RTW_MBO_INFO("%s : req-mode=0x%x, disassoc_timer=%u, " "validity_interval=%u, tsf=%llu, " "duration=%u\n", __func__, puser->hdr.req_mode, puser->hdr.disassoc_timer, puser->hdr.validity_interval, puser->hdr.term_duration.tsf, puser->hdr.term_duration.duration); } if (purl && url_len) { /* TODO */ } if (pbtm_cant) { struct wnm_btm_cant *pbtm_tb = NULL; u8 i, idx, found = _FALSE; for (i = 0; i < RTW_MAX_NB_RPT_NUM; i++) { puser_cant = &puser->btm_cants[i]; if (_rtw_memcmp(pbtm_cant->nb_rpt.bssid, puser_cant->nb_rpt.bssid, ETH_ALEN)) { puser_cant->nb_rpt.bss_info = \ pbtm_cant->nb_rpt.bss_info; puser_cant->nb_rpt.reg_class = \ pbtm_cant->nb_rpt.reg_class; puser_cant->nb_rpt.ch_num = \ pbtm_cant->nb_rpt.ch_num; puser_cant->nb_rpt.phy_type = \ pbtm_cant->nb_rpt.phy_type; puser_cant->preference = \ pbtm_cant->preference; idx = i; found = _TRUE; break; } } if (!found) { if (puser->candidate_cnt >= RTW_MAX_NB_RPT_NUM) puser->candidate_cnt = 0; puser_cant = &puser->btm_cants[puser->candidate_cnt]; puser_cant->nb_rpt.id = \ RTW_WLAN_ACTION_WNM_NB_RPT_ELEM; puser_cant->nb_rpt.len = 0x10; _rtw_memcpy(puser_cant->nb_rpt.bssid, pbtm_cant->nb_rpt.bssid, ETH_ALEN); puser_cant->nb_rpt.bss_info = \ pbtm_cant->nb_rpt.bss_info; puser_cant->nb_rpt.reg_class = \ pbtm_cant->nb_rpt.reg_class; puser_cant->nb_rpt.ch_num = \ pbtm_cant->nb_rpt.ch_num; puser_cant->nb_rpt.phy_type = \ pbtm_cant->nb_rpt.phy_type; puser_cant->preference = \ pbtm_cant->preference; idx = puser->candidate_cnt; puser->candidate_cnt++; } RTW_MBO_INFO("%s:%s idx=%u, bssid("MAC_FMT")," " bss_info(0x%04X), reg_class(0x%02X)," " ch(%d), phy_type(0x%02X), preference(0x%02X)\n", __func__, (found)?"update":"new", idx, MAC_ARG(puser_cant->nb_rpt.bssid), puser_cant->nb_rpt.bss_info, puser_cant->nb_rpt.reg_class, puser_cant->nb_rpt.ch_num, puser_cant->nb_rpt.phy_type, puser_cant->preference); } /* end of if (pbtm_cant) */ } static void rtw_mbo_reset_user_btm_req_preference(_adapter *padapter) { struct mlme_priv *pmlmepriv = &(padapter->mlmepriv); struct mbo_attr_info *pmbo_attr = &(pmlmepriv->mbo_attr); struct mbo_user_btm_req_pkt *puser = &(pmbo_attr->user_raw); struct wnm_btm_cant *puser_cant = NULL; u8 i; for (i = 0; i < RTW_MAX_NB_RPT_NUM; i++) { puser_cant = &puser->btm_cants[i]; if (_rtw_memcmp(adapter_mac_addr(padapter), puser_cant->nb_rpt.bssid, ETH_ALEN)) { puser_cant->preference = 0; RTW_MBO_INFO("%s : reset "MAC_FMT" BTM preference\n", __func__, MAC_ARG(puser_cant->nb_rpt.bssid)); break; } } } ssize_t rtw_mbo_proc_attr_set( struct file *pfile, const char __user *buffer, size_t count, loff_t *pos, void *pdata) { struct net_device *dev = pdata; _adapter *padapter = (_adapter *)rtw_netdev_priv(dev); struct mlme_priv *pmlmepriv = &(padapter->mlmepriv); struct mbo_attr_info *pmbo_attr = &(pmlmepriv->mbo_attr); struct mbo_user_btm_req_pkt *puser = &(pmbo_attr->user_raw); u32 id, val; u8 tmp[64] = {0}; if (count < 1) return -EFAULT; if (count > sizeof(tmp)) return -EFAULT; if (buffer && !copy_from_user(tmp, buffer, count)) { int num = sscanf(tmp, "%d %d", &id, &val); switch (id) { case RTW_MBO_ATTR_AP_CAP_ID: pmbo_attr->ap_cap_ind = val; break; case RTW_MBO_ATTR_CELL_DATA_CAP_ID: pmbo_attr->cell_data_cap = val; break; case RTW_MBO_ATTR_ASSOC_DISABLED_ID: pmbo_attr->assoc_disallow = val; break; case RTW_MBO_ATTR_TRANS_RES_ID: pmbo_attr->reason = val; break; case RTW_MBO_ATTR_ASSOC_RETRY_DELAY_ID: pmbo_attr->delay = val; break; case RTW_MBO_TEST_CMD_REST: RTW_INFO("%s : RTW_MBO_TEST_CMD_REST\n", __func__); _rtw_memset(pmbo_attr, 0, sizeof(struct mbo_attr_info)); pmbo_attr->mbo_spec_test = 1; break; case RTW_MBO_TEST_CMD_BTM_REQ_SET: if (count >= 10) { struct btm_req_hdr btm_hdr; u32 disassoc_imnt, term_bit; u32 term_tsf, term_duration; _rtw_memset(&btm_hdr, 0, sizeof(btm_hdr)); num = sscanf(tmp, "%d %u %u %u %u", &id, &disassoc_imnt, &term_bit, &term_tsf, &term_duration); if (num < 5) break; if (disassoc_imnt > 0) btm_hdr.req_mode |= \ DISASSOC_IMMINENT; if (term_bit > 0) btm_hdr.req_mode |= \ BSS_TERMINATION_INCLUDED; btm_hdr.term_duration.tsf = term_tsf; btm_hdr.term_duration.duration = \ term_duration; rtw_mbo_construct_user_btm_req( padapter, &btm_hdr, NULL, 0, NULL); } break; case RTW_MBO_TEST_CMD_BTM_REQ_SEND: if (count >= 12) { u8 mac_str[18] = {0}; u8 dst_mac[ETH_ALEN] = {0}; u32 cand_list = 0, disassoc_timer = 0; num = sscanf(tmp, "%d %s %u %u", &id, mac_str, &cand_list, &disassoc_timer); if (num < 4) break; if (sscanf(mac_str, MAC_SFMT, MAC_SARG( dst_mac)) != 6) { break; } puser->append_mbo_ie = _TRUE; puser->hdr.dialog_token++; puser->hdr.validity_interval = 0xf; if (cand_list > 0) puser->hdr.req_mode |= \ PREFERRED_CANDIDATE_LIST_INCLUDED; puser->hdr.disassoc_timer = \ disassoc_timer; if ((puser->hdr.req_mode & \ DISASSOC_IMMINENT) == \ DISASSOC_IMMINENT) { rtw_mbo_reset_user_btm_req_preference(padapter); if (pmbo_attr->delay == 0) pmbo_attr->delay = 1; if (puser->hdr.disassoc_timer == 0) puser->hdr.disassoc_timer = 1000; } if ((puser->hdr.req_mode & \ BSS_TERMINATION_INCLUDED) == \ BSS_TERMINATION_INCLUDED) { puser->append_mbo_ie = _FALSE; } if (!puser->candidate_cnt) { struct wnm_btm_cant cant; _rtw_memset(&cant, 0, sizeof(cant)); _rtw_memcpy(cant.nb_rpt.bssid, adapter_mac_addr(padapter), ETH_ALEN); cant.nb_rpt.reg_class = 115; cant.nb_rpt.ch_num = 36; cant.preference = 0; rtw_mbo_construct_user_btm_req( padapter, NULL, NULL, 0, &cant); } rtw_wnm_issue_btm_req(padapter, dst_mac, &puser->hdr, NULL, 0, (u8 *)&puser->btm_cants, puser->candidate_cnt); if ((puser->hdr.term_duration.duration) && (puser->hdr.req_mode & \ BSS_TERMINATION_INCLUDED) == \ BSS_TERMINATION_INCLUDED) { rtw_mbo_disassoc(padapter, dst_mac, 3, _TRUE); } if ((puser->hdr.req_mode & \ DISASSOC_IMMINENT) == \ DISASSOC_IMMINENT) { rtw_mbo_disassoc(padapter, dst_mac, 3, _TRUE); } } /* end of if (count >= 12) */ break; case RTW_MBO_TEST_CMD_NB_BSS_ADD: if (count >= 12) { u8 bss_str[18]; struct wnm_btm_cant cant; u32 op, op_ch, perf; num = sscanf(tmp, "%d %s %u %u %u", &id, bss_str, &op, &op_ch, &perf); if (num < 5) break; _rtw_memset(&cant, 0, sizeof(cant)); if (sscanf(bss_str, MAC_SFMT, MAC_SARG( cant.nb_rpt.bssid)) != 6) { break; } cant.nb_rpt.reg_class = op; cant.nb_rpt.ch_num = op_ch; cant.preference = perf; rtw_mbo_construct_user_btm_req( padapter, NULL, NULL, 0, &cant); } /* end of if (count >= 12) */ break; default: break; } } return count; } int rtw_mbo_proc_attr_get( struct seq_file *m, void *v) { struct net_device *dev = m->private; _adapter *padapter = (_adapter *)rtw_netdev_priv(dev); struct mlme_priv *pmlmepriv = &(padapter->mlmepriv); struct mbo_attr_info *pmbo_attr = &(pmlmepriv->mbo_attr); RTW_PRINT_SEL(m, "AP Capability Indication : %d\n", pmbo_attr->ap_cap_ind); RTW_PRINT_SEL(m, "Cellular Data Capabilities : %d\n", pmbo_attr->cell_data_cap); RTW_PRINT_SEL(m, "Association Disallowed : %d\n", pmbo_attr->assoc_disallow); return 0; } static void rtw_mbo_non_pref_chan_subelem_parsing( _adapter *padapter, u8 *subelem, size_t subelem_len) { u8 *pnon_pref_chans; u32 non_pref_chan_offset, op_subelem_len; u32 oui_offset = 3; /* wpa_supplicant don't apped OUI Type */ u32 oui_type_offset = 0; RTW_MBO_DUMP("Non-preferred Channel subelem : ", subelem , subelem_len); /* Subelem : Vendor Specific | Length | WFA OUI | OUI Type | MBO Attributes */ non_pref_chan_offset = 2 + oui_offset + oui_type_offset; pnon_pref_chans = subelem + non_pref_chan_offset; op_subelem_len = subelem_len - non_pref_chan_offset; /* wpa_supplicant don't indicate non_pref_chan length, so we cannot get how many non_pref_chan in a wnm notification */ RTW_MBO_DUMP("Non-preferred Channel : ", pnon_pref_chans, op_subelem_len); } void rtw_mbo_wnm_notification_parsing( _adapter *padapter, const u8 *pdata, size_t data_len) { u8 *paction; u8 category, action, dialog, type; u32 len; if ((pdata == NULL) || (data_len == 0)) return; RTW_MBO_DUMP("WNM notification data : ", pdata, data_len); paction = (u8 *)pdata + sizeof(struct rtw_ieee80211_hdr_3addr); category = paction[0]; action = paction[1]; dialog = paction[2]; type = paction[3]; if ((action == RTW_WLAN_ACTION_WNM_NOTIF_REQ) && (type == WLAN_EID_VENDOR_SPECIFIC)) { rtw_mbo_non_pref_chan_subelem_parsing(padapter, &paction[4], (data_len - sizeof(struct rtw_ieee80211_hdr_3addr))); } } void rtw_mbo_build_wnm_notification( _adapter *padapter, u8 **pframe, struct pkt_attrib *pattrib) { struct rf_ctl_t *prfctl = adapter_to_rfctl(padapter); struct npref_ch_rtp *prpt = &(prfctl->ch_rtp); struct npref_ch* pch; u8 subelem_id = WLAN_EID_VENDOR_SPECIFIC; u8 non_pref_ch_oui[] = {0x50, 0x6F, 0x9A, 0x2}; u8 cell_data_cap_oui[] = {0x50, 0x6F, 0x9A, 0x3}; u8 cell_data_con = rtw_mbo_cell_data_conn; u8 len, cell_data_con_len = 0, *pcont = *pframe; int i; if (rtw_mbo_cell_data_conn > 0) { len = 0x5; *pframe = rtw_mbo_set_1byte_ie(*pframe, &subelem_id, &(pattrib->pktlen)); *pframe = rtw_mbo_set_1byte_ie(*pframe, &len, &(pattrib->pktlen)); *pframe = rtw_mbo_set_4byte_ie(*pframe, cell_data_cap_oui, &(pattrib->pktlen)); *pframe = rtw_mbo_set_1byte_ie(*pframe, &cell_data_con, &(pattrib->pktlen)); RTW_MBO_INFO("%s : Cellular Data Capabilities subelemen\n", __func__); RTW_MBO_DUMP(":", pcont, len + 2); pcont += len + 2 ; } if (prpt->nm_of_rpt == 0) { len = 0x4; *pframe = rtw_mbo_set_1byte_ie(*pframe, &subelem_id, &(pattrib->pktlen)); *pframe = rtw_mbo_set_1byte_ie(*pframe, &len, &(pattrib->pktlen)); *pframe = rtw_mbo_set_4byte_ie(*pframe, non_pref_ch_oui, &(pattrib->pktlen)); RTW_MBO_INFO("%s :Non-preferred Channel Report subelement" " without data\n", __func__); return; } for (i=0; i < prpt->nm_of_rpt; i++) { pch = &prpt->ch_rpt[i]; /* OUI(3B) + OUT-type(1B) + op-class(1B) + ch list(nB) + Preference(1B) + reason(1B) */ len = pch->nm_of_ch + 7; *pframe = rtw_mbo_set_1byte_ie(*pframe, &subelem_id, &(pattrib->pktlen)); *pframe = rtw_mbo_set_1byte_ie(*pframe, &len, &(pattrib->pktlen)); *pframe = rtw_mbo_set_4byte_ie(*pframe, non_pref_ch_oui, &(pattrib->pktlen)); *pframe = rtw_mbo_set_1byte_ie(*pframe, &pch->op_class, &(pattrib->pktlen)); *pframe = rtw_mbo_set_nbyte_ie(*pframe, pch->nm_of_ch, pch->chs, &(pattrib->pktlen)); *pframe = rtw_mbo_set_1byte_ie(*pframe, &pch->preference, &(pattrib->pktlen)); *pframe = rtw_mbo_set_1byte_ie(*pframe, &pch->reason, &(pattrib->pktlen)); RTW_MBO_INFO("%s :Non-preferred Channel Report" " subelement\n", __func__); RTW_MBO_DUMP(":", pcont, len); pcont = *pframe; } } void rtw_mbo_build_probe_req_ies( _adapter *padapter, u8 **pframe, struct pkt_attrib *pattrib) { u32 len =0; rtw_mbo_build_extended_cap(padapter, pframe, pattrib); len = rtw_mbo_attr_sz_get(padapter, RTW_MBO_ATTR_CELL_DATA_CAP_ID); if ((len == 0) || (len > 3)) { RTW_ERR("MBO : build Cellular Data Capabilities" " attribute fail(len=%u)\n", len); return; } rtw_mbo_build_mbo_ie_hdr(pframe, pattrib, len); rtw_mbo_build_cell_data_cap_attr(padapter, pframe, pattrib); } void rtw_mbo_build_assoc_req_ies( _adapter *padapter, u8 **pframe, struct pkt_attrib *pattrib) { u32 len = 0; rtw_mbo_build_supp_op_class_elem(padapter, pframe, pattrib); len += rtw_mbo_attr_sz_get(padapter, RTW_MBO_ATTR_CELL_DATA_CAP_ID); len += rtw_mbo_attr_sz_get(padapter, RTW_MBO_ATTR_NPREF_CH_RPT_ID); if ((len == 0)|| (len < 3)) { RTW_ERR("MBO : build assoc MBO IE fail(len=%u)\n", len); return; } rtw_mbo_build_mbo_ie_hdr(pframe, pattrib, len); rtw_mbo_build_cell_data_cap_attr(padapter, pframe, pattrib); rtw_mbo_build_npref_ch_rpt_attr(padapter, pframe, pattrib); } static void rtw_mbo_build_ap_capability( _adapter *padapter, u8 **pframe, struct pkt_attrib *pattrib) { struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv); struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info); WLAN_BSSID_EX *cur_network = &(pmlmeinfo->network); struct mlme_priv *pmlmepriv = &(padapter->mlmepriv); struct mbo_attr_info *pmbo_attr = &(pmlmepriv->mbo_attr); u8 *pcap = NULL; u32 cap_len = 0, flen = 0; if ((pcap = rtw_mbo_attrs_get( (cur_network->IEs + _FIXED_IE_LENGTH_), (cur_network->IELength - _FIXED_IE_LENGTH_), RTW_MBO_ATTR_AP_CAP_ID, &cap_len, 0)) != NULL) return; flen += rtw_mbo_attr_sz_get(padapter, RTW_MBO_ATTR_AP_CAP_ID); if (pmbo_attr->assoc_disallow > 0) flen += rtw_mbo_attr_sz_get(padapter, RTW_MBO_ATTR_ASSOC_DISABLED_ID); if (flen > 0) { rtw_mbo_build_mbo_ie_hdr(pframe, pattrib, flen); rtw_mbo_build_ap_cap_Indication_attr(padapter, pframe, pattrib, pmbo_attr->ap_cap_ind); if (pmbo_attr->assoc_disallow > 0) { rtw_mbo_build_ap_disallowed_attr(padapter, pframe, pattrib, pmbo_attr->assoc_disallow); } } /* end of if (flen > 0) */ } void rtw_mbo_attr_info_init(_adapter *padapter) { struct mlme_priv *pmlmepriv = &(padapter->mlmepriv); struct mbo_attr_info *pmbo_attr = &(pmlmepriv->mbo_attr); _rtw_memset(pmbo_attr, 0, sizeof(struct mbo_attr_info)); } void rtw_mbo_process_assoc_req( _adapter *padapter, u8 *pie, int ie_len) { struct mlme_priv *pmlmepriv = &(padapter->mlmepriv); struct mbo_attr_info *pmbo_attr = &(pmlmepriv->mbo_attr); u8 non_pref_ch = 0, ch_op_pref = 1; u8 *pattr = NULL, *ptr = NULL; u32 attr_len = 0, ch_nums = 0; int i, j; if (!pie || !ie_len) return; if ((pattr = rtw_mbo_attrs_get(pie, ie_len, RTW_MBO_ATTR_NPREF_CH_RPT_ID, &attr_len, 1)) == NULL) return; if (attr_len < 3) return; ch_nums = (attr_len - 3); /* shfit to non-preferred ch rpt field */ ptr = pattr + 3; RTW_MBO_DUMP("non-preferred ch rpt :", ptr, ch_nums); ch_op_pref = *(ptr + ch_nums); RTW_MBO_INFO("%s : ch_op_pref=0x%02x\n", __func__, ch_op_pref); if (ch_op_pref >= 2) { RTW_MBO_INFO("%s : unknow ch operating preference(0x%02x)\n", __func__, ch_op_pref); return; } for (i = 0; i < ch_nums; i++) { if (i >= RTW_MBO_MAX_CH_LIST_NUM) break; non_pref_ch = *(ptr + i); RTW_MBO_INFO("%s : non-pref ch %u found in assoc-req\n", __func__, non_pref_ch); if (rtw_mbo_wifi_spec_test(padapter)) { struct mbo_user_btm_req_pkt *puser_raw = \ &(pmbo_attr->user_raw); struct wnm_btm_cant *pcant = NULL; for (j = 0; j < RTW_MAX_NB_RPT_NUM; j++) { pcant = &puser_raw->btm_cants[j]; if ((pcant->nb_rpt.ch_num == non_pref_ch) && \ (ch_op_pref == 0)) { RTW_MBO_INFO("%s : reset " "preference(%u) for non-preference ch\n", __func__, pcant->preference); pcant->preference = 0; } } } } /* end of for-loop ch_nums */ } void rtw_mbo_build_beacon_ies( _adapter *padapter, u8 **pframe, struct pkt_attrib *pattrib) { rtw_mbo_build_ap_capability(padapter, pframe, pattrib); } void rtw_mbo_build_probe_rsp_ies( _adapter *padapter, u8 **pframe, struct pkt_attrib *pattrib) { rtw_mbo_build_ap_capability(padapter, pframe, pattrib); } void rtw_mbo_build_assoc_rsp_ies( _adapter *padapter, u8 **pframe, struct pkt_attrib *pattrib) { struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv); struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info); WLAN_BSSID_EX *cur_network = &(pmlmeinfo->network); struct mlme_priv *pmlmepriv = &(padapter->mlmepriv); struct mbo_attr_info *pmbo_attr = &(pmlmepriv->mbo_attr); u8 *pcap = NULL; u32 len = 0, cap_len = 0 ; #if 1 if ((pcap = rtw_mbo_attrs_get( (cur_network->IEs + _FIXED_IE_LENGTH_), (cur_network->IELength - _FIXED_IE_LENGTH_), RTW_MBO_ATTR_AP_CAP_ID, &cap_len, 0)) != NULL) return; #endif len += rtw_mbo_attr_sz_get(padapter, RTW_MBO_ATTR_AP_CAP_ID); if (pmbo_attr->assoc_disallow > 0) len += rtw_mbo_attr_sz_get(padapter, RTW_MBO_ATTR_ASSOC_DISABLED_ID); if (len > 0) { rtw_mbo_build_mbo_ie_hdr(pframe, pattrib, len); rtw_mbo_build_ap_cap_Indication_attr( padapter, pframe, pattrib, pmbo_attr->ap_cap_ind); if (pmbo_attr->assoc_disallow > 0) { rtw_mbo_build_ap_disallowed_attr(padapter, pframe, pattrib, pmbo_attr->assoc_disallow); } } } void rtw_mbo_build_wnm_btmreq_reason_ies( _adapter *padapter, u8 **pframe, struct pkt_attrib *pattrib) { struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv); struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info); WLAN_BSSID_EX *cur_network = &(pmlmeinfo->network); struct mlme_priv *pmlmepriv = &(padapter->mlmepriv); struct mbo_attr_info *pmbo_attr = &(pmlmepriv->mbo_attr); struct mbo_user_btm_req_pkt *puser = &(pmbo_attr->user_raw); u8 *pcap = NULL; u32 len = 0, cap_len = 0 ; if (!puser->append_mbo_ie) return; len += rtw_mbo_attr_sz_get(padapter, RTW_MBO_ATTR_AP_CAP_ID); len += rtw_mbo_attr_sz_get(padapter, RTW_MBO_ATTR_TRANS_RES_ID); len += rtw_mbo_attr_sz_get(padapter, RTW_MBO_ATTR_ASSOC_RETRY_DELAY_ID); rtw_mbo_build_mbo_ie_hdr(pframe, pattrib, len); rtw_mbo_build_ap_cap_Indication_attr( padapter, pframe, pattrib, pmbo_attr->ap_cap_ind); rtw_mbo_build_ap_trans_reason_attr(padapter, pframe, pattrib, pmbo_attr->reason); rtw_mbo_build_ap_assoc_retry_delay_attr(padapter, pframe, pattrib, pmbo_attr->delay); } #endif /* CONFIG_RTW_MBO */