mwifiex: memory corruption in mwifiex_tdls_add_vht_capab()
[firefly-linux-kernel-4.4.55.git] / drivers / net / wireless / mwifiex / tdls.c
index 7fead7bf768f168360c43f89c5ccd43739de61b5..1ba2a16ee47143845f97837a66851d44589c2292 100644 (file)
 #include "wmm.h"
 #include "11n.h"
 #include "11n_rxreorder.h"
+#include "11ac.h"
 
 #define TDLS_REQ_FIX_LEN      6
 #define TDLS_RESP_FIX_LEN     8
 #define TDLS_CONFIRM_FIX_LEN  6
 
+static void
+mwifiex_restore_tdls_packets(struct mwifiex_private *priv, u8 *mac, u8 status)
+{
+       struct mwifiex_ra_list_tbl *ra_list;
+       struct list_head *tid_list;
+       struct sk_buff *skb, *tmp;
+       struct mwifiex_txinfo *tx_info;
+       unsigned long flags;
+       u32 tid;
+       u8 tid_down;
+
+       dev_dbg(priv->adapter->dev, "%s: %pM\n", __func__, mac);
+       spin_lock_irqsave(&priv->wmm.ra_list_spinlock, flags);
+
+       skb_queue_walk_safe(&priv->tdls_txq, skb, tmp) {
+               if (!ether_addr_equal(mac, skb->data))
+                       continue;
+
+               __skb_unlink(skb, &priv->tdls_txq);
+               tx_info = MWIFIEX_SKB_TXCB(skb);
+               tid = skb->priority;
+               tid_down = mwifiex_wmm_downgrade_tid(priv, tid);
+
+               if (status == TDLS_SETUP_COMPLETE) {
+                       ra_list = mwifiex_wmm_get_queue_raptr(priv, tid, mac);
+                       ra_list->tdls_link = true;
+                       tx_info->flags |= MWIFIEX_BUF_FLAG_TDLS_PKT;
+               } else {
+                       tid_list = &priv->wmm.tid_tbl_ptr[tid_down].ra_list;
+                       if (!list_empty(tid_list))
+                               ra_list = list_first_entry(tid_list,
+                                             struct mwifiex_ra_list_tbl, list);
+                       else
+                               ra_list = NULL;
+                       tx_info->flags &= ~MWIFIEX_BUF_FLAG_TDLS_PKT;
+               }
+
+               if (!ra_list) {
+                       mwifiex_write_data_complete(priv->adapter, skb, 0, -1);
+                       continue;
+               }
+
+               skb_queue_tail(&ra_list->skb_head, skb);
+
+               ra_list->ba_pkt_count++;
+               ra_list->total_pkt_count++;
+
+               if (atomic_read(&priv->wmm.highest_queued_prio) <
+                                                      tos_to_tid_inv[tid_down])
+                       atomic_set(&priv->wmm.highest_queued_prio,
+                                  tos_to_tid_inv[tid_down]);
+
+               atomic_inc(&priv->wmm.tx_pkts_queued);
+       }
+
+       spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, flags);
+       return;
+}
+
+static void mwifiex_hold_tdls_packets(struct mwifiex_private *priv, u8 *mac)
+{
+       struct mwifiex_ra_list_tbl *ra_list;
+       struct list_head *ra_list_head;
+       struct sk_buff *skb, *tmp;
+       unsigned long flags;
+       int i;
+
+       dev_dbg(priv->adapter->dev, "%s: %pM\n", __func__, mac);
+       spin_lock_irqsave(&priv->wmm.ra_list_spinlock, flags);
+
+       for (i = 0; i < MAX_NUM_TID; i++) {
+               if (!list_empty(&priv->wmm.tid_tbl_ptr[i].ra_list)) {
+                       ra_list_head = &priv->wmm.tid_tbl_ptr[i].ra_list;
+                       list_for_each_entry(ra_list, ra_list_head, list) {
+                               skb_queue_walk_safe(&ra_list->skb_head, skb,
+                                                   tmp) {
+                                       if (!ether_addr_equal(mac, skb->data))
+                                               continue;
+                                       __skb_unlink(skb, &ra_list->skb_head);
+                                       atomic_dec(&priv->wmm.tx_pkts_queued);
+                                       ra_list->total_pkt_count--;
+                                       skb_queue_tail(&priv->tdls_txq, skb);
+                               }
+                       }
+               }
+       }
+
+       spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, flags);
+       return;
+}
+
 /* This function appends rate TLV to scan config command. */
 static int
 mwifiex_tdls_append_rates_ie(struct mwifiex_private *priv,
@@ -60,7 +152,156 @@ mwifiex_tdls_append_rates_ie(struct mwifiex_private *priv,
        return 0;
 }
 
-static void mwifiex_tdls_add_ext_capab(struct sk_buff *skb)
+static void mwifiex_tdls_add_aid(struct mwifiex_private *priv,
+                               struct sk_buff *skb)
+{
+       struct ieee_types_assoc_rsp *assoc_rsp;
+       u8 *pos;
+
+       assoc_rsp = (struct ieee_types_assoc_rsp *)&priv->assoc_rsp_buf;
+       pos = (void *)skb_put(skb, 4);
+       *pos++ = WLAN_EID_AID;
+       *pos++ = 2;
+       *pos++ = le16_to_cpu(assoc_rsp->a_id);
+
+       return;
+}
+
+static int mwifiex_tdls_add_vht_capab(struct mwifiex_private *priv,
+                                     struct sk_buff *skb)
+{
+       struct ieee80211_vht_cap vht_cap;
+       u8 *pos;
+
+       pos = (void *)skb_put(skb, sizeof(struct ieee80211_vht_cap) + 2);
+       *pos++ = WLAN_EID_VHT_CAPABILITY;
+       *pos++ = sizeof(struct ieee80211_vht_cap);
+
+       memset(&vht_cap, 0, sizeof(struct ieee80211_vht_cap));
+
+       mwifiex_fill_vht_cap_tlv(priv, &vht_cap, priv->curr_bss_params.band);
+       memcpy(pos, &vht_cap, sizeof(vht_cap));
+
+       return 0;
+}
+
+static int mwifiex_tdls_add_vht_oper(struct mwifiex_private *priv,
+                                    u8 *mac, struct sk_buff *skb)
+{
+       struct mwifiex_bssdescriptor *bss_desc;
+       struct ieee80211_vht_operation *vht_oper;
+       struct ieee80211_vht_cap *vht_cap, *ap_vht_cap = NULL;
+       struct mwifiex_sta_node *sta_ptr;
+       struct mwifiex_adapter *adapter = priv->adapter;
+       u8 supp_chwd_set, peer_supp_chwd_set;
+       u8 *pos, ap_supp_chwd_set, chan_bw;
+       u16 mcs_map_user, mcs_map_resp, mcs_map_result;
+       u16 mcs_user, mcs_resp, nss;
+       u32 usr_vht_cap_info;
+
+       bss_desc = &priv->curr_bss_params.bss_descriptor;
+
+       sta_ptr = mwifiex_get_sta_entry(priv, mac);
+       if (unlikely(!sta_ptr)) {
+               dev_warn(adapter->dev, "TDLS peer station not found in list\n");
+               return -1;
+       }
+
+       if (!mwifiex_is_bss_in_11ac_mode(priv)) {
+               if (sta_ptr->tdls_cap.extcap.ext_capab[7] &
+                  WLAN_EXT_CAPA8_TDLS_WIDE_BW_ENABLED) {
+                       dev_dbg(adapter->dev,
+                               "TDLS peer doesn't support wider bandwitdh\n");
+                       return 0;
+               }
+       } else {
+               ap_vht_cap = bss_desc->bcn_vht_cap;
+       }
+
+       pos = (void *)skb_put(skb, sizeof(struct ieee80211_vht_operation) + 2);
+       *pos++ = WLAN_EID_VHT_OPERATION;
+       *pos++ = sizeof(struct ieee80211_vht_operation);
+       vht_oper = (struct ieee80211_vht_operation *)pos;
+
+       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;
+
+       /* find the minmum bandwith between AP/TDLS peers */
+       vht_cap = &sta_ptr->tdls_cap.vhtcap;
+       supp_chwd_set = GET_VHTCAP_CHWDSET(usr_vht_cap_info);
+       peer_supp_chwd_set =
+                        GET_VHTCAP_CHWDSET(le32_to_cpu(vht_cap->vht_cap_info));
+       supp_chwd_set = min_t(u8, supp_chwd_set, peer_supp_chwd_set);
+
+       /* We need check AP's bandwidth when TDLS_WIDER_BANDWIDTH is off */
+
+       if (ap_vht_cap && sta_ptr->tdls_cap.extcap.ext_capab[7] &
+           WLAN_EXT_CAPA8_TDLS_WIDE_BW_ENABLED) {
+               ap_supp_chwd_set =
+                     GET_VHTCAP_CHWDSET(le32_to_cpu(ap_vht_cap->vht_cap_info));
+               supp_chwd_set = min_t(u8, supp_chwd_set, ap_supp_chwd_set);
+       }
+
+       switch (supp_chwd_set) {
+       case IEEE80211_VHT_CHANWIDTH_80MHZ:
+               vht_oper->chan_width = IEEE80211_VHT_CHANWIDTH_80MHZ;
+               break;
+       case IEEE80211_VHT_CHANWIDTH_160MHZ:
+               vht_oper->chan_width = IEEE80211_VHT_CHANWIDTH_160MHZ;
+               break;
+       case IEEE80211_VHT_CHANWIDTH_80P80MHZ:
+               vht_oper->chan_width = IEEE80211_VHT_CHANWIDTH_80P80MHZ;
+               break;
+       default:
+               vht_oper->chan_width = IEEE80211_VHT_CHANWIDTH_USE_HT;
+               break;
+       }
+
+       mcs_map_user = GET_DEVRXMCSMAP(adapter->usr_dot_11ac_mcs_support);
+       mcs_map_resp = le16_to_cpu(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 == IEEE80211_VHT_MCS_NOT_SUPPORTED) ||
+                   (mcs_resp == IEEE80211_VHT_MCS_NOT_SUPPORTED))
+                       SET_VHTNSSMCS(mcs_map_result, nss,
+                                     IEEE80211_VHT_MCS_NOT_SUPPORTED);
+               else
+                       SET_VHTNSSMCS(mcs_map_result, nss,
+                                     min_t(u16, mcs_user, mcs_resp));
+       }
+
+       vht_oper->basic_mcs_set = cpu_to_le16(mcs_map_result);
+
+       switch (vht_oper->chan_width) {
+       case IEEE80211_VHT_CHANWIDTH_80MHZ:
+               chan_bw = IEEE80211_VHT_CHANWIDTH_80MHZ;
+               break;
+       case IEEE80211_VHT_CHANWIDTH_160MHZ:
+               chan_bw = IEEE80211_VHT_CHANWIDTH_160MHZ;
+               break;
+       case IEEE80211_VHT_CHANWIDTH_80P80MHZ:
+               chan_bw = IEEE80211_VHT_CHANWIDTH_80MHZ;
+               break;
+       default:
+               chan_bw = IEEE80211_VHT_CHANWIDTH_USE_HT;
+               break;
+       }
+       vht_oper->center_freq_seg1_idx =
+                       mwifiex_get_center_freq_index(priv, BAND_AAC,
+                                                     bss_desc->channel,
+                                                     chan_bw);
+
+       return 0;
+}
+
+static void mwifiex_tdls_add_ext_capab(struct mwifiex_private *priv,
+                                      struct sk_buff *skb)
 {
        struct ieee_types_extcap *extcap;
 
@@ -69,6 +310,9 @@ static void mwifiex_tdls_add_ext_capab(struct sk_buff *skb)
        extcap->ieee_hdr.len = 8;
        memset(extcap->ext_capab, 0, 8);
        extcap->ext_capab[4] |= WLAN_EXT_CAPA5_TDLS_ENABLED;
+
+       if (priv->adapter->is_hw_11ac_capable)
+               extcap->ext_capab[7] |= WLAN_EXT_CAPA8_TDLS_WIDE_BW_ENABLED;
 }
 
 static void mwifiex_tdls_add_qos_capab(struct sk_buff *skb)
