diff options
Diffstat (limited to 'rx.c')
-rw-r--r-- | rx.c | 91 |
1 files changed, 88 insertions, 3 deletions
@@ -294,6 +294,7 @@ static inline void mwl_rx_enable_sta_amsdu(struct mwl_priv *priv, drv_priv[0]); if (ether_addr_equal(sta->addr, sta_addr)) { sta_info->is_amsdu_allowed = true; + wiphy_dbg(priv->hw->wiphy, "enabling A-MSDU for %pM\n", sta_addr); break; } } @@ -338,6 +339,76 @@ static inline void mwl_rx_remove_dma_header(struct sk_buff *skb, __le16 qos) skb_pull(skb, sizeof(*tr) - hdrlen); } +static inline bool mwl_rx_process_mesh_amsdu(struct mwl_priv *priv, + struct sk_buff *skb, + struct ieee80211_rx_status *status) +{ + struct ieee80211_hdr *wh; + struct mwl_sta *sta_info; + struct ieee80211_sta *sta; + u8 *qc; + int wh_len; + int len; + u8 pad; + u8 *data; + u16 frame_len; + struct sk_buff *newskb; + + wh = (struct ieee80211_hdr *)skb->data; + + spin_lock_bh(&priv->sta_lock); + list_for_each_entry(sta_info, &priv->sta_list, list) { + sta = container_of((char *)sta_info, struct ieee80211_sta, + drv_priv[0]); + if (ether_addr_equal(sta->addr, wh->addr2)) { + if (!sta_info->is_mesh_node) { + spin_unlock_bh(&priv->sta_lock); + return false; + } + } + } + spin_unlock_bh(&priv->sta_lock); + + qc = ieee80211_get_qos_ctl(wh); + *qc &= ~IEEE80211_QOS_CTL_A_MSDU_PRESENT; + + wh_len = ieee80211_hdrlen(wh->frame_control); + len = wh_len; + data = skb->data; + + while (len < skb->len) { + frame_len = *(u8 *)(data + len + ETH_HLEN - 1) | + (*(u8 *)(data + len + ETH_HLEN - 2) << 8); + + if ((len + ETH_HLEN + frame_len) > skb->len) + break; + + newskb = dev_alloc_skb(wh_len + frame_len); + if (!newskb) + break; + + ether_addr_copy(wh->addr3, data + len); + ether_addr_copy(wh->addr4, data + len + ETH_ALEN); + memcpy(newskb->data, wh, wh_len); + memcpy(newskb->data + wh_len, data + len + ETH_HLEN, frame_len); + skb_put(newskb, wh_len + frame_len); + + pad = ((ETH_HLEN + frame_len) % 4) ? + (4 - (ETH_HLEN + frame_len) % 4) : 0; + len += (ETH_HLEN + frame_len + pad); + if (len < skb->len) + status->flag |= RX_FLAG_AMSDU_MORE; + else + status->flag &= ~RX_FLAG_AMSDU_MORE; + memcpy(IEEE80211_SKB_RXCB(newskb), status, sizeof(*status)); + ieee80211_rx(priv->hw, newskb); + } + + dev_kfree_skb_any(skb); + + return true; +} + static int mwl_rx_refill(struct mwl_priv *priv, struct mwl_rx_hndl *rx_hndl) { struct mwl_desc_data *desc; @@ -504,9 +575,11 @@ void mwl_rx_recv(unsigned long data) skb_put(prx_skb, pkt_len); mwl_rx_remove_dma_header(prx_skb, curr_hndl->pdesc->qos_ctrl); + wh = (struct ieee80211_hdr *)prx_skb->data; + if (ieee80211_is_mgmt(wh->frame_control)) { struct ieee80211_mgmt *mgmt; - __le16 capab; + u16 capab; mgmt = (struct ieee80211_mgmt *)prx_skb->data; @@ -515,12 +588,24 @@ void mwl_rx_recv(unsigned long data) WLAN_CATEGORY_BACK && mgmt->u.action.u.addba_resp.action_code == WLAN_ACTION_ADDBA_RESP)) { - capab = mgmt->u.action.u.addba_resp.capab; - if (le16_to_cpu(capab) & 1) + capab = le16_to_cpu(mgmt->u.action.u.addba_resp.capab); + wiphy_dbg(hw->wiphy, "%pM - addba_resp.capab=0x%hx\n", + mgmt->sa, capab); + if (capab & 1) mwl_rx_enable_sta_amsdu(priv, mgmt->sa); } } + if (ieee80211_is_data_qos(wh->frame_control) && + ieee80211_has_a4(wh->frame_control)) { + u8 *qc = ieee80211_get_qos_ctl(wh); + + if (*qc & IEEE80211_QOS_CTL_A_MSDU_PRESENT) + if (mwl_rx_process_mesh_amsdu(priv, prx_skb, + &status)) + goto out; + } + memcpy(IEEE80211_SKB_RXCB(prx_skb), &status, sizeof(status)); ieee80211_rx(hw, prx_skb); out: |