| /* |
| * Marvell Wireless LAN device driver: 802.11ac |
| * |
| * Copyright (C) 2013, Marvell International Ltd. |
| * |
| * This software file (the "File") is distributed by Marvell International |
| * Ltd. under the terms of the GNU General Public License Version 2, June 1991 |
| * (the "License"). You may use, redistribute and/or modify this File in |
| * accordance with the terms and conditions of the License, a copy of which |
| * is available by writing to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the |
| * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. |
| * |
| * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE |
| * ARE EXPRESSLY DISCLAIMED. The License provides additional details about |
| * this warranty disclaimer. |
| */ |
| |
| #include "decl.h" |
| #include "ioctl.h" |
| #include "fw.h" |
| #include "main.h" |
| #include "11ac.h" |
| |
| /* This function converts the 2-bit MCS map to the highest long GI |
| * VHT data rate. |
| */ |
| static u16 |
| mwifiex_convert_mcsmap_to_maxrate(struct mwifiex_private *priv, |
| u8 bands, u16 mcs_map) |
| { |
| u8 i, nss, max_mcs; |
| u16 max_rate = 0; |
| u32 usr_vht_cap_info = 0; |
| struct mwifiex_adapter *adapter = priv->adapter; |
| /* tables of the MCS map to the highest data rate (in Mbps) |
| * supported for long GI |
| */ |
| u16 max_rate_lgi_80MHZ[8][3] = { |
| {0x124, 0x15F, 0x186}, /* NSS = 1 */ |
| {0x249, 0x2BE, 0x30C}, /* NSS = 2 */ |
| {0x36D, 0x41D, 0x492}, /* NSS = 3 */ |
| {0x492, 0x57C, 0x618}, /* NSS = 4 */ |
| {0x5B6, 0x6DB, 0x79E}, /* NSS = 5 */ |
| {0x6DB, 0x83A, 0x0}, /* NSS = 6 */ |
| {0x7FF, 0x999, 0xAAA}, /* NSS = 7 */ |
| {0x924, 0xAF8, 0xC30} /* NSS = 8 */ |
| }; |
| u16 max_rate_lgi_160MHZ[8][3] = { |
| {0x249, 0x2BE, 0x30C}, /* NSS = 1 */ |
| {0x492, 0x57C, 0x618}, /* NSS = 2 */ |
| {0x6DB, 0x83A, 0x0}, /* NSS = 3 */ |
| {0x924, 0xAF8, 0xC30}, /* NSS = 4 */ |
| {0xB6D, 0xDB6, 0xF3C}, /* NSS = 5 */ |
| {0xDB6, 0x1074, 0x1248}, /* NSS = 6 */ |
| {0xFFF, 0x1332, 0x1554}, /* NSS = 7 */ |
| {0x1248, 0x15F0, 0x1860} /* NSS = 8 */ |
| }; |
| |
| if (bands & BAND_AAC) |
| usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_a; |
| else |
| usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_bg; |
| |
| /* find the max NSS supported */ |
| nss = 0; |
| for (i = 0; i < 8; i++) { |
| max_mcs = (mcs_map >> (2 * i)) & 0x3; |
| if (max_mcs < 3) |
| nss = i; |
| } |
| max_mcs = (mcs_map >> (2 * nss)) & 0x3; |
| |
| /* if max_mcs is 3, nss must be 0 (SS = 1). Thus, max mcs is MCS 9 */ |
| if (max_mcs >= 3) |
| max_mcs = 2; |
| |
| if (GET_VHTCAP_CHWDSET(usr_vht_cap_info)) { |
| /* support 160 MHz */ |
| max_rate = max_rate_lgi_160MHZ[nss][max_mcs]; |
| if (!max_rate) |
| /* MCS9 is not supported in NSS6 */ |
| max_rate = max_rate_lgi_160MHZ[nss][max_mcs - 1]; |
| } else { |
| max_rate = max_rate_lgi_80MHZ[nss][max_mcs]; |
| if (!max_rate) |
| /* MCS9 is not supported in NSS3 */ |
| max_rate = max_rate_lgi_80MHZ[nss][max_mcs - 1]; |
| } |
| |
| return max_rate; |
| } |
| |
| static void |
| mwifiex_fill_vht_cap_info(struct mwifiex_private *priv, |
| struct mwifiex_ie_types_vhtcap *vht_cap, u8 bands) |
| { |
| struct mwifiex_adapter *adapter = priv->adapter; |
| |
| if (bands & BAND_A) |
| vht_cap->vht_cap.vht_cap_info = |
| cpu_to_le32(adapter->usr_dot_11ac_dev_cap_a); |
| else |
| vht_cap->vht_cap.vht_cap_info = |
| cpu_to_le32(adapter->usr_dot_11ac_dev_cap_bg); |
| } |
| |
| static void |
| mwifiex_fill_vht_cap_tlv(struct mwifiex_private *priv, |
| struct mwifiex_ie_types_vhtcap *vht_cap, u8 bands) |
| { |
| struct mwifiex_adapter *adapter = priv->adapter; |
| u16 mcs_map_user, mcs_map_resp, mcs_map_result; |
| u16 mcs_user, mcs_resp, nss, tmp; |
| |
| /* Fill VHT cap info */ |
| mwifiex_fill_vht_cap_info(priv, vht_cap, bands); |
| |
| /* rx MCS Set: find the minimum of the user rx mcs and ap rx mcs */ |
| mcs_map_user = GET_DEVRXMCSMAP(adapter->usr_dot_11ac_mcs_support); |
| mcs_map_resp = le16_to_cpu(vht_cap->vht_cap.supp_mcs.rx_mcs_map); |
| mcs_map_result = 0; |
| |
| for (nss = 1; nss <= 8; nss++) { |
| mcs_user = GET_VHTNSSMCS(mcs_map_user, nss); |
| mcs_resp = GET_VHTNSSMCS(mcs_map_resp, nss); |
| |
| if ((mcs_user == NO_NSS_SUPPORT) || |
| (mcs_resp == NO_NSS_SUPPORT)) |
| SET_VHTNSSMCS(mcs_map_result, nss, NO_NSS_SUPPORT); |
| else |
| SET_VHTNSSMCS(mcs_map_result, nss, |
| min(mcs_user, mcs_resp)); |
| } |
| |
| vht_cap->vht_cap.supp_mcs.rx_mcs_map = cpu_to_le16(mcs_map_result); |
| |
| tmp = mwifiex_convert_mcsmap_to_maxrate(priv, bands, mcs_map_result); |
| vht_cap->vht_cap.supp_mcs.rx_highest = cpu_to_le16(tmp); |
| |
| /* tx MCS Set: find the minimum of the user tx mcs and ap tx mcs */ |
| mcs_map_user = GET_DEVTXMCSMAP(adapter->usr_dot_11ac_mcs_support); |
| mcs_map_resp = le16_to_cpu(vht_cap->vht_cap.supp_mcs.tx_mcs_map); |
| mcs_map_result = 0; |
| |
| for (nss = 1; nss <= 8; nss++) { |
| mcs_user = GET_VHTNSSMCS(mcs_map_user, nss); |
| mcs_resp = GET_VHTNSSMCS(mcs_map_resp, nss); |
| if ((mcs_user == NO_NSS_SUPPORT) || |
| (mcs_resp == NO_NSS_SUPPORT)) |
| SET_VHTNSSMCS(mcs_map_result, nss, NO_NSS_SUPPORT); |
| else |
| SET_VHTNSSMCS(mcs_map_result, nss, |
| min(mcs_user, mcs_resp)); |
| } |
| |
| vht_cap->vht_cap.supp_mcs.tx_mcs_map = cpu_to_le16(mcs_map_result); |
| |
| tmp = mwifiex_convert_mcsmap_to_maxrate(priv, bands, mcs_map_result); |
| vht_cap->vht_cap.supp_mcs.tx_highest = cpu_to_le16(tmp); |
| |
| return; |
| } |
| |
| int mwifiex_cmd_append_11ac_tlv(struct mwifiex_private *priv, |
| struct mwifiex_bssdescriptor *bss_desc, |
| u8 **buffer) |
| { |
| struct mwifiex_ie_types_vhtcap *vht_cap; |
| struct mwifiex_ie_types_oper_mode_ntf *oper_ntf; |
| struct ieee_types_oper_mode_ntf *ieee_oper_ntf; |
| struct mwifiex_ie_types_vht_oper *vht_op; |
| struct mwifiex_adapter *adapter = priv->adapter; |
| u8 supp_chwd_set; |
| u32 usr_vht_cap_info; |
| int ret_len = 0; |
| |
| if (bss_desc->bss_band & BAND_A) |
| usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_a; |
| else |
| usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_bg; |
| |
| /* VHT Capabilities IE */ |
| if (bss_desc->bcn_vht_cap) { |
| vht_cap = (struct mwifiex_ie_types_vhtcap *)*buffer; |
| memset(vht_cap, 0, sizeof(*vht_cap)); |
| vht_cap->header.type = cpu_to_le16(WLAN_EID_VHT_CAPABILITY); |
| vht_cap->header.len = |
| cpu_to_le16(sizeof(struct ieee80211_vht_cap)); |
| memcpy((u8 *)vht_cap + sizeof(struct mwifiex_ie_types_header), |
| (u8 *)bss_desc->bcn_vht_cap + |
| sizeof(struct ieee_types_header), |
| le16_to_cpu(vht_cap->header.len)); |
| |
| mwifiex_fill_vht_cap_tlv(priv, vht_cap, bss_desc->bss_band); |
| *buffer += sizeof(*vht_cap); |
| ret_len += sizeof(*vht_cap); |
| } |
| |
| /* VHT Operation IE */ |
| if (bss_desc->bcn_vht_oper) { |
| if (priv->bss_mode == HostCmd_BSS_MODE_IBSS) { |
| vht_op = (struct mwifiex_ie_types_vht_oper *)*buffer; |
| memset(vht_op, 0, sizeof(*vht_op)); |
| vht_op->header.type = |
| cpu_to_le16(WLAN_EID_VHT_OPERATION); |
| vht_op->header.len = cpu_to_le16(sizeof(*vht_op) - |
| sizeof(struct mwifiex_ie_types_header)); |
| memcpy((u8 *)vht_op + |
| sizeof(struct mwifiex_ie_types_header), |
| (u8 *)bss_desc->bcn_vht_oper + |
| sizeof(struct ieee_types_header), |
| le16_to_cpu(vht_op->header.len)); |
| |
| /* negotiate the channel width and central freq |
| * and keep the central freq as the peer suggests |
| */ |
| supp_chwd_set = GET_VHTCAP_CHWDSET(usr_vht_cap_info); |
| |
| switch (supp_chwd_set) { |
| case 0: |
| vht_op->chan_width = |
| min_t(u8, IEEE80211_VHT_CHANWIDTH_80MHZ, |
| bss_desc->bcn_vht_oper->chan_width); |
| break; |
| case 1: |
| vht_op->chan_width = |
| min_t(u8, IEEE80211_VHT_CHANWIDTH_160MHZ, |
| bss_desc->bcn_vht_oper->chan_width); |
| break; |
| case 2: |
| vht_op->chan_width = |
| min_t(u8, IEEE80211_VHT_CHANWIDTH_80P80MHZ, |
| bss_desc->bcn_vht_oper->chan_width); |
| break; |
| default: |
| vht_op->chan_width = |
| IEEE80211_VHT_CHANWIDTH_USE_HT; |
| break; |
| } |
| |
| *buffer += sizeof(*vht_op); |
| ret_len += sizeof(*vht_op); |
| } |
| } |
| |
| /* Operating Mode Notification IE */ |
| if (bss_desc->oper_mode) { |
| ieee_oper_ntf = bss_desc->oper_mode; |
| oper_ntf = (void *)*buffer; |
| memset(oper_ntf, 0, sizeof(*oper_ntf)); |
| oper_ntf->header.type = cpu_to_le16(WLAN_EID_OPMODE_NOTIF); |
| oper_ntf->header.len = cpu_to_le16(sizeof(u8)); |
| oper_ntf->oper_mode = ieee_oper_ntf->oper_mode; |
| *buffer += sizeof(*oper_ntf); |
| ret_len += sizeof(*oper_ntf); |
| } |
| |
| return ret_len; |
| } |