summaryrefslogtreecommitdiff
path: root/rx.c
diff options
context:
space:
mode:
Diffstat (limited to 'rx.c')
-rw-r--r--rx.c486
1 files changed, 486 insertions, 0 deletions
diff --git a/rx.c b/rx.c
new file mode 100644
index 0000000..34dafdf
--- /dev/null
+++ b/rx.c
@@ -0,0 +1,486 @@
+/*
+ * Copyright (C) 2006-2015, Marvell International Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/* Description: This file implements receive related functions.
+ */
+
+#include <linux/skbuff.h>
+
+#include "sysadpt.h"
+#include "dev.h"
+#include "rx.h"
+
+#define MAX_NUM_RX_RING_BYTES (SYSADPT_MAX_NUM_RX_DESC * \
+ sizeof(struct mwl_rx_desc))
+
+#define FIRST_RXD priv->desc_data[0].prx_ring[0]
+#define CURR_RXD priv->desc_data[0].prx_ring[curr_desc]
+#define NEXT_RXD priv->desc_data[0].prx_ring[curr_desc + 1]
+#define LAST_RXD priv->desc_data[0].prx_ring[SYSADPT_MAX_NUM_RX_DESC - 1]
+
+#define DECRYPT_ERR_MASK 0x80
+#define GENERAL_DECRYPT_ERR 0xFF
+#define TKIP_DECRYPT_MIC_ERR 0x02
+#define WEP_DECRYPT_ICV_ERR 0x04
+#define TKIP_DECRYPT_ICV_ERR 0x08
+
+#define W836X_RSSI_OFFSET 8
+
+/* Receive rate information constants */
+#define RX_RATE_INFO_FORMAT_11A 0
+#define RX_RATE_INFO_FORMAT_11B 1
+#define RX_RATE_INFO_FORMAT_11N 2
+#define RX_RATE_INFO_FORMAT_11AC 4
+
+#define RX_RATE_INFO_HT20 0
+#define RX_RATE_INFO_HT40 1
+#define RX_RATE_INFO_HT80 2
+
+#define RX_RATE_INFO_LONG_INTERVAL 0
+#define RX_RATE_INFO_SHORT_INTERVAL 1
+
+static int mwl_rx_ring_alloc(struct mwl_priv *priv)
+{
+ priv->desc_data[0].prx_ring =
+ (struct mwl_rx_desc *)
+ dma_alloc_coherent(&priv->pdev->dev,
+ MAX_NUM_RX_RING_BYTES,
+ &priv->desc_data[0].pphys_rx_ring,
+ GFP_KERNEL);
+
+ if (!priv->desc_data[0].prx_ring) {
+ wiphy_err(priv->hw->wiphy, "can not alloc mem");
+ return -ENOMEM;
+ }
+
+ memset(priv->desc_data[0].prx_ring, 0x00, MAX_NUM_RX_RING_BYTES);
+
+ return 0;
+}
+
+static int mwl_rx_ring_init(struct mwl_priv *priv)
+{
+ int curr_desc;
+ struct mwl_desc_data *desc;
+
+ desc = &priv->desc_data[0];
+
+ if (desc->prx_ring) {
+ desc->rx_buf_size = SYSADPT_MAX_AGGR_SIZE;
+
+ for (curr_desc = 0; curr_desc < SYSADPT_MAX_NUM_RX_DESC;
+ curr_desc++) {
+ CURR_RXD.psk_buff =
+ dev_alloc_skb(desc->rx_buf_size);
+
+ if (skb_linearize(CURR_RXD.psk_buff)) {
+ dev_kfree_skb_any(CURR_RXD.psk_buff);
+ wiphy_err(priv->hw->wiphy,
+ "need linearize memory");
+ return -ENOMEM;
+ }
+
+ skb_reserve(CURR_RXD.psk_buff,
+ SYSADPT_MIN_BYTES_HEADROOM);
+ CURR_RXD.rx_control = EAGLE_RXD_CTRL_DRIVER_OWN;
+ CURR_RXD.status = EAGLE_RXD_STATUS_OK;
+ CURR_RXD.qos_ctrl = 0x0000;
+ CURR_RXD.channel = 0x00;
+ CURR_RXD.rssi = 0x00;
+
+ if (CURR_RXD.psk_buff) {
+ dma_addr_t dma;
+ u32 val;
+
+ CURR_RXD.pkt_len =
+ cpu_to_le16(SYSADPT_MAX_AGGR_SIZE);
+ CURR_RXD.pbuff_data = CURR_RXD.psk_buff->data;
+ dma = pci_map_single(priv->pdev,
+ CURR_RXD.psk_buff->data,
+ desc->rx_buf_size,
+ PCI_DMA_FROMDEVICE);
+ CURR_RXD.pphys_buff_data =
+ cpu_to_le32(dma);
+ CURR_RXD.pnext = &NEXT_RXD;
+ val = (u32)desc->pphys_rx_ring +
+ ((curr_desc + 1) *
+ sizeof(struct mwl_rx_desc));
+ CURR_RXD.pphys_next =
+ cpu_to_le32(val);
+ } else {
+ wiphy_err(priv->hw->wiphy,
+ "rxdesc %i: no skbuff available",
+ curr_desc);
+ return -ENOMEM;
+ }
+ }
+ LAST_RXD.pphys_next =
+ cpu_to_le32((u32)desc->pphys_rx_ring);
+ LAST_RXD.pnext = &FIRST_RXD;
+ priv->desc_data[0].pnext_rx_desc = &FIRST_RXD;
+
+ return 0;
+ }
+
+ wiphy_err(priv->hw->wiphy, "no valid RX mem");
+
+ return -ENOMEM;
+}
+
+static void mwl_rx_ring_cleanup(struct mwl_priv *priv)
+{
+ int curr_desc;
+
+ if (priv->desc_data[0].prx_ring) {
+ for (curr_desc = 0; curr_desc < SYSADPT_MAX_NUM_RX_DESC;
+ curr_desc++) {
+ if (!CURR_RXD.psk_buff)
+ continue;
+
+ if (skb_shinfo(CURR_RXD.psk_buff)->nr_frags)
+ skb_shinfo(CURR_RXD.psk_buff)->nr_frags = 0;
+
+ if (skb_shinfo(CURR_RXD.psk_buff)->frag_list)
+ skb_shinfo(CURR_RXD.psk_buff)->frag_list = NULL;
+
+ pci_unmap_single(priv->pdev,
+ le32_to_cpu
+ (CURR_RXD.pphys_buff_data),
+ priv->desc_data[0].rx_buf_size,
+ PCI_DMA_FROMDEVICE);
+
+ dev_kfree_skb_any(CURR_RXD.psk_buff);
+
+ wiphy_info(priv->hw->wiphy,
+ "unmapped+free'd %i 0x%p 0x%x %i",
+ curr_desc, CURR_RXD.pbuff_data,
+ le32_to_cpu(CURR_RXD.pphys_buff_data),
+ priv->desc_data[0].rx_buf_size);
+
+ CURR_RXD.pbuff_data = NULL;
+ CURR_RXD.psk_buff = NULL;
+ }
+ }
+}
+
+static void mwl_rx_ring_free(struct mwl_priv *priv)
+{
+ if (priv->desc_data[0].prx_ring) {
+ mwl_rx_ring_cleanup(priv);
+
+ dma_free_coherent(&priv->pdev->dev,
+ MAX_NUM_RX_RING_BYTES,
+ priv->desc_data[0].prx_ring,
+ priv->desc_data[0].pphys_rx_ring);
+
+ priv->desc_data[0].prx_ring = NULL;
+ }
+
+ priv->desc_data[0].pnext_rx_desc = NULL;
+}
+
+static inline void mwl_rx_prepare_status(struct mwl_rx_desc *pdesc,
+ struct ieee80211_rx_status *status)
+{
+ u16 rate, format, nss, bw, gi, rt;
+
+ memset(status, 0, sizeof(*status));
+
+ status->signal = -(pdesc->rssi + W836X_RSSI_OFFSET);
+
+ rate = le16_to_cpu(pdesc->rate);
+ format = rate & MWL_RX_RATE_FORMAT_MASK;
+ nss = (rate & MWL_RX_RATE_NSS_MASK) >> MWL_RX_RATE_NSS_SHIFT;
+ bw = (rate & MWL_RX_RATE_BW_MASK) >> MWL_RX_RATE_BW_SHIFT;
+ gi = (rate & MWL_RX_RATE_GI_MASK) >> MWL_RX_RATE_GI_SHIFT;
+ rt = (rate & MWL_RX_RATE_RT_MASK) >> MWL_RX_RATE_RT_SHIFT;
+
+ switch (format) {
+ case RX_RATE_INFO_FORMAT_11N:
+ status->flag |= RX_FLAG_HT;
+ if (bw == RX_RATE_INFO_HT40)
+ status->flag |= RX_FLAG_40MHZ;
+ if (gi == RX_RATE_INFO_SHORT_INTERVAL)
+ status->flag |= RX_FLAG_SHORT_GI;
+ break;
+ case RX_RATE_INFO_FORMAT_11AC:
+ status->flag |= RX_FLAG_VHT;
+ if (bw == RX_RATE_INFO_HT40)
+ status->flag |= RX_FLAG_40MHZ;
+ if (bw == RX_RATE_INFO_HT80)
+ status->vht_flag |= RX_VHT_FLAG_80MHZ;
+ if (gi == RX_RATE_INFO_SHORT_INTERVAL)
+ status->flag |= RX_FLAG_SHORT_GI;
+ status->vht_nss = (nss + 1);
+ break;
+ }
+
+ status->rate_idx = rt;
+
+ if (pdesc->channel > BAND_24_CHANNEL_NUM) {
+ status->band = IEEE80211_BAND_5GHZ;
+ if ((!(status->flag & RX_FLAG_HT)) &&
+ (!(status->flag & RX_FLAG_VHT))) {
+ status->rate_idx -= 5;
+ if (status->rate_idx >= BAND_50_RATE_NUM)
+ status->rate_idx = BAND_50_RATE_NUM - 1;
+ }
+ } else {
+ status->band = IEEE80211_BAND_2GHZ;
+ if ((!(status->flag & RX_FLAG_HT)) &&
+ (!(status->flag & RX_FLAG_VHT))) {
+ if (status->rate_idx >= BAND_24_RATE_NUM)
+ status->rate_idx = BAND_24_RATE_NUM - 1;
+ }
+ }
+
+ status->freq = ieee80211_channel_to_frequency(pdesc->channel,
+ status->band);
+
+ /* check if status has a specific error bit (bit 7) set or indicates
+ * a general decrypt error
+ */
+ if ((pdesc->status == GENERAL_DECRYPT_ERR) ||
+ (pdesc->status & DECRYPT_ERR_MASK)) {
+ /* check if status is not equal to 0xFF
+ * the 0xFF check is for backward compatibility
+ */
+ if (pdesc->status != GENERAL_DECRYPT_ERR) {
+ if (((pdesc->status & (~DECRYPT_ERR_MASK)) &
+ TKIP_DECRYPT_MIC_ERR) && !((pdesc->status &
+ (WEP_DECRYPT_ICV_ERR | TKIP_DECRYPT_ICV_ERR)))) {
+ status->flag |= RX_FLAG_MMIC_ERROR;
+ }
+ }
+ }
+}
+
+static inline struct mwl_vif *mwl_rx_find_vif_bss(struct mwl_priv *priv,
+ u8 *bssid)
+{
+ struct mwl_vif *mwl_vif;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->vif_lock, flags);
+ list_for_each_entry(mwl_vif, &priv->vif_list, list) {
+ if (memcmp(bssid, mwl_vif->bssid, ETH_ALEN) == 0) {
+ spin_unlock_irqrestore(&priv->vif_lock, flags);
+ return mwl_vif;
+ }
+ }
+ spin_unlock_irqrestore(&priv->vif_lock, flags);
+
+ return NULL;
+}
+
+static inline void mwl_rx_remove_dma_header(struct sk_buff *skb, __le16 qos)
+{
+ struct mwl_dma_data *tr;
+ int hdrlen;
+
+ tr = (struct mwl_dma_data *)skb->data;
+ hdrlen = ieee80211_hdrlen(tr->wh.frame_control);
+
+ if (hdrlen != sizeof(tr->wh)) {
+ if (ieee80211_is_data_qos(tr->wh.frame_control)) {
+ memmove(tr->data - hdrlen, &tr->wh, hdrlen - 2);
+ *((__le16 *)(tr->data - 2)) = qos;
+ } else {
+ memmove(tr->data - hdrlen, &tr->wh, hdrlen);
+ }
+ }
+
+ if (hdrlen != sizeof(*tr))
+ skb_pull(skb, sizeof(*tr) - hdrlen);
+}
+
+static int mwl_rx_refill(struct mwl_priv *priv, struct mwl_rx_desc *pdesc)
+{
+ pdesc->psk_buff = dev_alloc_skb(priv->desc_data[0].rx_buf_size);
+
+ if (!pdesc->psk_buff)
+ goto nomem;
+
+ if (skb_linearize(pdesc->psk_buff)) {
+ dev_kfree_skb_any(pdesc->psk_buff);
+ wiphy_err(priv->hw->wiphy, "need linearize memory");
+ goto nomem;
+ }
+
+ skb_reserve(pdesc->psk_buff, SYSADPT_MIN_BYTES_HEADROOM);
+
+ pdesc->status = EAGLE_RXD_STATUS_OK;
+ pdesc->qos_ctrl = 0x0000;
+ pdesc->channel = 0x00;
+ pdesc->rssi = 0x00;
+
+ pdesc->pkt_len = cpu_to_le16(priv->desc_data[0].rx_buf_size);
+ pdesc->pbuff_data = pdesc->psk_buff->data;
+ pdesc->pphys_buff_data =
+ cpu_to_le32(pci_map_single(priv->pdev,
+ pdesc->psk_buff->data,
+ priv->desc_data[0].rx_buf_size,
+ PCI_DMA_BIDIRECTIONAL));
+
+ return 0;
+
+nomem:
+
+ wiphy_err(priv->hw->wiphy, "no memory");
+
+ return -ENOMEM;
+}
+
+int mwl_rx_init(struct ieee80211_hw *hw)
+{
+ struct mwl_priv *priv;
+ int rc;
+
+ priv = hw->priv;
+
+ rc = mwl_rx_ring_alloc(priv);
+ if (rc) {
+ wiphy_err(hw->wiphy, "allocating RX ring failed");
+ } else {
+ rc = mwl_rx_ring_init(priv);
+ if (rc) {
+ mwl_rx_ring_free(priv);
+ wiphy_err(hw->wiphy,
+ "initializing RX ring failed");
+ }
+ }
+
+ return rc;
+}
+
+void mwl_rx_deinit(struct ieee80211_hw *hw)
+{
+ struct mwl_priv *priv;
+
+ priv = hw->priv;
+
+ mwl_rx_ring_cleanup(priv);
+ mwl_rx_ring_free(priv);
+}
+
+void mwl_rx_recv(unsigned long data)
+{
+ struct ieee80211_hw *hw = (struct ieee80211_hw *)data;
+ struct mwl_priv *priv;
+ struct mwl_rx_desc *curr_desc;
+ int work_done = 0;
+ struct sk_buff *prx_skb = NULL;
+ int pkt_len;
+ struct ieee80211_rx_status status;
+ struct mwl_vif *mwl_vif = NULL;
+ struct ieee80211_hdr *wh;
+ u32 status_mask;
+
+ priv = hw->priv;
+
+ curr_desc = priv->desc_data[0].pnext_rx_desc;
+
+ if (!curr_desc) {
+ status_mask = readl(priv->iobase1 +
+ MACREG_REG_A2H_INTERRUPT_STATUS_MASK);
+ writel(status_mask | MACREG_A2HRIC_BIT_RX_RDY,
+ priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK);
+
+ priv->is_rx_schedule = false;
+
+ wiphy_warn(hw->wiphy, "busy or no receiving packets");
+ return;
+ }
+
+ while ((curr_desc->rx_control == EAGLE_RXD_CTRL_DMA_OWN) &&
+ (work_done < priv->recv_limit)) {
+ prx_skb = curr_desc->psk_buff;
+ if (!prx_skb)
+ goto out;
+ pci_unmap_single(priv->pdev,
+ le32_to_cpu(curr_desc->pphys_buff_data),
+ priv->desc_data[0].rx_buf_size,
+ PCI_DMA_FROMDEVICE);
+ pkt_len = le16_to_cpu(curr_desc->pkt_len);
+
+ if (skb_tailroom(prx_skb) < pkt_len) {
+ dev_kfree_skb_any(prx_skb);
+ goto out;
+ }
+
+ if (curr_desc->channel != hw->conf.chandef.chan->hw_value) {
+ dev_kfree_skb_any(prx_skb);
+ goto out;
+ }
+
+ mwl_rx_prepare_status(curr_desc, &status);
+
+ priv->noise = -curr_desc->noise_floor;
+
+ wh = &((struct mwl_dma_data *)prx_skb->data)->wh;
+
+ if (ieee80211_has_protected(wh->frame_control)) {
+ /* Check if hw crypto has been enabled for
+ * this bss. If yes, set the status flags
+ * accordingly
+ */
+ if (ieee80211_has_tods(wh->frame_control))
+ mwl_vif = mwl_rx_find_vif_bss(priv, wh->addr1);
+ else
+ mwl_vif = mwl_rx_find_vif_bss(priv, wh->addr2);
+
+ if (mwl_vif && mwl_vif->is_hw_crypto_enabled) {
+ /* When MMIC ERROR is encountered
+ * by the firmware, payload is
+ * dropped and only 32 bytes of
+ * mwl8k Firmware header is sent
+ * to the host.
+ *
+ * We need to add four bytes of
+ * key information. In it
+ * MAC80211 expects keyidx set to
+ * 0 for triggering Counter
+ * Measure of MMIC failure.
+ */
+ if (status.flag & RX_FLAG_MMIC_ERROR) {
+ struct mwl_dma_data *tr;
+
+ tr = (struct mwl_dma_data *)
+ prx_skb->data;
+ memset((void *)&tr->data, 0, 4);
+ pkt_len += 4;
+ }
+
+ if (!ieee80211_is_auth(wh->frame_control))
+ status.flag |= RX_FLAG_IV_STRIPPED |
+ RX_FLAG_DECRYPTED |
+ RX_FLAG_MMIC_STRIPPED;
+ }
+ }
+
+ skb_put(prx_skb, pkt_len);
+ mwl_rx_remove_dma_header(prx_skb, curr_desc->qos_ctrl);
+ memcpy(IEEE80211_SKB_RXCB(prx_skb), &status, sizeof(status));
+ ieee80211_rx(hw, prx_skb);
+out:
+ mwl_rx_refill(priv, curr_desc);
+ curr_desc->rx_control = EAGLE_RXD_CTRL_DRIVER_OWN;
+ curr_desc->qos_ctrl = 0;
+ curr_desc = curr_desc->pnext;
+ work_done++;
+ }
+
+ priv->desc_data[0].pnext_rx_desc = curr_desc;
+
+ status_mask = readl(priv->iobase1 +
+ MACREG_REG_A2H_INTERRUPT_STATUS_MASK);
+ writel(status_mask | MACREG_A2HRIC_BIT_RX_RDY,
+ priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK);
+
+ priv->is_rx_schedule = false;
+}