/* * Copyright (c) 2006-2015 Marvell International Ltd. * * 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. */ /* Description: This file implements receive related functions. */ #include #include "mwl_sysadpt.h" #include "mwl_dev.h" #include "mwl_debug.h" #include "mwl_rx.h" /* CONSTANTS AND MACROS */ #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 /* PRIVATE FUNCTION DECLARATION */ static int mwl_rx_ring_alloc(struct mwl_priv *priv); static int mwl_rx_ring_init(struct mwl_priv *priv); static void mwl_rx_ring_cleanup(struct mwl_priv *priv); static void mwl_rx_ring_free(struct mwl_priv *priv); static inline void mwl_rx_prepare_status(struct mwl_rx_desc *pdesc, struct ieee80211_rx_status *status); static inline struct mwl_vif *mwl_rx_find_vif_bss(struct list_head *vif_list, u8 *bssid); static inline void mwl_rx_remove_dma_header(struct sk_buff *skb, u16 qos); static int mwl_rx_refill(struct mwl_priv *priv, struct mwl_rx_desc *pdesc); /* PUBLIC FUNCTION DEFINITION */ int mwl_rx_init(struct ieee80211_hw *hw) { struct mwl_priv *priv; int rc; WLDBG_ENTER(DBG_LEVEL_4); BUG_ON(!hw); priv = hw->priv; BUG_ON(!priv); rc = mwl_rx_ring_alloc(priv); if (rc) { WLDBG_ERROR(DBG_LEVEL_4, "allocating RX ring failed"); } else { rc = mwl_rx_ring_init(priv); if (rc) { mwl_rx_ring_free(priv); WLDBG_ERROR(DBG_LEVEL_4, "initializing RX ring failed"); } } WLDBG_EXIT(DBG_LEVEL_4); return rc; } void mwl_rx_deinit(struct ieee80211_hw *hw) { struct mwl_priv *priv; WLDBG_ENTER(DBG_LEVEL_4); BUG_ON(!hw); priv = hw->priv; BUG_ON(!priv); mwl_rx_ring_cleanup(priv); mwl_rx_ring_free(priv); WLDBG_EXIT(DBG_LEVEL_4); } 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; WLDBG_ENTER(DBG_LEVEL_4); BUG_ON(!hw); priv = hw->priv; BUG_ON(!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; WLDBG_EXIT_INFO(DBG_LEVEL_4, "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, ENDIAN_SWAP32(curr_desc->pphys_buff_data), priv->desc_data[0].rx_buf_size, PCI_DMA_FROMDEVICE); pkt_len = curr_desc->pkt_len; if (skb_tailroom(prx_skb) < pkt_len) { WLDBG_PRINT("Critical error: %x %x %x %x", skb_tailroom(prx_skb), pkt_len, curr_desc, curr_desc->pbuff_data); 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->vif_list, wh->addr1); else mwl_vif = mwl_rx_find_vif_bss(&priv->vif_list, 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; WLDBG_EXIT(DBG_LEVEL_4); } /* PRIVATE FUNCTION DEFINITION */ static int mwl_rx_ring_alloc(struct mwl_priv *priv) { WLDBG_ENTER_INFO(DBG_LEVEL_4, "allocating %i (0x%x) bytes", MAX_NUM_RX_RING_BYTES, MAX_NUM_RX_RING_BYTES); BUG_ON(!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) { WLDBG_ERROR(DBG_LEVEL_4, "can not alloc mem"); WLDBG_EXIT_INFO(DBG_LEVEL_4, "no memory"); return -ENOMEM; } memset(priv->desc_data[0].prx_ring, 0x00, MAX_NUM_RX_RING_BYTES); WLDBG_EXIT_INFO(DBG_LEVEL_4, "RX ring vaddr: 0x%x paddr: 0x%x", priv->desc_data[0].prx_ring, priv->desc_data[0].pphys_rx_ring); return 0; } static int mwl_rx_ring_init(struct mwl_priv *priv) { int curr_desc; struct mwl_desc_data *desc; WLDBG_ENTER_INFO(DBG_LEVEL_4, "initializing %i descriptors", SYSADPT_MAX_NUM_RX_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); WLDBG_ERROR(DBG_LEVEL_4, "need linearize memory"); WLDBG_EXIT_INFO(DBG_LEVEL_4, "no suitable 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 = 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 = ENDIAN_SWAP32(dma); CURR_RXD.pnext = &NEXT_RXD; val = (u32)desc->pphys_rx_ring + ((curr_desc + 1) * sizeof(struct mwl_rx_desc)); CURR_RXD.pphys_next = ENDIAN_SWAP32(val); WLDBG_INFO(DBG_LEVEL_4, "rxdesc:%i 0x%x(%i) 0x%x(%i)", curr_desc, EAGLE_TXD_STATUS_IDLE, EAGLE_TXD_STATUS_IDLE, desc->rx_buf_size, desc->rx_buf_size); WLDBG_INFO(DBG_LEVEL_4, "rxdesc: %i vnext: 0x%p pnext: 0x%x", curr_desc, CURR_RXD.pnext, ENDIAN_SWAP32(CURR_RXD.pphys_next)); } else { WLDBG_ERROR(DBG_LEVEL_4, "rxdesc %i: no skbuff available", curr_desc); WLDBG_EXIT_INFO(DBG_LEVEL_4, "no socket buffer"); return -ENOMEM; } } LAST_RXD.pphys_next = ENDIAN_SWAP32((u32)desc->pphys_rx_ring); LAST_RXD.pnext = &FIRST_RXD; priv->desc_data[0].pnext_rx_desc = &FIRST_RXD; WLDBG_EXIT_INFO(DBG_LEVEL_4, "last rxdesc vnext:0x%p pnext:0x%x vfirst 0x%x", LAST_RXD.pnext, ENDIAN_SWAP32(LAST_RXD.pphys_next), desc->pnext_rx_desc); return 0; } WLDBG_ERROR(DBG_LEVEL_4, "no valid RX mem"); WLDBG_EXIT_INFO(DBG_LEVEL_4, "no valid RX mem"); return -ENOMEM; } static void mwl_rx_ring_cleanup(struct mwl_priv *priv) { int curr_desc; WLDBG_ENTER(DBG_LEVEL_4); BUG_ON(!priv); 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, ENDIAN_SWAP32 (CURR_RXD.pphys_buff_data), priv->desc_data[0].rx_buf_size, PCI_DMA_FROMDEVICE); dev_kfree_skb_any(CURR_RXD.psk_buff); WLDBG_INFO(DBG_LEVEL_4, "unmapped+free'd %i 0x%p 0x%x %i", curr_desc, CURR_RXD.pbuff_data, ENDIAN_SWAP32(CURR_RXD.pphys_buff_data), priv->desc_data[0].rx_buf_size); CURR_RXD.pbuff_data = NULL; CURR_RXD.psk_buff = NULL; } } WLDBG_EXIT(DBG_LEVEL_4); } static void mwl_rx_ring_free(struct mwl_priv *priv) { WLDBG_ENTER(DBG_LEVEL_4); BUG_ON(!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; WLDBG_EXIT(DBG_LEVEL_4); } static inline void mwl_rx_prepare_status(struct mwl_rx_desc *pdesc, struct ieee80211_rx_status *status) { WLDBG_ENTER(DBG_LEVEL_4); BUG_ON(!pdesc); BUG_ON(!status); memset(status, 0, sizeof(*status)); status->signal = -(pdesc->rssi + W836X_RSSI_OFFSET); switch (pdesc->rate.format) { case RX_RATE_INFO_FORMAT_11N: status->flag |= RX_FLAG_HT; if (pdesc->rate.bw == RX_RATE_INFO_HT40) status->flag |= RX_FLAG_40MHZ; if (pdesc->rate.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 (pdesc->rate.bw == RX_RATE_INFO_HT40) status->flag |= RX_FLAG_40MHZ; if (pdesc->rate.bw == RX_RATE_INFO_HT80) status->vht_flag |= RX_VHT_FLAG_80MHZ; if (pdesc->rate.gi == RX_RATE_INFO_SHORT_INTERVAL) status->flag |= RX_FLAG_SHORT_GI; status->vht_nss = (pdesc->rate.nss + 1); break; } status->rate_idx = pdesc->rate.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; } } } WLDBG_EXIT(DBG_LEVEL_4); } static inline struct mwl_vif *mwl_rx_find_vif_bss(struct list_head *vif_list, u8 *bssid) { struct mwl_vif *mwl_vif; list_for_each_entry(mwl_vif, vif_list, list) { if (memcmp(bssid, mwl_vif->bssid, ETH_ALEN) == 0) return mwl_vif; } return NULL; } static inline void mwl_rx_remove_dma_header(struct sk_buff *skb, u16 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); *((u16 *)(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) { WLDBG_ENTER(DBG_LEVEL_4); BUG_ON(!priv); BUG_ON(!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); WLDBG_ERROR(DBG_LEVEL_4, "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 = priv->desc_data[0].rx_buf_size; pdesc->pbuff_data = pdesc->psk_buff->data; pdesc->pphys_buff_data = ENDIAN_SWAP32(pci_map_single(priv->pdev, pdesc->psk_buff->data, priv->desc_data[0].rx_buf_size, PCI_DMA_BIDIRECTIONAL)); WLDBG_EXIT(DBG_LEVEL_4); return 0; nomem: WLDBG_EXIT_INFO(DBG_LEVEL_4, "no memory"); return -ENOMEM; }