ath10k: implement rx reorder support
New firmware and firmware (qca6174 hw3.0+ and fw 266+) are capable of full aggregation rx reordering. If it's enabled then Rx is handled via a new, separate htt event. The rx ring behaviour is changed a little to support the new rx scheme. These changes shouldn't affect qca988x performance. Signed-off-by: Michal Kazior <michal.kazior@tieto.com> Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
This commit is contained in:

committed by
Kalle Valo

parent
8582bf3be7
commit
c545070e40
@@ -1061,6 +1061,18 @@ int ath10k_core_start(struct ath10k *ar, enum ath10k_firmware_mode mode)
|
|||||||
goto err_hif_stop;
|
goto err_hif_stop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If firmware indicates Full Rx Reorder support it must be used in a
|
||||||
|
* slightly different manner. Let HTT code know.
|
||||||
|
*/
|
||||||
|
ar->htt.rx_ring.in_ord_rx = !!(test_bit(WMI_SERVICE_RX_FULL_REORDER,
|
||||||
|
ar->wmi.svc_map));
|
||||||
|
|
||||||
|
status = ath10k_htt_rx_ring_refill(ar);
|
||||||
|
if (status) {
|
||||||
|
ath10k_err(ar, "failed to refill htt rx ring: %d\n", status);
|
||||||
|
goto err_hif_stop;
|
||||||
|
}
|
||||||
|
|
||||||
/* we don't care about HTT in UTF mode */
|
/* we don't care about HTT in UTF mode */
|
||||||
if (mode == ATH10K_FIRMWARE_MODE_NORMAL) {
|
if (mode == ATH10K_FIRMWARE_MODE_NORMAL) {
|
||||||
status = ath10k_htt_setup(&ar->htt);
|
status = ath10k_htt_setup(&ar->htt);
|
||||||
|
@@ -99,6 +99,7 @@ struct ath10k_skb_cb {
|
|||||||
|
|
||||||
struct ath10k_skb_rxcb {
|
struct ath10k_skb_rxcb {
|
||||||
dma_addr_t paddr;
|
dma_addr_t paddr;
|
||||||
|
struct hlist_node hlist;
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline struct ath10k_skb_cb *ATH10K_SKB_CB(struct sk_buff *skb)
|
static inline struct ath10k_skb_cb *ATH10K_SKB_CB(struct sk_buff *skb)
|
||||||
@@ -114,6 +115,9 @@ static inline struct ath10k_skb_rxcb *ATH10K_SKB_RXCB(struct sk_buff *skb)
|
|||||||
return (struct ath10k_skb_rxcb *)skb->cb;
|
return (struct ath10k_skb_rxcb *)skb->cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define ATH10K_RXCB_SKB(rxcb) \
|
||||||
|
container_of((void *)rxcb, struct sk_buff, cb)
|
||||||
|
|
||||||
static inline u32 host_interest_item_address(u32 item_offset)
|
static inline u32 host_interest_item_address(u32 item_offset)
|
||||||
{
|
{
|
||||||
return QCA988X_HOST_INTEREST_ADDRESS + item_offset;
|
return QCA988X_HOST_INTEREST_ADDRESS + item_offset;
|
||||||
|
@@ -21,6 +21,7 @@
|
|||||||
#include <linux/bug.h>
|
#include <linux/bug.h>
|
||||||
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
||||||
#include <linux/dmapool.h>
|
#include <linux/dmapool.h>
|
||||||
|
#include <linux/hashtable.h>
|
||||||
#include <net/mac80211.h>
|
#include <net/mac80211.h>
|
||||||
|
|
||||||
#include "htc.h"
|
#include "htc.h"
|
||||||
@@ -286,7 +287,19 @@ enum htt_t2h_msg_type {
|
|||||||
HTT_T2H_MSG_TYPE_RC_UPDATE_IND = 0xc,
|
HTT_T2H_MSG_TYPE_RC_UPDATE_IND = 0xc,
|
||||||
HTT_T2H_MSG_TYPE_TX_INSPECT_IND = 0xd,
|
HTT_T2H_MSG_TYPE_TX_INSPECT_IND = 0xd,
|
||||||
HTT_T2H_MSG_TYPE_MGMT_TX_COMPLETION = 0xe,
|
HTT_T2H_MSG_TYPE_MGMT_TX_COMPLETION = 0xe,
|
||||||
|
HTT_T2H_MSG_TYPE_TX_CREDIT_UPDATE_IND = 0xf,
|
||||||
|
HTT_T2H_MSG_TYPE_RX_PN_IND = 0x10,
|
||||||
|
HTT_T2H_MSG_TYPE_RX_OFFLOAD_DELIVER_IND = 0x11,
|
||||||
|
HTT_T2H_MSG_TYPE_RX_IN_ORD_PADDR_IND = 0x12,
|
||||||
|
/* 0x13 reservd */
|
||||||
|
HTT_T2H_MSG_TYPE_WDI_IPA_OP_RESPONSE = 0x14,
|
||||||
|
|
||||||
|
/* FIXME: Do not depend on this event id. Numbering of this event id is
|
||||||
|
* broken across different firmware revisions and HTT version fails to
|
||||||
|
* indicate this.
|
||||||
|
*/
|
||||||
HTT_T2H_MSG_TYPE_TEST,
|
HTT_T2H_MSG_TYPE_TEST,
|
||||||
|
|
||||||
/* keep this last */
|
/* keep this last */
|
||||||
HTT_T2H_NUM_MSGS
|
HTT_T2H_NUM_MSGS
|
||||||
};
|
};
|
||||||
@@ -655,6 +668,53 @@ struct htt_rx_fragment_indication {
|
|||||||
#define HTT_RX_FRAG_IND_INFO1_FLUSH_SEQ_NUM_END_MASK 0x00000FC0
|
#define HTT_RX_FRAG_IND_INFO1_FLUSH_SEQ_NUM_END_MASK 0x00000FC0
|
||||||
#define HTT_RX_FRAG_IND_INFO1_FLUSH_SEQ_NUM_END_LSB 6
|
#define HTT_RX_FRAG_IND_INFO1_FLUSH_SEQ_NUM_END_LSB 6
|
||||||
|
|
||||||
|
struct htt_rx_pn_ind {
|
||||||
|
__le16 peer_id;
|
||||||
|
u8 tid;
|
||||||
|
u8 seqno_start;
|
||||||
|
u8 seqno_end;
|
||||||
|
u8 pn_ie_count;
|
||||||
|
u8 reserved;
|
||||||
|
u8 pn_ies[0];
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
struct htt_rx_offload_msdu {
|
||||||
|
__le16 msdu_len;
|
||||||
|
__le16 peer_id;
|
||||||
|
u8 vdev_id;
|
||||||
|
u8 tid;
|
||||||
|
u8 fw_desc;
|
||||||
|
u8 payload[0];
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
struct htt_rx_offload_ind {
|
||||||
|
u8 reserved;
|
||||||
|
__le16 msdu_count;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
struct htt_rx_in_ord_msdu_desc {
|
||||||
|
__le32 msdu_paddr;
|
||||||
|
__le16 msdu_len;
|
||||||
|
u8 fw_desc;
|
||||||
|
u8 reserved;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
struct htt_rx_in_ord_ind {
|
||||||
|
u8 info;
|
||||||
|
__le16 peer_id;
|
||||||
|
u8 vdev_id;
|
||||||
|
u8 reserved;
|
||||||
|
__le16 msdu_count;
|
||||||
|
struct htt_rx_in_ord_msdu_desc msdu_descs[0];
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
#define HTT_RX_IN_ORD_IND_INFO_TID_MASK 0x0000001f
|
||||||
|
#define HTT_RX_IN_ORD_IND_INFO_TID_LSB 0
|
||||||
|
#define HTT_RX_IN_ORD_IND_INFO_OFFLOAD_MASK 0x00000020
|
||||||
|
#define HTT_RX_IN_ORD_IND_INFO_OFFLOAD_LSB 5
|
||||||
|
#define HTT_RX_IN_ORD_IND_INFO_FRAG_MASK 0x00000040
|
||||||
|
#define HTT_RX_IN_ORD_IND_INFO_FRAG_LSB 6
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* target -> host test message definition
|
* target -> host test message definition
|
||||||
*
|
*
|
||||||
@@ -1150,6 +1210,9 @@ struct htt_resp {
|
|||||||
struct htt_rx_test rx_test;
|
struct htt_rx_test rx_test;
|
||||||
struct htt_pktlog_msg pktlog_msg;
|
struct htt_pktlog_msg pktlog_msg;
|
||||||
struct htt_stats_conf stats_conf;
|
struct htt_stats_conf stats_conf;
|
||||||
|
struct htt_rx_pn_ind rx_pn_ind;
|
||||||
|
struct htt_rx_offload_ind rx_offload_ind;
|
||||||
|
struct htt_rx_in_ord_ind rx_in_ord_ind;
|
||||||
};
|
};
|
||||||
} __packed;
|
} __packed;
|
||||||
|
|
||||||
@@ -1197,6 +1260,20 @@ struct ath10k_htt {
|
|||||||
* filled.
|
* filled.
|
||||||
*/
|
*/
|
||||||
struct sk_buff **netbufs_ring;
|
struct sk_buff **netbufs_ring;
|
||||||
|
|
||||||
|
/* This is used only with firmware supporting IN_ORD_IND.
|
||||||
|
*
|
||||||
|
* With Full Rx Reorder the HTT Rx Ring is more of a temporary
|
||||||
|
* buffer ring from which buffer addresses are copied by the
|
||||||
|
* firmware to MAC Rx ring. Firmware then delivers IN_ORD_IND
|
||||||
|
* pointing to specific (re-ordered) buffers.
|
||||||
|
*
|
||||||
|
* FIXME: With kernel generic hashing functions there's a lot
|
||||||
|
* of hash collisions for sk_buffs.
|
||||||
|
*/
|
||||||
|
bool in_ord_rx;
|
||||||
|
DECLARE_HASHTABLE(skb_table, 4);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Ring of buffer addresses -
|
* Ring of buffer addresses -
|
||||||
* This ring holds the "physical" device address of the
|
* This ring holds the "physical" device address of the
|
||||||
@@ -1270,6 +1347,7 @@ struct ath10k_htt {
|
|||||||
struct tasklet_struct txrx_compl_task;
|
struct tasklet_struct txrx_compl_task;
|
||||||
struct sk_buff_head tx_compl_q;
|
struct sk_buff_head tx_compl_q;
|
||||||
struct sk_buff_head rx_compl_q;
|
struct sk_buff_head rx_compl_q;
|
||||||
|
struct sk_buff_head rx_in_ord_compl_q;
|
||||||
|
|
||||||
/* rx_status template */
|
/* rx_status template */
|
||||||
struct ieee80211_rx_status rx_status;
|
struct ieee80211_rx_status rx_status;
|
||||||
@@ -1333,6 +1411,7 @@ int ath10k_htt_tx_alloc(struct ath10k_htt *htt);
|
|||||||
void ath10k_htt_tx_free(struct ath10k_htt *htt);
|
void ath10k_htt_tx_free(struct ath10k_htt *htt);
|
||||||
|
|
||||||
int ath10k_htt_rx_alloc(struct ath10k_htt *htt);
|
int ath10k_htt_rx_alloc(struct ath10k_htt *htt);
|
||||||
|
int ath10k_htt_rx_ring_refill(struct ath10k *ar);
|
||||||
void ath10k_htt_rx_free(struct ath10k_htt *htt);
|
void ath10k_htt_rx_free(struct ath10k_htt *htt);
|
||||||
|
|
||||||
void ath10k_htt_htc_tx_complete(struct ath10k *ar, struct sk_buff *skb);
|
void ath10k_htt_htc_tx_complete(struct ath10k *ar, struct sk_buff *skb);
|
||||||
|
@@ -25,8 +25,8 @@
|
|||||||
|
|
||||||
#include <linux/log2.h>
|
#include <linux/log2.h>
|
||||||
|
|
||||||
#define HTT_RX_RING_SIZE 1024
|
#define HTT_RX_RING_SIZE HTT_RX_RING_SIZE_MAX
|
||||||
#define HTT_RX_RING_FILL_LEVEL 1000
|
#define HTT_RX_RING_FILL_LEVEL (((HTT_RX_RING_SIZE) / 2) - 1)
|
||||||
|
|
||||||
/* when under memory pressure rx ring refill may fail and needs a retry */
|
/* when under memory pressure rx ring refill may fail and needs a retry */
|
||||||
#define HTT_RX_RING_REFILL_RETRY_MS 50
|
#define HTT_RX_RING_REFILL_RETRY_MS 50
|
||||||
@@ -34,31 +34,70 @@
|
|||||||
static int ath10k_htt_rx_get_csum_state(struct sk_buff *skb);
|
static int ath10k_htt_rx_get_csum_state(struct sk_buff *skb);
|
||||||
static void ath10k_htt_txrx_compl_task(unsigned long ptr);
|
static void ath10k_htt_txrx_compl_task(unsigned long ptr);
|
||||||
|
|
||||||
|
static struct sk_buff *
|
||||||
|
ath10k_htt_rx_find_skb_paddr(struct ath10k *ar, u32 paddr)
|
||||||
|
{
|
||||||
|
struct ath10k_skb_rxcb *rxcb;
|
||||||
|
|
||||||
|
hash_for_each_possible(ar->htt.rx_ring.skb_table, rxcb, hlist, paddr)
|
||||||
|
if (rxcb->paddr == paddr)
|
||||||
|
return ATH10K_RXCB_SKB(rxcb);
|
||||||
|
|
||||||
|
WARN_ON_ONCE(1);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static void ath10k_htt_rx_ring_free(struct ath10k_htt *htt)
|
static void ath10k_htt_rx_ring_free(struct ath10k_htt *htt)
|
||||||
{
|
{
|
||||||
struct sk_buff *skb;
|
struct sk_buff *skb;
|
||||||
struct ath10k_skb_rxcb *cb;
|
struct ath10k_skb_rxcb *rxcb;
|
||||||
|
struct hlist_node *n;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < htt->rx_ring.fill_cnt; i++) {
|
if (htt->rx_ring.in_ord_rx) {
|
||||||
skb = htt->rx_ring.netbufs_ring[i];
|
hash_for_each_safe(htt->rx_ring.skb_table, i, n, rxcb, hlist) {
|
||||||
cb = ATH10K_SKB_RXCB(skb);
|
skb = ATH10K_RXCB_SKB(rxcb);
|
||||||
dma_unmap_single(htt->ar->dev, cb->paddr,
|
dma_unmap_single(htt->ar->dev, rxcb->paddr,
|
||||||
skb->len + skb_tailroom(skb),
|
skb->len + skb_tailroom(skb),
|
||||||
DMA_FROM_DEVICE);
|
DMA_FROM_DEVICE);
|
||||||
dev_kfree_skb_any(skb);
|
hash_del(&rxcb->hlist);
|
||||||
|
dev_kfree_skb_any(skb);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (i = 0; i < htt->rx_ring.size; i++) {
|
||||||
|
skb = htt->rx_ring.netbufs_ring[i];
|
||||||
|
if (!skb)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
rxcb = ATH10K_SKB_RXCB(skb);
|
||||||
|
dma_unmap_single(htt->ar->dev, rxcb->paddr,
|
||||||
|
skb->len + skb_tailroom(skb),
|
||||||
|
DMA_FROM_DEVICE);
|
||||||
|
dev_kfree_skb_any(skb);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
htt->rx_ring.fill_cnt = 0;
|
htt->rx_ring.fill_cnt = 0;
|
||||||
|
hash_init(htt->rx_ring.skb_table);
|
||||||
|
memset(htt->rx_ring.netbufs_ring, 0,
|
||||||
|
htt->rx_ring.size * sizeof(htt->rx_ring.netbufs_ring[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __ath10k_htt_rx_ring_fill_n(struct ath10k_htt *htt, int num)
|
static int __ath10k_htt_rx_ring_fill_n(struct ath10k_htt *htt, int num)
|
||||||
{
|
{
|
||||||
struct htt_rx_desc *rx_desc;
|
struct htt_rx_desc *rx_desc;
|
||||||
|
struct ath10k_skb_rxcb *rxcb;
|
||||||
struct sk_buff *skb;
|
struct sk_buff *skb;
|
||||||
dma_addr_t paddr;
|
dma_addr_t paddr;
|
||||||
int ret = 0, idx;
|
int ret = 0, idx;
|
||||||
|
|
||||||
|
/* The Full Rx Reorder firmware has no way of telling the host
|
||||||
|
* implicitly when it copied HTT Rx Ring buffers to MAC Rx Ring.
|
||||||
|
* To keep things simple make sure ring is always half empty. This
|
||||||
|
* guarantees there'll be no replenishment overruns possible.
|
||||||
|
*/
|
||||||
|
BUILD_BUG_ON(HTT_RX_RING_FILL_LEVEL >= HTT_RX_RING_SIZE / 2);
|
||||||
|
|
||||||
idx = __le32_to_cpu(*htt->rx_ring.alloc_idx.vaddr);
|
idx = __le32_to_cpu(*htt->rx_ring.alloc_idx.vaddr);
|
||||||
while (num > 0) {
|
while (num > 0) {
|
||||||
skb = dev_alloc_skb(HTT_RX_BUF_SIZE + HTT_RX_DESC_ALIGN);
|
skb = dev_alloc_skb(HTT_RX_BUF_SIZE + HTT_RX_DESC_ALIGN);
|
||||||
@@ -86,11 +125,18 @@ static int __ath10k_htt_rx_ring_fill_n(struct ath10k_htt *htt, int num)
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
ATH10K_SKB_RXCB(skb)->paddr = paddr;
|
rxcb = ATH10K_SKB_RXCB(skb);
|
||||||
|
rxcb->paddr = paddr;
|
||||||
htt->rx_ring.netbufs_ring[idx] = skb;
|
htt->rx_ring.netbufs_ring[idx] = skb;
|
||||||
htt->rx_ring.paddrs_ring[idx] = __cpu_to_le32(paddr);
|
htt->rx_ring.paddrs_ring[idx] = __cpu_to_le32(paddr);
|
||||||
htt->rx_ring.fill_cnt++;
|
htt->rx_ring.fill_cnt++;
|
||||||
|
|
||||||
|
if (htt->rx_ring.in_ord_rx) {
|
||||||
|
hash_add(htt->rx_ring.skb_table,
|
||||||
|
&ATH10K_SKB_RXCB(skb)->hlist,
|
||||||
|
(u32)paddr);
|
||||||
|
}
|
||||||
|
|
||||||
num--;
|
num--;
|
||||||
idx++;
|
idx++;
|
||||||
idx &= htt->rx_ring.size_mask;
|
idx &= htt->rx_ring.size_mask;
|
||||||
@@ -158,22 +204,20 @@ static void ath10k_htt_rx_ring_refill_retry(unsigned long arg)
|
|||||||
ath10k_htt_rx_msdu_buff_replenish(htt);
|
ath10k_htt_rx_msdu_buff_replenish(htt);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ath10k_htt_rx_ring_clean_up(struct ath10k_htt *htt)
|
int ath10k_htt_rx_ring_refill(struct ath10k *ar)
|
||||||
{
|
{
|
||||||
struct sk_buff *skb;
|
struct ath10k_htt *htt = &ar->htt;
|
||||||
int i;
|
int ret;
|
||||||
|
|
||||||
for (i = 0; i < htt->rx_ring.size; i++) {
|
spin_lock_bh(&htt->rx_ring.lock);
|
||||||
skb = htt->rx_ring.netbufs_ring[i];
|
ret = ath10k_htt_rx_ring_fill_n(htt, (htt->rx_ring.fill_level -
|
||||||
if (!skb)
|
htt->rx_ring.fill_cnt));
|
||||||
continue;
|
spin_unlock_bh(&htt->rx_ring.lock);
|
||||||
|
|
||||||
dma_unmap_single(htt->ar->dev, ATH10K_SKB_RXCB(skb)->paddr,
|
if (ret)
|
||||||
skb->len + skb_tailroom(skb),
|
ath10k_htt_rx_ring_free(htt);
|
||||||
DMA_FROM_DEVICE);
|
|
||||||
dev_kfree_skb_any(skb);
|
return ret;
|
||||||
htt->rx_ring.netbufs_ring[i] = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ath10k_htt_rx_free(struct ath10k_htt *htt)
|
void ath10k_htt_rx_free(struct ath10k_htt *htt)
|
||||||
@@ -184,8 +228,9 @@ void ath10k_htt_rx_free(struct ath10k_htt *htt)
|
|||||||
|
|
||||||
skb_queue_purge(&htt->tx_compl_q);
|
skb_queue_purge(&htt->tx_compl_q);
|
||||||
skb_queue_purge(&htt->rx_compl_q);
|
skb_queue_purge(&htt->rx_compl_q);
|
||||||
|
skb_queue_purge(&htt->rx_in_ord_compl_q);
|
||||||
|
|
||||||
ath10k_htt_rx_ring_clean_up(htt);
|
ath10k_htt_rx_ring_free(htt);
|
||||||
|
|
||||||
dma_free_coherent(htt->ar->dev,
|
dma_free_coherent(htt->ar->dev,
|
||||||
(htt->rx_ring.size *
|
(htt->rx_ring.size *
|
||||||
@@ -217,6 +262,7 @@ static inline struct sk_buff *ath10k_htt_rx_netbuf_pop(struct ath10k_htt *htt)
|
|||||||
idx = htt->rx_ring.sw_rd_idx.msdu_payld;
|
idx = htt->rx_ring.sw_rd_idx.msdu_payld;
|
||||||
msdu = htt->rx_ring.netbufs_ring[idx];
|
msdu = htt->rx_ring.netbufs_ring[idx];
|
||||||
htt->rx_ring.netbufs_ring[idx] = NULL;
|
htt->rx_ring.netbufs_ring[idx] = NULL;
|
||||||
|
htt->rx_ring.paddrs_ring[idx] = 0;
|
||||||
|
|
||||||
idx++;
|
idx++;
|
||||||
idx &= htt->rx_ring.size_mask;
|
idx &= htt->rx_ring.size_mask;
|
||||||
@@ -384,6 +430,82 @@ static void ath10k_htt_rx_replenish_task(unsigned long ptr)
|
|||||||
ath10k_htt_rx_msdu_buff_replenish(htt);
|
ath10k_htt_rx_msdu_buff_replenish(htt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct sk_buff *ath10k_htt_rx_pop_paddr(struct ath10k_htt *htt,
|
||||||
|
u32 paddr)
|
||||||
|
{
|
||||||
|
struct ath10k *ar = htt->ar;
|
||||||
|
struct ath10k_skb_rxcb *rxcb;
|
||||||
|
struct sk_buff *msdu;
|
||||||
|
|
||||||
|
lockdep_assert_held(&htt->rx_ring.lock);
|
||||||
|
|
||||||
|
msdu = ath10k_htt_rx_find_skb_paddr(ar, paddr);
|
||||||
|
if (!msdu)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
rxcb = ATH10K_SKB_RXCB(msdu);
|
||||||
|
hash_del(&rxcb->hlist);
|
||||||
|
htt->rx_ring.fill_cnt--;
|
||||||
|
|
||||||
|
dma_unmap_single(htt->ar->dev, rxcb->paddr,
|
||||||
|
msdu->len + skb_tailroom(msdu),
|
||||||
|
DMA_FROM_DEVICE);
|
||||||
|
ath10k_dbg_dump(ar, ATH10K_DBG_HTT_DUMP, NULL, "htt rx netbuf pop: ",
|
||||||
|
msdu->data, msdu->len + skb_tailroom(msdu));
|
||||||
|
|
||||||
|
return msdu;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ath10k_htt_rx_pop_paddr_list(struct ath10k_htt *htt,
|
||||||
|
struct htt_rx_in_ord_ind *ev,
|
||||||
|
struct sk_buff_head *list)
|
||||||
|
{
|
||||||
|
struct ath10k *ar = htt->ar;
|
||||||
|
struct htt_rx_in_ord_msdu_desc *msdu_desc = ev->msdu_descs;
|
||||||
|
struct htt_rx_desc *rxd;
|
||||||
|
struct sk_buff *msdu;
|
||||||
|
int msdu_count;
|
||||||
|
bool is_offload;
|
||||||
|
u32 paddr;
|
||||||
|
|
||||||
|
lockdep_assert_held(&htt->rx_ring.lock);
|
||||||
|
|
||||||
|
msdu_count = __le16_to_cpu(ev->msdu_count);
|
||||||
|
is_offload = !!(ev->info & HTT_RX_IN_ORD_IND_INFO_OFFLOAD_MASK);
|
||||||
|
|
||||||
|
while (msdu_count--) {
|
||||||
|
paddr = __le32_to_cpu(msdu_desc->msdu_paddr);
|
||||||
|
|
||||||
|
msdu = ath10k_htt_rx_pop_paddr(htt, paddr);
|
||||||
|
if (!msdu) {
|
||||||
|
__skb_queue_purge(list);
|
||||||
|
return -ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
__skb_queue_tail(list, msdu);
|
||||||
|
|
||||||
|
if (!is_offload) {
|
||||||
|
rxd = (void *)msdu->data;
|
||||||
|
|
||||||
|
trace_ath10k_htt_rx_desc(ar, rxd, sizeof(*rxd));
|
||||||
|
|
||||||
|
skb_put(msdu, sizeof(*rxd));
|
||||||
|
skb_pull(msdu, sizeof(*rxd));
|
||||||
|
skb_put(msdu, __le16_to_cpu(msdu_desc->msdu_len));
|
||||||
|
|
||||||
|
if (!(__le32_to_cpu(rxd->attention.flags) &
|
||||||
|
RX_ATTENTION_FLAGS_MSDU_DONE)) {
|
||||||
|
ath10k_warn(htt->ar, "tried to pop an incomplete frame, oops!\n");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msdu_desc++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int ath10k_htt_rx_alloc(struct ath10k_htt *htt)
|
int ath10k_htt_rx_alloc(struct ath10k_htt *htt)
|
||||||
{
|
{
|
||||||
struct ath10k *ar = htt->ar;
|
struct ath10k *ar = htt->ar;
|
||||||
@@ -429,7 +551,7 @@ int ath10k_htt_rx_alloc(struct ath10k_htt *htt)
|
|||||||
|
|
||||||
htt->rx_ring.alloc_idx.vaddr = vaddr;
|
htt->rx_ring.alloc_idx.vaddr = vaddr;
|
||||||
htt->rx_ring.alloc_idx.paddr = paddr;
|
htt->rx_ring.alloc_idx.paddr = paddr;
|
||||||
htt->rx_ring.sw_rd_idx.msdu_payld = 0;
|
htt->rx_ring.sw_rd_idx.msdu_payld = htt->rx_ring.size_mask;
|
||||||
*htt->rx_ring.alloc_idx.vaddr = 0;
|
*htt->rx_ring.alloc_idx.vaddr = 0;
|
||||||
|
|
||||||
/* Initialize the Rx refill retry timer */
|
/* Initialize the Rx refill retry timer */
|
||||||
@@ -438,14 +560,15 @@ int ath10k_htt_rx_alloc(struct ath10k_htt *htt)
|
|||||||
spin_lock_init(&htt->rx_ring.lock);
|
spin_lock_init(&htt->rx_ring.lock);
|
||||||
|
|
||||||
htt->rx_ring.fill_cnt = 0;
|
htt->rx_ring.fill_cnt = 0;
|
||||||
if (__ath10k_htt_rx_ring_fill_n(htt, htt->rx_ring.fill_level))
|
htt->rx_ring.sw_rd_idx.msdu_payld = 0;
|
||||||
goto err_fill_ring;
|
hash_init(htt->rx_ring.skb_table);
|
||||||
|
|
||||||
tasklet_init(&htt->rx_replenish_task, ath10k_htt_rx_replenish_task,
|
tasklet_init(&htt->rx_replenish_task, ath10k_htt_rx_replenish_task,
|
||||||
(unsigned long)htt);
|
(unsigned long)htt);
|
||||||
|
|
||||||
skb_queue_head_init(&htt->tx_compl_q);
|
skb_queue_head_init(&htt->tx_compl_q);
|
||||||
skb_queue_head_init(&htt->rx_compl_q);
|
skb_queue_head_init(&htt->rx_compl_q);
|
||||||
|
skb_queue_head_init(&htt->rx_in_ord_compl_q);
|
||||||
|
|
||||||
tasklet_init(&htt->txrx_compl_task, ath10k_htt_txrx_compl_task,
|
tasklet_init(&htt->txrx_compl_task, ath10k_htt_txrx_compl_task,
|
||||||
(unsigned long)htt);
|
(unsigned long)htt);
|
||||||
@@ -454,12 +577,6 @@ int ath10k_htt_rx_alloc(struct ath10k_htt *htt)
|
|||||||
htt->rx_ring.size, htt->rx_ring.fill_level);
|
htt->rx_ring.size, htt->rx_ring.fill_level);
|
||||||
return 0;
|
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:
|
err_dma_idx:
|
||||||
dma_free_coherent(htt->ar->dev,
|
dma_free_coherent(htt->ar->dev,
|
||||||
(htt->rx_ring.size *
|
(htt->rx_ring.size *
|
||||||
@@ -1583,6 +1700,194 @@ static void ath10k_htt_rx_delba(struct ath10k *ar, struct htt_resp *resp)
|
|||||||
spin_unlock_bh(&ar->data_lock);
|
spin_unlock_bh(&ar->data_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int ath10k_htt_rx_extract_amsdu(struct sk_buff_head *list,
|
||||||
|
struct sk_buff_head *amsdu)
|
||||||
|
{
|
||||||
|
struct sk_buff *msdu;
|
||||||
|
struct htt_rx_desc *rxd;
|
||||||
|
|
||||||
|
if (skb_queue_empty(list))
|
||||||
|
return -ENOBUFS;
|
||||||
|
|
||||||
|
if (WARN_ON(!skb_queue_empty(amsdu)))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
while ((msdu = __skb_dequeue(list))) {
|
||||||
|
__skb_queue_tail(amsdu, msdu);
|
||||||
|
|
||||||
|
rxd = (void *)msdu->data - sizeof(*rxd);
|
||||||
|
if (rxd->msdu_end.info0 &
|
||||||
|
__cpu_to_le32(RX_MSDU_END_INFO0_LAST_MSDU))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
msdu = skb_peek_tail(amsdu);
|
||||||
|
rxd = (void *)msdu->data - sizeof(*rxd);
|
||||||
|
if (!(rxd->msdu_end.info0 &
|
||||||
|
__cpu_to_le32(RX_MSDU_END_INFO0_LAST_MSDU))) {
|
||||||
|
skb_queue_splice_init(amsdu, list);
|
||||||
|
return -EAGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ath10k_htt_rx_h_rx_offload_prot(struct ieee80211_rx_status *status,
|
||||||
|
struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
|
||||||
|
|
||||||
|
if (!ieee80211_has_protected(hdr->frame_control))
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Offloaded frames are already decrypted but firmware insists they are
|
||||||
|
* protected in the 802.11 header. Strip the flag. Otherwise mac80211
|
||||||
|
* will drop the frame.
|
||||||
|
*/
|
||||||
|
|
||||||
|
hdr->frame_control &= ~__cpu_to_le16(IEEE80211_FCTL_PROTECTED);
|
||||||
|
status->flag |= RX_FLAG_DECRYPTED |
|
||||||
|
RX_FLAG_IV_STRIPPED |
|
||||||
|
RX_FLAG_MMIC_STRIPPED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ath10k_htt_rx_h_rx_offload(struct ath10k *ar,
|
||||||
|
struct sk_buff_head *list)
|
||||||
|
{
|
||||||
|
struct ath10k_htt *htt = &ar->htt;
|
||||||
|
struct ieee80211_rx_status *status = &htt->rx_status;
|
||||||
|
struct htt_rx_offload_msdu *rx;
|
||||||
|
struct sk_buff *msdu;
|
||||||
|
size_t offset;
|
||||||
|
|
||||||
|
while ((msdu = __skb_dequeue(list))) {
|
||||||
|
/* Offloaded frames don't have Rx descriptor. Instead they have
|
||||||
|
* a short meta information header.
|
||||||
|
*/
|
||||||
|
|
||||||
|
rx = (void *)msdu->data;
|
||||||
|
|
||||||
|
skb_put(msdu, sizeof(*rx));
|
||||||
|
skb_pull(msdu, sizeof(*rx));
|
||||||
|
|
||||||
|
if (skb_tailroom(msdu) < __le16_to_cpu(rx->msdu_len)) {
|
||||||
|
ath10k_warn(ar, "dropping frame: offloaded rx msdu is too long!\n");
|
||||||
|
dev_kfree_skb_any(msdu);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
skb_put(msdu, __le16_to_cpu(rx->msdu_len));
|
||||||
|
|
||||||
|
/* Offloaded rx header length isn't multiple of 2 nor 4 so the
|
||||||
|
* actual payload is unaligned. Align the frame. Otherwise
|
||||||
|
* mac80211 complains. This shouldn't reduce performance much
|
||||||
|
* because these offloaded frames are rare.
|
||||||
|
*/
|
||||||
|
offset = 4 - ((unsigned long)msdu->data & 3);
|
||||||
|
skb_put(msdu, offset);
|
||||||
|
memmove(msdu->data + offset, msdu->data, msdu->len);
|
||||||
|
skb_pull(msdu, offset);
|
||||||
|
|
||||||
|
/* FIXME: The frame is NWifi. Re-construct QoS Control
|
||||||
|
* if possible later.
|
||||||
|
*/
|
||||||
|
|
||||||
|
memset(status, 0, sizeof(*status));
|
||||||
|
status->flag |= RX_FLAG_NO_SIGNAL_VAL;
|
||||||
|
|
||||||
|
ath10k_htt_rx_h_rx_offload_prot(status, msdu);
|
||||||
|
ath10k_htt_rx_h_channel(ar, status);
|
||||||
|
ath10k_process_rx(ar, status, msdu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ath10k_htt_rx_in_ord_ind(struct ath10k *ar, struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
struct ath10k_htt *htt = &ar->htt;
|
||||||
|
struct htt_resp *resp = (void *)skb->data;
|
||||||
|
struct ieee80211_rx_status *status = &htt->rx_status;
|
||||||
|
struct sk_buff_head list;
|
||||||
|
struct sk_buff_head amsdu;
|
||||||
|
u16 peer_id;
|
||||||
|
u16 msdu_count;
|
||||||
|
u8 vdev_id;
|
||||||
|
u8 tid;
|
||||||
|
bool offload;
|
||||||
|
bool frag;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
lockdep_assert_held(&htt->rx_ring.lock);
|
||||||
|
|
||||||
|
if (htt->rx_confused)
|
||||||
|
return;
|
||||||
|
|
||||||
|
skb_pull(skb, sizeof(resp->hdr));
|
||||||
|
skb_pull(skb, sizeof(resp->rx_in_ord_ind));
|
||||||
|
|
||||||
|
peer_id = __le16_to_cpu(resp->rx_in_ord_ind.peer_id);
|
||||||
|
msdu_count = __le16_to_cpu(resp->rx_in_ord_ind.msdu_count);
|
||||||
|
vdev_id = resp->rx_in_ord_ind.vdev_id;
|
||||||
|
tid = SM(resp->rx_in_ord_ind.info, HTT_RX_IN_ORD_IND_INFO_TID);
|
||||||
|
offload = !!(resp->rx_in_ord_ind.info &
|
||||||
|
HTT_RX_IN_ORD_IND_INFO_OFFLOAD_MASK);
|
||||||
|
frag = !!(resp->rx_in_ord_ind.info & HTT_RX_IN_ORD_IND_INFO_FRAG_MASK);
|
||||||
|
|
||||||
|
ath10k_dbg(ar, ATH10K_DBG_HTT,
|
||||||
|
"htt rx in ord vdev %i peer %i tid %i offload %i frag %i msdu count %i\n",
|
||||||
|
vdev_id, peer_id, tid, offload, frag, msdu_count);
|
||||||
|
|
||||||
|
if (skb->len < msdu_count * sizeof(*resp->rx_in_ord_ind.msdu_descs)) {
|
||||||
|
ath10k_warn(ar, "dropping invalid in order rx indication\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The event can deliver more than 1 A-MSDU. Each A-MSDU is later
|
||||||
|
* extracted and processed.
|
||||||
|
*/
|
||||||
|
__skb_queue_head_init(&list);
|
||||||
|
ret = ath10k_htt_rx_pop_paddr_list(htt, &resp->rx_in_ord_ind, &list);
|
||||||
|
if (ret < 0) {
|
||||||
|
ath10k_warn(ar, "failed to pop paddr list: %d\n", ret);
|
||||||
|
htt->rx_confused = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Offloaded frames are very different and need to be handled
|
||||||
|
* separately.
|
||||||
|
*/
|
||||||
|
if (offload)
|
||||||
|
ath10k_htt_rx_h_rx_offload(ar, &list);
|
||||||
|
|
||||||
|
while (!skb_queue_empty(&list)) {
|
||||||
|
__skb_queue_head_init(&amsdu);
|
||||||
|
ret = ath10k_htt_rx_extract_amsdu(&list, &amsdu);
|
||||||
|
switch (ret) {
|
||||||
|
case 0:
|
||||||
|
/* Note: The in-order indication may report interleaved
|
||||||
|
* frames from different PPDUs meaning reported rx rate
|
||||||
|
* to mac80211 isn't accurate/reliable. It's still
|
||||||
|
* better to report something than nothing though. This
|
||||||
|
* should still give an idea about rx rate to the user.
|
||||||
|
*/
|
||||||
|
ath10k_htt_rx_h_ppdu(ar, &amsdu, status);
|
||||||
|
ath10k_htt_rx_h_filter(ar, &amsdu, status);
|
||||||
|
ath10k_htt_rx_h_mpdu(ar, &amsdu, status);
|
||||||
|
ath10k_htt_rx_h_deliver(ar, &amsdu, status);
|
||||||
|
break;
|
||||||
|
case -EAGAIN:
|
||||||
|
/* fall through */
|
||||||
|
default:
|
||||||
|
/* Should not happen. */
|
||||||
|
ath10k_warn(ar, "failed to extract amsdu: %d\n", ret);
|
||||||
|
htt->rx_confused = true;
|
||||||
|
__skb_queue_purge(&list);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasklet_schedule(&htt->rx_replenish_task);
|
||||||
|
}
|
||||||
|
|
||||||
void ath10k_htt_t2h_msg_handler(struct ath10k *ar, struct sk_buff *skb)
|
void ath10k_htt_t2h_msg_handler(struct ath10k *ar, struct sk_buff *skb)
|
||||||
{
|
{
|
||||||
struct ath10k_htt *htt = &ar->htt;
|
struct ath10k_htt *htt = &ar->htt;
|
||||||
@@ -1705,6 +2010,20 @@ void ath10k_htt_t2h_msg_handler(struct ath10k *ar, struct sk_buff *skb)
|
|||||||
*/
|
*/
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case HTT_T2H_MSG_TYPE_RX_IN_ORD_PADDR_IND: {
|
||||||
|
spin_lock_bh(&htt->rx_ring.lock);
|
||||||
|
__skb_queue_tail(&htt->rx_in_ord_compl_q, skb);
|
||||||
|
spin_unlock_bh(&htt->rx_ring.lock);
|
||||||
|
tasklet_schedule(&htt->txrx_compl_task);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case HTT_T2H_MSG_TYPE_TX_CREDIT_UPDATE_IND:
|
||||||
|
/* FIXME: This WMI-TLV event is overlapping with 10.2
|
||||||
|
* CHAN_CHANGE - both being 0xF. Neither is being used in
|
||||||
|
* practice so no immediate action is necessary. Nevertheless
|
||||||
|
* HTT may need an abstraction layer like WMI has one day.
|
||||||
|
*/
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
ath10k_warn(ar, "htt event (%d) not handled\n",
|
ath10k_warn(ar, "htt event (%d) not handled\n",
|
||||||
resp->hdr.msg_type);
|
resp->hdr.msg_type);
|
||||||
@@ -1720,6 +2039,7 @@ void ath10k_htt_t2h_msg_handler(struct ath10k *ar, struct sk_buff *skb)
|
|||||||
static void ath10k_htt_txrx_compl_task(unsigned long ptr)
|
static void ath10k_htt_txrx_compl_task(unsigned long ptr)
|
||||||
{
|
{
|
||||||
struct ath10k_htt *htt = (struct ath10k_htt *)ptr;
|
struct ath10k_htt *htt = (struct ath10k_htt *)ptr;
|
||||||
|
struct ath10k *ar = htt->ar;
|
||||||
struct htt_resp *resp;
|
struct htt_resp *resp;
|
||||||
struct sk_buff *skb;
|
struct sk_buff *skb;
|
||||||
|
|
||||||
@@ -1736,5 +2056,10 @@ static void ath10k_htt_txrx_compl_task(unsigned long ptr)
|
|||||||
ath10k_htt_rx_handler(htt, &resp->rx_ind);
|
ath10k_htt_rx_handler(htt, &resp->rx_ind);
|
||||||
dev_kfree_skb_any(skb);
|
dev_kfree_skb_any(skb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while ((skb = __skb_dequeue(&htt->rx_in_ord_compl_q))) {
|
||||||
|
ath10k_htt_rx_in_ord_ind(ar, skb);
|
||||||
|
dev_kfree_skb_any(skb);
|
||||||
|
}
|
||||||
spin_unlock_bh(&htt->rx_ring.lock);
|
spin_unlock_bh(&htt->rx_ring.lock);
|
||||||
}
|
}
|
||||||
|
@@ -1052,8 +1052,15 @@ static struct sk_buff *ath10k_wmi_tlv_op_gen_init(struct ath10k *ar)
|
|||||||
|
|
||||||
cfg->num_vdevs = __cpu_to_le32(TARGET_TLV_NUM_VDEVS);
|
cfg->num_vdevs = __cpu_to_le32(TARGET_TLV_NUM_VDEVS);
|
||||||
cfg->num_peers = __cpu_to_le32(TARGET_TLV_NUM_PEERS);
|
cfg->num_peers = __cpu_to_le32(TARGET_TLV_NUM_PEERS);
|
||||||
cfg->num_offload_peers = __cpu_to_le32(0);
|
|
||||||
cfg->num_offload_reorder_bufs = __cpu_to_le32(0);
|
if (test_bit(WMI_SERVICE_RX_FULL_REORDER, ar->wmi.svc_map)) {
|
||||||
|
cfg->num_offload_peers = __cpu_to_le32(3);
|
||||||
|
cfg->num_offload_reorder_bufs = __cpu_to_le32(3);
|
||||||
|
} else {
|
||||||
|
cfg->num_offload_peers = __cpu_to_le32(0);
|
||||||
|
cfg->num_offload_reorder_bufs = __cpu_to_le32(0);
|
||||||
|
}
|
||||||
|
|
||||||
cfg->num_peer_keys = __cpu_to_le32(2);
|
cfg->num_peer_keys = __cpu_to_le32(2);
|
||||||
cfg->num_tids = __cpu_to_le32(TARGET_TLV_NUM_TIDS);
|
cfg->num_tids = __cpu_to_le32(TARGET_TLV_NUM_TIDS);
|
||||||
cfg->ast_skid_limit = __cpu_to_le32(0x10);
|
cfg->ast_skid_limit = __cpu_to_le32(0x10);
|
||||||
|
Reference in New Issue
Block a user