ath10k: add amsdu support for monitor mode
authorYu Wang <yyuwang@codeaurora.org>
Thu, 20 Dec 2018 07:53:26 +0000 (09:53 +0200)
committerKalle Valo <kvalo@codeaurora.org>
Thu, 20 Dec 2018 16:52:11 +0000 (18:52 +0200)
When processing HTT_T2H_MSG_TYPE_RX_IN_ORD_PADDR_IND, if the length of a msdu
is larger than the tailroom of the rx skb, skb_over_panic issue will happen
when calling skb_put.  In monitor mode, amsdu will be handled in this path, and
msdu_len of the first msdu_desc is the length of the entire amsdu, which might
be larger than the maximum length of a skb, in such case, it will hit the issue
upon.

To fix this issue, process msdu list separately for monitor mode.

Successfully tested with:
QCA6174 (FW version: RM.4.4.1.c2-00057-QCARMSWP-1).

Signed-off-by: Yu Wang <yyuwang@codeaurora.org>
[kvalo@codeaurora.org: cosmetic cleanup]
Signed-off-by: Kalle Valo <kvalo@codeaurora.org>
drivers/net/wireless/ath/ath10k/htt_rx.c

index 984b0458d07707b55d3f6812da70f336f0a43775..e04c080e6a377b2cb58740b130b8db70a9a9511f 100644 (file)
@@ -469,6 +469,166 @@ static struct sk_buff *ath10k_htt_rx_pop_paddr(struct ath10k_htt *htt,
        return msdu;
 }
 
+static inline void ath10k_htt_append_frag_list(struct sk_buff *skb_head,
+                                              struct sk_buff *frag_list,
+                                              unsigned int frag_len)
+{
+       skb_shinfo(skb_head)->frag_list = frag_list;
+       skb_head->data_len = frag_len;
+       skb_head->len += skb_head->data_len;
+}
+
+static int ath10k_htt_rx_handle_amsdu_mon_32(struct ath10k_htt *htt,
+                                            struct sk_buff *msdu,
+                                            struct htt_rx_in_ord_msdu_desc **msdu_desc)
+{
+       struct ath10k *ar = htt->ar;
+       u32 paddr;
+       struct sk_buff *frag_buf;
+       struct sk_buff *prev_frag_buf;
+       u8 last_frag;
+       struct htt_rx_in_ord_msdu_desc *ind_desc = *msdu_desc;
+       struct htt_rx_desc *rxd;
+       int amsdu_len = __le16_to_cpu(ind_desc->msdu_len);
+
+       rxd = (void *)msdu->data;
+       trace_ath10k_htt_rx_desc(ar, rxd, sizeof(*rxd));
+
+       skb_put(msdu, sizeof(struct htt_rx_desc));
+       skb_pull(msdu, sizeof(struct htt_rx_desc));
+       skb_put(msdu, min(amsdu_len, HTT_RX_MSDU_SIZE));
+       amsdu_len -= msdu->len;
+
+       last_frag = ind_desc->reserved;
+       if (last_frag) {
+               if (amsdu_len) {
+                       ath10k_warn(ar, "invalid amsdu len %u, left %d",
+                                   __le16_to_cpu(ind_desc->msdu_len),
+                                   amsdu_len);
+               }
+               return 0;
+       }
+
+       ind_desc++;
+       paddr = __le32_to_cpu(ind_desc->msdu_paddr);
+       frag_buf = ath10k_htt_rx_pop_paddr(htt, paddr);
+       if (!frag_buf) {
+               ath10k_warn(ar, "failed to pop frag-1 paddr: 0x%x", paddr);
+               return -ENOENT;
+       }
+
+       skb_put(frag_buf, min(amsdu_len, HTT_RX_BUF_SIZE));
+       ath10k_htt_append_frag_list(msdu, frag_buf, amsdu_len);
+
+       amsdu_len -= frag_buf->len;
+       prev_frag_buf = frag_buf;
+       last_frag = ind_desc->reserved;
+       while (!last_frag) {
+               ind_desc++;
+               paddr = __le32_to_cpu(ind_desc->msdu_paddr);
+               frag_buf = ath10k_htt_rx_pop_paddr(htt, paddr);
+               if (!frag_buf) {
+                       ath10k_warn(ar, "failed to pop frag-n paddr: 0x%x",
+                                   paddr);
+                       prev_frag_buf->next = NULL;
+                       return -ENOENT;
+               }
+
+               skb_put(frag_buf, min(amsdu_len, HTT_RX_BUF_SIZE));
+               last_frag = ind_desc->reserved;
+               amsdu_len -= frag_buf->len;
+
+               prev_frag_buf->next = frag_buf;
+               prev_frag_buf = frag_buf;
+       }
+
+       if (amsdu_len) {
+               ath10k_warn(ar, "invalid amsdu len %u, left %d",
+                           __le16_to_cpu(ind_desc->msdu_len), amsdu_len);
+       }
+
+       *msdu_desc = ind_desc;
+
+       prev_frag_buf->next = NULL;
+       return 0;
+}
+
+static int
+ath10k_htt_rx_handle_amsdu_mon_64(struct ath10k_htt *htt,
+                                 struct sk_buff *msdu,
+                                 struct htt_rx_in_ord_msdu_desc_ext **msdu_desc)
+{
+       struct ath10k *ar = htt->ar;
+       u64 paddr;
+       struct sk_buff *frag_buf;
+       struct sk_buff *prev_frag_buf;
+       u8 last_frag;
+       struct htt_rx_in_ord_msdu_desc_ext *ind_desc = *msdu_desc;
+       struct htt_rx_desc *rxd;
+       int amsdu_len = __le16_to_cpu(ind_desc->msdu_len);
+
+       rxd = (void *)msdu->data;
+       trace_ath10k_htt_rx_desc(ar, rxd, sizeof(*rxd));
+
+       skb_put(msdu, sizeof(struct htt_rx_desc));
+       skb_pull(msdu, sizeof(struct htt_rx_desc));
+       skb_put(msdu, min(amsdu_len, HTT_RX_MSDU_SIZE));
+       amsdu_len -= msdu->len;
+
+       last_frag = ind_desc->reserved;
+       if (last_frag) {
+               if (amsdu_len) {
+                       ath10k_warn(ar, "invalid amsdu len %u, left %d",
+                                   __le16_to_cpu(ind_desc->msdu_len),
+                                   amsdu_len);
+               }
+               return 0;
+       }
+
+       ind_desc++;
+       paddr = __le64_to_cpu(ind_desc->msdu_paddr);
+       frag_buf = ath10k_htt_rx_pop_paddr(htt, paddr);
+       if (!frag_buf) {
+               ath10k_warn(ar, "failed to pop frag-1 paddr: 0x%llx", paddr);
+               return -ENOENT;
+       }
+
+       skb_put(frag_buf, min(amsdu_len, HTT_RX_BUF_SIZE));
+       ath10k_htt_append_frag_list(msdu, frag_buf, amsdu_len);
+
+       amsdu_len -= frag_buf->len;
+       prev_frag_buf = frag_buf;
+       last_frag = ind_desc->reserved;
+       while (!last_frag) {
+               ind_desc++;
+               paddr = __le64_to_cpu(ind_desc->msdu_paddr);
+               frag_buf = ath10k_htt_rx_pop_paddr(htt, paddr);
+               if (!frag_buf) {
+                       ath10k_warn(ar, "failed to pop frag-n paddr: 0x%llx",
+                                   paddr);
+                       prev_frag_buf->next = NULL;
+                       return -ENOENT;
+               }
+
+               skb_put(frag_buf, min(amsdu_len, HTT_RX_BUF_SIZE));
+               last_frag = ind_desc->reserved;
+               amsdu_len -= frag_buf->len;
+
+               prev_frag_buf->next = frag_buf;
+               prev_frag_buf = frag_buf;
+       }
+
+       if (amsdu_len) {
+               ath10k_warn(ar, "invalid amsdu len %u, left %d",
+                           __le16_to_cpu(ind_desc->msdu_len), amsdu_len);
+       }
+
+       *msdu_desc = ind_desc;
+
+       prev_frag_buf->next = NULL;
+       return 0;
+}
+
 static int ath10k_htt_rx_pop_paddr32_list(struct ath10k_htt *htt,
                                          struct htt_rx_in_ord_ind *ev,
                                          struct sk_buff_head *list)
