Commit 85bd0107 authored by Yu Wang's avatar Yu Wang Committed by Kalle Valo
Browse files

ath10k: add amsdu support for monitor mode



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: default avatarYu Wang <yyuwang@codeaurora.org>
[kvalo@codeaurora.org: cosmetic cleanup]
Signed-off-by: default avatarKalle Valo <kvalo@codeaurora.org>
parent 553a7cca
Loading
Loading
Loading
Loading
+186 −2
Original line number Diff line number Diff line
@@ -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) {