@@ -122,7 +366,16 @@ static int mwifiex_prep_tdls_encap_data(struct mwifiex_private *priv,
                        return ret;
                }
 
-               mwifiex_tdls_add_ext_capab(skb);
+               if (priv->adapter->is_hw_11ac_capable) {
+                       ret = mwifiex_tdls_add_vht_capab(priv, skb);
+                       if (ret) {
+                               dev_kfree_skb_any(skb);
+                               return ret;
+                       }
+                       mwifiex_tdls_add_aid(priv, skb);
+               }
+
+               mwifiex_tdls_add_ext_capab(priv, skb);
                mwifiex_tdls_add_qos_capab(skb);
                break;
 
@@ -150,7 +403,16 @@ static int mwifiex_prep_tdls_encap_data(struct mwifiex_private *priv,
                        return ret;
                }
 
-               mwifiex_tdls_add_ext_capab(skb);
+               if (priv->adapter->is_hw_11ac_capable) {
+                       ret = mwifiex_tdls_add_vht_capab(priv, skb);
+                       if (ret) {
+                               dev_kfree_skb_any(skb);
+                               return ret;
+                       }
+                       mwifiex_tdls_add_aid(priv, skb);
+               }
+
+               mwifiex_tdls_add_ext_capab(priv, skb);
                mwifiex_tdls_add_qos_capab(skb);
                break;
 
