forked from Minki/linux
e8a50f8ba4
Configure interface combination for AP running on channels where radar detection is required. It allows only one type of interface - AP on DFS channel and limits number of AP interfaces to 8. Setup WMI channel flags accordingly to mac channel configuration. CAC based on additional monitor vdev is started if required for current channel. kvalo: dropped ATH10K_DFS_CERTIFIED config option as this the DFS still depends on few mac80211 and cfg80211 patches which are on mac80211-next.git right now. The config option will be added later once all dependencies are available. Signed-off-by: Marek Puzyniak <marek.puzyniak@tieto.com> Signed-off-by: Michal Kazior <michal.kazior@tieto.com> Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
1228 lines
34 KiB
C
1228 lines
34 KiB
C
/*
|
|
* Copyright (c) 2005-2011 Atheros Communications Inc.
|
|
* Copyright (c) 2011-2013 Qualcomm Atheros, Inc.
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include "core.h"
|
|
#include "htc.h"
|
|
#include "htt.h"
|
|
#include "txrx.h"
|
|
#include "debug.h"
|
|
#include "trace.h"
|
|
|
|
#include <linux/log2.h>
|
|
|
|
/* slightly larger than one large A-MPDU */
|
|
#define HTT_RX_RING_SIZE_MIN 128
|
|
|
|
/* roughly 20 ms @ 1 Gbps of 1500B MSDUs */
|
|
#define HTT_RX_RING_SIZE_MAX 2048
|
|
|
|
#define HTT_RX_AVG_FRM_BYTES 1000
|
|
|
|
/* ms, very conservative */
|
|
#define HTT_RX_HOST_LATENCY_MAX_MS 20
|
|
|
|
/* ms, conservative */
|
|
#define HTT_RX_HOST_LATENCY_WORST_LIKELY_MS 10
|
|
|
|
/* when under memory pressure rx ring refill may fail and needs a retry */
|
|
#define HTT_RX_RING_REFILL_RETRY_MS 50
|
|
|
|
|
|
static int ath10k_htt_rx_get_csum_state(struct sk_buff *skb);
|
|
|
|
|
|
static int ath10k_htt_rx_ring_size(struct ath10k_htt *htt)
|
|
{
|
|
int size;
|
|
|
|
/*
|
|
* It is expected that the host CPU will typically be able to
|
|
* service the rx indication from one A-MPDU before the rx
|
|
* indication from the subsequent A-MPDU happens, roughly 1-2 ms
|
|
* later. However, the rx ring should be sized very conservatively,
|
|
* to accomodate the worst reasonable delay before the host CPU
|
|
* services a rx indication interrupt.
|
|
*
|
|
* The rx ring need not be kept full of empty buffers. In theory,
|
|
* the htt host SW can dynamically track the low-water mark in the
|
|
* rx ring, and dynamically adjust the level to which the rx ring
|
|
* is filled with empty buffers, to dynamically meet the desired
|
|
* low-water mark.
|
|
*
|
|
* In contrast, it's difficult to resize the rx ring itself, once
|
|
* it's in use. Thus, the ring itself should be sized very
|
|
* conservatively, while the degree to which the ring is filled
|
|
* with empty buffers should be sized moderately conservatively.
|
|
*/
|
|
|
|
/* 1e6 bps/mbps / 1e3 ms per sec = 1000 */
|
|
size =
|
|
htt->max_throughput_mbps +
|
|
1000 /
|
|
(8 * HTT_RX_AVG_FRM_BYTES) * HTT_RX_HOST_LATENCY_MAX_MS;
|
|
|
|
if (size < HTT_RX_RING_SIZE_MIN)
|
|
size = HTT_RX_RING_SIZE_MIN;
|
|
|
|
if (size > HTT_RX_RING_SIZE_MAX)
|
|
size = HTT_RX_RING_SIZE_MAX;
|
|
|
|
size = roundup_pow_of_two(size);
|
|
|
|
return size;
|
|
}
|
|
|
|
static int ath10k_htt_rx_ring_fill_level(struct ath10k_htt *htt)
|
|
{
|
|
int size;
|
|
|
|
/* 1e6 bps/mbps / 1e3 ms per sec = 1000 */
|
|
size =
|
|
htt->max_throughput_mbps *
|
|
1000 /
|
|
(8 * HTT_RX_AVG_FRM_BYTES) * HTT_RX_HOST_LATENCY_WORST_LIKELY_MS;
|
|
|
|
/*
|
|
* Make sure the fill level is at least 1 less than the ring size.
|
|
* Leaving 1 element empty allows the SW to easily distinguish
|
|
* between a full ring vs. an empty ring.
|
|
*/
|
|
if (size >= htt->rx_ring.size)
|
|
size = htt->rx_ring.size - 1;
|
|
|
|
return size;
|
|
}
|
|
|
|
static void ath10k_htt_rx_ring_free(struct ath10k_htt *htt)
|
|
{
|
|
struct sk_buff *skb;
|
|
struct ath10k_skb_cb *cb;
|
|
int i;
|
|
|
|
for (i = 0; i < htt->rx_ring.fill_cnt; i++) {
|
|
skb = htt->rx_ring.netbufs_ring[i];
|
|
cb = ATH10K_SKB_CB(skb);
|
|
dma_unmap_single(htt->ar->dev, cb->paddr,
|
|
skb->len + skb_tailroom(skb),
|
|
DMA_FROM_DEVICE);
|
|
dev_kfree_skb_any(skb);
|
|
}
|
|
|
|
htt->rx_ring.fill_cnt = 0;
|
|
}
|
|
|
|
static int __ath10k_htt_rx_ring_fill_n(struct ath10k_htt *htt, int num)
|
|
{
|
|
struct htt_rx_desc *rx_desc;
|
|
struct sk_buff *skb;
|
|
dma_addr_t paddr;
|
|
int ret = 0, idx;
|
|
|
|
idx = __le32_to_cpu(*(htt->rx_ring.alloc_idx.vaddr));
|
|
while (num > 0) {
|
|
skb = dev_alloc_skb(HTT_RX_BUF_SIZE + HTT_RX_DESC_ALIGN);
|
|
if (!skb) {
|
|
ret = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
if (!IS_ALIGNED((unsigned long)skb->data, HTT_RX_DESC_ALIGN))
|
|
skb_pull(skb,
|
|
PTR_ALIGN(skb->data, HTT_RX_DESC_ALIGN) -
|
|
skb->data);
|
|
|
|
/* Clear rx_desc attention word before posting to Rx ring */
|
|
rx_desc = (struct htt_rx_desc *)skb->data;
|
|
rx_desc->attention.flags = __cpu_to_le32(0);
|
|
|
|
paddr = dma_map_single(htt->ar->dev, skb->data,
|
|
skb->len + skb_tailroom(skb),
|
|
DMA_FROM_DEVICE);
|
|
|
|
if (unlikely(dma_mapping_error(htt->ar->dev, paddr))) {
|
|
dev_kfree_skb_any(skb);
|
|
ret = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
ATH10K_SKB_CB(skb)->paddr = paddr;
|
|
htt->rx_ring.netbufs_ring[idx] = skb;
|
|
htt->rx_ring.paddrs_ring[idx] = __cpu_to_le32(paddr);
|
|
htt->rx_ring.fill_cnt++;
|
|
|
|
num--;
|
|
idx++;
|
|
idx &= htt->rx_ring.size_mask;
|
|
}
|
|
|
|
fail:
|
|
*(htt->rx_ring.alloc_idx.vaddr) = __cpu_to_le32(idx);
|
|
return ret;
|
|
}
|
|
|
|
static int ath10k_htt_rx_ring_fill_n(struct ath10k_htt *htt, int num)
|
|
{
|
|
lockdep_assert_held(&htt->rx_ring.lock);
|
|
return __ath10k_htt_rx_ring_fill_n(htt, num);
|
|
}
|
|
|
|
static void ath10k_htt_rx_msdu_buff_replenish(struct ath10k_htt *htt)
|
|
{
|
|
int ret, num_deficit, num_to_fill;
|
|
|
|
/* Refilling the whole RX ring buffer proves to be a bad idea. The
|
|
* reason is RX may take up significant amount of CPU cycles and starve
|
|
* other tasks, e.g. TX on an ethernet device while acting as a bridge
|
|
* with ath10k wlan interface. This ended up with very poor performance
|
|
* once CPU the host system was overwhelmed with RX on ath10k.
|
|
*
|
|
* By limiting the number of refills the replenishing occurs
|
|
* progressively. This in turns makes use of the fact tasklets are
|
|
* processed in FIFO order. This means actual RX processing can starve
|
|
* out refilling. If there's not enough buffers on RX ring FW will not
|
|
* report RX until it is refilled with enough buffers. This
|
|
* automatically balances load wrt to CPU power.
|
|
*
|
|
* This probably comes at a cost of lower maximum throughput but
|
|
* improves the avarage and stability. */
|
|
spin_lock_bh(&htt->rx_ring.lock);
|
|
num_deficit = htt->rx_ring.fill_level - htt->rx_ring.fill_cnt;
|
|
num_to_fill = min(ATH10K_HTT_MAX_NUM_REFILL, num_deficit);
|
|
num_deficit -= num_to_fill;
|
|
ret = ath10k_htt_rx_ring_fill_n(htt, num_to_fill);
|
|
if (ret == -ENOMEM) {
|
|
/*
|
|
* Failed to fill it to the desired level -
|
|
* we'll start a timer and try again next time.
|
|
* As long as enough buffers are left in the ring for
|
|
* another A-MPDU rx, no special recovery is needed.
|
|
*/
|
|
mod_timer(&htt->rx_ring.refill_retry_timer, jiffies +
|
|
msecs_to_jiffies(HTT_RX_RING_REFILL_RETRY_MS));
|
|
} else if (num_deficit > 0) {
|
|
tasklet_schedule(&htt->rx_replenish_task);
|
|
}
|
|
spin_unlock_bh(&htt->rx_ring.lock);
|
|
}
|
|
|
|
static void ath10k_htt_rx_ring_refill_retry(unsigned long arg)
|
|
{
|
|
struct ath10k_htt *htt = (struct ath10k_htt *)arg;
|
|
ath10k_htt_rx_msdu_buff_replenish(htt);
|
|
}
|
|
|
|
static unsigned ath10k_htt_rx_ring_elems(struct ath10k_htt *htt)
|
|
{
|
|
return (__le32_to_cpu(*htt->rx_ring.alloc_idx.vaddr) -
|
|
htt->rx_ring.sw_rd_idx.msdu_payld) & htt->rx_ring.size_mask;
|
|
}
|
|
|
|
void ath10k_htt_rx_detach(struct ath10k_htt *htt)
|
|
{
|
|
int sw_rd_idx = htt->rx_ring.sw_rd_idx.msdu_payld;
|
|
|
|
del_timer_sync(&htt->rx_ring.refill_retry_timer);
|
|
tasklet_kill(&htt->rx_replenish_task);
|
|
|
|
while (sw_rd_idx != __le32_to_cpu(*(htt->rx_ring.alloc_idx.vaddr))) {
|
|
struct sk_buff *skb =
|
|
htt->rx_ring.netbufs_ring[sw_rd_idx];
|
|
struct ath10k_skb_cb *cb = ATH10K_SKB_CB(skb);
|
|
|
|
dma_unmap_single(htt->ar->dev, cb->paddr,
|
|
skb->len + skb_tailroom(skb),
|
|
DMA_FROM_DEVICE);
|
|
dev_kfree_skb_any(htt->rx_ring.netbufs_ring[sw_rd_idx]);
|
|
sw_rd_idx++;
|
|
sw_rd_idx &= htt->rx_ring.size_mask;
|
|
}
|
|
|
|
dma_free_coherent(htt->ar->dev,
|
|
(htt->rx_ring.size *
|
|
sizeof(htt->rx_ring.paddrs_ring)),
|
|
htt->rx_ring.paddrs_ring,
|
|
htt->rx_ring.base_paddr);
|
|
|
|
dma_free_coherent(htt->ar->dev,
|
|
sizeof(*htt->rx_ring.alloc_idx.vaddr),
|
|
htt->rx_ring.alloc_idx.vaddr,
|
|
htt->rx_ring.alloc_idx.paddr);
|
|
|
|
kfree(htt->rx_ring.netbufs_ring);
|
|
}
|
|
|
|
static inline struct sk_buff *ath10k_htt_rx_netbuf_pop(struct ath10k_htt *htt)
|
|
{
|
|
int idx;
|
|
struct sk_buff *msdu;
|
|
|
|
spin_lock_bh(&htt->rx_ring.lock);
|
|
|
|
if (ath10k_htt_rx_ring_elems(htt) == 0)
|
|
ath10k_warn("htt rx ring is empty!\n");
|
|
|
|
idx = htt->rx_ring.sw_rd_idx.msdu_payld;
|
|
msdu = htt->rx_ring.netbufs_ring[idx];
|
|
|
|
idx++;
|
|
idx &= htt->rx_ring.size_mask;
|
|
htt->rx_ring.sw_rd_idx.msdu_payld = idx;
|
|
htt->rx_ring.fill_cnt--;
|
|
|
|
spin_unlock_bh(&htt->rx_ring.lock);
|
|
return msdu;
|
|
}
|
|
|
|
static void ath10k_htt_rx_free_msdu_chain(struct sk_buff *skb)
|
|
{
|
|
struct sk_buff *next;
|
|
|
|
while (skb) {
|
|
next = skb->next;
|
|
dev_kfree_skb_any(skb);
|
|
skb = next;
|
|
}
|
|
}
|
|
|
|
static int ath10k_htt_rx_amsdu_pop(struct ath10k_htt *htt,
|
|
u8 **fw_desc, int *fw_desc_len,
|
|
struct sk_buff **head_msdu,
|
|
struct sk_buff **tail_msdu)
|
|
{
|
|
int msdu_len, msdu_chaining = 0;
|
|
struct sk_buff *msdu;
|
|
struct htt_rx_desc *rx_desc;
|
|
|
|
if (ath10k_htt_rx_ring_elems(htt) == 0)
|
|
ath10k_warn("htt rx ring is empty!\n");
|
|
|
|
if (htt->rx_confused) {
|
|
ath10k_warn("htt is confused. refusing rx\n");
|
|
return 0;
|
|
}
|
|
|
|
msdu = *head_msdu = ath10k_htt_rx_netbuf_pop(htt);
|
|
while (msdu) {
|
|
int last_msdu, msdu_len_invalid, msdu_chained;
|
|
|
|
dma_unmap_single(htt->ar->dev,
|
|
ATH10K_SKB_CB(msdu)->paddr,
|
|
msdu->len + skb_tailroom(msdu),
|
|
DMA_FROM_DEVICE);
|
|
|
|
ath10k_dbg_dump(ATH10K_DBG_HTT_DUMP, NULL, "htt rx: ",
|
|
msdu->data, msdu->len + skb_tailroom(msdu));
|
|
|
|
rx_desc = (struct htt_rx_desc *)msdu->data;
|
|
|
|
/* FIXME: we must report msdu payload since this is what caller
|
|
* expects now */
|
|
skb_put(msdu, offsetof(struct htt_rx_desc, msdu_payload));
|
|
skb_pull(msdu, offsetof(struct htt_rx_desc, msdu_payload));
|
|
|
|
/*
|
|
* Sanity check - confirm the HW is finished filling in the
|
|
* rx data.
|
|
* If the HW and SW are working correctly, then it's guaranteed
|
|
* that the HW's MAC DMA is done before this point in the SW.
|
|
* To prevent the case that we handle a stale Rx descriptor,
|
|
* just assert for now until we have a way to recover.
|
|
*/
|
|
if (!(__le32_to_cpu(rx_desc->attention.flags)
|
|
& RX_ATTENTION_FLAGS_MSDU_DONE)) {
|
|
ath10k_htt_rx_free_msdu_chain(*head_msdu);
|
|
*head_msdu = NULL;
|
|
msdu = NULL;
|
|
ath10k_err("htt rx stopped. cannot recover\n");
|
|
htt->rx_confused = true;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Copy the FW rx descriptor for this MSDU from the rx
|
|
* indication message into the MSDU's netbuf. HL uses the
|
|
* same rx indication message definition as LL, and simply
|
|
* appends new info (fields from the HW rx desc, and the
|
|
* MSDU payload itself). So, the offset into the rx
|
|
* indication message only has to account for the standard
|
|
* offset of the per-MSDU FW rx desc info within the
|
|
* message, and how many bytes of the per-MSDU FW rx desc
|
|
* info have already been consumed. (And the endianness of
|
|
* the host, since for a big-endian host, the rx ind
|
|
* message contents, including the per-MSDU rx desc bytes,
|
|
* were byteswapped during upload.)
|
|
*/
|
|
if (*fw_desc_len > 0) {
|
|
rx_desc->fw_desc.info0 = **fw_desc;
|
|
/*
|
|
* The target is expected to only provide the basic
|
|
* per-MSDU rx descriptors. Just to be sure, verify
|
|
* that the target has not attached extension data
|
|
* (e.g. LRO flow ID).
|
|
*/
|
|
|
|
/* or more, if there's extension data */
|
|
(*fw_desc)++;
|
|
(*fw_desc_len)--;
|
|
} else {
|
|
/*
|
|
* When an oversized AMSDU happened, FW will lost
|
|
* some of MSDU status - in this case, the FW
|
|
* descriptors provided will be less than the
|
|
* actual MSDUs inside this MPDU. Mark the FW
|
|
* descriptors so that it will still deliver to
|
|
* upper stack, if no CRC error for this MPDU.
|
|
*
|
|
* FIX THIS - the FW descriptors are actually for
|
|
* MSDUs in the end of this A-MSDU instead of the
|
|
* beginning.
|
|
*/
|
|
rx_desc->fw_desc.info0 = 0;
|
|
}
|
|
|
|
msdu_len_invalid = !!(__le32_to_cpu(rx_desc->attention.flags)
|
|
& (RX_ATTENTION_FLAGS_MPDU_LENGTH_ERR |
|
|
RX_ATTENTION_FLAGS_MSDU_LENGTH_ERR));
|
|
msdu_len = MS(__le32_to_cpu(rx_desc->msdu_start.info0),
|
|
RX_MSDU_START_INFO0_MSDU_LENGTH);
|
|
msdu_chained = rx_desc->frag_info.ring2_more_count;
|
|
|
|
if (msdu_len_invalid)
|
|
msdu_len = 0;
|
|
|
|
skb_trim(msdu, 0);
|
|
skb_put(msdu, min(msdu_len, HTT_RX_MSDU_SIZE));
|
|
msdu_len -= msdu->len;
|
|
|
|
/* FIXME: Do chained buffers include htt_rx_desc or not? */
|
|
while (msdu_chained--) {
|
|
struct sk_buff *next = ath10k_htt_rx_netbuf_pop(htt);
|
|
|
|
dma_unmap_single(htt->ar->dev,
|
|
ATH10K_SKB_CB(next)->paddr,
|
|
next->len + skb_tailroom(next),
|
|
DMA_FROM_DEVICE);
|
|
|
|
ath10k_dbg_dump(ATH10K_DBG_HTT_DUMP, NULL, "htt rx: ",
|
|
next->data,
|
|
next->len + skb_tailroom(next));
|
|
|
|
skb_trim(next, 0);
|
|
skb_put(next, min(msdu_len, HTT_RX_BUF_SIZE));
|
|
msdu_len -= next->len;
|
|
|
|
msdu->next = next;
|
|
msdu = next;
|
|
msdu_chaining = 1;
|
|
}
|
|
|
|
if (msdu_len > 0) {
|
|
/* This may suggest FW bug? */
|
|
ath10k_warn("htt rx msdu len not consumed (%d)\n",
|
|
msdu_len);
|
|
}
|
|
|
|
last_msdu = __le32_to_cpu(rx_desc->msdu_end.info0) &
|
|
RX_MSDU_END_INFO0_LAST_MSDU;
|
|
|
|
if (last_msdu) {
|
|
msdu->next = NULL;
|
|
break;
|
|
} else {
|
|
struct sk_buff *next = ath10k_htt_rx_netbuf_pop(htt);
|
|
msdu->next = next;
|
|
msdu = next;
|
|
}
|
|
}
|
|
*tail_msdu = msdu;
|
|
|
|
/*
|
|
* Don't refill the ring yet.
|
|
*
|
|
* First, the elements popped here are still in use - it is not
|
|
* safe to overwrite them until the matching call to
|
|
* mpdu_desc_list_next. Second, for efficiency it is preferable to
|
|
* refill the rx ring with 1 PPDU's worth of rx buffers (something
|
|
* like 32 x 3 buffers), rather than one MPDU's worth of rx buffers
|
|
* (something like 3 buffers). Consequently, we'll rely on the txrx
|
|
* SW to tell us when it is done pulling all the PPDU's rx buffers
|
|
* out of the rx ring, and then refill it just once.
|
|
*/
|
|
|
|
return msdu_chaining;
|
|
}
|
|
|
|
static void ath10k_htt_rx_replenish_task(unsigned long ptr)
|
|
{
|
|
struct ath10k_htt *htt = (struct ath10k_htt *)ptr;
|
|
ath10k_htt_rx_msdu_buff_replenish(htt);
|
|
}
|
|
|
|
int ath10k_htt_rx_attach(struct ath10k_htt *htt)
|
|
{
|
|
dma_addr_t paddr;
|
|
void *vaddr;
|
|
struct timer_list *timer = &htt->rx_ring.refill_retry_timer;
|
|
|
|
htt->rx_ring.size = ath10k_htt_rx_ring_size(htt);
|
|
if (!is_power_of_2(htt->rx_ring.size)) {
|
|
ath10k_warn("htt rx ring size is not power of 2\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
htt->rx_ring.size_mask = htt->rx_ring.size - 1;
|
|
|
|
/*
|
|
* Set the initial value for the level to which the rx ring
|
|
* should be filled, based on the max throughput and the
|
|
* worst likely latency for the host to fill the rx ring
|
|
* with new buffers. In theory, this fill level can be
|
|
* dynamically adjusted from the initial value set here, to
|
|
* reflect the actual host latency rather than a
|
|
* conservative assumption about the host latency.
|
|
*/
|
|
htt->rx_ring.fill_level = ath10k_htt_rx_ring_fill_level(htt);
|
|
|
|
htt->rx_ring.netbufs_ring =
|
|
kmalloc(htt->rx_ring.size * sizeof(struct sk_buff *),
|
|
GFP_KERNEL);
|
|
if (!htt->rx_ring.netbufs_ring)
|
|
goto err_netbuf;
|
|
|
|
vaddr = dma_alloc_coherent(htt->ar->dev,
|
|
(htt->rx_ring.size * sizeof(htt->rx_ring.paddrs_ring)),
|
|
&paddr, GFP_DMA);
|
|
if (!vaddr)
|
|
goto err_dma_ring;
|
|
|
|
htt->rx_ring.paddrs_ring = vaddr;
|
|
htt->rx_ring.base_paddr = paddr;
|
|
|
|
vaddr = dma_alloc_coherent(htt->ar->dev,
|
|
sizeof(*htt->rx_ring.alloc_idx.vaddr),
|
|
&paddr, GFP_DMA);
|
|
if (!vaddr)
|
|
goto err_dma_idx;
|
|
|
|
htt->rx_ring.alloc_idx.vaddr = vaddr;
|
|
htt->rx_ring.alloc_idx.paddr = paddr;
|
|
htt->rx_ring.sw_rd_idx.msdu_payld = 0;
|
|
*htt->rx_ring.alloc_idx.vaddr = 0;
|
|
|
|
/* Initialize the Rx refill retry timer */
|
|
setup_timer(timer, ath10k_htt_rx_ring_refill_retry, (unsigned long)htt);
|
|
|
|
spin_lock_init(&htt->rx_ring.lock);
|
|
|
|
htt->rx_ring.fill_cnt = 0;
|
|
if (__ath10k_htt_rx_ring_fill_n(htt, htt->rx_ring.fill_level))
|
|
goto err_fill_ring;
|
|
|
|
tasklet_init(&htt->rx_replenish_task, ath10k_htt_rx_replenish_task,
|
|
(unsigned long)htt);
|
|
|
|
ath10k_dbg(ATH10K_DBG_BOOT, "htt rx ring size %d fill_level %d\n",
|
|
htt->rx_ring.size, htt->rx_ring.fill_level);
|
|
return 0;
|
|
|
|
err_fill_ring:
|
|
ath10k_htt_rx_ring_free(htt);
|
|
dma_free_coherent(htt->ar->dev,
|
|
sizeof(*htt->rx_ring.alloc_idx.vaddr),
|
|
htt->rx_ring.alloc_idx.vaddr,
|
|
htt->rx_ring.alloc_idx.paddr);
|
|
err_dma_idx:
|
|
dma_free_coherent(htt->ar->dev,
|
|
(htt->rx_ring.size *
|
|
sizeof(htt->rx_ring.paddrs_ring)),
|
|
htt->rx_ring.paddrs_ring,
|
|
htt->rx_ring.base_paddr);
|
|
err_dma_ring:
|
|
kfree(htt->rx_ring.netbufs_ring);
|
|
err_netbuf:
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static int ath10k_htt_rx_crypto_param_len(enum htt_rx_mpdu_encrypt_type type)
|
|
{
|
|
switch (type) {
|
|
case HTT_RX_MPDU_ENCRYPT_WEP40:
|
|
case HTT_RX_MPDU_ENCRYPT_WEP104:
|
|
return 4;
|
|
case HTT_RX_MPDU_ENCRYPT_TKIP_WITHOUT_MIC:
|
|
case HTT_RX_MPDU_ENCRYPT_WEP128: /* not tested */
|
|
case HTT_RX_MPDU_ENCRYPT_TKIP_WPA:
|
|
case HTT_RX_MPDU_ENCRYPT_WAPI: /* not tested */
|
|
case HTT_RX_MPDU_ENCRYPT_AES_CCM_WPA2:
|
|
return 8;
|
|
case HTT_RX_MPDU_ENCRYPT_NONE:
|
|
return 0;
|
|
}
|
|
|
|
ath10k_warn("unknown encryption type %d\n", type);
|
|
return 0;
|
|
}
|
|
|
|
static int ath10k_htt_rx_crypto_tail_len(enum htt_rx_mpdu_encrypt_type type)
|
|
{
|
|
switch (type) {
|
|
case HTT_RX_MPDU_ENCRYPT_NONE:
|
|
case HTT_RX_MPDU_ENCRYPT_WEP40:
|
|
case HTT_RX_MPDU_ENCRYPT_WEP104:
|
|
case HTT_RX_MPDU_ENCRYPT_WEP128:
|
|
case HTT_RX_MPDU_ENCRYPT_WAPI:
|
|
return 0;
|
|
case HTT_RX_MPDU_ENCRYPT_TKIP_WITHOUT_MIC:
|
|
case HTT_RX_MPDU_ENCRYPT_TKIP_WPA:
|
|
return 4;
|
|
case HTT_RX_MPDU_ENCRYPT_AES_CCM_WPA2:
|
|
return 8;
|
|
}
|
|
|
|
ath10k_warn("unknown encryption type %d\n", type);
|
|
return 0;
|
|
}
|
|
|
|
/* Applies for first msdu in chain, before altering it. */
|
|
static struct ieee80211_hdr *ath10k_htt_rx_skb_get_hdr(struct sk_buff *skb)
|
|
{
|
|
struct htt_rx_desc *rxd;
|
|
enum rx_msdu_decap_format fmt;
|
|
|
|
rxd = (void *)skb->data - sizeof(*rxd);
|
|
fmt = MS(__le32_to_cpu(rxd->msdu_start.info1),
|
|
RX_MSDU_START_INFO1_DECAP_FORMAT);
|
|
|
|
if (fmt == RX_MSDU_DECAP_RAW)
|
|
return (void *)skb->data;
|
|
else
|
|
return (void *)skb->data - RX_HTT_HDR_STATUS_LEN;
|
|
}
|
|
|
|
/* This function only applies for first msdu in an msdu chain */
|
|
static bool ath10k_htt_rx_hdr_is_amsdu(struct ieee80211_hdr *hdr)
|
|
{
|
|
if (ieee80211_is_data_qos(hdr->frame_control)) {
|
|
u8 *qc = ieee80211_get_qos_ctl(hdr);
|
|
if (qc[0] & 0x80)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
struct rfc1042_hdr {
|
|
u8 llc_dsap;
|
|
u8 llc_ssap;
|
|
u8 llc_ctrl;
|
|
u8 snap_oui[3];
|
|
__be16 snap_type;
|
|
} __packed;
|
|
|
|
struct amsdu_subframe_hdr {
|
|
u8 dst[ETH_ALEN];
|
|
u8 src[ETH_ALEN];
|
|
__be16 len;
|
|
} __packed;
|
|
|
|
static void ath10k_htt_rx_amsdu(struct ath10k_htt *htt,
|
|
struct htt_rx_info *info)
|
|
{
|
|
struct htt_rx_desc *rxd;
|
|
struct sk_buff *first;
|
|
struct sk_buff *skb = info->skb;
|
|
enum rx_msdu_decap_format fmt;
|
|
enum htt_rx_mpdu_encrypt_type enctype;
|
|
struct ieee80211_hdr *hdr;
|
|
u8 hdr_buf[64], addr[ETH_ALEN], *qos;
|
|
unsigned int hdr_len;
|
|
|
|
rxd = (void *)skb->data - sizeof(*rxd);
|
|
enctype = MS(__le32_to_cpu(rxd->mpdu_start.info0),
|
|
RX_MPDU_START_INFO0_ENCRYPT_TYPE);
|
|
|
|
hdr = (struct ieee80211_hdr *)rxd->rx_hdr_status;
|
|
hdr_len = ieee80211_hdrlen(hdr->frame_control);
|
|
memcpy(hdr_buf, hdr, hdr_len);
|
|
hdr = (struct ieee80211_hdr *)hdr_buf;
|
|
|
|
first = skb;
|
|
while (skb) {
|
|
void *decap_hdr;
|
|
int len;
|
|
|
|
rxd = (void *)skb->data - sizeof(*rxd);
|
|
fmt = MS(__le32_to_cpu(rxd->msdu_start.info1),
|
|
RX_MSDU_START_INFO1_DECAP_FORMAT);
|
|
decap_hdr = (void *)rxd->rx_hdr_status;
|
|
|
|
skb->ip_summed = ath10k_htt_rx_get_csum_state(skb);
|
|
|
|
/* First frame in an A-MSDU chain has more decapped data. */
|
|
if (skb == first) {
|
|
len = round_up(ieee80211_hdrlen(hdr->frame_control), 4);
|
|
len += round_up(ath10k_htt_rx_crypto_param_len(enctype),
|
|
4);
|
|
decap_hdr += len;
|
|
}
|
|
|
|
switch (fmt) {
|
|
case RX_MSDU_DECAP_RAW:
|
|
/* remove trailing FCS */
|
|
skb_trim(skb, skb->len - FCS_LEN);
|
|
break;
|
|
case RX_MSDU_DECAP_NATIVE_WIFI:
|
|
/* pull decapped header and copy DA */
|
|
hdr = (struct ieee80211_hdr *)skb->data;
|
|
hdr_len = ieee80211_hdrlen(hdr->frame_control);
|
|
memcpy(addr, ieee80211_get_DA(hdr), ETH_ALEN);
|
|
skb_pull(skb, hdr_len);
|
|
|
|
/* push original 802.11 header */
|
|
hdr = (struct ieee80211_hdr *)hdr_buf;
|
|
hdr_len = ieee80211_hdrlen(hdr->frame_control);
|
|
memcpy(skb_push(skb, hdr_len), hdr, hdr_len);
|
|
|
|
/* original A-MSDU header has the bit set but we're
|
|
* not including A-MSDU subframe header */
|
|
hdr = (struct ieee80211_hdr *)skb->data;
|
|
qos = ieee80211_get_qos_ctl(hdr);
|
|
qos[0] &= ~IEEE80211_QOS_CTL_A_MSDU_PRESENT;
|
|
|
|
/* original 802.11 header has a different DA */
|
|
memcpy(ieee80211_get_DA(hdr), addr, ETH_ALEN);
|
|
break;
|
|
case RX_MSDU_DECAP_ETHERNET2_DIX:
|
|
/* strip ethernet header and insert decapped 802.11
|
|
* header, amsdu subframe header and rfc1042 header */
|
|
|
|
len = 0;
|
|
len += sizeof(struct rfc1042_hdr);
|
|
len += sizeof(struct amsdu_subframe_hdr);
|
|
|
|
skb_pull(skb, sizeof(struct ethhdr));
|
|
memcpy(skb_push(skb, len), decap_hdr, len);
|
|
memcpy(skb_push(skb, hdr_len), hdr, hdr_len);
|
|
break;
|
|
case RX_MSDU_DECAP_8023_SNAP_LLC:
|
|
/* insert decapped 802.11 header making a singly
|
|
* A-MSDU */
|
|
memcpy(skb_push(skb, hdr_len), hdr, hdr_len);
|
|
break;
|
|
}
|
|
|
|
info->skb = skb;
|
|
info->encrypt_type = enctype;
|
|
skb = skb->next;
|
|
info->skb->next = NULL;
|
|
|
|
if (skb)
|
|
info->amsdu_more = true;
|
|
|
|
ath10k_process_rx(htt->ar, info);
|
|
}
|
|
|
|
/* FIXME: It might be nice to re-assemble the A-MSDU when there's a
|
|
* monitor interface active for sniffing purposes. */
|
|
}
|
|
|
|
static void ath10k_htt_rx_msdu(struct ath10k_htt *htt, struct htt_rx_info *info)
|
|
{
|
|
struct sk_buff *skb = info->skb;
|
|
struct htt_rx_desc *rxd;
|
|
struct ieee80211_hdr *hdr;
|
|
enum rx_msdu_decap_format fmt;
|
|
enum htt_rx_mpdu_encrypt_type enctype;
|
|
int hdr_len;
|
|
void *rfc1042;
|
|
|
|
/* This shouldn't happen. If it does than it may be a FW bug. */
|
|
if (skb->next) {
|
|
ath10k_warn("received chained non A-MSDU frame\n");
|
|
ath10k_htt_rx_free_msdu_chain(skb->next);
|
|
skb->next = NULL;
|
|
}
|
|
|
|
rxd = (void *)skb->data - sizeof(*rxd);
|
|
fmt = MS(__le32_to_cpu(rxd->msdu_start.info1),
|
|
RX_MSDU_START_INFO1_DECAP_FORMAT);
|
|
enctype = MS(__le32_to_cpu(rxd->mpdu_start.info0),
|
|
RX_MPDU_START_INFO0_ENCRYPT_TYPE);
|
|
hdr = (struct ieee80211_hdr *)rxd->rx_hdr_status;
|
|
hdr_len = ieee80211_hdrlen(hdr->frame_control);
|
|
|
|
skb->ip_summed = ath10k_htt_rx_get_csum_state(skb);
|
|
|
|
switch (fmt) {
|
|
case RX_MSDU_DECAP_RAW:
|
|
/* remove trailing FCS */
|
|
skb_trim(skb, skb->len - FCS_LEN);
|
|
break;
|
|
case RX_MSDU_DECAP_NATIVE_WIFI:
|
|
/* Pull decapped header */
|
|
hdr = (struct ieee80211_hdr *)skb->data;
|
|
hdr_len = ieee80211_hdrlen(hdr->frame_control);
|
|
skb_pull(skb, hdr_len);
|
|
|
|
/* Push original header */
|
|
hdr = (struct ieee80211_hdr *)rxd->rx_hdr_status;
|
|
hdr_len = ieee80211_hdrlen(hdr->frame_control);
|
|
memcpy(skb_push(skb, hdr_len), hdr, hdr_len);
|
|
break;
|
|
case RX_MSDU_DECAP_ETHERNET2_DIX:
|
|
/* strip ethernet header and insert decapped 802.11 header and
|
|
* rfc1042 header */
|
|
|
|
rfc1042 = hdr;
|
|
rfc1042 += roundup(hdr_len, 4);
|
|
rfc1042 += roundup(ath10k_htt_rx_crypto_param_len(enctype), 4);
|
|
|
|
skb_pull(skb, sizeof(struct ethhdr));
|
|
memcpy(skb_push(skb, sizeof(struct rfc1042_hdr)),
|
|
rfc1042, sizeof(struct rfc1042_hdr));
|
|
memcpy(skb_push(skb, hdr_len), hdr, hdr_len);
|
|
break;
|
|
case RX_MSDU_DECAP_8023_SNAP_LLC:
|
|
/* remove A-MSDU subframe header and insert
|
|
* decapped 802.11 header. rfc1042 header is already there */
|
|
|
|
skb_pull(skb, sizeof(struct amsdu_subframe_hdr));
|
|
memcpy(skb_push(skb, hdr_len), hdr, hdr_len);
|
|
break;
|
|
}
|
|
|
|
info->skb = skb;
|
|
info->encrypt_type = enctype;
|
|
|
|
ath10k_process_rx(htt->ar, info);
|
|
}
|
|
|
|
static bool ath10k_htt_rx_has_decrypt_err(struct sk_buff *skb)
|
|
{
|
|
struct htt_rx_desc *rxd;
|
|
u32 flags;
|
|
|
|
rxd = (void *)skb->data - sizeof(*rxd);
|
|
flags = __le32_to_cpu(rxd->attention.flags);
|
|
|
|
if (flags & RX_ATTENTION_FLAGS_DECRYPT_ERR)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool ath10k_htt_rx_has_fcs_err(struct sk_buff *skb)
|
|
{
|
|
struct htt_rx_desc *rxd;
|
|
u32 flags;
|
|
|
|
rxd = (void *)skb->data - sizeof(*rxd);
|
|
flags = __le32_to_cpu(rxd->attention.flags);
|
|
|
|
if (flags & RX_ATTENTION_FLAGS_FCS_ERR)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static int ath10k_htt_rx_get_csum_state(struct sk_buff *skb)
|
|
{
|
|
struct htt_rx_desc *rxd;
|
|
u32 flags, info;
|
|
bool is_ip4, is_ip6;
|
|
bool is_tcp, is_udp;
|
|
bool ip_csum_ok, tcpudp_csum_ok;
|
|
|
|
rxd = (void *)skb->data - sizeof(*rxd);
|
|
flags = __le32_to_cpu(rxd->attention.flags);
|
|
info = __le32_to_cpu(rxd->msdu_start.info1);
|
|
|
|
is_ip4 = !!(info & RX_MSDU_START_INFO1_IPV4_PROTO);
|
|
is_ip6 = !!(info & RX_MSDU_START_INFO1_IPV6_PROTO);
|
|
is_tcp = !!(info & RX_MSDU_START_INFO1_TCP_PROTO);
|
|
is_udp = !!(info & RX_MSDU_START_INFO1_UDP_PROTO);
|
|
ip_csum_ok = !(flags & RX_ATTENTION_FLAGS_IP_CHKSUM_FAIL);
|
|
tcpudp_csum_ok = !(flags & RX_ATTENTION_FLAGS_TCP_UDP_CHKSUM_FAIL);
|
|
|
|
if (!is_ip4 && !is_ip6)
|
|
return CHECKSUM_NONE;
|
|
if (!is_tcp && !is_udp)
|
|
return CHECKSUM_NONE;
|
|
if (!ip_csum_ok)
|
|
return CHECKSUM_NONE;
|
|
if (!tcpudp_csum_ok)
|
|
return CHECKSUM_NONE;
|
|
|
|
return CHECKSUM_UNNECESSARY;
|
|
}
|
|
|
|
static void ath10k_htt_rx_handler(struct ath10k_htt *htt,
|
|
struct htt_rx_indication *rx)
|
|
{
|
|
struct htt_rx_info info;
|
|
struct htt_rx_indication_mpdu_range *mpdu_ranges;
|
|
struct ieee80211_hdr *hdr;
|
|
int num_mpdu_ranges;
|
|
int fw_desc_len;
|
|
u8 *fw_desc;
|
|
int i, j;
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
|
|
fw_desc_len = __le16_to_cpu(rx->prefix.fw_rx_desc_bytes);
|
|
fw_desc = (u8 *)&rx->fw_desc;
|
|
|
|
num_mpdu_ranges = MS(__le32_to_cpu(rx->hdr.info1),
|
|
HTT_RX_INDICATION_INFO1_NUM_MPDU_RANGES);
|
|
mpdu_ranges = htt_rx_ind_get_mpdu_ranges(rx);
|
|
|
|
ath10k_dbg_dump(ATH10K_DBG_HTT_DUMP, NULL, "htt rx ind: ",
|
|
rx, sizeof(*rx) +
|
|
(sizeof(struct htt_rx_indication_mpdu_range) *
|
|
num_mpdu_ranges));
|
|
|
|
for (i = 0; i < num_mpdu_ranges; i++) {
|
|
info.status = mpdu_ranges[i].mpdu_range_status;
|
|
|
|
for (j = 0; j < mpdu_ranges[i].mpdu_count; j++) {
|
|
struct sk_buff *msdu_head, *msdu_tail;
|
|
enum htt_rx_mpdu_status status;
|
|
int msdu_chaining;
|
|
|
|
msdu_head = NULL;
|
|
msdu_tail = NULL;
|
|
msdu_chaining = ath10k_htt_rx_amsdu_pop(htt,
|
|
&fw_desc,
|
|
&fw_desc_len,
|
|
&msdu_head,
|
|
&msdu_tail);
|
|
|
|
if (!msdu_head) {
|
|
ath10k_warn("htt rx no data!\n");
|
|
continue;
|
|
}
|
|
|
|
if (msdu_head->len == 0) {
|
|
ath10k_dbg(ATH10K_DBG_HTT,
|
|
"htt rx dropping due to zero-len\n");
|
|
ath10k_htt_rx_free_msdu_chain(msdu_head);
|
|
continue;
|
|
}
|
|
|
|
if (ath10k_htt_rx_has_decrypt_err(msdu_head)) {
|
|
ath10k_htt_rx_free_msdu_chain(msdu_head);
|
|
continue;
|
|
}
|
|
|
|
status = info.status;
|
|
|
|
/* Skip mgmt frames while we handle this in WMI */
|
|
if (status == HTT_RX_IND_MPDU_STATUS_MGMT_CTRL) {
|
|
ath10k_htt_rx_free_msdu_chain(msdu_head);
|
|
continue;
|
|
}
|
|
|
|
if (status != HTT_RX_IND_MPDU_STATUS_OK &&
|
|
status != HTT_RX_IND_MPDU_STATUS_TKIP_MIC_ERR &&
|
|
!htt->ar->monitor_enabled) {
|
|
ath10k_dbg(ATH10K_DBG_HTT,
|
|
"htt rx ignoring frame w/ status %d\n",
|
|
status);
|
|
ath10k_htt_rx_free_msdu_chain(msdu_head);
|
|
continue;
|
|
}
|
|
|
|
if (test_bit(ATH10K_CAC_RUNNING, &htt->ar->dev_flags)) {
|
|
ath10k_htt_rx_free_msdu_chain(msdu_head);
|
|
continue;
|
|
}
|
|
|
|
/* FIXME: we do not support chaining yet.
|
|
* this needs investigation */
|
|
if (msdu_chaining) {
|
|
ath10k_warn("msdu_chaining is true\n");
|
|
ath10k_htt_rx_free_msdu_chain(msdu_head);
|
|
continue;
|
|
}
|
|
|
|
info.skb = msdu_head;
|
|
info.fcs_err = ath10k_htt_rx_has_fcs_err(msdu_head);
|
|
info.signal = ATH10K_DEFAULT_NOISE_FLOOR;
|
|
info.signal += rx->ppdu.combined_rssi;
|
|
|
|
info.rate.info0 = rx->ppdu.info0;
|
|
info.rate.info1 = __le32_to_cpu(rx->ppdu.info1);
|
|
info.rate.info2 = __le32_to_cpu(rx->ppdu.info2);
|
|
|
|
hdr = ath10k_htt_rx_skb_get_hdr(msdu_head);
|
|
|
|
if (ath10k_htt_rx_hdr_is_amsdu(hdr))
|
|
ath10k_htt_rx_amsdu(htt, &info);
|
|
else
|
|
ath10k_htt_rx_msdu(htt, &info);
|
|
}
|
|
}
|
|
|
|
tasklet_schedule(&htt->rx_replenish_task);
|
|
}
|
|
|
|
static void ath10k_htt_rx_frag_handler(struct ath10k_htt *htt,
|
|
struct htt_rx_fragment_indication *frag)
|
|
{
|
|
struct sk_buff *msdu_head, *msdu_tail;
|
|
struct htt_rx_desc *rxd;
|
|
enum rx_msdu_decap_format fmt;
|
|
struct htt_rx_info info = {};
|
|
struct ieee80211_hdr *hdr;
|
|
int msdu_chaining;
|
|
bool tkip_mic_err;
|
|
bool decrypt_err;
|
|
u8 *fw_desc;
|
|
int fw_desc_len, hdrlen, paramlen;
|
|
int trim;
|
|
|
|
fw_desc_len = __le16_to_cpu(frag->fw_rx_desc_bytes);
|
|
fw_desc = (u8 *)frag->fw_msdu_rx_desc;
|
|
|
|
msdu_head = NULL;
|
|
msdu_tail = NULL;
|
|
msdu_chaining = ath10k_htt_rx_amsdu_pop(htt, &fw_desc, &fw_desc_len,
|
|
&msdu_head, &msdu_tail);
|
|
|
|
ath10k_dbg(ATH10K_DBG_HTT_DUMP, "htt rx frag ahead\n");
|
|
|
|
if (!msdu_head) {
|
|
ath10k_warn("htt rx frag no data\n");
|
|
return;
|
|
}
|
|
|
|
if (msdu_chaining || msdu_head != msdu_tail) {
|
|
ath10k_warn("aggregation with fragmentation?!\n");
|
|
ath10k_htt_rx_free_msdu_chain(msdu_head);
|
|
return;
|
|
}
|
|
|
|
/* FIXME: implement signal strength */
|
|
|
|
hdr = (struct ieee80211_hdr *)msdu_head->data;
|
|
rxd = (void *)msdu_head->data - sizeof(*rxd);
|
|
tkip_mic_err = !!(__le32_to_cpu(rxd->attention.flags) &
|
|
RX_ATTENTION_FLAGS_TKIP_MIC_ERR);
|
|
decrypt_err = !!(__le32_to_cpu(rxd->attention.flags) &
|
|
RX_ATTENTION_FLAGS_DECRYPT_ERR);
|
|
fmt = MS(__le32_to_cpu(rxd->msdu_start.info1),
|
|
RX_MSDU_START_INFO1_DECAP_FORMAT);
|
|
|
|
if (fmt != RX_MSDU_DECAP_RAW) {
|
|
ath10k_warn("we dont support non-raw fragmented rx yet\n");
|
|
dev_kfree_skb_any(msdu_head);
|
|
goto end;
|
|
}
|
|
|
|
info.skb = msdu_head;
|
|
info.status = HTT_RX_IND_MPDU_STATUS_OK;
|
|
info.encrypt_type = MS(__le32_to_cpu(rxd->mpdu_start.info0),
|
|
RX_MPDU_START_INFO0_ENCRYPT_TYPE);
|
|
info.skb->ip_summed = ath10k_htt_rx_get_csum_state(info.skb);
|
|
|
|
if (tkip_mic_err) {
|
|
ath10k_warn("tkip mic error\n");
|
|
info.status = HTT_RX_IND_MPDU_STATUS_TKIP_MIC_ERR;
|
|
}
|
|
|
|
if (decrypt_err) {
|
|
ath10k_warn("decryption err in fragmented rx\n");
|
|
dev_kfree_skb_any(info.skb);
|
|
goto end;
|
|
}
|
|
|
|
if (info.encrypt_type != HTT_RX_MPDU_ENCRYPT_NONE) {
|
|
hdrlen = ieee80211_hdrlen(hdr->frame_control);
|
|
paramlen = ath10k_htt_rx_crypto_param_len(info.encrypt_type);
|
|
|
|
/* It is more efficient to move the header than the payload */
|
|
memmove((void *)info.skb->data + paramlen,
|
|
(void *)info.skb->data,
|
|
hdrlen);
|
|
skb_pull(info.skb, paramlen);
|
|
hdr = (struct ieee80211_hdr *)info.skb->data;
|
|
}
|
|
|
|
/* remove trailing FCS */
|
|
trim = 4;
|
|
|
|
/* remove crypto trailer */
|
|
trim += ath10k_htt_rx_crypto_tail_len(info.encrypt_type);
|
|
|
|
/* last fragment of TKIP frags has MIC */
|
|
if (!ieee80211_has_morefrags(hdr->frame_control) &&
|
|
info.encrypt_type == HTT_RX_MPDU_ENCRYPT_TKIP_WPA)
|
|
trim += 8;
|
|
|
|
if (trim > info.skb->len) {
|
|
ath10k_warn("htt rx fragment: trailer longer than the frame itself? drop\n");
|
|
dev_kfree_skb_any(info.skb);
|
|
goto end;
|
|
}
|
|
|
|
skb_trim(info.skb, info.skb->len - trim);
|
|
|
|
ath10k_dbg_dump(ATH10K_DBG_HTT_DUMP, NULL, "htt frag mpdu: ",
|
|
info.skb->data, info.skb->len);
|
|
ath10k_process_rx(htt->ar, &info);
|
|
|
|
end:
|
|
if (fw_desc_len > 0) {
|
|
ath10k_dbg(ATH10K_DBG_HTT,
|
|
"expecting more fragmented rx in one indication %d\n",
|
|
fw_desc_len);
|
|
}
|
|
}
|
|
|
|
void ath10k_htt_t2h_msg_handler(struct ath10k *ar, struct sk_buff *skb)
|
|
{
|
|
struct ath10k_htt *htt = &ar->htt;
|
|
struct htt_resp *resp = (struct htt_resp *)skb->data;
|
|
|
|
/* confirm alignment */
|
|
if (!IS_ALIGNED((unsigned long)skb->data, 4))
|
|
ath10k_warn("unaligned htt message, expect trouble\n");
|
|
|
|
ath10k_dbg(ATH10K_DBG_HTT, "HTT RX, msg_type: 0x%0X\n",
|
|
resp->hdr.msg_type);
|
|
switch (resp->hdr.msg_type) {
|
|
case HTT_T2H_MSG_TYPE_VERSION_CONF: {
|
|
htt->target_version_major = resp->ver_resp.major;
|
|
htt->target_version_minor = resp->ver_resp.minor;
|
|
complete(&htt->target_version_received);
|
|
break;
|
|
}
|
|
case HTT_T2H_MSG_TYPE_RX_IND: {
|
|
ath10k_htt_rx_handler(htt, &resp->rx_ind);
|
|
break;
|
|
}
|
|
case HTT_T2H_MSG_TYPE_PEER_MAP: {
|
|
struct htt_peer_map_event ev = {
|
|
.vdev_id = resp->peer_map.vdev_id,
|
|
.peer_id = __le16_to_cpu(resp->peer_map.peer_id),
|
|
};
|
|
memcpy(ev.addr, resp->peer_map.addr, sizeof(ev.addr));
|
|
ath10k_peer_map_event(htt, &ev);
|
|
break;
|
|
}
|
|
case HTT_T2H_MSG_TYPE_PEER_UNMAP: {
|
|
struct htt_peer_unmap_event ev = {
|
|
.peer_id = __le16_to_cpu(resp->peer_unmap.peer_id),
|
|
};
|
|
ath10k_peer_unmap_event(htt, &ev);
|
|
break;
|
|
}
|
|
case HTT_T2H_MSG_TYPE_MGMT_TX_COMPLETION: {
|
|
struct htt_tx_done tx_done = {};
|
|
int status = __le32_to_cpu(resp->mgmt_tx_completion.status);
|
|
|
|
tx_done.msdu_id =
|
|
__le32_to_cpu(resp->mgmt_tx_completion.desc_id);
|
|
|
|
switch (status) {
|
|
case HTT_MGMT_TX_STATUS_OK:
|
|
break;
|
|
case HTT_MGMT_TX_STATUS_RETRY:
|
|
tx_done.no_ack = true;
|
|
break;
|
|
case HTT_MGMT_TX_STATUS_DROP:
|
|
tx_done.discard = true;
|
|
break;
|
|
}
|
|
|
|
ath10k_txrx_tx_unref(htt, &tx_done);
|
|
break;
|
|
}
|
|
case HTT_T2H_MSG_TYPE_TX_COMPL_IND: {
|
|
struct htt_tx_done tx_done = {};
|
|
int status = MS(resp->data_tx_completion.flags,
|
|
HTT_DATA_TX_STATUS);
|
|
__le16 msdu_id;
|
|
int i;
|
|
|
|
switch (status) {
|
|
case HTT_DATA_TX_STATUS_NO_ACK:
|
|
tx_done.no_ack = true;
|
|
break;
|
|
case HTT_DATA_TX_STATUS_OK:
|
|
break;
|
|
case HTT_DATA_TX_STATUS_DISCARD:
|
|
case HTT_DATA_TX_STATUS_POSTPONE:
|
|
case HTT_DATA_TX_STATUS_DOWNLOAD_FAIL:
|
|
tx_done.discard = true;
|
|
break;
|
|
default:
|
|
ath10k_warn("unhandled tx completion status %d\n",
|
|
status);
|
|
tx_done.discard = true;
|
|
break;
|
|
}
|
|
|
|
ath10k_dbg(ATH10K_DBG_HTT, "htt tx completion num_msdus %d\n",
|
|
resp->data_tx_completion.num_msdus);
|
|
|
|
for (i = 0; i < resp->data_tx_completion.num_msdus; i++) {
|
|
msdu_id = resp->data_tx_completion.msdus[i];
|
|
tx_done.msdu_id = __le16_to_cpu(msdu_id);
|
|
ath10k_txrx_tx_unref(htt, &tx_done);
|
|
}
|
|
break;
|
|
}
|
|
case HTT_T2H_MSG_TYPE_SEC_IND: {
|
|
struct ath10k *ar = htt->ar;
|
|
struct htt_security_indication *ev = &resp->security_indication;
|
|
|
|
ath10k_dbg(ATH10K_DBG_HTT,
|
|
"sec ind peer_id %d unicast %d type %d\n",
|
|
__le16_to_cpu(ev->peer_id),
|
|
!!(ev->flags & HTT_SECURITY_IS_UNICAST),
|
|
MS(ev->flags, HTT_SECURITY_TYPE));
|
|
complete(&ar->install_key_done);
|
|
break;
|
|
}
|
|
case HTT_T2H_MSG_TYPE_RX_FRAG_IND: {
|
|
ath10k_dbg_dump(ATH10K_DBG_HTT_DUMP, NULL, "htt event: ",
|
|
skb->data, skb->len);
|
|
ath10k_htt_rx_frag_handler(htt, &resp->rx_frag_ind);
|
|
break;
|
|
}
|
|
case HTT_T2H_MSG_TYPE_TEST:
|
|
/* FIX THIS */
|
|
break;
|
|
case HTT_T2H_MSG_TYPE_STATS_CONF:
|
|
trace_ath10k_htt_stats(skb->data, skb->len);
|
|
break;
|
|
case HTT_T2H_MSG_TYPE_TX_INSPECT_IND:
|
|
case HTT_T2H_MSG_TYPE_RX_ADDBA:
|
|
case HTT_T2H_MSG_TYPE_RX_DELBA:
|
|
case HTT_T2H_MSG_TYPE_RX_FLUSH:
|
|
default:
|
|
ath10k_dbg(ATH10K_DBG_HTT, "htt event (%d) not handled\n",
|
|
resp->hdr.msg_type);
|
|
ath10k_dbg_dump(ATH10K_DBG_HTT_DUMP, NULL, "htt event: ",
|
|
skb->data, skb->len);
|
|
break;
|
|
};
|
|
|
|
/* Free the indication buffer */
|
|
dev_kfree_skb_any(skb);
|
|
}
|