@@ -477,7 +637,7 @@ static int ath10k_htt_rx_pop_paddr32_list(struct ath10k_htt *htt,
        struct htt_rx_in_ord_msdu_desc *msdu_desc = ev->msdu_descs32;
        struct htt_rx_desc *rxd;
        struct sk_buff *msdu;
-       int msdu_count;
+       int msdu_count, ret;
        bool is_offload;
        u32 paddr;
 
@@ -495,6 +655,18 @@ static int ath10k_htt_rx_pop_paddr32_list(struct ath10k_htt *htt,
                        return -ENOENT;
                }
 
+               if (!is_offload && ar->monitor_arvif) {
+                       ret = ath10k_htt_rx_handle_amsdu_mon_32(htt, msdu,
+                                                               &msdu_desc);
+                       if (ret) {
+                               __skb_queue_purge(list);
+                               return ret;
+                       }
+                       __skb_queue_tail(list, msdu);
+                       msdu_desc++;
+                       continue;
+               }
+
                __skb_queue_tail(list, msdu);
 
                if (!is_offload) {
@@ -527,7 +699,7 @@ static int ath10k_htt_rx_pop_paddr64_list(struct ath10k_htt *htt,
        struct htt_rx_in_ord_msdu_desc_ext *msdu_desc = ev->msdu_descs64;
        struct htt_rx_desc *rxd;
        struct sk_buff *msdu;
-       int msdu_count;
+       int msdu_count, ret;
        bool is_offload;
        u64 paddr;
 
@@ -544,6 +716,18 @@ static int ath10k_htt_rx_pop_paddr64_list(struct ath10k_htt *htt,
                        return -ENOENT;
                }
 
+               if (!is_offload && ar->monitor_arvif) {
+                       ret = ath10k_htt_rx_handle_amsdu_mon_64(htt, msdu,
+                                                               &msdu_desc);
+                       if (ret) {
+                               __skb_queue_purge(list);
+                               return ret;
+                       }
+                       __skb_queue_tail(list, msdu);
+                       msdu_desc++;
+                       continue;
+               }
+
                __skb_queue_tail(list, msdu);
 
                if (!is_offload) {