@@ -160,6 +422,13 @@ static int mwifiex_prep_tdls_encap_data(struct mwifiex_private *priv,
                skb_put(skb, sizeof(tf->u.setup_cfm));
                tf->u.setup_cfm.status_code = cpu_to_le16(status_code);
                tf->u.setup_cfm.dialog_token = dialog_token;
+               if (priv->adapter->is_hw_11ac_capable) {
+                       ret = mwifiex_tdls_add_vht_oper(priv, peer, skb);
+                       if (ret) {
+                               dev_kfree_skb_any(skb);
+                               return ret;
+                       }
+               }
                break;
 
        case WLAN_TDLS_TEARDOWN:
@@ -222,6 +491,11 @@ int mwifiex_send_tdls_data_frame(struct mwifiex_private *priv,
                  sizeof(struct ieee80211_tdls_lnkie) +
                  extra_ies_len;
 
+       if (priv->adapter->is_hw_11ac_capable)
+               skb_len += sizeof(struct ieee_types_vht_cap) +
+                          sizeof(struct ieee_types_vht_oper) +
+                          sizeof(struct ieee_types_aid);
+
        skb = dev_alloc_skb(skb_len);
        if (!skb) {
                dev_err(priv->adapter->dev,
@@ -344,7 +618,16 @@ mwifiex_construct_tdls_action_frame(struct mwifiex_private *priv, u8 *peer,
                        return ret;
                }
 
-               mwifiex_tdls_add_ext_capab(skb);
+               if (priv->adapter->is_hw_11ac_capable) {
+                       ret = mwifiex_tdls_add_vht_capab(priv, skb);
+                       if (ret) {
+                               dev_kfree_skb_any(skb);
+                               return ret;
+                       }
+                       mwifiex_tdls_add_aid(priv, skb);
+               }
+
+               mwifiex_tdls_add_ext_capab(priv, skb);
                mwifiex_tdls_add_qos_capab(skb);
                break;
        default:
@@ -381,6 +664,11 @@ int mwifiex_send_tdls_action_frame(struct mwifiex_private *priv,
                  3 + /* Qos Info */
                  ETH_ALEN; /* Address4 */
 
+       if (priv->adapter->is_hw_11ac_capable)
+               skb_len += sizeof(struct ieee_types_vht_cap) +
+                          sizeof(struct ieee_types_vht_oper) +
+                          sizeof(struct ieee_types_aid);
+
        skb = dev_alloc_skb(skb_len);
        if (!skb) {
                dev_err(priv->adapter->dev,
@@ -535,6 +823,22 @@ void mwifiex_process_tdls_action_frame(struct mwifiex_private *priv,
                case WLAN_EID_QOS_CAPA:
                        sta_ptr->tdls_cap.qos_info = pos[2];
                        break;
+               case WLAN_EID_VHT_OPERATION:
+                       if (priv->adapter->is_hw_11ac_capable)
+                               memcpy(&sta_ptr->tdls_cap.vhtoper, pos,
+                                      sizeof(struct ieee80211_vht_operation));
+                       break;
+               case WLAN_EID_VHT_CAPABILITY:
+                       if (priv->adapter->is_hw_11ac_capable) {
+                               memcpy((u8 *)&sta_ptr->tdls_cap.vhtcap, pos,
+                                      sizeof(struct ieee80211_vht_cap));
+                               sta_ptr->is_11ac_enabled = 1;
+                       }
+                       break;
+               case WLAN_EID_AID:
+                       if (priv->adapter->is_hw_11ac_capable)
+                               sta_ptr->tdls_cap.aid =
+                                             le16_to_cpu(*(__le16 *)(pos + 2));
                default:
                        break;
                }
@@ -543,6 +847,54 @@ void mwifiex_process_tdls_action_frame(struct mwifiex_private *priv,
        return;
 }
 
+static int
+mwifiex_tdls_process_config_link(struct mwifiex_private *priv, u8 *peer)
+{
+       struct mwifiex_sta_node *sta_ptr;
+       struct mwifiex_ds_tdls_oper tdls_oper;
+
+       memset(&tdls_oper, 0, sizeof(struct mwifiex_ds_tdls_oper));
+       sta_ptr = mwifiex_get_sta_entry(priv, peer);
+
+       if (!sta_ptr || sta_ptr->tdls_status == TDLS_SETUP_FAILURE) {
+               dev_err(priv->adapter->dev,
+                       "link absent for peer %pM; cannot config\n", peer);
+               return -EINVAL;
+       }
+
+       memcpy(&tdls_oper.peer_mac, peer, ETH_ALEN);
+       tdls_oper.tdls_action = MWIFIEX_TDLS_CONFIG_LINK;
+       return mwifiex_send_cmd_sync(priv, HostCmd_CMD_TDLS_OPER,
+                                    HostCmd_ACT_GEN_SET, 0, &tdls_oper);
+}
+
+static int
+mwifiex_tdls_process_create_link(struct mwifiex_private *priv, u8 *peer)
+{
+       struct mwifiex_sta_node *sta_ptr;
+       struct mwifiex_ds_tdls_oper tdls_oper;
+
+       memset(&tdls_oper, 0, sizeof(struct mwifiex_ds_tdls_oper));
+       sta_ptr = mwifiex_get_sta_entry(priv, peer);
+
+       if (sta_ptr && sta_ptr->tdls_status == TDLS_SETUP_INPROGRESS) {
+               dev_dbg(priv->adapter->dev,
+                       "Setup already in progress for peer %pM\n", peer);
+               return 0;
+       }
+
+       sta_ptr = mwifiex_add_sta_entry(priv, peer);
+       if (!sta_ptr)
+               return -ENOMEM;
+
+       sta_ptr->tdls_status = TDLS_SETUP_INPROGRESS;
+       mwifiex_hold_tdls_packets(priv, peer);
+       memcpy(&tdls_oper.peer_mac, peer, ETH_ALEN);
+       tdls_oper.tdls_action = MWIFIEX_TDLS_CREATE_LINK;
+       return mwifiex_send_cmd_sync(priv, HostCmd_CMD_TDLS_OPER,
+                                    HostCmd_ACT_GEN_SET, 0, &tdls_oper);
+}
+
 static int
 mwifiex_tdls_process_disable_link(struct mwifiex_private *priv, u8 *peer)
 {
@@ -565,6 +917,7 @@ mwifiex_tdls_process_disable_link(struct mwifiex_private *priv, u8 *peer)
                mwifiex_del_sta_entry(priv, peer);
        }
 
+       mwifiex_restore_tdls_packets(priv, peer, TDLS_LINK_TEARDOWN);
        memcpy(&tdls_oper.peer_mac, peer, ETH_ALEN);
        tdls_oper.tdls_action = MWIFIEX_TDLS_DISABLE_LINK;
        return mwifiex_send_cmd_sync(priv, HostCmd_CMD_TDLS_OPER,
@@ -608,6 +961,7 @@ mwifiex_tdls_process_enable_link(struct mwifiex_private *priv, u8 *peer)
                }
 
                memset(sta_ptr->rx_seq, 0xff, sizeof(sta_ptr->rx_seq));
+               mwifiex_restore_tdls_packets(priv, peer, TDLS_SETUP_COMPLETE);
        } else {
                dev_dbg(priv->adapter->dev,
                        "tdls: enable link %pM failed\n", peer);
@@ -620,6 +974,7 @@ mwifiex_tdls_process_enable_link(struct mwifiex_private *priv, u8 *peer)
                                               flags);
                        mwifiex_del_sta_entry(priv, peer);
                }
+               mwifiex_restore_tdls_packets(priv, peer, TDLS_LINK_TEARDOWN);
 
                return -1;
        }
@@ -634,6 +989,56 @@ int mwifiex_tdls_oper(struct mwifiex_private *priv, u8 *peer, u8 action)
                return mwifiex_tdls_process_enable_link(priv, peer);
        case MWIFIEX_TDLS_DISABLE_LINK:
                return mwifiex_tdls_process_disable_link(priv, peer);
+       case MWIFIEX_TDLS_CREATE_LINK:
+               return mwifiex_tdls_process_create_link(priv, peer);
+       case MWIFIEX_TDLS_CONFIG_LINK:
+               return mwifiex_tdls_process_config_link(priv, peer);
        }
        return 0;
 }
+
+int mwifiex_get_tdls_link_status(struct mwifiex_private *priv, u8 *mac)
+{
+       struct mwifiex_sta_node *sta_ptr;
+
+       sta_ptr = mwifiex_get_sta_entry(priv, mac);
+       if (sta_ptr)
+               return sta_ptr->tdls_status;
+
+       return TDLS_NOT_SETUP;
+}
+
+void mwifiex_disable_all_tdls_links(struct mwifiex_private *priv)
+{
+       struct mwifiex_sta_node *sta_ptr;
+       struct mwifiex_ds_tdls_oper tdls_oper;
+       unsigned long flags;
+
+       if (list_empty(&priv->sta_list))
+               return;
+
+       list_for_each_entry(sta_ptr, &priv->sta_list, list) {
+               memset(&tdls_oper, 0, sizeof(struct mwifiex_ds_tdls_oper));
+
+               if (sta_ptr->is_11n_enabled) {
+                       mwifiex_11n_cleanup_reorder_tbl(priv);
+                       spin_lock_irqsave(&priv->wmm.ra_list_spinlock,
+                                         flags);
+                       mwifiex_11n_delete_all_tx_ba_stream_tbl(priv);
+                       spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock,
+                                              flags);
+               }
+
+               mwifiex_restore_tdls_packets(priv, sta_ptr->mac_addr,
+                                            TDLS_LINK_TEARDOWN);
+               memcpy(&tdls_oper.peer_mac, sta_ptr->mac_addr, ETH_ALEN);
+               tdls_oper.tdls_action = MWIFIEX_TDLS_DISABLE_LINK;
+               if (mwifiex_send_cmd_async(priv, HostCmd_CMD_TDLS_OPER,
+                                          HostCmd_ACT_GEN_SET, 0, &tdls_oper))
+                       dev_warn(priv->adapter->dev,
+                                "Disable link failed for TDLS peer %pM",
+                                sta_ptr->mac_addr);
+       }
+
+       mwifiex_del_all_sta_list(priv);
+}