| /* |
| * Copyright (c) 2011-2018 The Linux Foundation. All rights reserved. |
| * |
| * Previously licensed under the ISC license by 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. |
| */ |
| |
| /* |
| * This file was originally distributed by Qualcomm Atheros, Inc. |
| * under proprietary terms before Copyright ownership was assigned |
| * to the Linux Foundation. |
| */ |
| |
| /** |
| * @file htt_rx.c |
| * @brief Implement receive aspects of HTT. |
| * @details |
| * This file contains three categories of HTT rx code: |
| * 1. An abstraction of the rx descriptor, to hide the |
| * differences between the HL vs. LL rx descriptor. |
| * 2. Functions for providing access to the (series of) |
| * rx descriptor(s) and rx frame(s) associated with |
| * an rx indication message. |
| * 3. Functions for setting up and using the MAC DMA |
| * rx ring (applies to LL only). |
| */ |
| |
| #include <adf_os_mem.h> /* adf_os_mem_alloc,free, etc. */ |
| #include <adf_os_types.h> /* adf_os_print, a_bool_t */ |
| #include <adf_nbuf.h> /* adf_nbuf_t, etc. */ |
| #include <adf_os_timer.h> /* adf_os_timer_free */ |
| |
| #include <htt.h> /* HTT_HL_RX_DESC_SIZE */ |
| #include <ol_cfg.h> |
| #include <ol_rx.h> |
| #include <ol_htt_rx_api.h> |
| #include <ol_txrx_peer_find.h> |
| #include <htt_internal.h> /* HTT_ASSERT, htt_pdev_t, HTT_RX_BUF_SIZE */ |
| #include "regtable.h" |
| #include "adf_trace.h" |
| |
| #include <ieee80211_common.h> /* ieee80211_frame, ieee80211_qoscntl */ |
| #include <ieee80211_defines.h> /* ieee80211_rx_status */ |
| |
| #include <wma_api.h> |
| #ifdef DEBUG_DMA_DONE |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,0)) |
| #include <asm/barrier.h> |
| #else |
| #include <asm/system.h> |
| #endif |
| #endif |
| #include <pktlog_ac_fmt.h> |
| |
| #ifdef DEBUG_DMA_DONE |
| extern int process_wma_set_command(int sessid, int paramid, |
| int sval, int vpdev); |
| #endif |
| |
| #ifdef PKT_DUMP |
| #define HTT_PKT_DUMP(x) x |
| #else |
| #define HTT_PKT_DUMP(x) /* no-op */ |
| #endif |
| |
| /* AR9888v1 WORKAROUND for EV#112367 */ |
| /* FIX THIS - remove this WAR when the bug is fixed */ |
| #define PEREGRINE_1_0_ZERO_LEN_PHY_ERR_WAR |
| |
| /*--- setup / tear-down functions -------------------------------------------*/ |
| |
| #ifndef HTT_RX_RING_SIZE_MIN |
| #define HTT_RX_RING_SIZE_MIN 128 /* slightly larger than one large A-MPDU */ |
| #endif |
| |
| #ifndef HTT_RX_RING_SIZE_MAX |
| #define HTT_RX_RING_SIZE_MAX 2048 /* roughly 20 ms @ 1 Gbps of 1500B MSDUs */ |
| #endif |
| |
| #ifndef HTT_RX_AVG_FRM_BYTES |
| #define HTT_RX_AVG_FRM_BYTES 1000 |
| #endif |
| |
| #ifndef HTT_RX_HOST_LATENCY_MAX_MS |
| #define HTT_RX_HOST_LATENCY_MAX_MS 20 /* ms */ /* very conservative */ |
| #endif |
| |
| #ifndef HTT_RX_HOST_LATENCY_WORST_LIKELY_MS |
| #define HTT_RX_HOST_LATENCY_WORST_LIKELY_MS 10 /* ms */ /* conservative */ |
| #endif |
| |
| #ifndef HTT_RX_RING_REFILL_RETRY_TIME_MS |
| #define HTT_RX_RING_REFILL_RETRY_TIME_MS 50 |
| #endif |
| |
| void |
| htt_rx_hash_deinit(struct htt_pdev_t *pdev); |
| |
| /* |
| * This function is used both below within this file (which the compiler |
| * will hopefully inline), and out-line from other files via the |
| * htt_rx_msdu_first_msdu_flag function pointer. |
| */ |
| static inline a_bool_t |
| htt_rx_msdu_first_msdu_flag_hl(htt_pdev_handle pdev, void *msdu_desc) |
| { |
| return ((u_int8_t*)msdu_desc - sizeof(struct hl_htt_rx_ind_base)) |
| [HTT_ENDIAN_BYTE_IDX_SWAP(HTT_RX_IND_HL_FLAG_OFFSET)] & |
| HTT_RX_IND_HL_FLAG_FIRST_MSDU ? A_TRUE : A_FALSE; |
| } |
| |
| static a_bool_t |
| htt_rx_msdu_first_msdu_flag_ll(htt_pdev_handle pdev, void *msdu_desc) |
| { |
| struct htt_host_rx_desc_base *rx_desc = |
| (struct htt_host_rx_desc_base *) msdu_desc; |
| return (a_bool_t) |
| (((*(((u_int32_t *) &rx_desc->msdu_end) + 4)) & |
| RX_MSDU_END_4_FIRST_MSDU_MASK) >> |
| RX_MSDU_END_4_FIRST_MSDU_LSB); |
| } |
| |
| u_int16_t |
| htt_rx_msdu_rx_desc_size_hl( |
| htt_pdev_handle pdev, |
| void *msdu_desc |
| ) |
| { |
| return ((u_int8_t*)(msdu_desc) - HTT_RX_IND_HL_BYTES) |
| [HTT_ENDIAN_BYTE_IDX_SWAP(HTT_RX_IND_HL_RX_DESC_LEN_OFFSET)]; |
| } |
| |
| static int |
| htt_rx_ring_size(struct htt_pdev_t *pdev) |
| { |
| 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. |
| */ |
| size = |
| ol_cfg_max_thruput_mbps(pdev->ctrl_pdev) * |
| 1000 /* 1e6 bps/mbps / 1e3 ms per sec = 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 = adf_os_get_pwr2(size); |
| return size; |
| } |
| |
| static int |
| htt_rx_ring_fill_level(struct htt_pdev_t *pdev) |
| { |
| int size; |
| |
| size = |
| ol_cfg_max_thruput_mbps(pdev->ctrl_pdev) * |
| 1000 /* 1e6 bps/mbps / 1e3 ms per sec = 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 >= pdev->rx_ring.size) { |
| size = pdev->rx_ring.size - 1; |
| } |
| return size; |
| } |
| |
| static void |
| htt_rx_ring_refill_retry(void *arg) |
| { |
| htt_pdev_handle pdev = (htt_pdev_handle)arg; |
| htt_rx_msdu_buff_replenish(pdev); |
| } |
| |
| void |
| htt_rx_ring_fill_n(struct htt_pdev_t *pdev, int num) |
| { |
| int idx; |
| a_status_t status; |
| struct htt_host_rx_desc_base *rx_desc; |
| |
| idx = *(pdev->rx_ring.alloc_idx.vaddr); |
| while (num > 0) { |
| u_int32_t paddr; |
| adf_nbuf_t rx_netbuf; |
| int headroom; |
| |
| #ifdef QCA_ARP_SPOOFING_WAR |
| rx_netbuf = adf_rx_nbuf_alloc(pdev->osdev, HTT_RX_BUF_SIZE, 0, 4, |
| FALSE); |
| #else |
| rx_netbuf = adf_nbuf_alloc(pdev->osdev, HTT_RX_BUF_SIZE, 0, 4, FALSE); |
| #endif |
| if (!rx_netbuf) { |
| adf_os_timer_cancel(&pdev->rx_ring.refill_retry_timer); |
| /* |
| * 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. |
| */ |
| #ifdef DEBUG_DMA_DONE |
| pdev->rx_ring.dbg_refill_cnt++; |
| #endif |
| adf_os_timer_start(&pdev->rx_ring.refill_retry_timer, |
| HTT_RX_RING_REFILL_RETRY_TIME_MS); |
| goto fail; |
| } |
| |
| /* Clear rx_desc attention word before posting to Rx ring */ |
| rx_desc = htt_rx_desc(rx_netbuf); |
| *(u_int32_t *)&rx_desc->attention = 0; |
| |
| #ifdef DEBUG_DMA_DONE |
| *(u_int32_t *)&rx_desc->msdu_end = 1; |
| |
| #define MAGIC_PATTERN 0xDEADBEEF |
| *(u_int32_t *)&rx_desc->msdu_start = MAGIC_PATTERN; |
| |
| /* To ensure that attention bit is reset and msdu_end is set before |
| calling dma_map */ |
| smp_mb(); |
| #endif |
| /* |
| * Adjust adf_nbuf_data to point to the location in the buffer |
| * where the rx descriptor will be filled in. |
| */ |
| headroom = adf_nbuf_data(rx_netbuf) - (u_int8_t *) rx_desc; |
| adf_nbuf_push_head(rx_netbuf, headroom); |
| |
| #ifdef DEBUG_DMA_DONE |
| status = adf_nbuf_map(pdev->osdev, rx_netbuf, ADF_OS_DMA_BIDIRECTIONAL); |
| #else |
| status = adf_nbuf_map(pdev->osdev, rx_netbuf, ADF_OS_DMA_FROM_DEVICE); |
| #endif |
| if (status != A_STATUS_OK) { |
| adf_nbuf_free(rx_netbuf); |
| goto fail; |
| } |
| paddr = adf_nbuf_get_frag_paddr_lo(rx_netbuf, 0); |
| if (pdev->cfg.is_full_reorder_offload) { |
| if(adf_os_unlikely( |
| htt_rx_hash_list_insert(pdev, paddr, rx_netbuf))) { |
| adf_os_print("%s: hash insert failed!\n", __FUNCTION__); |
| #ifdef DEBUG_DMA_DONE |
| adf_nbuf_unmap(pdev->osdev, rx_netbuf, |
| ADF_OS_DMA_BIDIRECTIONAL); |
| #else |
| adf_nbuf_unmap(pdev->osdev, rx_netbuf, ADF_OS_DMA_FROM_DEVICE); |
| #endif |
| adf_nbuf_free(rx_netbuf); |
| goto fail; |
| } |
| #ifdef DEBUG_RX_RING_BUFFER |
| if (pdev->rx_buff_list) { |
| pdev->rx_buff_list[pdev->rx_buff_index].paddr = paddr; |
| pdev->rx_buff_list[pdev->rx_buff_index].in_use = true; |
| pdev->rx_buff_list[pdev->rx_buff_index].vaddr = rx_netbuf; |
| NBUF_MAP_ID(rx_netbuf) = pdev->rx_buff_index; |
| if(++pdev->rx_buff_index == HTT_RX_RING_BUFF_DBG_LIST) |
| pdev->rx_buff_index = 0; |
| } |
| #endif |
| } else { |
| pdev->rx_ring.buf.netbufs_ring[idx] = rx_netbuf; |
| } |
| pdev->rx_ring.buf.paddrs_ring[idx] = paddr; |
| pdev->rx_ring.fill_cnt++; |
| |
| num--; |
| idx++; |
| idx &= pdev->rx_ring.size_mask; |
| } |
| |
| fail: |
| /* |
| * Make sure alloc index write is reflected correctly before FW polls |
| * remote ring write index as compiler can reorder the instructions |
| * based on optimizations. |
| */ |
| |
| adf_os_mb(); |
| *(pdev->rx_ring.alloc_idx.vaddr) = idx; |
| return; |
| } |
| |
| unsigned |
| htt_rx_ring_elems(struct htt_pdev_t *pdev) |
| { |
| return |
| (*pdev->rx_ring.alloc_idx.vaddr - pdev->rx_ring.sw_rd_idx.msdu_payld) & |
| pdev->rx_ring.size_mask; |
| } |
| |
| unsigned int |
| htt_rx_in_order_ring_elems(struct htt_pdev_t *pdev) |
| { |
| return |
| (*pdev->rx_ring.alloc_idx.vaddr - *pdev->rx_ring.target_idx.vaddr) & |
| pdev->rx_ring.size_mask; |
| } |
| |
| void |
| htt_rx_detach(struct htt_pdev_t *pdev) |
| { |
| |
| if (pdev->cfg.is_high_latency) { |
| return; |
| } |
| |
| adf_os_timer_cancel(&pdev->rx_ring.refill_retry_timer); |
| adf_os_timer_free(&pdev->rx_ring.refill_retry_timer); |
| |
| if (pdev->cfg.is_full_reorder_offload) { |
| adf_os_mem_free_consistent( |
| pdev->osdev, |
| sizeof(u_int32_t), |
| pdev->rx_ring.target_idx.vaddr, |
| pdev->rx_ring.target_idx.paddr, |
| adf_os_get_dma_mem_context((&pdev->rx_ring.target_idx), memctx)); |
| htt_rx_hash_deinit(pdev); |
| } else { |
| int sw_rd_idx = pdev->rx_ring.sw_rd_idx.msdu_payld; |
| |
| while (sw_rd_idx != *(pdev->rx_ring.alloc_idx.vaddr)) { |
| #ifdef DEBUG_DMA_DONE |
| adf_nbuf_unmap( |
| pdev->osdev, pdev->rx_ring.buf.netbufs_ring[sw_rd_idx], |
| ADF_OS_DMA_BIDIRECTIONAL); |
| #else |
| adf_nbuf_unmap( |
| pdev->osdev, pdev->rx_ring.buf.netbufs_ring[sw_rd_idx], |
| ADF_OS_DMA_FROM_DEVICE); |
| #endif |
| adf_nbuf_free(pdev->rx_ring.buf.netbufs_ring[sw_rd_idx]); |
| sw_rd_idx++; |
| sw_rd_idx &= pdev->rx_ring.size_mask; |
| } |
| adf_os_mem_free(pdev->rx_ring.buf.netbufs_ring); |
| } |
| |
| adf_os_mem_free_consistent( |
| pdev->osdev, |
| sizeof(u_int32_t), |
| pdev->rx_ring.alloc_idx.vaddr, |
| pdev->rx_ring.alloc_idx.paddr, |
| adf_os_get_dma_mem_context((&pdev->rx_ring.alloc_idx), memctx)); |
| |
| adf_os_mem_free_consistent( |
| pdev->osdev, |
| pdev->rx_ring.size * sizeof(u_int32_t), |
| pdev->rx_ring.buf.paddrs_ring, |
| pdev->rx_ring.base_paddr, |
| adf_os_get_dma_mem_context((&pdev->rx_ring.buf), memctx)); |
| } |
| |
| /*--- rx descriptor field access functions ----------------------------------*/ |
| /* |
| * These functions need to use bit masks and shifts to extract fields |
| * from the rx descriptors, rather than directly using the bitfields. |
| * For example, use |
| * (desc & FIELD_MASK) >> FIELD_LSB |
| * rather than |
| * desc.field |
| * This allows the functions to work correctly on either little-endian |
| * machines (no endianness conversion needed) or big-endian machines |
| * (endianness conversion provided automatically by the HW DMA's |
| * byte-swizzling). |
| */ |
| /* FIX THIS: APPLIES TO LL ONLY */ |
| |
| /** |
| * htt_rx_mpdu_desc_retry_ll() - Returns the retry bit from the Rx descriptor |
| * for the Low Latency driver |
| * @pdev: Handle (pointer) to HTT pdev. |
| * @mpdu_desc: Void pointer to the Rx descriptor for MPDU |
| * before the beginning of the payload. |
| * |
| * This function returns the retry bit of the 802.11 header for the |
| * provided rx MPDU descriptor. |
| * |
| * Return: boolean -- true if retry is set, false otherwise |
| */ |
| bool |
| htt_rx_mpdu_desc_retry_ll(htt_pdev_handle pdev, void *mpdu_desc) |
| { |
| struct htt_host_rx_desc_base *rx_desc = |
| (struct htt_host_rx_desc_base *) mpdu_desc; |
| |
| return |
| (bool)(((*((uint32_t *) &rx_desc->mpdu_start)) & |
| RX_MPDU_START_0_RETRY_MASK) >> |
| RX_MPDU_START_0_RETRY_LSB); |
| } |
| |
| /** |
| * htt_rx_mpdu_desc_retry_hl() - Returns the retry bit from the Rx descriptor |
| * for the High Latency driver |
| * @pdev: Handle (pointer) to HTT pdev. |
| * @mpdu_desc: Void pointer to the Rx descriptor for MPDU |
| * before the beginning of the payload. |
| * |
| * This function returns the retry bit of the 802.11 header for the |
| * provided rx MPDU descriptor. For the high latency driver, this function |
| * pretends as if the retry bit is never set so that the mcast duplicate |
| * detection never fails. |
| * |
| * Return: boolean -- false always for HL |
| */ |
| bool |
| htt_rx_mpdu_desc_retry_hl(htt_pdev_handle pdev, void *mpdu_desc) |
| { |
| return false; |
| } |
| |
| u_int16_t |
| htt_rx_mpdu_desc_seq_num_ll(htt_pdev_handle pdev, void *mpdu_desc) |
| { |
| struct htt_host_rx_desc_base *rx_desc = |
| (struct htt_host_rx_desc_base *) mpdu_desc; |
| |
| return |
| (u_int16_t)(((*((u_int32_t *) &rx_desc->mpdu_start)) & |
| RX_MPDU_START_0_SEQ_NUM_MASK) >> |
| RX_MPDU_START_0_SEQ_NUM_LSB); |
| } |
| |
| u_int16_t |
| htt_rx_mpdu_desc_seq_num_hl(htt_pdev_handle pdev, void *mpdu_desc) |
| { |
| if (pdev->rx_desc_size_hl) { |
| return pdev->cur_seq_num_hl = |
| (u_int16_t)(HTT_WORD_GET(*(u_int32_t*)mpdu_desc, |
| HTT_HL_RX_DESC_MPDU_SEQ_NUM)); |
| } else { |
| return (u_int16_t)(pdev->cur_seq_num_hl); |
| } |
| } |
| |
| /* FIX THIS: APPLIES TO LL ONLY */ |
| void |
| htt_rx_mpdu_desc_pn_ll( |
| htt_pdev_handle pdev, |
| void *mpdu_desc, |
| union htt_rx_pn_t *pn, |
| int pn_len_bits) |
| { |
| struct htt_host_rx_desc_base *rx_desc = |
| (struct htt_host_rx_desc_base *) mpdu_desc; |
| |
| switch (pn_len_bits) { |
| case 24: |
| /* bits 23:0 */ |
| pn->pn24 = |
| rx_desc->mpdu_start.pn_31_0 & 0xffffff; |
| break; |
| case 48: |
| /* bits 31:0 */ |
| pn->pn48 = rx_desc->mpdu_start.pn_31_0; |
| /* bits 47:32 */ |
| pn->pn48 |= |
| ((u_int64_t) ((*(((u_int32_t *) &rx_desc->mpdu_start) + 2)) |
| & RX_MPDU_START_2_PN_47_32_MASK)) |
| << (32 - RX_MPDU_START_2_PN_47_32_LSB); |
| break; |
| case 128: |
| /* bits 31:0 */ |
| pn->pn128[0] = rx_desc->mpdu_start.pn_31_0; |
| /* bits 47:32 */ |
| pn->pn128[0] |= |
| ((u_int64_t) ((*(((u_int32_t *) &rx_desc->mpdu_start) + 2)) |
| & RX_MPDU_START_2_PN_47_32_MASK)) |
| << (32 - RX_MPDU_START_2_PN_47_32_LSB); |
| /* bits 63:48 */ |
| pn->pn128[0] |= |
| ((u_int64_t) ((*(((u_int32_t *) &rx_desc->msdu_end) + 2)) |
| & RX_MSDU_END_1_EXT_WAPI_PN_63_48_MASK)) |
| << (48 - RX_MSDU_END_1_EXT_WAPI_PN_63_48_LSB); |
| /* bits 95:64 */ |
| pn->pn128[1] = rx_desc->msdu_end.ext_wapi_pn_95_64; |
| /* bits 127:96 */ |
| pn->pn128[1] |= |
| ((u_int64_t) rx_desc->msdu_end.ext_wapi_pn_127_96) << 32; |
| break; |
| default: |
| adf_os_print( |
| "Error: invalid length spec (%d bits) for PN\n", pn_len_bits); |
| }; |
| } |
| |
| /* HL case */ |
| void |
| htt_rx_mpdu_desc_pn_hl( |
| htt_pdev_handle pdev, |
| void *mpdu_desc, |
| union htt_rx_pn_t *pn, |
| int pn_len_bits) |
| { |
| if (htt_rx_msdu_first_msdu_flag_hl(pdev, mpdu_desc) == A_TRUE) { |
| /* Fix Me: only for little endian */ |
| struct hl_htt_rx_desc_base *rx_desc = |
| (struct hl_htt_rx_desc_base *) mpdu_desc; |
| u_int32_t *word_ptr = (u_int32_t *)pn->pn128; |
| |
| /* TODO: for Host of big endian */ |
| switch (pn_len_bits) { |
| case 128: |
| /* bits 128:64 */ |
| *(word_ptr + 3) = rx_desc->pn_127_96; |
| /* bits 63:0 */ |
| *(word_ptr + 2) = rx_desc->pn_95_64; |
| case 48: |
| /* bits 48:0 |
| * copy 64 bits |
| */ |
| *(word_ptr + 1) = rx_desc->u0.pn_63_32; |
| case 24: |
| /* bits 23:0 |
| * copy 32 bits |
| */ |
| *(word_ptr + 0) = rx_desc->pn_31_0; |
| break; |
| default: |
| adf_os_print( |
| "Error: invalid length spec (%d bits) for PN\n", pn_len_bits); |
| adf_os_assert(0); |
| }; |
| } else { |
| /* not first msdu, no pn info */ |
| adf_os_print( |
| "Error: get pn from a not-first msdu.\n"); |
| adf_os_assert(0); |
| } |
| } |
| |
| /** |
| * htt_rx_mpdu_desc_tid_ll() - Returns the TID value from the Rx descriptor |
| * for Low Latency driver |
| * @pdev: Handle (pointer) to HTT pdev. |
| * @mpdu_desc: Void pointer to the Rx descriptor for the MPDU |
| * before the beginning of the payload. |
| * |
| * This function returns the TID set in the 802.11 QoS Control for the MPDU |
| * in the packet header, by looking at the mpdu_start of the Rx descriptor. |
| * Rx descriptor gets a copy of the TID from the MAC. |
| * |
| * Return: Actual TID set in the packet header. |
| */ |
| uint8_t |
| htt_rx_mpdu_desc_tid_ll(htt_pdev_handle pdev, void *mpdu_desc) |
| { |
| struct htt_host_rx_desc_base *rx_desc = |
| (struct htt_host_rx_desc_base *) mpdu_desc; |
| |
| return |
| (uint8_t)(((*(((uint32_t *) &rx_desc->mpdu_start) + 2)) & |
| RX_MPDU_START_2_TID_MASK) >> |
| RX_MPDU_START_2_TID_LSB); |
| } |
| |
| /** |
| * htt_rx_mpdu_desc_tid_hl() - Returns the TID value from the Rx descriptor |
| * for High Latency driver |
| * @pdev: Handle (pointer) to HTT pdev. |
| * @mpdu_desc: Void pointer to the Rx descriptor for the MPDU |
| * before the beginning of the payload. |
| * |
| * This function returns the TID set in the 802.11 QoS Control for the MPDU |
| * in the packet header, by looking at the mpdu_start of the Rx descriptor. |
| * Rx descriptor gets a copy of the TID from the MAC. |
| * For the HL driver, this is currently uimplemented and always returns |
| * an invalid tid. It is the responsibility of the caller to make |
| * sure that return value is checked for valid range. |
| * |
| * Return: Invalid TID value (0xff) for HL driver. |
| */ |
| uint8_t |
| htt_rx_mpdu_desc_tid_hl(htt_pdev_handle pdev, void *mpdu_desc) |
| { |
| return 0xff; /* Invalid TID */ |
| } |
| |
| u_int32_t |
| htt_rx_mpdu_desc_tsf32( |
| htt_pdev_handle pdev, |
| void *mpdu_desc) |
| { |
| /* FIX THIS */ |
| return 0; |
| } |
| |
| /* FIX THIS: APPLIES TO LL ONLY */ |
| char * |
| htt_rx_mpdu_wifi_hdr_retrieve(htt_pdev_handle pdev, void *mpdu_desc) |
| { |
| struct htt_host_rx_desc_base *rx_desc = |
| (struct htt_host_rx_desc_base *) mpdu_desc; |
| return rx_desc->rx_hdr_status; |
| } |
| |
| /* FIX THIS: APPLIES TO LL ONLY */ |
| a_bool_t |
| htt_rx_msdu_desc_completes_mpdu_ll(htt_pdev_handle pdev, void *msdu_desc) |
| { |
| struct htt_host_rx_desc_base *rx_desc = |
| (struct htt_host_rx_desc_base *) msdu_desc; |
| return (a_bool_t) |
| (((*(((u_int32_t *) &rx_desc->msdu_end) + 4)) & |
| RX_MSDU_END_4_LAST_MSDU_MASK) >> |
| RX_MSDU_END_4_LAST_MSDU_LSB); |
| } |
| |
| a_bool_t |
| htt_rx_msdu_desc_completes_mpdu_hl(htt_pdev_handle pdev, void *msdu_desc) |
| { |
| return ( |
| ((u_int8_t*)(msdu_desc) - sizeof(struct hl_htt_rx_ind_base)) |
| [HTT_ENDIAN_BYTE_IDX_SWAP(HTT_RX_IND_HL_FLAG_OFFSET)] |
| & HTT_RX_IND_HL_FLAG_LAST_MSDU) |
| ? A_TRUE : A_FALSE; |
| } |
| |
| /* FIX THIS: APPLIES TO LL ONLY */ |
| int |
| htt_rx_msdu_has_wlan_mcast_flag_ll(htt_pdev_handle pdev, void *msdu_desc) |
| { |
| struct htt_host_rx_desc_base *rx_desc = |
| (struct htt_host_rx_desc_base *) msdu_desc; |
| /* HW rx desc: the mcast_bcast flag is only valid if first_msdu is set */ |
| return |
| ((*(((u_int32_t *) &rx_desc->msdu_end) + 4)) & |
| RX_MSDU_END_4_FIRST_MSDU_MASK) >> |
| RX_MSDU_END_4_FIRST_MSDU_LSB; |
| } |
| |
| int |
| htt_rx_msdu_has_wlan_mcast_flag_hl(htt_pdev_handle pdev, void *msdu_desc) |
| { |
| /* currently, only first msdu has hl rx_desc */ |
| return htt_rx_msdu_first_msdu_flag_hl(pdev, msdu_desc) == A_TRUE; |
| } |
| |
| /* FIX THIS: APPLIES TO LL ONLY */ |
| a_bool_t |
| htt_rx_msdu_is_wlan_mcast_ll(htt_pdev_handle pdev, void *msdu_desc) |
| { |
| struct htt_host_rx_desc_base *rx_desc = |
| (struct htt_host_rx_desc_base *) msdu_desc; |
| return |
| ((*((u_int32_t *) &rx_desc->attention)) & |
| RX_ATTENTION_0_MCAST_BCAST_MASK) >> |
| RX_ATTENTION_0_MCAST_BCAST_LSB; |
| } |
| |
| a_bool_t |
| htt_rx_msdu_is_wlan_mcast_hl(htt_pdev_handle pdev, void *msdu_desc) |
| { |
| struct hl_htt_rx_desc_base *rx_desc = |
| (struct hl_htt_rx_desc_base *) msdu_desc; |
| return |
| HTT_WORD_GET(*(u_int32_t*)rx_desc, HTT_HL_RX_DESC_MCAST_BCAST); |
| } |
| |
| /* FIX THIS: APPLIES TO LL ONLY */ |
| int |
| htt_rx_msdu_is_frag_ll(htt_pdev_handle pdev, void *msdu_desc) |
| { |
| struct htt_host_rx_desc_base *rx_desc = |
| (struct htt_host_rx_desc_base *) msdu_desc; |
| return |
| ((*((u_int32_t *) &rx_desc->attention)) & |
| RX_ATTENTION_0_FRAGMENT_MASK) >> |
| RX_ATTENTION_0_FRAGMENT_LSB; |
| } |
| |
| int |
| htt_rx_msdu_is_frag_hl(htt_pdev_handle pdev, void *msdu_desc) |
| { |
| struct hl_htt_rx_desc_base *rx_desc = |
| (struct hl_htt_rx_desc_base *) msdu_desc; |
| |
| return |
| HTT_WORD_GET(*(u_int32_t*)rx_desc, HTT_HL_RX_DESC_MCAST_BCAST); |
| } |
| |
| static inline |
| u_int8_t |
| htt_rx_msdu_fw_desc_get(htt_pdev_handle pdev, void *msdu_desc) |
| { |
| /* |
| * HL and LL use the same format for FW rx desc, but have the FW rx desc |
| * in different locations. |
| * In LL, the FW rx descriptor has been copied into the same |
| * htt_host_rx_desc_base struct that holds the HW rx desc. |
| * In HL, the FW rx descriptor, along with the MSDU payload, |
| * is in the same buffer as the rx indication message. |
| * |
| * Use the FW rx desc offset configured during startup to account for |
| * this difference between HL vs. LL. |
| * |
| * An optimization would be to define the LL and HL msdu_desc pointer |
| * in such a way that they both use the same offset to the FW rx desc. |
| * Then the following functions could be converted to macros, without |
| * needing to expose the htt_pdev_t definition outside HTT. |
| */ |
| return *(((u_int8_t *) msdu_desc) + pdev->rx_fw_desc_offset); |
| } |
| |
| int |
| htt_rx_msdu_discard(htt_pdev_handle pdev, void *msdu_desc) |
| { |
| return htt_rx_msdu_fw_desc_get(pdev, msdu_desc) & FW_RX_DESC_DISCARD_M; |
| } |
| |
| int |
| htt_rx_msdu_forward(htt_pdev_handle pdev, void *msdu_desc) |
| { |
| return htt_rx_msdu_fw_desc_get(pdev, msdu_desc) & FW_RX_DESC_FORWARD_M; |
| } |
| |
| int |
| htt_rx_msdu_inspect(htt_pdev_handle pdev, void *msdu_desc) |
| { |
| return htt_rx_msdu_fw_desc_get(pdev, msdu_desc) & FW_RX_DESC_INSPECT_M; |
| } |
| |
| void |
| htt_rx_msdu_actions( |
| htt_pdev_handle pdev, |
| void *msdu_desc, |
| int *discard, |
| int *forward, |
| int *inspect) |
| { |
| u_int8_t rx_msdu_fw_desc = htt_rx_msdu_fw_desc_get(pdev, msdu_desc); |
| #ifdef HTT_DEBUG_DATA |
| HTT_PRINT("act:0x%x ",rx_msdu_fw_desc); |
| #endif |
| *discard = rx_msdu_fw_desc & FW_RX_DESC_DISCARD_M; |
| *forward = rx_msdu_fw_desc & FW_RX_DESC_FORWARD_M; |
| *inspect = rx_msdu_fw_desc & FW_RX_DESC_INSPECT_M; |
| } |
| |
| static inline adf_nbuf_t |
| htt_rx_netbuf_pop( |
| htt_pdev_handle pdev) |
| { |
| int idx; |
| adf_nbuf_t msdu; |
| |
| HTT_ASSERT1(htt_rx_ring_elems(pdev) != 0); |
| |
| #ifdef DEBUG_DMA_DONE |
| pdev->rx_ring.dbg_ring_idx++; |
| pdev->rx_ring.dbg_ring_idx &= pdev->rx_ring.size_mask; |
| #endif |
| |
| idx = pdev->rx_ring.sw_rd_idx.msdu_payld; |
| msdu = pdev->rx_ring.buf.netbufs_ring[idx]; |
| idx++; |
| idx &= pdev->rx_ring.size_mask; |
| pdev->rx_ring.sw_rd_idx.msdu_payld = idx; |
| pdev->rx_ring.fill_cnt--; |
| return msdu; |
| } |
| |
| static inline adf_nbuf_t |
| htt_rx_in_order_netbuf_pop( |
| htt_pdev_handle pdev, u_int32_t paddr) |
| { |
| HTT_ASSERT1(htt_rx_in_order_ring_elems(pdev) != 0); |
| pdev->rx_ring.fill_cnt--; |
| return htt_rx_hash_list_lookup(pdev, paddr); |
| } |
| |
| /* FIX ME: this function applies only to LL rx descs. An equivalent for HL rx descs is needed. */ |
| #ifdef CHECKSUM_OFFLOAD |
| static inline |
| void |
| htt_set_checksum_result_ll(htt_pdev_handle pdev, adf_nbuf_t msdu, |
| struct htt_host_rx_desc_base *rx_desc) |
| { |
| #define MAX_IP_VER 2 |
| #define MAX_PROTO_VAL 4 |
| struct rx_msdu_start *rx_msdu = &rx_desc->msdu_start; |
| unsigned int proto = (rx_msdu->tcp_proto) | (rx_msdu->udp_proto << 1); |
| |
| /* |
| * HW supports TCP & UDP checksum offload for ipv4 and ipv6 |
| */ |
| static const adf_nbuf_l4_rx_cksum_type_t |
| cksum_table[][MAX_PROTO_VAL][MAX_IP_VER] = |
| { |
| { |
| /* non-fragmented IP packet */ |
| /* non TCP/UDP packet */ |
| { ADF_NBUF_RX_CKSUM_NONE, ADF_NBUF_RX_CKSUM_NONE }, |
| /* TCP packet */ |
| { ADF_NBUF_RX_CKSUM_TCP, ADF_NBUF_RX_CKSUM_TCPIPV6}, |
| /* UDP packet */ |
| { ADF_NBUF_RX_CKSUM_UDP, ADF_NBUF_RX_CKSUM_UDPIPV6 }, |
| /* invalid packet type */ |
| { ADF_NBUF_RX_CKSUM_NONE, ADF_NBUF_RX_CKSUM_NONE }, |
| }, |
| { |
| /* fragmented IP packet */ |
| { ADF_NBUF_RX_CKSUM_NONE, ADF_NBUF_RX_CKSUM_NONE }, |
| { ADF_NBUF_RX_CKSUM_NONE, ADF_NBUF_RX_CKSUM_NONE }, |
| { ADF_NBUF_RX_CKSUM_NONE, ADF_NBUF_RX_CKSUM_NONE }, |
| { ADF_NBUF_RX_CKSUM_NONE, ADF_NBUF_RX_CKSUM_NONE }, |
| } |
| }; |
| |
| adf_nbuf_rx_cksum_t cksum = { |
| cksum_table[rx_msdu->ip_frag][proto][rx_msdu->ipv6_proto], |
| ADF_NBUF_RX_CKSUM_NONE, |
| 0 |
| } ; |
| |
| if (cksum.l4_type != (adf_nbuf_l4_rx_cksum_type_t)ADF_NBUF_RX_CKSUM_NONE) { |
| cksum.l4_result = ((*(u_int32_t *) &rx_desc->attention) & |
| RX_ATTENTION_0_TCP_UDP_CHKSUM_FAIL_MASK) ? |
| ADF_NBUF_RX_CKSUM_NONE : |
| ADF_NBUF_RX_CKSUM_TCP_UDP_UNNECESSARY; |
| } |
| adf_nbuf_set_rx_cksum(msdu, &cksum ); |
| #undef MAX_IP_VER |
| #undef MAX_PROTO_VAL |
| } |
| |
| static inline |
| void |
| htt_set_checksum_result_hl(adf_nbuf_t msdu, |
| struct htt_host_rx_desc_base *rx_desc) |
| { |
| u_int8_t flag = ((u_int8_t*)rx_desc - sizeof(struct hl_htt_rx_ind_base))[HTT_ENDIAN_BYTE_IDX_SWAP(HTT_RX_IND_HL_FLAG_OFFSET)]; |
| int is_ipv6 = flag & HTT_RX_IND_HL_FLAG_IPV6 ? 1:0; |
| int is_tcp = flag & HTT_RX_IND_HL_FLAG_TCP ? 1:0; |
| int is_udp = flag & HTT_RX_IND_HL_FLAG_UDP ? 1:0; |
| |
| adf_nbuf_rx_cksum_t cksum = { |
| ADF_NBUF_RX_CKSUM_NONE, |
| ADF_NBUF_RX_CKSUM_NONE, |
| 0 |
| } ; |
| |
| switch ((is_udp << 2) | (is_tcp << 1) | (is_ipv6 << 0)) { |
| case 0x4: |
| cksum.l4_type = ADF_NBUF_RX_CKSUM_UDP; |
| break; |
| case 0x2: |
| cksum.l4_type = ADF_NBUF_RX_CKSUM_TCP; |
| break; |
| case 0x5: |
| cksum.l4_type = ADF_NBUF_RX_CKSUM_UDPIPV6; |
| break; |
| case 0x3: |
| cksum.l4_type = ADF_NBUF_RX_CKSUM_TCPIPV6; |
| break; |
| default: |
| cksum.l4_type = ADF_NBUF_RX_CKSUM_NONE; |
| break; |
| } |
| if (cksum.l4_type != (adf_nbuf_l4_rx_cksum_type_t)ADF_NBUF_RX_CKSUM_NONE) { |
| cksum.l4_result = flag & HTT_RX_IND_HL_FLAG_C4_FAILED ? |
| ADF_NBUF_RX_CKSUM_NONE : ADF_NBUF_RX_CKSUM_TCP_UDP_UNNECESSARY; |
| } |
| adf_nbuf_set_rx_cksum(msdu, &cksum ); |
| } |
| |
| #else |
| #define htt_set_checksum_result_ll(pdev, msdu, rx_desc) /* no-op */ |
| #define htt_set_checksum_result_hl(msdu, rx_desc) /* no-op */ |
| #endif |
| |
| #ifdef DEBUG_DMA_DONE |
| void |
| htt_rx_print_rx_indication( |
| adf_nbuf_t rx_ind_msg, |
| htt_pdev_handle pdev) |
| { |
| u_int32_t *msg_word; |
| int byte_offset; |
| int mpdu_range, num_mpdu_range; |
| |
| msg_word = (u_int32_t *)adf_nbuf_data(rx_ind_msg); |
| |
| adf_os_print("------------------HTT RX IND-----------------------------\n"); |
| adf_os_print("alloc idx paddr %x (*vaddr) %d\n", |
| pdev->rx_ring.alloc_idx.paddr, |
| *pdev->rx_ring.alloc_idx.vaddr); |
| |
| adf_os_print("sw_rd_idx msdu_payld %d msdu_desc %d\n", |
| pdev->rx_ring.sw_rd_idx.msdu_payld, |
| pdev->rx_ring.sw_rd_idx.msdu_desc); |
| |
| adf_os_print("dbg_ring_idx %d\n", pdev->rx_ring.dbg_ring_idx); |
| |
| adf_os_print("fill_level %d fill_cnt %d\n",pdev->rx_ring.fill_level, |
| pdev->rx_ring.fill_cnt); |
| |
| adf_os_print("initial msdu_payld %d curr mpdu range %d curr mpdu cnt %d\n", |
| pdev->rx_ring.dbg_initial_msdu_payld, |
| pdev->rx_ring.dbg_mpdu_range, |
| pdev->rx_ring.dbg_mpdu_count); |
| |
| /* Print the RX_IND contents */ |
| |
| adf_os_print("peer id %x RV %x FV %x ext_tid %x msg_type %x\n", |
| HTT_RX_IND_PEER_ID_GET(*msg_word), |
| HTT_RX_IND_REL_VALID_GET(*msg_word), |
| HTT_RX_IND_FLUSH_VALID_GET(*msg_word), |
| HTT_RX_IND_EXT_TID_GET(*msg_word), |
| HTT_T2H_MSG_TYPE_GET(*msg_word)); |
| |
| adf_os_print("num_mpdu_ranges %x rel_seq_num_end %x rel_seq_num_start %x\n" |
| " flush_seq_num_end %x flush_seq_num_start %x\n", |
| HTT_RX_IND_NUM_MPDU_RANGES_GET(*(msg_word + 1)), |
| HTT_RX_IND_REL_SEQ_NUM_END_GET(*(msg_word + 1)), |
| HTT_RX_IND_REL_SEQ_NUM_START_GET(*(msg_word + 1)), |
| HTT_RX_IND_FLUSH_SEQ_NUM_END_GET(*(msg_word + 1)), |
| HTT_RX_IND_FLUSH_SEQ_NUM_START_GET(*(msg_word + 1))); |
| |
| adf_os_print("fw_rx_desc_bytes %x\n", HTT_RX_IND_FW_RX_DESC_BYTES_GET( |
| *(msg_word + 2 + HTT_RX_PPDU_DESC_SIZE32))); |
| |
| /* receive MSDU desc for current frame */ |
| byte_offset = HTT_ENDIAN_BYTE_IDX_SWAP(HTT_RX_IND_FW_RX_DESC_BYTE_OFFSET + |
| pdev->rx_ind_msdu_byte_idx); |
| |
| adf_os_print("msdu byte idx %x msdu desc %x\n", pdev->rx_ind_msdu_byte_idx, |
| HTT_RX_IND_FW_RX_DESC_BYTES_GET( |
| *(msg_word + 2 + HTT_RX_PPDU_DESC_SIZE32))); |
| |
| num_mpdu_range = HTT_RX_IND_NUM_MPDU_RANGES_GET(*(msg_word + 1)); |
| |
| for (mpdu_range = 0; mpdu_range < num_mpdu_range; mpdu_range++) { |
| enum htt_rx_status status; |
| int num_mpdus; |
| |
| htt_rx_ind_mpdu_range_info( |
| pdev, rx_ind_msg, mpdu_range, &status, &num_mpdus); |
| |
| adf_os_print("mpdu_range %x status %x num_mpdus %x\n", |
| pdev->rx_ind_msdu_byte_idx, status, num_mpdus); |
| } |
| adf_os_print("---------------------------------------------------------\n"); |
| } |
| #endif |
| |
| #ifdef DEBUG_DMA_DONE |
| #define MAX_DONE_BIT_CHECK_ITER 5 |
| #endif |
| |
| int |
| htt_rx_amsdu_pop_ll( |
| htt_pdev_handle pdev, |
| adf_nbuf_t rx_ind_msg, |
| adf_nbuf_t *head_msdu, |
| adf_nbuf_t *tail_msdu) |
| { |
| int msdu_len, msdu_chaining = 0; |
| adf_nbuf_t msdu; |
| struct htt_host_rx_desc_base *rx_desc; |
| u_int8_t *rx_ind_data; |
| u_int32_t *msg_word, num_msdu_bytes; |
| enum htt_t2h_msg_type msg_type; |
| |
| HTT_ASSERT1(htt_rx_ring_elems(pdev) != 0); |
| rx_ind_data = adf_nbuf_data(rx_ind_msg); |
| msg_word = (u_int32_t *)rx_ind_data; |
| |
| msg_type = HTT_T2H_MSG_TYPE_GET(*msg_word); |
| |
| if (adf_os_unlikely(HTT_T2H_MSG_TYPE_RX_FRAG_IND == msg_type)) { |
| num_msdu_bytes = HTT_RX_FRAG_IND_FW_RX_DESC_BYTES_GET( |
| *(msg_word + HTT_RX_FRAG_IND_HDR_PREFIX_SIZE32)); |
| } else { |
| num_msdu_bytes = HTT_RX_IND_FW_RX_DESC_BYTES_GET( |
| *(msg_word + HTT_RX_IND_HDR_PREFIX_SIZE32 + |
| HTT_RX_PPDU_DESC_SIZE32)); |
| } |
| msdu = *head_msdu = htt_rx_netbuf_pop(pdev); |
| while (1) { |
| int last_msdu, msdu_len_invalid, msdu_chained; |
| int byte_offset; |
| |
| /* |
| * Set the netbuf length to be the entire buffer length initially, |
| * so the unmap will unmap the entire buffer. |
| */ |
| adf_nbuf_set_pktlen(msdu, HTT_RX_BUF_SIZE); |
| #ifdef DEBUG_DMA_DONE |
| adf_nbuf_unmap(pdev->osdev, msdu, ADF_OS_DMA_BIDIRECTIONAL); |
| #else |
| adf_nbuf_unmap(pdev->osdev, msdu, ADF_OS_DMA_FROM_DEVICE); |
| #endif |
| |
| /* cache consistency has been taken care of by the adf_nbuf_unmap */ |
| |
| /* |
| * Now read the rx descriptor. |
| * Set the length to the appropriate value. |
| * Check if this MSDU completes a MPDU. |
| */ |
| rx_desc = htt_rx_desc(msdu); |
| |
| /* |
| * Make the netbuf's data pointer point to the payload rather |
| * than the descriptor. |
| */ |
| adf_nbuf_pull_head(msdu, HTT_RX_STD_DESC_RESERVATION); |
| |
| /* |
| * 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. |
| */ |
| |
| #ifdef DEBUG_DMA_DONE |
| if (adf_os_unlikely(!((*(u_int32_t *) &rx_desc->attention) |
| & RX_ATTENTION_0_MSDU_DONE_MASK))) { |
| |
| int dbg_iter = MAX_DONE_BIT_CHECK_ITER; |
| |
| |
| adf_os_print("malformed frame\n"); |
| |
| while (dbg_iter && |
| (!((*(u_int32_t *) &rx_desc->attention) & |
| RX_ATTENTION_0_MSDU_DONE_MASK))) { |
| adf_os_mdelay(1); |
| |
| adf_os_invalidate_range((void *)rx_desc, |
| (void*)((char *)rx_desc + |
| HTT_RX_STD_DESC_RESERVATION)); |
| |
| adf_os_print("debug iter %d success %d\n", dbg_iter, |
| pdev->rx_ring.dbg_sync_success); |
| |
| dbg_iter--; |
| } |
| |
| if (adf_os_unlikely(!((*(u_int32_t *) &rx_desc->attention) |
| & RX_ATTENTION_0_MSDU_DONE_MASK))) |
| { |
| |
| #ifdef HTT_RX_RESTORE |
| adf_os_print("RX done bit error detected!\n"); |
| adf_nbuf_set_next(msdu, NULL); |
| *tail_msdu = msdu; |
| pdev->rx_ring.rx_reset = 1; |
| return msdu_chaining; |
| #else |
| process_wma_set_command(0,(int)GEN_PARAM_CRASH_INJECT, |
| 0, GEN_CMD); |
| HTT_ASSERT_ALWAYS(0); |
| #endif |
| } |
| pdev->rx_ring.dbg_sync_success++; |
| adf_os_print("debug iter %d success %d\n", dbg_iter, |
| pdev->rx_ring.dbg_sync_success); |
| } |
| #else |
| HTT_ASSERT_ALWAYS( |
| (*(u_int32_t *) &rx_desc->attention) & |
| RX_ATTENTION_0_MSDU_DONE_MASK); |
| #endif |
| /* |
| * 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 (pdev->rx_ind_msdu_byte_idx < num_msdu_bytes) { |
| if (adf_os_unlikely(HTT_T2H_MSG_TYPE_RX_FRAG_IND == msg_type)) { |
| byte_offset = HTT_ENDIAN_BYTE_IDX_SWAP( |
| HTT_RX_FRAG_IND_FW_DESC_BYTE_OFFSET); |
| } else { |
| byte_offset = HTT_ENDIAN_BYTE_IDX_SWAP( |
| HTT_RX_IND_FW_RX_DESC_BYTE_OFFSET + |
| pdev->rx_ind_msdu_byte_idx); |
| } |
| |
| *((u_int8_t *) &rx_desc->fw_desc.u.val) = rx_ind_data[byte_offset]; |
| /* |
| * 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). |
| */ |
| /* |
| * The assertion below currently doesn't work for RX_FRAG_IND |
| * messages, since their format differs from the RX_IND format |
| * (no FW rx PPDU desc in the current RX_FRAG_IND message). |
| * If the RX_FRAG_IND message format is updated to match the |
| * RX_IND message format, then the following assertion can be |
| * restored. |
| */ |
| //adf_os_assert((rx_ind_data[byte_offset] & FW_RX_DESC_EXT_M) == 0); |
| pdev->rx_ind_msdu_byte_idx += 1; // or more, if there's ext data |
| } 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. |
| */ |
| *((u_int8_t *) &rx_desc->fw_desc.u.val) = 0; |
| } |
| |
| /* |
| * TCP/UDP checksum offload support |
| */ |
| htt_set_checksum_result_ll(pdev, msdu, rx_desc); |
| |
| msdu_len_invalid = (*(u_int32_t *) &rx_desc->attention) & |
| RX_ATTENTION_0_MPDU_LENGTH_ERR_MASK; |
| msdu_chained = (((*(u_int32_t *) &rx_desc->frag_info) & |
| RX_FRAG_INFO_0_RING2_MORE_COUNT_MASK) >> |
| RX_FRAG_INFO_0_RING2_MORE_COUNT_LSB); |
| msdu_len = |
| ((*((u_int32_t *) &rx_desc->msdu_start)) & |
| RX_MSDU_START_0_MSDU_LENGTH_MASK) >> |
| RX_MSDU_START_0_MSDU_LENGTH_LSB; |
| |
| do { |
| if (!msdu_len_invalid && !msdu_chained) { |
| #if defined(PEREGRINE_1_0_ZERO_LEN_PHY_ERR_WAR) |
| if (msdu_len > 0x3000) { |
| break; |
| } |
| #endif |
| adf_nbuf_trim_tail( |
| msdu, HTT_RX_BUF_SIZE - (RX_STD_DESC_SIZE + msdu_len)); |
| } |
| } while (0); |
| |
| while (msdu_chained--) { |
| adf_nbuf_t next = |
| htt_rx_netbuf_pop(pdev); |
| adf_nbuf_set_pktlen(next, HTT_RX_BUF_SIZE); |
| msdu_len -= HTT_RX_BUF_SIZE; |
| adf_nbuf_set_next(msdu, next); |
| msdu = next; |
| msdu_chaining = 1; |
| |
| if (msdu_chained == 0) { |
| /* Trim the last one to the correct size - accounting for |
| * inconsistent HW lengths cuasing length overflows and |
| * underflows |
| */ |
| if (((unsigned)msdu_len) > |
| ((unsigned)(HTT_RX_BUF_SIZE - RX_STD_DESC_SIZE))) { |
| msdu_len = (HTT_RX_BUF_SIZE - RX_STD_DESC_SIZE); |
| } |
| |
| adf_nbuf_trim_tail( |
| next, HTT_RX_BUF_SIZE - (RX_STD_DESC_SIZE + msdu_len)); |
| } |
| } |
| |
| last_msdu = |
| ((*(((u_int32_t *) &rx_desc->msdu_end) + 4)) & |
| RX_MSDU_END_4_LAST_MSDU_MASK) >> |
| RX_MSDU_END_4_LAST_MSDU_LSB; |
| |
| if (last_msdu) { |
| adf_nbuf_set_next(msdu, NULL); |
| break; |
| } else { |
| adf_nbuf_t next = htt_rx_netbuf_pop(pdev); |
| adf_nbuf_set_next(msdu, 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; |
| } |
| |
| int |
| htt_rx_amsdu_pop_hl( |
| htt_pdev_handle pdev, |
| adf_nbuf_t rx_ind_msg, |
| adf_nbuf_t *head_msdu, |
| adf_nbuf_t *tail_msdu) |
| { |
| pdev->rx_desc_size_hl = |
| (adf_nbuf_data(rx_ind_msg)) |
| [HTT_ENDIAN_BYTE_IDX_SWAP( |
| HTT_RX_IND_HL_RX_DESC_LEN_OFFSET)]; |
| |
| /* point to the rx desc */ |
| adf_nbuf_pull_head(rx_ind_msg, |
| sizeof(struct hl_htt_rx_ind_base)); |
| *head_msdu = *tail_msdu = rx_ind_msg; |
| |
| #ifdef CHECKSUM_OFFLOAD |
| htt_set_checksum_result_hl(rx_ind_msg, (struct htt_host_rx_desc_base *)(adf_nbuf_data(rx_ind_msg))); |
| #endif |
| |
| adf_nbuf_set_next(*tail_msdu, NULL); |
| return 0; |
| } |
| |
| int |
| htt_rx_frag_pop_hl( |
| htt_pdev_handle pdev, |
| adf_nbuf_t frag_msg, |
| adf_nbuf_t *head_msdu, |
| adf_nbuf_t *tail_msdu) |
| { |
| adf_nbuf_pull_head(frag_msg, HTT_RX_FRAG_IND_BYTES); |
| pdev->rx_desc_size_hl = |
| (adf_nbuf_data(frag_msg)) |
| [HTT_ENDIAN_BYTE_IDX_SWAP( |
| HTT_RX_IND_HL_RX_DESC_LEN_OFFSET)]; |
| |
| /* point to the rx desc */ |
| adf_nbuf_pull_head(frag_msg, |
| sizeof(struct hl_htt_rx_ind_base)); |
| *head_msdu = *tail_msdu = frag_msg; |
| |
| adf_nbuf_set_next(*tail_msdu, NULL); |
| return 0; |
| } |
| |
| int |
| htt_rx_offload_msdu_cnt_ll( |
| htt_pdev_handle pdev) |
| { |
| return htt_rx_ring_elems(pdev); |
| } |
| |
| int |
| htt_rx_offload_msdu_pop_ll( |
| htt_pdev_handle pdev, |
| adf_nbuf_t offload_deliver_msg, |
| int *vdev_id, |
| int *peer_id, |
| int *tid, |
| u_int8_t *fw_desc, |
| adf_nbuf_t *head_buf, |
| adf_nbuf_t *tail_buf) |
| { |
| adf_nbuf_t buf; |
| u_int32_t *msdu_hdr, msdu_len; |
| |
| *head_buf = *tail_buf = buf = htt_rx_netbuf_pop(pdev); |
| /* Fake read mpdu_desc to keep desc ptr in sync */ |
| htt_rx_mpdu_desc_list_next(pdev, NULL); |
| adf_nbuf_set_pktlen(buf, HTT_RX_BUF_SIZE); |
| #ifdef DEBUG_DMA_DONE |
| adf_nbuf_unmap(pdev->osdev, buf, ADF_OS_DMA_BIDIRECTIONAL); |
| #else |
| adf_nbuf_unmap(pdev->osdev, buf, ADF_OS_DMA_FROM_DEVICE); |
| #endif |
| msdu_hdr = (u_int32_t *)adf_nbuf_data(buf); |
| |
| /* First dword */ |
| msdu_len = HTT_RX_OFFLOAD_DELIVER_IND_MSDU_LEN_GET(*msdu_hdr); |
| *peer_id = HTT_RX_OFFLOAD_DELIVER_IND_MSDU_PEER_ID_GET(*msdu_hdr); |
| |
| /* Second dword */ |
| msdu_hdr++; |
| *vdev_id = HTT_RX_OFFLOAD_DELIVER_IND_MSDU_VDEV_ID_GET(*msdu_hdr); |
| *tid = HTT_RX_OFFLOAD_DELIVER_IND_MSDU_TID_GET(*msdu_hdr); |
| *fw_desc = HTT_RX_OFFLOAD_DELIVER_IND_MSDU_DESC_GET(*msdu_hdr); |
| |
| adf_nbuf_pull_head(buf, HTT_RX_OFFLOAD_DELIVER_IND_MSDU_HDR_BYTES); |
| adf_nbuf_set_pktlen(buf, msdu_len); |
| return 0; |
| } |
| |
| int |
| htt_rx_offload_paddr_msdu_pop_ll( |
| htt_pdev_handle pdev, |
| u_int32_t * msg_word, |
| int msdu_iter, |
| int *vdev_id, |
| int *peer_id, |
| int *tid, |
| u_int8_t *fw_desc, |
| adf_nbuf_t *head_buf, |
| adf_nbuf_t *tail_buf) |
| { |
| adf_nbuf_t buf; |
| u_int32_t *msdu_hdr, msdu_len; |
| u_int32_t * curr_msdu; |
| u_int32_t paddr; |
| |
| curr_msdu = msg_word + (msdu_iter * HTT_RX_IN_ORD_PADDR_IND_MSDU_DWORDS); |
| paddr = HTT_RX_IN_ORD_PADDR_IND_PADDR_GET(*curr_msdu); |
| *head_buf = *tail_buf = buf = htt_rx_in_order_netbuf_pop(pdev, paddr); |
| |
| if (adf_os_unlikely(NULL == buf)) { |
| adf_os_print("%s: netbuf pop failed!\n", __FUNCTION__); |
| return 0; |
| } |
| adf_nbuf_set_pktlen(buf, HTT_RX_BUF_SIZE); |
| #ifdef DEBUG_DMA_DONE |
| adf_nbuf_unmap(pdev->osdev, buf, ADF_OS_DMA_BIDIRECTIONAL); |
| #else |
| adf_nbuf_unmap(pdev->osdev, buf, ADF_OS_DMA_FROM_DEVICE); |
| #endif |
| |
| if (pdev->cfg.is_first_wakeup_packet) { |
| if (HTT_RX_IN_ORD_PADDR_IND_MSDU_INFO_GET(*(curr_msdu + 1)) & |
| FW_MSDU_INFO_FIRST_WAKEUP_M) { |
| adf_nbuf_update_skb_mark(buf, HTT_MARK_FIRST_WAKEUP_PACKET); |
| adf_os_print("%s: First packet after WOW Wakeup rcvd\n", __func__); |
| } |
| } |
| |
| msdu_hdr = (u_int32_t *)adf_nbuf_data(buf); |
| |
| /* First dword */ |
| msdu_len = HTT_RX_OFFLOAD_DELIVER_IND_MSDU_LEN_GET(*msdu_hdr); /* 2 bytes */ |
| *peer_id = HTT_RX_OFFLOAD_DELIVER_IND_MSDU_PEER_ID_GET(*msdu_hdr); /* 2 bytes */ |
| |
| /* Second dword */ |
| msdu_hdr++; |
| *vdev_id = HTT_RX_OFFLOAD_DELIVER_IND_MSDU_VDEV_ID_GET(*msdu_hdr); /* 1 bytes */ |
| *tid = HTT_RX_OFFLOAD_DELIVER_IND_MSDU_TID_GET(*msdu_hdr); /* 1 bytes */ |
| *fw_desc = HTT_RX_OFFLOAD_DELIVER_IND_MSDU_DESC_GET(*msdu_hdr); |
| |
| adf_nbuf_pull_head(buf, HTT_RX_OFFLOAD_DELIVER_IND_MSDU_HDR_BYTES); |
| adf_nbuf_set_pktlen(buf, msdu_len); |
| return 0; |
| } |
| |
| /** |
| * htt_handle_amsdu_packet() - Handle consecutive fragments of amsdu |
| * @msdu: pointer to first msdu of amsdu |
| * @pdev: Handle to htt_pdev_handle |
| * @msg_word: Input and output variable, so pointer to HTT msg pointer |
| * @amsdu_len: remaining length of all N-1 msdu msdu's |
| * |
| * This function handles the (N-1) msdu's of amsdu, N'th msdu is already |
| * handled by calling function. N-1 msdu's are tied using frags_list. |
| * msdu_info field updated by FW indicates if this is last msdu. All the |
| * msdu's before last msdu will be of MAX payload. |
| * |
| * Return: 1 on success and 0 on failure. |
| */ |
| int |
| htt_handle_amsdu_packet(adf_nbuf_t msdu, |
| htt_pdev_handle pdev, |
| uint32_t **msg_word, |
| uint32_t amsdu_len) |
| { |
| adf_nbuf_t frag_nbuf; |
| adf_nbuf_t prev_frag_nbuf; |
| uint32_t len; |
| uint32_t last_frag; |
| |
| *msg_word += HTT_RX_IN_ORD_PADDR_IND_MSDU_DWORDS; |
| frag_nbuf = htt_rx_in_order_netbuf_pop(pdev, |
| HTT_RX_IN_ORD_PADDR_IND_PADDR_GET(**msg_word)); |
| if (adf_os_unlikely(NULL == frag_nbuf)) { |
| adf_os_print("%s: netbuf pop failed!\n", __func__); |
| return 0; |
| } |
| last_frag = ((struct htt_rx_in_ord_paddr_ind_msdu32_t *)*msg_word)-> |
| msdu_info; |
| adf_nbuf_append_ext_list(msdu, frag_nbuf, amsdu_len); |
| adf_nbuf_set_pktlen(frag_nbuf, HTT_RX_BUF_SIZE); |
| adf_nbuf_unmap(pdev->osdev, frag_nbuf, ADF_OS_DMA_FROM_DEVICE); |
| /* For msdu's other than parent will not have htt_host_rx_desc_base */ |
| len = MIN(amsdu_len, HTT_RX_BUF_SIZE); |
| amsdu_len -= len; |
| adf_nbuf_trim_tail(frag_nbuf, HTT_RX_BUF_SIZE - len); |
| |
| HTT_PKT_DUMP(vos_trace_hex_dump(VOS_MODULE_ID_TXRX, |
| VOS_TRACE_LEVEL_FATAL, |
| adf_nbuf_data(frag_nbuf), |
| adf_nbuf_len(frag_nbuf))); |
| prev_frag_nbuf = frag_nbuf; |
| while (!last_frag) { |
| *msg_word += HTT_RX_IN_ORD_PADDR_IND_MSDU_DWORDS; |
| frag_nbuf = htt_rx_in_order_netbuf_pop(pdev, |
| HTT_RX_IN_ORD_PADDR_IND_PADDR_GET(**msg_word)); |
| last_frag = ((struct htt_rx_in_ord_paddr_ind_msdu32_t *) |
| *msg_word)->msdu_info; |
| |
| if (adf_os_unlikely(NULL == frag_nbuf)) { |
| adf_os_print("%s: netbuf pop failed!\n", __func__); |
| prev_frag_nbuf->next = NULL; |
| return 0; |
| } |
| adf_nbuf_set_pktlen(frag_nbuf, HTT_RX_BUF_SIZE); |
| adf_nbuf_unmap(pdev->osdev, frag_nbuf, ADF_OS_DMA_FROM_DEVICE); |
| |
| len = MIN(amsdu_len, HTT_RX_BUF_SIZE); |
| amsdu_len -= len; |
| adf_nbuf_trim_tail(frag_nbuf, HTT_RX_BUF_SIZE - len); |
| HTT_PKT_DUMP(vos_trace_hex_dump(VOS_MODULE_ID_TXRX, |
| VOS_TRACE_LEVEL_FATAL, |
| adf_nbuf_data(frag_nbuf), |
| adf_nbuf_len(frag_nbuf))); |
| |
| adf_nbuf_set_next(prev_frag_nbuf, frag_nbuf); |
| prev_frag_nbuf = frag_nbuf; |
| } |
| adf_nbuf_set_next(prev_frag_nbuf, NULL); |
| return 1; |
| } |
| #ifdef RX_HASH_DEBUG |
| #define HTT_RX_CHECK_MSDU_COUNT(msdu_count) HTT_ASSERT_ALWAYS(msdu_count) |
| #else |
| #define HTT_RX_CHECK_MSDU_COUNT(msdu_count) /* no-op */ |
| #endif |
| |
| /** |
| * get_rate() - Get rate interms of 500Kbps extracted from htt_rx_desc |
| * @l_sig_rate_select: OFDM or CCK rate |
| * @l_sig_rate: |
| * |
| * If l_sig_rate_select is 0: |
| * 0x8: OFDM 48 Mbps |
| * 0x9: OFDM 24 Mbps |
| * 0xA: OFDM 12 Mbps |
| * 0xB: OFDM 6 Mbps |
| * 0xC: OFDM 54 Mbps |
| * 0xD: OFDM 36 Mbps |
| * 0xE: OFDM 18 Mbps |
| * 0xF: OFDM 9 Mbps |
| * If l_sig_rate_select is 1: |
| * 0x8: CCK 11 Mbps long preamble |
| * 0x9: CCK 5.5 Mbps long preamble |
| * 0xA: CCK 2 Mbps long preamble |
| * 0xB: CCK 1 Mbps long preamble |
| * 0xC: CCK 11 Mbps short preamble |
| * 0xD: CCK 5.5 Mbps short preamble |
| * 0xE: CCK 2 Mbps short preamble |
| * |
| * Return: rate interms of 500Kbps. |
| */ |
| static unsigned char get_rate(uint32_t l_sig_rate_select, uint32_t l_sig_rate) |
| { |
| char ret = 0x0; |
| |
| if (l_sig_rate_select == 0) { |
| switch (l_sig_rate) { |
| case 0x8: |
| ret = 0x60; |
| break; |
| case 0x9: |
| ret = 0x30; |
| break; |
| case 0xA: |
| ret = 0x18; |
| break; |
| case 0xB: |
| ret = 0x0c; |
| break; |
| case 0xC: |
| ret = 0x6c; |
| break; |
| case 0xD: |
| ret = 0x48; |
| break; |
| case 0xE: |
| ret = 0x24; |
| break; |
| case 0xF: |
| ret = 0x12; |
| break; |
| default: |
| break; |
| } |
| } else if (l_sig_rate_select == 1) { |
| switch (l_sig_rate) { |
| case 0x8: |
| ret = 0x16; |
| break; |
| case 0x9: |
| ret = 0x0B; |
| break; |
| case 0xA: |
| ret = 0x04; |
| break; |
| case 0xB: |
| ret = 0x02; |
| break; |
| case 0xC: |
| ret = 0x16; |
| break; |
| case 0xD: |
| ret = 0x0B; |
| break; |
| case 0xE: |
| ret = 0x04; |
| break; |
| default: |
| break; |
| } |
| } else { |
| adf_os_print("Invalid rate info\n"); |
| } |
| return ret; |
| } |
| |
| /** |
| * get_nr_antenna() - get number of antenna |
| * @rx_desc: pointer to htt_host_rx_desc_base. |
| * |
| * Return: number of antenna. |
| */ |
| unsigned char get_nr_antenna(struct htt_host_rx_desc_base *rx_desc) |
| { |
| uint8_t preamble_type = |
| (uint8_t)rx_desc->ppdu_start.preamble_type; |
| uint8_t mcs, nss = 1; |
| |
| switch (preamble_type) { |
| case 8: |
| case 9: |
| mcs = (uint8_t)(rx_desc->ppdu_start.ht_sig_vht_sig_a_1 & 0x7f); |
| nss = mcs>>3; |
| break; |
| case 0x0c: /* VHT w/o TxBF */ |
| case 0x0d: /* VHT w/ TxBF */ |
| mcs = (uint8_t)((rx_desc->ppdu_start.ht_sig_vht_sig_a_2 |
| >> 4) & 0xf); |
| nss = (uint8_t)((rx_desc->ppdu_start.ht_sig_vht_sig_a_1 |
| >> 10) & 0x7); |
| break; |
| default: |
| break; |
| } |
| return nss; |
| } |
| |
| /** |
| * get_ht_vht_info_ll() - get ht/vht information |
| * @rx_desc: pointer to PPDU start |
| * @rx_status: pointer to mon_rx_status. |
| * |
| * This function retrieve MCS/VHT info by parsing preamble, |
| * vht_sig_a1 and vht_sig_a2, which follows ieee80211 spec. |
| * |
| * Return: None. |
| */ |
| static void get_ht_vht_info_ll(struct rx_ppdu_start rx_desc, |
| struct mon_rx_status *rx_status) |
| { |
| uint8_t preamble_type = |
| (uint8_t)rx_desc.preamble_type; |
| uint32_t ht_sig_vht_sig_a_1 = rx_desc.ht_sig_vht_sig_a_1; |
| uint32_t ht_sig_vht_sig_a_2 = rx_desc.ht_sig_vht_sig_a_2; |
| switch (preamble_type) { |
| case 8: |
| case 9: |
| rx_status->mcs_info.valid = 1; |
| rx_status->vht_info.valid = 0; |
| rx_status->mcs_info.mcs = ht_sig_vht_sig_a_1 & 0x7f; |
| rx_status->nr_ant = rx_status->mcs_info.mcs >> 3; |
| rx_status->mcs_info.bw = (ht_sig_vht_sig_a_1 >> 7) & 0x1; |
| rx_status->mcs_info.smoothing = ht_sig_vht_sig_a_2 & 0x1; |
| rx_status->mcs_info.not_sounding = |
| (ht_sig_vht_sig_a_2 >> 1) & 0x1; |
| rx_status->mcs_info.aggregation = |
| (ht_sig_vht_sig_a_2 >> 3) & 0x1; |
| rx_status->mcs_info.stbc = (ht_sig_vht_sig_a_2 >> 4) & 0x3; |
| rx_status->mcs_info.fec = (ht_sig_vht_sig_a_2 >> 6) & 0x1; |
| rx_status->mcs_info.sgi = (ht_sig_vht_sig_a_2 >> 7) & 0x1; |
| rx_status->mcs_info.ness = (ht_sig_vht_sig_a_2 >> 8) & 0x3; |
| break; |
| case 0x0c: /* VHT w/o TxBF */ |
| case 0x0d: /* VHT w/ TxBF */ |
| rx_status->vht_info.valid = 1; |
| rx_status->mcs_info.valid = 0; |
| rx_status->vht_info.bw = ht_sig_vht_sig_a_1 & 0x3; |
| rx_status->vht_info.stbc = (ht_sig_vht_sig_a_1 >> 3) & 0x1; |
| /* Currently only handle SU case */ |
| rx_status->vht_info.gid = (ht_sig_vht_sig_a_1 >> 4) & 0x3f; |
| rx_status->vht_info.nss = (ht_sig_vht_sig_a_1 >> 10) & 0x7; |
| rx_status->nr_ant = (ht_sig_vht_sig_a_1 >> 10) & 0x7; |
| rx_status->vht_info.paid = (ht_sig_vht_sig_a_1 >> 13) & 0x1ff; |
| rx_status->vht_info.txps_forbidden = |
| (ht_sig_vht_sig_a_1 >> 22) & 0x1; |
| rx_status->vht_info.sgi = ht_sig_vht_sig_a_2 & 0x1; |
| rx_status->vht_info.sgi_disambiguation = |
| (ht_sig_vht_sig_a_2 >> 1) & 0x1; |
| rx_status->vht_info.coding = (ht_sig_vht_sig_a_2 >> 2) & 0x1; |
| rx_status->vht_info.ldpc_extra_symbol = |
| (ht_sig_vht_sig_a_2 >> 3) & 0x1; |
| rx_status->vht_info.mcs = (ht_sig_vht_sig_a_2 >> 4) & 0xf; |
| rx_status->vht_info.beamformed = |
| (ht_sig_vht_sig_a_2 >> 8) & 0x1; |
| break; |
| default: |
| rx_status->mcs_info.valid = 0; |
| rx_status->vht_info.valid = 0; |
| rx_status->nr_ant = 1; |
| break; |
| } |
| } |
| |
| /** |
| * htt_get_radiotap_rx_status_ll() - Update information about the rx status, |
| * which is used later for radiotap updation. |
| * @rx_desc: Pointer to struct htt_host_rx_desc_base |
| * @rx_status: Return variable updated with rx_status |
| * |
| * Return: None |
| */ |
| void htt_get_radiotap_rx_status_ll(struct htt_host_rx_desc_base *rx_desc, |
| struct mon_rx_status *rx_status) |
| { |
| uint16_t channel_flags = 0; |
| |
| rx_status->tsft = (u_int64_t)rx_desc->ppdu_end.tsf_timestamp; |
| /* IEEE80211_RADIOTAP_F_FCS */ |
| rx_status->flags |= 0x10; |
| rx_status->rate = get_rate(rx_desc->ppdu_start.l_sig_rate_select, |
| rx_desc->ppdu_start.l_sig_rate); |
| channel_flags |= rx_desc->ppdu_start.l_sig_rate_select? |
| IEEE80211_CHAN_CCK : IEEE80211_CHAN_OFDM; |
| rx_status->chan_flags = channel_flags; |
| rx_status->ant_signal_db = rx_desc->ppdu_start.rssi_comb; |
| rx_status->nr_ant = get_nr_antenna(rx_desc); |
| get_ht_vht_info_ll(rx_desc->ppdu_start, rx_status); |
| } |
| |
| struct mon_rx_status g_ll_rx_status; |
| |
| /** |
| * htt_rx_mon_amsdu_rx_in_order_pop_ll() - Monitor mode HTT Rx in order pop |
| * function |
| * @pdev: Handle to htt_pdev_handle |
| * @rx_ind_msg: In order indication message. |
| * @head_msdu: Return variable pointing to head msdu. |
| * @tail_msdu: Return variable pointing to tail msdu. |
| * |
| * This function pops the msdu based on paddr:length of inorder indication |
| * message. |
| * |
| * Return: 1 for sucess, 0 on failure. |
| */ |
| int |
| htt_rx_mon_amsdu_rx_in_order_pop_ll(htt_pdev_handle pdev, adf_nbuf_t rx_ind_msg, |
| adf_nbuf_t *head_msdu, |
| adf_nbuf_t *tail_msdu) |
| { |
| adf_nbuf_t msdu, next, prev = NULL; |
| uint8_t *rx_ind_data; |
| uint32_t *msg_word; |
| uint32_t msdu_count = 0; |
| struct htt_host_rx_desc_base *rx_desc; |
| struct mon_rx_status rx_status = {0}; |
| struct htt_rx_in_ord_paddr_ind_hdr_t *host_msg_hdr; |
| uint32_t amsdu_len; |
| uint32_t len; |
| uint32_t last_frag; |
| uint32_t ch_freq; |
| |
| HTT_ASSERT1(htt_rx_in_order_ring_elems(pdev) != 0); |
| |
| rx_ind_data = adf_nbuf_data(rx_ind_msg); |
| msg_word = (uint32_t *)rx_ind_data; |
| host_msg_hdr = (struct htt_rx_in_ord_paddr_ind_hdr_t *)rx_ind_data; |
| ch_freq = vos_chan_to_freq(host_msg_hdr->reserved_1); |
| |
| HTT_PKT_DUMP(vos_trace_hex_dump(VOS_MODULE_ID_TXRX, |
| VOS_TRACE_LEVEL_FATAL, |
| (void *)rx_ind_data, |
| (int)adf_nbuf_len(rx_ind_msg))); |
| |
| /* Get the total number of MSDUs */ |
| msdu_count = HTT_RX_IN_ORD_PADDR_IND_MSDU_CNT_GET(*(msg_word + 1)); |
| HTT_RX_CHECK_MSDU_COUNT(msdu_count); |
| |
| msg_word = (uint32_t *)(rx_ind_data + |
| HTT_RX_IN_ORD_PADDR_IND_HDR_BYTES); |
| |
| (*head_msdu) = msdu = htt_rx_in_order_netbuf_pop(pdev, |
| HTT_RX_IN_ORD_PADDR_IND_PADDR_GET(*msg_word)); |
| |
| if (adf_os_unlikely(NULL == msdu)) { |
| adf_os_print("%s: netbuf pop failed!\n", __func__); |
| *tail_msdu = NULL; |
| return 0; |
| } |
| while (msdu_count > 0) { |
| |
| msdu_count--; |
| /* |
| * Set the netbuf length to be the entire buffer length |
| * initially, so the unmap will unmap the entire buffer. |
| */ |
| adf_nbuf_set_pktlen(msdu, HTT_RX_BUF_SIZE); |
| adf_nbuf_unmap(pdev->osdev, msdu, ADF_OS_DMA_FROM_DEVICE); |
| |
| /* |
| * cache consistency has been taken care of by the |
| * adf_nbuf_unmap |
| */ |
| rx_desc = htt_rx_desc(msdu); |
| HTT_PKT_DUMP(htt_print_rx_desc(rx_desc)); |
| /* |
| * Make the netbuf's data pointer point to the payload rather |
| * than the descriptor. |
| */ |
| if(rx_desc->attention.first_mpdu) { |
| memset(&rx_status, 0, sizeof(struct mon_rx_status)); |
| rx_status.chan = (uint16_t)ch_freq; |
| htt_get_radiotap_rx_status_ll(rx_desc, &rx_status); |
| memcpy(&g_ll_rx_status,&rx_status,sizeof(struct mon_rx_status)); |
| } |
| /* |
| * 250 bytes of RX_STD_DESC size should be sufficient for |
| * radiotap. |
| */ |
| adf_nbuf_update_radiotap(&g_ll_rx_status, msdu, |
| HTT_RX_STD_DESC_RESERVATION); |
| amsdu_len = HTT_RX_IN_ORD_PADDR_IND_MSDU_LEN_GET(*(msg_word |
| + 1)); |
| /* |
| * MAX_RX_PAYLOAD_SZ when we have AMSDU packet. amsdu_len in |
| * which case is the total length of sum of all AMSDU's |
| */ |
| len = MIN(amsdu_len, MAX_RX_PAYLOAD_SZ); |
| amsdu_len -= len; |
| adf_nbuf_trim_tail(msdu, |
| HTT_RX_BUF_SIZE - |
| (RX_STD_DESC_SIZE + len)); |
| |
| |
| HTT_PKT_DUMP(vos_trace_hex_dump(VOS_MODULE_ID_TXRX, |
| VOS_TRACE_LEVEL_FATAL, |
| adf_nbuf_data(msdu), |
| adf_nbuf_len(msdu))); |
| last_frag = ((struct htt_rx_in_ord_paddr_ind_msdu32_t *) |
| msg_word)->msdu_info; |
| |
| /* Handle amsdu packet */ |
| if (!last_frag) { |
| /* |
| * For AMSDU packet msdu->len is sum of all the msdu's |
| * length, msdu->data_len is sum of length's of |
| * remaining msdu's other than parent. |
| */ |
| if (!htt_handle_amsdu_packet(msdu, pdev, &msg_word, |
| amsdu_len)) { |
| adf_os_print("%s: failed to handle amsdu packet\n", |
| __func__); |
| return 0; |
| } |
| } |
| /* check if this is the last msdu */ |
| if (msdu_count) { |
| msg_word += HTT_RX_IN_ORD_PADDR_IND_MSDU_DWORDS; |
| next = htt_rx_in_order_netbuf_pop(pdev, |
| HTT_RX_IN_ORD_PADDR_IND_PADDR_GET(*msg_word)); |
| if (adf_os_unlikely(NULL == next)) { |
| adf_os_print("%s: netbuf pop failed!\n", |
| __func__); |
| *tail_msdu = NULL; |
| return 0; |
| } |
| adf_nbuf_set_next(msdu, next); |
| prev = msdu; |
| msdu = next; |
| } else { |
| *tail_msdu = msdu; |
| adf_nbuf_set_next(msdu, NULL); |
| } |
| } |
| |
| return 1; |
| } |
| |
| /** |
| * get_ht_vht_info() - get ht/vht information |
| * @rx_desc: pointer to htt_host_rx_desc_base. |
| * @rx_status: pointer to mon_rx_status. |
| * |
| * This function retrieve MCS/VHT info by parsing preamble, |
| * vht_sig_a1 and vht_sig_a2, which follows ieee80211 spec. |
| * Since high latency path doesn't config PPDU/MPDU start/end, |
| * it only uses the info which htt_rx_ppdu_desc_t has. |
| * |
| * Return: None. |
| */ |
| static void get_ht_vht_info_hl(struct htt_rx_ppdu_desc_t *rx_desc, |
| struct mon_rx_status *rx_status) |
| { |
| uint8_t preamble_type = |
| (uint8_t)rx_desc->preamble_type; |
| |
| switch (preamble_type) { |
| case 8: |
| case 9: |
| rx_status->mcs_info.valid = 1; |
| rx_status->vht_info.valid = 0; |
| rx_status->mcs_info.mcs = rx_desc->vht_sig_a1 & 0x7f; |
| rx_status->nr_ant = rx_status->mcs_info.mcs >> 3; |
| rx_status->mcs_info.bw = (rx_desc->vht_sig_a1 >> 7) & 0x1; |
| rx_status->mcs_info.smoothing = rx_desc->vht_sig_a2 & 0x1; |
| rx_status->mcs_info.not_sounding = |
| (rx_desc->vht_sig_a2 >> 1) & 0x1; |
| rx_status->mcs_info.aggregation = |
| (rx_desc->vht_sig_a2 >> 3) & 0x1; |
| rx_status->mcs_info.stbc = (rx_desc->vht_sig_a2 >> 4) & 0x3; |
| rx_status->mcs_info.fec = (rx_desc->vht_sig_a2 >> 6) & 0x1; |
| rx_status->mcs_info.sgi = (rx_desc->vht_sig_a2 >> 7) & 0x1; |
| rx_status->mcs_info.ness = (rx_desc->vht_sig_a2 >> 8) & 0x3; |
| break; |
| case 0x0c: /* VHT w/o TxBF */ |
| case 0x0d: /* VHT w/ TxBF */ |
| rx_status->vht_info.valid = 1; |
| rx_status->mcs_info.valid = 0; |
| rx_status->vht_info.bw = rx_desc->vht_sig_a1 & 0x3; |
| rx_status->vht_info.stbc = (rx_desc->vht_sig_a1 >> 3) & 0x1; |
| /* Currently only handle SU case */ |
| rx_status->vht_info.gid = (rx_desc->vht_sig_a1 >> 4) & 0x3f; |
| rx_status->vht_info.nss = (rx_desc->vht_sig_a1 >> 10) & 0x7; |
| rx_status->nr_ant = (rx_desc->vht_sig_a1 >> 10) & 0x7; |
| rx_status->vht_info.paid = (rx_desc->vht_sig_a1 >> 13) & 0x1ff; |
| rx_status->vht_info.txps_forbidden = |
| (rx_desc->vht_sig_a1 >> 22) & 0x1; |
| rx_status->vht_info.sgi = rx_desc->vht_sig_a2 & 0x1; |
| rx_status->vht_info.sgi_disambiguation = |
| (rx_desc->vht_sig_a2 >> 1) & 0x1; |
| rx_status->vht_info.coding = (rx_desc->vht_sig_a2 >> 2) & 0x1; |
| rx_status->vht_info.ldpc_extra_symbol = |
| (rx_desc->vht_sig_a2 >> 3) & 0x1; |
| rx_status->vht_info.mcs = (rx_desc->vht_sig_a2 |
| >> 4) & 0xf; |
| rx_status->vht_info.beamformed = (rx_desc->vht_sig_a2 |
| >> 8) & 0x1; |
| break; |
| default: |
| rx_status->mcs_info.valid = 0; |
| rx_status->vht_info.valid = 0; |
| rx_status->nr_ant = 1; |
| break; |
| } |
| } |
| |
| /** |
| * htt_get_radiotap_rx_status_hl() - Update information about the |
| * rx status, which is used later for radiotap update. |
| * @rx_desc: Pointer to struct htt_rx_ppdu_desc_t |
| * @rx_status: Return variable updated with rx_status |
| * |
| * Return: None |
| */ |
| void htt_get_radiotap_rx_status_hl(struct htt_rx_ppdu_desc_t *rx_desc, |
| struct mon_rx_status *rx_status) |
| { |
| uint16_t channel_flags = 0; |
| |
| rx_status->tsft = (u_int64_t)rx_desc->tsf32; |
| /* IEEE80211_RADIOTAP_F_FCS */ |
| rx_status->flags |= 0x10; |
| rx_status->rate = get_rate(rx_desc->legacy_rate_sel, |
| rx_desc->legacy_rate); |
| channel_flags |= rx_desc->legacy_rate_sel ? |
| IEEE80211_CHAN_CCK : IEEE80211_CHAN_OFDM; |
| if (rx_status->chan) |
| channel_flags |= |
| (vos_chan_to_band(vos_freq_to_chan(rx_status->chan)) |
| == VOS_BAND_2GHZ ? |
| IEEE80211_CHAN_2GHZ : IEEE80211_CHAN_5GHZ); |
| rx_status->chan_flags = channel_flags; |
| rx_status->ant_signal_db = rx_desc->rssi_cmb; |
| get_ht_vht_info_hl(rx_desc, rx_status); |
| } |
| |
| /** |
| * htt_rx_mon_amsdu_pop_hl() - pop amsdu in HL monitor mode |
| * @pdev: Pointer to struct htt_pdev_handle |
| * @rx_ind_msg: htt rx indication message |
| * @head_msdu: head msdu |
| * @tail_msdu: tail msdu |
| * |
| * Return: 0 - success, others - failure |
| */ |
| int |
| htt_rx_mon_amsdu_pop_hl( |
| htt_pdev_handle pdev, |
| adf_nbuf_t rx_ind_msg, |
| adf_nbuf_t *head_msdu, |
| adf_nbuf_t *tail_msdu) |
| { |
| struct htt_rx_ppdu_desc_t *rx_ppdu_desc; |
| void *rx_desc, *rx_mpdu_desc; |
| struct mon_rx_status rx_status = {0}; |
| int rtap_len = 0; |
| uint16_t center_freq; |
| uint16_t chan1; |
| uint16_t chan2; |
| uint8_t phymode; |
| a_bool_t ret; |
| |
| pdev->rx_desc_size_hl = |
| (adf_nbuf_data(rx_ind_msg)) |
| [HTT_ENDIAN_BYTE_IDX_SWAP( |
| HTT_RX_IND_HL_RX_DESC_LEN_OFFSET)]; |
| |
| adf_nbuf_pull_head(rx_ind_msg, |
| sizeof(struct hl_htt_rx_ind_base)); |
| |
| *head_msdu = *tail_msdu = rx_ind_msg; |
| |
| rx_desc = htt_rx_msdu_desc_retrieve(pdev, *head_msdu); |
| rx_ppdu_desc = (struct htt_rx_ppdu_desc_t *)((uint8_t *)(rx_desc) - |
| HTT_RX_IND_HL_BYTES + HTT_RX_IND_HDR_PREFIX_BYTES); |
| |
| rx_mpdu_desc = |
| htt_rx_mpdu_desc_list_next(pdev, rx_ind_msg); |
| ret = htt_rx_msdu_center_freq(pdev, NULL, rx_mpdu_desc, |
| ¢er_freq, &chan1, &chan2, &phymode); |
| |
| if (ret == A_TRUE) |
| rx_status.chan = center_freq; |
| else |
| rx_status.chan = 0; |
| |
| htt_get_radiotap_rx_status_hl(rx_ppdu_desc, &rx_status); |
| /* |
| * set headroom size to 0 to append to tail of skb. For HL path, |
| * rx desc size is variable and will be used later in ol_rx_deliver |
| * function to reset adf_nbuf to payload. So, to avoid overwriting |
| * the rx desc, radiotap header is added to the tail of adf_nbuf |
| * at first and move to head before indicating to OS. |
| */ |
| rtap_len = adf_nbuf_update_radiotap(&rx_status, *head_msdu, 0); |
| |
| adf_nbuf_set_next(*tail_msdu, NULL); |
| return 0; |
| } |
| |
| int |
| htt_rx_mac_header_mon_process( |
| htt_pdev_handle pdev, |
| adf_nbuf_t rx_ind_msg, |
| adf_nbuf_t *head_msdu, |
| adf_nbuf_t *tail_msdu) |
| { |
| struct htt_hw_rx_desc_base *hw_desc; |
| struct ieee80211_frame_addr4 *mac_array; |
| uint8_t rtap_buf[sizeof(struct ieee80211_radiotap_header) + 100] = {0}; |
| uint16_t rtap_len; |
| uint32_t *msg_word; |
| uint8_t *rx_ind_data; |
| adf_nbuf_t msdu = NULL; |
| |
| /* num of mac header in rx_ind_msg */ |
| int num_elems; |
| int elem; |
| uint32_t tsf; |
| uint32_t rssi_comb; |
| |
| rx_ind_data = adf_nbuf_data(rx_ind_msg); |
| msg_word = (uint32_t *)rx_ind_data; |
| msg_word++; |
| num_elems = HTT_T2H_MONITOR_MAC_HEADER_NUM_MPDU_GET(*msg_word); |
| |
| /* what's the num_elem max value? */ |
| if (num_elems <= 0) |
| return 0; |
| |
| /* get htt_hw_rx_desc_base_rx_desc pointer */ |
| hw_desc = (struct htt_hw_rx_desc_base *) |
| (rx_ind_data + HTT_T2H_MONITOR_MAC_HEADER_IND_HDR_SIZE); |
| |
| rssi_comb = hw_desc->ppdu_start.rssi_comb; |
| tsf = hw_desc->ppdu_end.tsf_timestamp; |
| |
| /* construct one radiotap header */ |
| rtap_len = adf_nbuf_construct_radiotap( |
| rtap_buf, |
| tsf, |
| rssi_comb); |
| |
| /* get ieee80211_frame_addr4 array pointer*/ |
| mac_array = (struct ieee80211_frame_addr4 *) |
| (rx_ind_data + HTT_T2H_MONITOR_MAC_HEADER_IND_HDR_SIZE + |
| sizeof(struct htt_hw_rx_desc_base)); |
| |
| for (elem = 0; elem < num_elems; elem++) { |
| uint8_t *dest = NULL; |
| /* |
| * copy each mac header + |
| * radiotap header into single msdu buff |
| */ |
| msdu = adf_nbuf_alloc( |
| pdev->osdev, |
| rtap_len + sizeof(struct ieee80211_frame_addr4), |
| 0, 4, TRUE); |
| if (!msdu) |
| return A_NO_MEMORY; |
| |
| dest = adf_nbuf_put_tail(msdu, rtap_len); |
| if (!dest) { |
| adf_os_print("%s: No buffer to save radiotap len %d\n", |
| __func__, rtap_len); |
| return A_NO_MEMORY; |
| } |
| adf_os_mem_copy(dest, rtap_buf, rtap_len); |
| |
| dest = adf_nbuf_put_tail(msdu, |
| sizeof(struct ieee80211_frame_addr4)); |
| if (!dest) { |
| adf_os_print("%s: No buffer for mac header %u\n", |
| __func__, |
| (unsigned int) |
| sizeof(struct ieee80211_frame_addr4)); |
| return A_NO_MEMORY; |
| } |
| adf_os_mem_copy(dest, &mac_array[elem], |
| sizeof(struct ieee80211_frame_addr4)); |
| |
| adf_nbuf_set_next(msdu, NULL); |
| if (*head_msdu == NULL) { |
| *head_msdu = msdu; |
| *tail_msdu = msdu; |
| } else { |
| adf_nbuf_set_next(*tail_msdu, msdu); |
| *tail_msdu = msdu; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int |
| htt_rx_offload_msdu_cnt_hl( |
| htt_pdev_handle pdev) |
| { |
| return 1; |
| } |
| |
| /* Return values: 1 - success, 0 - failure */ |
| int |
| htt_rx_offload_msdu_pop_hl( |
| htt_pdev_handle pdev, |
| adf_nbuf_t offload_deliver_msg, |
| int *vdev_id, |
| int *peer_id, |
| int *tid, |
| u_int8_t *fw_desc, |
| adf_nbuf_t *head_buf, |
| adf_nbuf_t *tail_buf) |
| { |
| adf_nbuf_t buf; |
| u_int32_t *msdu_hdr, msdu_len; |
| u_int32_t *first_word; |
| u_int8_t fake_desc_size; |
| int ret = 0; |
| |
| *head_buf = *tail_buf = buf = offload_deliver_msg; |
| msdu_hdr = (u_int32_t *)adf_nbuf_data(buf); |
| |
| /* First dword */ |
| first_word = msdu_hdr; |
| |
| /* Second dword */ |
| msdu_hdr++; |
| msdu_len = HTT_RX_OFFLOAD_DELIVER_IND_MSDU_LEN_GET(*msdu_hdr); |
| *peer_id = HTT_RX_OFFLOAD_DELIVER_IND_MSDU_PEER_ID_GET(*msdu_hdr); |
| |
| /* Third dword */ |
| msdu_hdr++; |
| *vdev_id = HTT_RX_OFFLOAD_DELIVER_IND_MSDU_VDEV_ID_GET(*msdu_hdr); |
| *tid = HTT_RX_OFFLOAD_DELIVER_IND_MSDU_TID_GET(*msdu_hdr); |
| *fw_desc = HTT_RX_OFFLOAD_DELIVER_IND_MSDU_DESC_GET(*msdu_hdr); |
| |
| /* align forwarding case with normal rx path, in HL platform rx_desc |
| * is located in payload area, and there are 8 bytes between ending |
| * of htt header and starting of rx_desc, so pull 8bytes and then |
| * throw the packet to rx path. |
| * |
| * Normal rx htt header |
| * |
| * |------------------------------------| |
| * | htt_rx_ind_hdr_prefix_t | |
| * |------------------------------------| |
| * | htt_rx_ppdu_desc_t | |
| * |------------------------------------| |
| * | htt_rx_ind_hdr_suffix_t | |
| * |------------------------------------| |
| * | flags | len | ver | fw_rx_desc_base| |
| * |------------------------------------| |
| * | range | |
| * |------------------------------------| |
| * | rx desc | |
| * |
| */ |
| adf_nbuf_pull_head(buf, HTT_RX_IND_HL_BYTES - HTT_RX_IND_HDR_BYTES); |
| |
| fake_desc_size = HTT_RX_OFFLOAD_DELIVER_IND_MSDU_HDR_BYTES |
| + HTT_RX_OFFLOAD_DELIVER_IND_HDR_BYTES |
| - (HTT_RX_IND_HL_BYTES - HTT_RX_IND_HDR_BYTES); |
| |
| msdu_len += fake_desc_size; |
| |
| |
| if (msdu_len <= adf_nbuf_len(buf)) { |
| *first_word = 0; |
| /* put rx desc len in the location showed in above table */ |
| ((u_int8_t*)first_word)[HTT_RX_IND_HL_RX_DESC_LEN_OFFSET |
| - HTT_RX_IND_FW_RX_DESC_BYTE_OFFSET] = fake_desc_size; |
| |
| *(u_int8_t*)first_word = *fw_desc; |
| |
| adf_nbuf_set_pktlen(buf, msdu_len); |
| } else { |
| adf_os_print("%s: drop frame with invalid msdu len %d %d\n", |
| __FUNCTION__, msdu_len, (int)adf_nbuf_len(buf)); |
| adf_nbuf_free(offload_deliver_msg); |
| ret = -1; |
| } |
| return ret; |
| } |
| |
| /* Return values: 1 - success, 0 - failure */ |
| int |
| htt_rx_amsdu_rx_in_order_pop_ll( |
| htt_pdev_handle pdev, |
| adf_nbuf_t rx_ind_msg, |
| adf_nbuf_t *head_msdu, |
| adf_nbuf_t *tail_msdu) |
| { |
| adf_nbuf_t msdu, next, prev = NULL; |
| u_int8_t *rx_ind_data; |
| u_int32_t *msg_word; |
| unsigned int msdu_count = 0; |
| u_int8_t offload_ind; |
| struct htt_host_rx_desc_base *rx_desc; |
| enum rx_pkt_fate status = RX_PKT_FATE_SUCCESS; |
| uint16_t peer_id; |
| struct ol_txrx_peer_t *peer; |
| |
| HTT_ASSERT1(htt_rx_in_order_ring_elems(pdev) != 0); |
| |
| rx_ind_data = adf_nbuf_data(rx_ind_msg); |
| msg_word = (u_int32_t *)rx_ind_data; |
| |
| offload_ind = HTT_RX_IN_ORD_PADDR_IND_OFFLOAD_GET(*msg_word); |
| |
| /* Get the total number of MSDUs */ |
| msdu_count = HTT_RX_IN_ORD_PADDR_IND_MSDU_CNT_GET(*(msg_word + 1)); |
| HTT_RX_CHECK_MSDU_COUNT(msdu_count); |
| peer_id = HTT_RX_IN_ORD_PADDR_IND_PEER_ID_GET( |
| *(u_int32_t *)rx_ind_data); |
| |
| msg_word = (u_int32_t *)(rx_ind_data + HTT_RX_IN_ORD_PADDR_IND_HDR_BYTES); |
| if (offload_ind) { |
| ol_rx_offload_paddr_deliver_ind_handler(pdev, msdu_count, |
| msg_word); |
| *head_msdu = *tail_msdu = NULL; |
| return 0; |
| } |
| |
| peer = ol_txrx_peer_find_by_id(pdev->txrx_pdev, peer_id); |
| if (!peer) |
| adf_os_print(KERN_DEBUG "%s: invalid peer id %d and msdu count %d\n", |
| __func__, peer_id, msdu_count); |
| |
| (*head_msdu) = msdu = |
| htt_rx_in_order_netbuf_pop(pdev, |
| HTT_RX_IN_ORD_PADDR_IND_PADDR_GET(*msg_word)); |
| |
| if (adf_os_unlikely(NULL == msdu)) { |
| adf_os_print("%s: netbuf pop failed!\n", __FUNCTION__); |
| *tail_msdu = NULL; |
| return 0; |
| } |
| |
| while (msdu_count > 0) { |
| |
| /* |
| * Set the netbuf length to be the entire buffer length initially, |
| * so the unmap will unmap the entire buffer. |
| */ |
| adf_nbuf_set_pktlen(msdu, HTT_RX_BUF_SIZE); |
| #ifdef DEBUG_DMA_DONE |
| adf_nbuf_unmap(pdev->osdev, msdu, ADF_OS_DMA_BIDIRECTIONAL); |
| #else |
| adf_nbuf_unmap(pdev->osdev, msdu, ADF_OS_DMA_FROM_DEVICE); |
| #endif |
| |
| /* cache consistency has been taken care of by the adf_nbuf_unmap */ |
| |
| rx_desc = htt_rx_desc(msdu); |
| /* |
| * Make the netbuf's data pointer point to the payload rather |
| * than the descriptor. |
| */ |
| adf_nbuf_pull_head(msdu, HTT_RX_STD_DESC_RESERVATION); |
| |
| adf_dp_trace_set_track(msdu, ADF_RX); |
| NBUF_SET_PACKET_TRACK(msdu, NBUF_TX_PKT_DATA_TRACK); |
| ol_rx_log_packet(pdev, peer_id, msdu); |
| DPTRACE(adf_dp_trace(msdu, |
| ADF_DP_TRACE_RX_HTT_PACKET_PTR_RECORD, |
| adf_nbuf_data_addr(msdu), |
| sizeof(adf_nbuf_data(msdu)), ADF_RX)); |
| |
| adf_nbuf_trim_tail( |
| msdu, HTT_RX_BUF_SIZE - (RX_STD_DESC_SIZE + |
| HTT_RX_IN_ORD_PADDR_IND_MSDU_LEN_GET(*(msg_word + 1)))); |
| |
| *((u_int8_t *) &rx_desc->fw_desc.u.val) = |
| HTT_RX_IN_ORD_PADDR_IND_FW_DESC_GET(*(msg_word + 1)); |
| |
| msdu_count--; |
| |
| /* calling callback function for packet logging */ |
| |
| if (adf_os_unlikely((*((u_int8_t *) &rx_desc->fw_desc.u.val)) & |
| FW_RX_DESC_MIC_ERR_M)) |
| status = RX_PKT_FATE_FW_DROP_INVALID; |
| if (pdev->rx_pkt_dump_cb) |
| pdev->rx_pkt_dump_cb(msdu, peer, status); |
| |
| if (adf_os_unlikely((*((u_int8_t *) &rx_desc->fw_desc.u.val)) & |
| FW_RX_DESC_MIC_ERR_M)) { |
| u_int8_t tid = |
| HTT_RX_IN_ORD_PADDR_IND_EXT_TID_GET(*(u_int32_t *)rx_ind_data); |
| |
| ol_rx_mic_error_handler(pdev->txrx_pdev, tid, peer_id, rx_desc, msdu); |
| |
| htt_rx_desc_frame_free(pdev, msdu); |
| |
| /* if this is the last msdu */ |
| if (!msdu_count) { |
| /* if this is the only msdu */ |
| if (!prev) { |
| *head_msdu = *tail_msdu = NULL; |
| return 0; |
| } else { |
| *tail_msdu = prev; |
| adf_nbuf_set_next(prev, NULL); |
| return 1; |
| } |
| } else { /* if this is not the last msdu */ |
| /* get the next msdu */ |
| msg_word += HTT_RX_IN_ORD_PADDR_IND_MSDU_DWORDS; |
| next = htt_rx_in_order_netbuf_pop(pdev, |
| HTT_RX_IN_ORD_PADDR_IND_PADDR_GET(*msg_word)); |
| if (adf_os_unlikely(NULL == next)) { |
| adf_os_print("%s: netbuf pop failed!\n", __FUNCTION__); |
| *tail_msdu = NULL; |
| return 0; |
| } |
| |
| /* if this is not the first msdu, update the next pointer of the |
| preceding msdu */ |
| if (prev) { |
| adf_nbuf_set_next(prev, next); |
| } else {/* if this is the first msdu, update the head pointer */ |
| *head_msdu = next; |
| } |
| msdu = next; |
| continue; |
| } |
| } |
| |
| /* Update checksum result */ |
| htt_set_checksum_result_ll(pdev, msdu, rx_desc); |
| |
| /* check if this is the last msdu */ |
| if (msdu_count) { |
| msg_word += HTT_RX_IN_ORD_PADDR_IND_MSDU_DWORDS; |
| next = htt_rx_in_order_netbuf_pop(pdev, |
| HTT_RX_IN_ORD_PADDR_IND_PADDR_GET(*msg_word)); |
| if (adf_os_unlikely(NULL == next)) { |
| adf_os_print("%s: netbuf pop failed!\n", __FUNCTION__); |
| *tail_msdu = NULL; |
| return 0; |
| } |
| adf_nbuf_set_next(msdu, next); |
| prev = msdu; |
| msdu = next; |
| } |
| else { |
| *tail_msdu = msdu; |
| adf_nbuf_set_next(msdu, NULL); |
| } |
| } |
| |
| return 1; |
| } |
| |
| /* Util fake function that has same prototype as adf_nbuf_clone that just |
| * retures the same nbuf |
| */ |
| adf_nbuf_t |
| htt_rx_adf_noclone_buf(adf_nbuf_t buf) |
| { |
| return buf; |
| } |
| |
| /* FIXME: This is a HW definition not provded by HW, where does it go ? */ |
| enum { |
| HW_RX_DECAP_FORMAT_RAW = 0, |
| HW_RX_DECAP_FORMAT_NWIFI, |
| HW_RX_DECAP_FORMAT_8023, |
| HW_RX_DECAP_FORMAT_ETH2, |
| }; |
| |
| #define HTT_FCS_LEN (4) |
| |
| static void |
| htt_rx_parse_ppdu_start_status( |
| struct htt_host_rx_desc_base *rx_desc, |
| struct ieee80211_rx_status *rs) |
| { |
| |
| struct rx_ppdu_start *ppdu_start = &rx_desc->ppdu_start; |
| |
| /* RSSI */ |
| rs->rs_rssi = ppdu_start->rssi_comb; |
| |
| /* PHY rate */ |
| /* rs_ratephy coding |
| [b3 - b0] |
| 0 -> OFDM |
| 1 -> CCK |
| 2 -> HT |
| 3 -> VHT |
| OFDM / CCK |
| [b7 - b4 ] => LSIG rate |
| [b23 - b8 ] => service field (b'12 static/dynamic, b'14..b'13 BW for VHT) |
| [b31 - b24 ] => Reserved |
| HT / VHT |
| [b15 - b4 ] => SIG A_2 12 LSBs |
| [b31 - b16] => SIG A_1 16 LSBs |
| |
| */ |
| if (ppdu_start->preamble_type == 0x4 ) { |
| rs->rs_ratephy = ppdu_start->l_sig_rate_select; |
| rs->rs_ratephy |= ppdu_start->l_sig_rate << 4; |
| rs->rs_ratephy |= ppdu_start->service << 8; |
| } else { |
| rs->rs_ratephy = |
| (ppdu_start->preamble_type & 0x4) ? 3 : 2; |
| rs->rs_ratephy |= |
| (ppdu_start->ht_sig_vht_sig_a_2 & 0xFFF) << 4; |
| rs->rs_ratephy |= |
| (ppdu_start->ht_sig_vht_sig_a_1 & 0xFFFF) << 16; |
| } |
| |
| return; |
| } |
| |
| |
| /* This function is used by montior mode code to restitch an MSDU list |
| * corresponding to an MPDU back into an MPDU by linking up the skbs. |
| */ |
| adf_nbuf_t |
| htt_rx_restitch_mpdu_from_msdus( |
| htt_pdev_handle pdev, |
| adf_nbuf_t head_msdu, |
| struct ieee80211_rx_status *rx_status, |
| unsigned clone_not_reqd) |
| { |
| |
| adf_nbuf_t msdu, mpdu_buf, prev_buf, msdu_orig, head_frag_list_cloned; |
| adf_nbuf_t (*clone_nbuf_fn)(adf_nbuf_t buf); |
| unsigned decap_format, wifi_hdr_len, sec_hdr_len, msdu_llc_len, |
| mpdu_buf_len, decap_hdr_pull_bytes, frag_list_sum_len, dir, |
| is_amsdu, is_first_frag, amsdu_pad, msdu_len; |
| struct htt_host_rx_desc_base *rx_desc; |
| char *hdr_desc; |
| unsigned char *dest; |
| struct ieee80211_frame *wh; |
| struct ieee80211_qoscntl*qos; |
| |
| /* If this packet does not go up the normal stack path we dont need to |
| * waste cycles cloning the packets |
| */ |
| clone_nbuf_fn = |
| clone_not_reqd ? htt_rx_adf_noclone_buf : adf_nbuf_clone; |
| |
| /* The nbuf has been pulled just beyond the status and points to the |
| * payload |
| */ |
| msdu_orig = head_msdu; |
| rx_desc = htt_rx_desc(msdu_orig); |
| |
| /* Fill out the rx_status from the PPDU start and end fields */ |
| if (rx_desc->attention.first_mpdu) { |
| htt_rx_parse_ppdu_start_status(rx_desc, rx_status); |
| |
| /* The timestamp is no longer valid - It will be valid only for the |
| * last MPDU |
| */ |
| rx_status->rs_tstamp.tsf = ~0; |
| } |
| |
| decap_format = |
| GET_FIELD(&rx_desc->msdu_start, RX_MSDU_START_2_DECAP_FORMAT); |
| |
| head_frag_list_cloned = NULL; |
| |
| /* Easy case - The MSDU status indicates that this is a non-decapped |
| * packet in RAW mode. |
| * return |
| */ |
| if (decap_format == HW_RX_DECAP_FORMAT_RAW) { |
| /* Note that this path might suffer from headroom unavailabilty - |
| * but the RX status is usually enough |
| */ |
| mpdu_buf = clone_nbuf_fn(head_msdu); |
| |
| prev_buf = mpdu_buf; |
| |
| frag_list_sum_len = 0; |
| is_first_frag = 1; |
| msdu_len = adf_nbuf_len(mpdu_buf); |
| |
| /* Drop the zero-length msdu */ |
| if (!msdu_len) { |
| goto mpdu_stitch_fail; |
| } |
| msdu_orig = adf_nbuf_next(head_msdu); |
| |
| while (msdu_orig) { |
| |
| /* TODO: intra AMSDU padding - do we need it ??? */ |
| msdu = clone_nbuf_fn(msdu_orig); |
| if (!msdu) { |
| goto mpdu_stitch_fail; |
| } |
| |
| if (is_first_frag) { |
| is_first_frag = 0; |
| head_frag_list_cloned = msdu; |
| } |
| |
| msdu_len = adf_nbuf_len(msdu); |
| /* Drop the zero-length msdu */ |
| if (!msdu_len) { |
| goto mpdu_stitch_fail; |
| } |
| |
| frag_list_sum_len += msdu_len; |
| |
| /* Maintain the linking of the cloned MSDUS */ |
| adf_nbuf_set_next_ext(prev_buf, msdu); |
| |
| /* Move to the next */ |
| prev_buf = msdu; |
| msdu_orig = adf_nbuf_next(msdu_orig); |
| } |
| |
| /* The last msdu length need be larger than HTT_FCS_LEN */ |
| if (msdu_len < HTT_FCS_LEN) { |
| goto mpdu_stitch_fail; |
| } |
| |
| adf_nbuf_trim_tail(prev_buf, HTT_FCS_LEN); |
| |
| /* If there were more fragments to this RAW frame */ |
| if (head_frag_list_cloned) { |
| adf_nbuf_append_ext_list(mpdu_buf, head_frag_list_cloned, |
| frag_list_sum_len); |
| } |
| |
| goto mpdu_stitch_done; |
| } |
| |
| /* Decap mode: |
| * Calculate the amount of header in decapped packet to knock off based |
| * on the decap type and the corresponding number of raw bytes to copy |
| * status header |
| */ |
| |
| hdr_desc = &rx_desc->rx_hdr_status[0]; |
| |
| /* Base size */ |
| wifi_hdr_len = sizeof(struct ieee80211_frame); |
| wh = (struct ieee80211_frame*)hdr_desc; |
| |
| dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; |
| if (dir == IEEE80211_FC1_DIR_DSTODS) { |
| wifi_hdr_len += 6; |
| } |
| |
| is_amsdu = 0; |
| if (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_QOS) { |
| qos = (struct ieee80211_qoscntl*) |
| (hdr_desc + wifi_hdr_len); |
| wifi_hdr_len += 2; |
| |
| is_amsdu = (qos->i_qos[0] & IEEE80211_QOS_AMSDU); |
| } |
| |
| /* TODO: Any security headers associated with MPDU */ |
| sec_hdr_len = 0; |
| |
| /* MSDU related stuff LLC - AMSDU subframe header etc */ |
| msdu_llc_len = is_amsdu ? (14 + 8) : 8; |
| |
| mpdu_buf_len = wifi_hdr_len + sec_hdr_len + msdu_llc_len; |
| |
| /* "Decap" header to remove from MSDU buffer */ |
| decap_hdr_pull_bytes = 14; |
| |
| /* Allocate a new nbuf for holding the 802.11 header retrieved from the |
| * status of the now decapped first msdu. Leave enough headroom for |
| * accomodating any radio-tap /prism like PHY header |
| */ |
| #define HTT_MAX_MONITOR_HEADER (512) |
| mpdu_buf = adf_nbuf_alloc(pdev->osdev, |
| HTT_MAX_MONITOR_HEADER + mpdu_buf_len, |
| HTT_MAX_MONITOR_HEADER, 4, FALSE); |
| |
| if (!mpdu_buf) { |
| goto mpdu_stitch_fail; |
| } |
| |
| /* Copy the MPDU related header and enc headers into the first buffer |
| * - Note that there can be a 2 byte pad between heaader and enc header |
| */ |
| |
| prev_buf = mpdu_buf; |
| dest = adf_nbuf_put_tail(prev_buf, wifi_hdr_len); |
| if (!dest) { |
| goto mpdu_stitch_fail; |
| } |
| adf_os_mem_copy(dest, hdr_desc, wifi_hdr_len); |
| hdr_desc += wifi_hdr_len; |
| |
| /* NOTE - This padding is present only in the RAW header status - not |
| * when the MSDU data payload is in RAW format. |
| */ |
| /* Skip the "IV pad" */ |
| if (wifi_hdr_len & 0x3) { |
| hdr_desc += 2; |
| } |
| |
| #if 0 |
| dest = adf_nbuf_put_tail(prev_buf, sec_hdr_len); |
| adf_os_mem_copy(dest, hdr_desc, sec_hdr_len); |
| hdr_desc += sec_hdr_len; |
| #endif |
| |
| /* The first LLC len is copied into the MPDU buffer */ |
| frag_list_sum_len = 0; |
| frag_list_sum_len -= msdu_llc_len; |
| |
| msdu_orig = head_msdu; |
| is_first_frag = 1; |
| amsdu_pad = 0; |
| |
| while (msdu_orig) { |
| |
| /* TODO: intra AMSDU padding - do we need it ??? */ |
| |
| msdu = clone_nbuf_fn(msdu_orig); |
| if (!msdu) { |
| goto mpdu_stitch_fail; |
| } |
| |
| if (is_first_frag) { |
| is_first_frag = 0; |
| head_frag_list_cloned = msdu; |
| } else { |
| |
| /* Maintain the linking of the cloned MSDUS */ |
| adf_nbuf_set_next_ext(prev_buf, msdu); |
| |
| /* Reload the hdr ptr only on non-first MSDUs */ |
| rx_desc = htt_rx_desc(msdu_orig); |
| hdr_desc = &rx_desc->rx_hdr_status[0]; |
| |
| } |
| |
| /* Copy this buffers MSDU related status into the prev buffer */ |
| dest = adf_nbuf_put_tail(prev_buf, msdu_llc_len + amsdu_pad); |
| dest += amsdu_pad; |
| adf_os_mem_copy(dest, hdr_desc, msdu_llc_len); |
| |
| |
| /* Push the MSDU buffer beyond the decap header */ |
| adf_nbuf_pull_head(msdu, decap_hdr_pull_bytes); |
| frag_list_sum_len += msdu_llc_len + adf_nbuf_len(msdu) + amsdu_pad; |
| |
| /* Set up intra-AMSDU pad to be added to start of next buffer - |
| * AMSDU pad is 4 byte pad on AMSDU subframe */ |
| amsdu_pad = (msdu_llc_len + adf_nbuf_len(msdu)) & 0x3; |
| amsdu_pad = amsdu_pad ? ( 4 - amsdu_pad) : 0; |
| |
| /* TODO FIXME How do we handle MSDUs that have fraglist - Should |
| * probably iterate all the frags cloning them along the way and |
| * and also updating the prev_buf pointer |
| */ |
| |
| /* Move to the next */ |
| prev_buf = msdu; |
| msdu_orig = adf_nbuf_next(msdu_orig); |
| |
| } |
| |
| #if 0 |
| /* Add in the trailer section - encryption trailer + FCS */ |
| adf_nbuf_put_tail(prev_buf, HTT_FCS_LEN); |
| frag_list_sum_len += HTT_FCS_LEN; |
| #endif |
| |
| /* TODO: Convert this to suitable adf routines */ |
| adf_nbuf_append_ext_list(mpdu_buf, head_frag_list_cloned, |
| frag_list_sum_len); |
| |
| mpdu_stitch_done: |
| /* Check if this buffer contains the PPDU end status for TSF */ |
| if (rx_desc->attention.last_mpdu) { |
| rx_status->rs_tstamp.tsf = rx_desc->ppdu_end.tsf_timestamp; |
| } |
| |
| /* All the nbufs have been linked into the ext list and then unlink the nbuf list */ |
| if (clone_not_reqd) { |
| msdu = head_msdu; |
| while (msdu) { |
| msdu_orig = msdu; |
| msdu = adf_nbuf_next(msdu); |
| adf_nbuf_set_next(msdu_orig, NULL); |
| } |
| } |
| |
| return (mpdu_buf); |
| |
| |
| mpdu_stitch_fail: |
| /* Free these alloced buffers and the orig buffers in non-clone case */ |
| if (!clone_not_reqd) { |
| /* Free the head buffer */ |
| if (mpdu_buf) { |
| adf_nbuf_free(mpdu_buf); |
| } |
| |
| /* Free the partial list */ |
| while (head_frag_list_cloned) { |
| msdu = head_frag_list_cloned; |
| head_frag_list_cloned = adf_nbuf_next_ext(head_frag_list_cloned); |
| adf_nbuf_free(msdu); |
| } |
| } else { |
| /* Free the alloced head buffer */ |
| if (decap_format != HW_RX_DECAP_FORMAT_RAW) { |
| if (mpdu_buf) { |
| adf_nbuf_free(mpdu_buf); |
| } |
| } |
| |
| /* Free the orig buffers */ |
| msdu = head_msdu; |
| while (msdu) { |
| msdu_orig = msdu; |
| msdu = adf_nbuf_next(msdu); |
| adf_nbuf_free(msdu_orig); |
| } |
| } |
| |
| return NULL; |
| } |
| |
| int16_t |
| htt_rx_mpdu_desc_rssi_dbm(htt_pdev_handle pdev, void *mpdu_desc) |
| { |
| /* |
| * Currently the RSSI is provided only as a field in the |
| * HTT_T2H_RX_IND message, rather than in each rx descriptor. |
| */ |
| return HTT_RSSI_INVALID; |
| } |
| |
| |
| /* |
| * htt_rx_amsdu_pop - |
| * global function pointer that is programmed during attach to point |
| * to either htt_rx_amsdu_pop_ll or htt_rx_amsdu_pop_hl or |
| * htt_rx_amsdu_rx_in_order_pop_ll. |
| */ |
| int (*htt_rx_amsdu_pop)( |
| htt_pdev_handle pdev, |
| adf_nbuf_t rx_ind_msg, |
| adf_nbuf_t *head_msdu, |
| adf_nbuf_t *tail_msdu); |
| |
| /* |
| * htt_rx_frag_pop - |
| * global function pointer that is programmed during attach to point |
| * to either htt_rx_amsdu_pop_ll or htt_rx_frag_pop_hl. |
| */ |
| int (*htt_rx_frag_pop)( |
| htt_pdev_handle pdev, |
| adf_nbuf_t rx_ind_msg, |
| adf_nbuf_t *head_msdu, |
| adf_nbuf_t *tail_msdu); |
| |
| int |
| (*htt_rx_offload_msdu_cnt)( |
| htt_pdev_handle pdev); |
| |
| int |
| (*htt_rx_offload_msdu_pop)( |
| htt_pdev_handle pdev, |
| adf_nbuf_t offload_deliver_msg, |
| int *vdev_id, |
| int *peer_id, |
| int *tid, |
| u_int8_t *fw_desc, |
| adf_nbuf_t *head_buf, |
| adf_nbuf_t *tail_buf); |
| |
| void *(*htt_rx_mpdu_desc_list_next)( |
| htt_pdev_handle pdev, |
| adf_nbuf_t rx_ind_msg); |
| |
| bool (*htt_rx_mpdu_desc_retry)( |
| htt_pdev_handle pdev, void *mpdu_desc); |
| |
| u_int16_t (*htt_rx_mpdu_desc_seq_num)( |
| htt_pdev_handle pdev, void *mpdu_desc); |
| |
| void (*htt_rx_mpdu_desc_pn)( |
| htt_pdev_handle pdev, |
| void *mpdu_desc, |
| union htt_rx_pn_t *pn, |
| int pn_len_bits); |
| |
| uint8_t (*htt_rx_mpdu_desc_tid)( |
| htt_pdev_handle pdev, void *mpdu_desc); |
| |
| a_bool_t (*htt_rx_msdu_desc_completes_mpdu)( |
| htt_pdev_handle pdev, void *msdu_desc); |
| |
| a_bool_t (*htt_rx_msdu_first_msdu_flag)( |
| htt_pdev_handle pdev, void *msdu_desc); |
| |
| int (*htt_rx_msdu_has_wlan_mcast_flag)( |
| htt_pdev_handle pdev, void *msdu_desc); |
| |
| a_bool_t (*htt_rx_msdu_is_wlan_mcast)( |
| htt_pdev_handle pdev, void *msdu_desc); |
| |
| int (*htt_rx_msdu_is_frag)( |
| htt_pdev_handle pdev, void *msdu_desc); |
| |
| void *(*htt_rx_msdu_desc_retrieve)( |
| htt_pdev_handle pdev, adf_nbuf_t msdu); |
| |
| a_bool_t (*htt_rx_mpdu_is_encrypted)( |
| htt_pdev_handle pdev, |
| void *mpdu_desc); |
| |
| a_bool_t (*htt_rx_msdu_desc_key_id)( |
| htt_pdev_handle pdev, |
| void *mpdu_desc, u_int8_t *key_id); |
| |
| a_bool_t (*htt_rx_msdu_chan_info_present)( |
| htt_pdev_handle pdev, |
| void *mpdu_desc); |
| |
| a_bool_t (*htt_rx_msdu_center_freq)( |
| htt_pdev_handle pdev, |
| struct ol_txrx_peer_t *peer, |
| void *mpdu_desc, |
| uint16_t *primary_chan_center_freq_mhz, |
| uint16_t *contig_chan1_center_freq_mhz, |
| uint16_t *contig_chan2_center_freq_mhz, |
| uint8_t *phy_mode); |
| |
| void * |
| htt_rx_mpdu_desc_list_next_ll(htt_pdev_handle pdev, adf_nbuf_t rx_ind_msg) |
| { |
| int idx = pdev->rx_ring.sw_rd_idx.msdu_desc; |
| adf_nbuf_t netbuf = pdev->rx_ring.buf.netbufs_ring[idx]; |
| pdev->rx_ring.sw_rd_idx.msdu_desc = pdev->rx_ring.sw_rd_idx.msdu_payld; |
| return (void *) htt_rx_desc(netbuf); |
| } |
| |
| void * |
| htt_rx_in_ord_mpdu_desc_list_next_ll(htt_pdev_handle pdev, adf_nbuf_t netbuf) |
| { |
| return (void*)htt_rx_desc(netbuf); |
| } |
| |
| void * |
| htt_rx_mpdu_desc_list_next_hl(htt_pdev_handle pdev, adf_nbuf_t rx_ind_msg) |
| { |
| /* |
| * for HL, the returned value is not mpdu_desc, |
| * it's translated hl_rx_desc just after the hl_ind_msg |
| */ |
| void *mpdu_desc = (void *) adf_nbuf_data(rx_ind_msg); |
| |
| /* for HL AMSDU, we can't point to payload now, because |
| * hl rx desc is not fixed, we can't retrive the desc |
| * by minus rx_desc_size when release. keep point to hl rx desc |
| * now. |
| */ |
| #if 0 |
| adf_nbuf_pull_head(rx_ind_msg, pdev->rx_desc_size_hl); |
| #endif |
| |
| return mpdu_desc; |
| } |
| |
| void * |
| htt_rx_msdu_desc_retrieve_ll(htt_pdev_handle pdev, adf_nbuf_t msdu) |
| { |
| return htt_rx_desc(msdu); |
| } |
| |
| void * |
| htt_rx_msdu_desc_retrieve_hl(htt_pdev_handle pdev, adf_nbuf_t msdu) |
| { |
| /* currently for HL AMSDU, we don't point to payload. |
| * we shift to payload in ol_rx_deliver later |
| */ |
| return adf_nbuf_data(msdu); |
| } |
| |
| a_bool_t htt_rx_mpdu_is_encrypted_ll(htt_pdev_handle pdev, void *mpdu_desc) |
| { |
| struct htt_host_rx_desc_base *rx_desc = (struct htt_host_rx_desc_base *) mpdu_desc; |
| |
| return (((*((u_int32_t *) &rx_desc->mpdu_start)) & |
| RX_MPDU_START_0_ENCRYPTED_MASK) >> |
| RX_MPDU_START_0_ENCRYPTED_LSB) ? A_TRUE : A_FALSE; |
| } |
| |
| a_bool_t htt_rx_mpdu_is_encrypted_hl(htt_pdev_handle pdev, void *mpdu_desc) |
| { |
| if (htt_rx_msdu_first_msdu_flag_hl(pdev, mpdu_desc) == A_TRUE) { |
| /* Fix Me: only for little endian */ |
| struct hl_htt_rx_desc_base *rx_desc = |
| (struct hl_htt_rx_desc_base *) mpdu_desc; |
| |
| return HTT_WORD_GET(*(u_int32_t*)rx_desc, HTT_HL_RX_DESC_MPDU_ENC); |
| }else { |
| /* not first msdu, no encrypt info for hl */ |
| adf_os_print( |
| "Error: get encrypted from a not-first msdu.\n"); |
| adf_os_assert(0); |
| return -1; |
| } |
| } |
| |
| a_bool_t |
| htt_rx_msdu_chan_info_present_ll(htt_pdev_handle pdev, void *mpdu_desc) |
| { |
| return A_FALSE; |
| } |
| |
| a_bool_t |
| htt_rx_msdu_chan_info_present_hl(htt_pdev_handle pdev, void *mpdu_desc) |
| { |
| if (htt_rx_msdu_first_msdu_flag_hl(pdev, mpdu_desc) == A_TRUE && |
| HTT_WORD_GET(*(u_int32_t*)mpdu_desc, |
| HTT_HL_RX_DESC_CHAN_INFO_PRESENT)) { |
| return A_TRUE; |
| } |
| |
| return A_FALSE; |
| } |
| |
| a_bool_t |
| htt_rx_msdu_center_freq_ll(htt_pdev_handle pdev, |
| struct ol_txrx_peer_t *peer, |
| void *mpdu_desc, |
| uint16_t *primary_chan_center_freq_mhz, |
| uint16_t *contig_chan1_center_freq_mhz, |
| uint16_t *contig_chan2_center_freq_mhz, |
| uint8_t *phy_mode) |
| { |
| if (primary_chan_center_freq_mhz) |
| *primary_chan_center_freq_mhz = 0; |
| if (contig_chan1_center_freq_mhz) |
| *contig_chan1_center_freq_mhz = 0; |
| if (contig_chan2_center_freq_mhz) |
| *contig_chan2_center_freq_mhz = 0; |
| if (phy_mode) |
| *phy_mode = 0; |
| return A_FALSE; |
| } |
| |
| a_bool_t |
| htt_rx_msdu_center_freq_hl(htt_pdev_handle pdev, |
| struct ol_txrx_peer_t *peer, |
| void *mpdu_desc, |
| uint16_t *primary_chan_center_freq_mhz, |
| uint16_t *contig_chan1_center_freq_mhz, |
| uint16_t *contig_chan2_center_freq_mhz, |
| uint8_t *phy_mode) |
| { |
| int pn_len, index; |
| uint32_t *chan_info; |
| |
| index = htt_rx_msdu_is_wlan_mcast(pdev, mpdu_desc) ? |
| txrx_sec_mcast : txrx_sec_ucast; |
| |
| pn_len = (peer ? |
| pdev->txrx_pdev->rx_pn[peer->security[index].sec_type].len : |
| 0); |
| chan_info = (uint32_t*) ((uint8_t*)mpdu_desc + |
| HTT_HL_RX_DESC_PN_OFFSET + pn_len); |
| |
| if (htt_rx_msdu_chan_info_present_hl(pdev, mpdu_desc)) { |
| if (primary_chan_center_freq_mhz) |
| *primary_chan_center_freq_mhz = |
| HTT_WORD_GET(*chan_info, |
| HTT_CHAN_INFO_PRIMARY_CHAN_CENTER_FREQ); |
| if (contig_chan1_center_freq_mhz) |
| *contig_chan1_center_freq_mhz = |
| HTT_WORD_GET(*chan_info, |
| HTT_CHAN_INFO_CONTIG_CHAN1_CENTER_FREQ); |
| chan_info++; |
| if (contig_chan2_center_freq_mhz) |
| *contig_chan2_center_freq_mhz = |
| HTT_WORD_GET(*chan_info, |
| HTT_CHAN_INFO_CONTIG_CHAN2_CENTER_FREQ); |
| if (phy_mode) |
| *phy_mode = |
| HTT_WORD_GET(*chan_info, |
| HTT_CHAN_INFO_PHY_MODE); |
| return A_TRUE; |
| } |
| |
| if (primary_chan_center_freq_mhz) |
| *primary_chan_center_freq_mhz = 0; |
| if (contig_chan1_center_freq_mhz) |
| *contig_chan1_center_freq_mhz = 0; |
| if (contig_chan2_center_freq_mhz) |
| *contig_chan2_center_freq_mhz = 0; |
| if (phy_mode) |
| *phy_mode = 0; |
| return A_FALSE; |
| } |
| |
| a_bool_t |
| htt_rx_msdu_desc_key_id_ll(htt_pdev_handle pdev, void *mpdu_desc, |
| u_int8_t *key_id) |
| { |
| struct htt_host_rx_desc_base *rx_desc = (struct htt_host_rx_desc_base *) |
| mpdu_desc; |
| |
| if (!htt_rx_msdu_first_msdu_flag_ll(pdev, mpdu_desc)) |
| return A_FALSE; |
| |
| *key_id = ((*(((u_int32_t *) &rx_desc->msdu_end) + 1)) & |
| (RX_MSDU_END_1_KEY_ID_OCT_MASK >> RX_MSDU_END_1_KEY_ID_OCT_LSB)); |
| |
| return A_TRUE; |
| } |
| |
| a_bool_t |
| htt_rx_msdu_desc_key_id_hl(htt_pdev_handle htt_pdev, void *mpdu_desc, u_int8_t *key_id) |
| { |
| if (htt_rx_msdu_first_msdu_flag_hl(htt_pdev, mpdu_desc) == A_TRUE) { |
| /* Fix Me: only for little endian */ |
| struct hl_htt_rx_desc_base *rx_desc = |
| (struct hl_htt_rx_desc_base *) mpdu_desc; |
| |
| *key_id = rx_desc->key_id_oct; |
| return A_TRUE; |
| } |
| |
| return A_FALSE; |
| } |
| |
| void |
| htt_rx_desc_frame_free( |
| htt_pdev_handle htt_pdev, |
| adf_nbuf_t msdu) |
| { |
| adf_nbuf_free(msdu); |
| } |
| |
| void |
| htt_rx_msdu_desc_free(htt_pdev_handle htt_pdev, adf_nbuf_t msdu) |
| { |
| /* |
| * The rx descriptor is in the same buffer as the rx MSDU payload, |
| * and does not need to be freed separately. |
| */ |
| } |
| |
| void |
| htt_rx_msdu_buff_replenish(htt_pdev_handle pdev) |
| { |
| if (adf_os_atomic_dec_and_test(&pdev->rx_ring.refill_ref_cnt)) { |
| if (!pdev->cfg.is_high_latency) { |
| int num_to_fill; |
| num_to_fill = pdev->rx_ring.fill_level - pdev->rx_ring.fill_cnt; |
| htt_rx_ring_fill_n(pdev, num_to_fill /* okay if <= 0 */); |
| } |
| } |
| adf_os_atomic_inc(&pdev->rx_ring.refill_ref_cnt); |
| } |
| |
| #define AR600P_ASSEMBLE_HW_RATECODE(_rate, _nss, _pream) \ |
| (((_pream) << 6) | ((_nss) << 4) | (_rate)) |
| |
| enum AR600P_HW_RATECODE_PREAM_TYPE { |
| AR600P_HW_RATECODE_PREAM_OFDM, |
| AR600P_HW_RATECODE_PREAM_CCK, |
| AR600P_HW_RATECODE_PREAM_HT, |
| AR600P_HW_RATECODE_PREAM_VHT, |
| }; |
| |
| #if 0 |
| void htt_rx_get_vowext_stats(adf_nbuf_t msdu, struct vow_extstats *vowstats) |
| { |
| u_int32_t *ppdu; |
| u_int8_t preamble_type; |
| u_int8_t rate = 0, nss=0, bw=0, sgi = 0, mcs = 0, rs_flags=0; |
| struct htt_host_rx_desc_base *rx_desc; |
| rx_desc = htt_rx_desc(msdu); |
| |
| ppdu = ((u_int32_t *)&rx_desc->ppdu_start); |
| preamble_type = (ppdu[5] & 0xff000000) >> 24; |
| switch(preamble_type) |
| { |
| /* HT */ |
| case 8: /* HT w/o TxBF */ |
| case 9:/* HT w/ TxBF */ |
| mcs = (u_int8_t)(ppdu[6] & 0x7f); |
| nss = mcs>>3; |
| mcs %= 8; |
| bw = (u_int8_t)((ppdu[6] >> 7) & 1); |
| sgi = (u_int8_t)((ppdu[6] >> 7) & 1); |
| rate = AR600P_ASSEMBLE_HW_RATECODE(mcs, nss, AR600P_HW_RATECODE_PREAM_HT); |
| if (bw) { |
| rs_flags |= HAL_RX_40; |
| } |
| if (sgi) { |
| rs_flags |= HAL_RX_GI; |
| } |
| break; |
| /* VHT */ |
| case 0x0c: /* VHT w/o TxBF */ |
| case 0x0d: /* VHT w/ TxBF */ |
| mcs = (u_int8_t)((ppdu[7] >> 4) & 0xf); |
| nss = (u_int8_t)((ppdu[6] >> 10) & 0x7); |
| bw = (u_int8_t)((ppdu[6] & 3)); |
| sgi = (u_int8_t)((ppdu[7]) & 1); |
| rate = AR600P_ASSEMBLE_HW_RATECODE(mcs, nss, AR600P_HW_RATECODE_PREAM_VHT); |
| break; |
| } |
| |
| vowstats->rx_bw = bw; /* band width 0 - 20 , 1 - 40 , 2 - 80 */ |
| vowstats->rx_sgi = sgi; /* 1 - short GI */ |
| vowstats->rx_nss= nss; /* Nss */ |
| vowstats->rx_mcs = mcs; |
| vowstats->rx_ratecode = rate; |
| vowstats->rx_rs_flags= rs_flags; /* rsflags */ |
| |
| vowstats->rx_rssi_ctl0 = (ppdu[0] & 0x000000ff); /* rssi ctl0 */ |
| vowstats->rx_rssi_ctl1 = (ppdu[1] & 0x000000ff); /* rssi ctl1 */ |
| vowstats->rx_rssi_ctl2 = (ppdu[2] & 0x000000ff); /* rssi ctl2 */ |
| vowstats->rx_rssi_ext0 = (ppdu[0] & 0x0000ff00) >> 8; /* rssi ext0 */ |
| vowstats->rx_rssi_ext1 = (ppdu[1] & 0x0000ff00) >> 8; /* rssi ext1 */ |
| vowstats->rx_rssi_ext2 = (ppdu[2] & 0x0000ff00) >> 8; /* rssi ext2 */ |
| vowstats->rx_rssi_comb = (ppdu[4] & 0x000000ff); /* rssi comb */ |
| |
| ppdu = ((u_int32_t *)&rx_desc->ppdu_end); |
| /* Time stamp */ |
| vowstats->rx_macTs = ppdu[16]; |
| |
| ppdu = ((u_int32_t *)&rx_desc->attention); |
| /* more data */ |
| vowstats->rx_moreaggr = (ppdu[0] & RX_ATTENTION_0_MORE_DATA_MASK); |
| |
| /* sequence number */ |
| ppdu = ((u_int32_t *)&rx_desc->mpdu_start); |
| vowstats->rx_seqno = (ppdu[0] & 0x0fff0000) >> 16; |
| |
| } |
| |
| #endif |
| |
| /*--- RX In Order Hash Code --------------------------------------------------*/ |
| |
| /* Number of buckets in the hash table */ |
| #define RX_NUM_HASH_BUCKETS 1024 /* This should always be a power of 2 */ |
| #define RX_NUM_HASH_BUCKETS_MASK (RX_NUM_HASH_BUCKETS - 1) |
| |
| /* Number of hash entries allocated per bucket */ |
| #define RX_ENTRIES_SIZE 10 |
| |
| #define RX_HASH_FUNCTION(a) (((a >> 14) ^ (a >> 4)) & RX_NUM_HASH_BUCKETS_MASK) |
| |
| #ifdef RX_HASH_DEBUG_LOG |
| #define RX_HASH_LOG(x) x |
| #else |
| #define RX_HASH_LOG(x) /* no-op */ |
| #endif |
| |
| /* Initializes the circular linked list */ |
| static inline void htt_list_init(htt_list_head * head) |
| { |
| head->prev = head; |
| head->next = head; |
| } |
| |
| /* Adds entry to the end of the linked list */ |
| static inline void |
| htt_list_add_tail(htt_list_head * head, htt_list_node * node) |
| { |
| head->prev->next = node; |
| node->prev = head->prev; |
| node->next = head; |
| head->prev = node; |
| } |
| |
| /* Removes the entry corresponding to the input node from the linked list */ |
| static inline void |
| htt_list_remove(htt_list_node * node) |
| { |
| node->prev->next = node->next; |
| node->next->prev = node->prev; |
| } |
| |
| /* Helper macro to iterate through the linked list */ |
| #define HTT_LIST_ITER_FWD(iter, head) for( iter=(head)->next; \ |
| (iter)!=(head); (iter)=(iter)->next ) \ |
| |
| #ifdef RX_HASH_DEBUG |
| /* Hash cookie related macros */ |
| #define HTT_RX_HASH_COOKIE 0xDEED |
| |
| #define HTT_RX_HASH_COOKIE_SET(hash_element)\ |
| hash_element->cookie = HTT_RX_HASH_COOKIE |
| |
| #define HTT_RX_HASH_COOKIE_CHECK(hash_element)\ |
| HTT_ASSERT_ALWAYS(hash_element->cookie == HTT_RX_HASH_COOKIE) |
| |
| /* Hash count related macros */ |
| #define HTT_RX_HASH_COUNT_INCR(hash_bucket)\ |
| hash_bucket->count++ |
| |
| #define HTT_RX_HASH_COUNT_DECR(hash_bucket)\ |
| hash_bucket->count-- |
| |
| #define HTT_RX_HASH_COUNT_RESET(hash_bucket) hash_bucket->count = 0 |
| |
| #define HTT_RX_HASH_COUNT_PRINT(hash_bucket)\ |
| RX_HASH_LOG(adf_os_print(" count %d\n", hash_bucket->count)) |
| #else /* RX_HASH_DEBUG */ |
| /* Hash cookie related macros */ |
| #define HTT_RX_HASH_COOKIE_SET(hash_element) /* no-op */ |
| #define HTT_RX_HASH_COOKIE_CHECK(hash_element) /* no-op */ |
| /* Hash count related macros */ |
| #define HTT_RX_HASH_COUNT_INCR(hash_bucket) /* no-op */ |
| #define HTT_RX_HASH_COUNT_DECR(hash_bucket) /* no-op */ |
| #define HTT_RX_HASH_COUNT_PRINT(hash_bucket) /* no-op */ |
| #define HTT_RX_HASH_COUNT_RESET(hash_bucket) /* no-op */ |
| #endif /* RX_HASH_DEBUG */ |
| |
| |
| /* Inserts the given "physical address - network buffer" pair into the |
| hash table for the given pdev. This function will do the following: |
| 1. Determine which bucket to insert the pair into |
| 2. First try to allocate the hash entry for this pair from the pre-allocated |
| entries list |
| 3. If there are no more entries in the pre-allocated entries list, allocate |
| the hash entry from the hash memory pool |
| Note: this function is not thread-safe |
| Returns 0 - success, 1 - failure */ |
| int |
| htt_rx_hash_list_insert(struct htt_pdev_t *pdev, u_int32_t paddr, |
| adf_nbuf_t netbuf) |
| { |
| int i; |
| struct htt_rx_hash_entry * hash_element = NULL; |
| |
| i = RX_HASH_FUNCTION(paddr); |
| |
| /* Check if there are any entries in the pre-allocated free list */ |
| if( pdev->rx_ring.hash_table[i]->freepool.next != |
| &pdev->rx_ring.hash_table[i]->freepool) { |
| |
| hash_element = |
| (struct htt_rx_hash_entry *)((char *)pdev->rx_ring.hash_table[i] |
| ->freepool.next - |
| pdev->rx_ring.listnode_offset); |
| if (adf_os_unlikely(NULL == hash_element)) { |
| HTT_ASSERT_ALWAYS(0); |
| return 1; |
| } |
| |
| htt_list_remove(pdev->rx_ring.hash_table[i]->freepool.next); |
| } |
| else { |
| hash_element = adf_os_mem_alloc(pdev->osdev, |
| sizeof(struct htt_rx_hash_entry)); |
| if (adf_os_unlikely(NULL == hash_element)) { |
| HTT_ASSERT_ALWAYS(0); |
| return 1; |
| } |
| hash_element->fromlist = 0; |
| } |
| |
| hash_element->netbuf = netbuf; |
| hash_element->paddr = paddr; |
| HTT_RX_HASH_COOKIE_SET(hash_element); |
| |
| htt_list_add_tail(&pdev->rx_ring.hash_table[i]->listhead, |
| &hash_element->listnode); |
| |
| RX_HASH_LOG(adf_os_print("rx hash: %s: paddr 0x%x netbuf %pK bucket %d\n", |
| __FUNCTION__, paddr, netbuf,(int)i)); |
| |
| HTT_RX_HASH_COUNT_INCR(pdev->rx_ring.hash_table[i]); |
| HTT_RX_HASH_COUNT_PRINT(pdev->rx_ring.hash_table[i]); |
| |
| return 0; |
| } |
| |
| /* Given a physical address this function will find the corresponding network |
| buffer from the hash table. |
| Note: this function is not thread-safe */ |
| adf_nbuf_t |
| htt_rx_hash_list_lookup(struct htt_pdev_t *pdev, u_int32_t paddr) |
| { |
| u_int32_t i; |
| htt_list_node * list_iter = NULL; |
| adf_nbuf_t netbuf = NULL; |
| struct htt_rx_hash_entry * hash_entry; |
| |
| i = RX_HASH_FUNCTION(paddr); |
| |
| HTT_LIST_ITER_FWD(list_iter, &pdev->rx_ring.hash_table[i]->listhead) |
| { |
| hash_entry = (struct htt_rx_hash_entry *) |
| ((char *)list_iter - pdev->rx_ring.listnode_offset); |
| |
| HTT_RX_HASH_COOKIE_CHECK(hash_entry); |
| |
| if (hash_entry->paddr == paddr) { |
| #ifdef DEBUG_RX_RING_BUFFER |
| uint32_t index; |
| #endif |
| /* Found the entry corresponding to paddr */ |
| netbuf = hash_entry->netbuf; |
| htt_list_remove(&hash_entry->listnode); |
| HTT_RX_HASH_COUNT_DECR(pdev->rx_ring.hash_table[i]); |
| /* if the rx entry is from the pre-allocated list, return it */ |
| if (hash_entry->fromlist) { |
| htt_list_add_tail(&pdev->rx_ring.hash_table[i]->freepool, |
| &hash_entry->listnode); |
| } |
| else { |
| adf_os_mem_free(hash_entry); |
| } |
| #ifdef DEBUG_RX_RING_BUFFER |
| if (pdev->rx_buff_list) { |
| index = NBUF_MAP_ID(netbuf); |
| if (index < HTT_RX_RING_BUFF_DBG_LIST) { |
| pdev->rx_buff_list[index].in_use = false; |
| } |
| } |
| #endif |
| break; |
| } |
| } |
| |
| RX_HASH_LOG(adf_os_print("rx hash: %s: paddr 0x%x, netbuf %pK, bucket %d\n", |
| __FUNCTION__, paddr, netbuf,(int)i)); |
| HTT_RX_HASH_COUNT_PRINT(pdev->rx_ring.hash_table[i]); |
| |
| if (netbuf == NULL) { |
| adf_os_print("rx hash: %s: no entry found for 0x%x!!!\n", |
| __FUNCTION__, paddr); |
| HTT_ASSERT_ALWAYS(0); |
| } |
| |
| return netbuf; |
| } |
| |
| /* Initialization function of the rx buffer hash table. This function will |
| allocate a hash table of a certain pre-determined size and initialize all |
| the elements */ |
| int |
| htt_rx_hash_init(struct htt_pdev_t *pdev) |
| { |
| int i,j; |
| |
| HTT_ASSERT2(ADF_OS_IS_PWR2(RX_NUM_HASH_BUCKETS)); |
| |
| /* hash table is array of bucket pointers */ |
| pdev->rx_ring.hash_table = adf_os_mem_alloc( |
| pdev->osdev, RX_NUM_HASH_BUCKETS * sizeof(struct htt_rx_hash_bucket *)); |
| |
| if ( NULL == pdev->rx_ring.hash_table) { |
| adf_os_print("rx hash table allocation failed!\n"); |
| return 1; |
| } |
| |
| for (i = 0; i < RX_NUM_HASH_BUCKETS; i++) { |
| |
| /* pre-allocate bucket and pool of entries for this bucket */ |
| pdev->rx_ring.hash_table[i] = adf_os_mem_alloc( |
| pdev->osdev, (sizeof(struct htt_rx_hash_bucket) + |
| (RX_ENTRIES_SIZE * sizeof(struct htt_rx_hash_entry)))); |
| |
| HTT_RX_HASH_COUNT_RESET(pdev->rx_ring.hash_table[i]); |
| |
| /* initialize the hash table buckets */ |
| htt_list_init(&pdev->rx_ring.hash_table[i]->listhead); |
| |
| /* initialize the hash table free pool per bucket */ |
| htt_list_init(&pdev->rx_ring.hash_table[i]->freepool); |
| |
| pdev->rx_ring.hash_table[i]->entries = (struct htt_rx_hash_entry *) |
| ((uint8_t *)pdev->rx_ring.hash_table[i] + |
| sizeof(struct htt_rx_hash_bucket)); |
| |
| if (NULL == pdev->rx_ring.hash_table[i]->entries) { |
| adf_os_print("rx hash entries allocation for bucket %d failed!\n", |
| (int)i); |
| while (i) { |
| i--; |
| adf_os_mem_free(pdev->rx_ring.hash_table[i]); |
| } |
| adf_os_mem_free(pdev->rx_ring.hash_table); |
| pdev->rx_ring.hash_table = NULL; |
| return 1; |
| } |
| |
| /* initialize the free list with pre-allocated entries */ |
| for (j = 0; j < RX_ENTRIES_SIZE; j++) { |
| pdev->rx_ring.hash_table[i]->entries[j].fromlist = 1; |
| htt_list_add_tail(&pdev->rx_ring.hash_table[i]->freepool, |
| &pdev->rx_ring.hash_table[i]->entries[j].listnode); |
| } |
| } |
| |
| pdev->rx_ring.listnode_offset = |
| adf_os_offsetof(struct htt_rx_hash_entry, listnode); |
| |
| return 0; |
| } |
| |
| /* De -initialization function of the rx buffer hash table. This function will |
| free up the hash table which includes freeing all the pending rx buffers*/ |
| void |
| htt_rx_hash_deinit(struct htt_pdev_t *pdev) |
| { |
| |
| u_int32_t i; |
| struct htt_rx_hash_entry * hash_entry; |
| htt_list_node * list_iter = NULL; |
| |
| if (NULL == pdev->rx_ring.hash_table) { |
| return; |
| } |
| |
| for (i = 0; i < RX_NUM_HASH_BUCKETS; i++) { |
| /* Free the hash entries in hash bucket i */ |
| list_iter = pdev->rx_ring.hash_table[i]->listhead.next; |
| while (list_iter != &pdev->rx_ring.hash_table[i]->listhead) { |
| hash_entry = |
| (struct htt_rx_hash_entry *)((char *)list_iter - |
| pdev->rx_ring.listnode_offset); |
| if (hash_entry->netbuf) { |
| #ifdef DEBUG_DMA_DONE |
| adf_nbuf_unmap(pdev->osdev, hash_entry->netbuf, |
| ADF_OS_DMA_BIDIRECTIONAL); |
| #else |
| adf_nbuf_unmap(pdev->osdev, hash_entry->netbuf, |
| ADF_OS_DMA_FROM_DEVICE); |
| #endif |
| adf_nbuf_free(hash_entry->netbuf); |
| hash_entry->paddr = 0; |
| } |
| list_iter = list_iter->next; |
| |
| if (!hash_entry->fromlist) { |
| adf_os_mem_free(hash_entry); |
| } |
| } |
| |
| adf_os_mem_free(pdev->rx_ring.hash_table[i]); |
| |
| } |
| if (NULL != pdev->rx_ring.hash_table) { |
| adf_os_mem_free(pdev->rx_ring.hash_table); |
| pdev->rx_ring.hash_table = NULL; |
| } |
| } |
| |
| void |
| htt_rx_hash_dump_table(struct htt_pdev_t *pdev) |
| { |
| u_int32_t i; |
| struct htt_rx_hash_entry * hash_entry; |
| htt_list_node * list_iter = NULL; |
| |
| for (i = 0; i < RX_NUM_HASH_BUCKETS; i++) { |
| HTT_LIST_ITER_FWD(list_iter, &pdev->rx_ring.hash_table[i]->listhead) |
| { |
| hash_entry = |
| (struct htt_rx_hash_entry *)((char *)list_iter - |
| pdev->rx_ring.listnode_offset); |
| adf_os_print("hash_table[%d]: netbuf %pK paddr 0x%x\n", |
| i, hash_entry->netbuf, hash_entry->paddr); |
| } |
| } |
| } |
| |
| /*--- RX In Order Hash Code --------------------------------------------------*/ |
| |
| /* move the function to the end of file |
| * to omit ll/hl pre-declaration |
| */ |
| int |
| htt_rx_attach(struct htt_pdev_t *pdev) |
| { |
| adf_os_dma_addr_t paddr; |
| if (!pdev->cfg.is_high_latency) { |
| pdev->rx_ring.size = htt_rx_ring_size(pdev); |
| HTT_ASSERT2(ADF_OS_IS_PWR2(pdev->rx_ring.size)); |
| pdev->rx_ring.size_mask = pdev->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. |
| */ |
| pdev->rx_ring.fill_level = htt_rx_ring_fill_level(pdev); |
| |
| if (pdev->cfg.is_full_reorder_offload) { |
| if (htt_rx_hash_init(pdev)) { |
| goto fail1; |
| } |
| |
| /* allocate the target index */ |
| pdev->rx_ring.target_idx.vaddr = adf_os_mem_alloc_consistent( |
| pdev->osdev, |
| sizeof(u_int32_t), |
| &paddr, |
| adf_os_get_dma_mem_context((&pdev->rx_ring.target_idx), memctx)); |
| if (!pdev->rx_ring.target_idx.vaddr) { |
| goto fail1; |
| } |
| pdev->rx_ring.target_idx.paddr = paddr; |
| *pdev->rx_ring.target_idx.vaddr = 0; |
| } else { |
| pdev->rx_ring.buf.netbufs_ring = adf_os_mem_alloc( |
| pdev->osdev, pdev->rx_ring.size * sizeof(adf_nbuf_t)); |
| if (!pdev->rx_ring.buf.netbufs_ring) { |
| goto fail1; |
| } |
| |
| pdev->rx_ring.sw_rd_idx.msdu_payld = 0; |
| pdev->rx_ring.sw_rd_idx.msdu_desc = 0; |
| } |
| |
| pdev->rx_ring.buf.paddrs_ring = adf_os_mem_alloc_consistent( |
| pdev->osdev, |
| pdev->rx_ring.size * sizeof(u_int32_t), |
| &paddr, |
| adf_os_get_dma_mem_context((&pdev->rx_ring.buf), memctx)); |
| if (!pdev->rx_ring.buf.paddrs_ring) { |
| goto fail2; |
| } |
| pdev->rx_ring.base_paddr = paddr; |
| pdev->rx_ring.alloc_idx.vaddr = adf_os_mem_alloc_consistent( |
| pdev->osdev, |
| sizeof(u_int32_t), |
| &paddr, |
| adf_os_get_dma_mem_context((&pdev->rx_ring.alloc_idx), memctx)); |
| if (!pdev->rx_ring.alloc_idx.vaddr) { |
| goto fail3; |
| } |
| pdev->rx_ring.alloc_idx.paddr = paddr; |
| *pdev->rx_ring.alloc_idx.vaddr = 0; |
| |
| /* |
| * Initialize the Rx refill reference counter to be one so that |
| * only one thread is allowed to refill the Rx ring. |
| */ |
| adf_os_atomic_init(&pdev->rx_ring.refill_ref_cnt); |
| adf_os_atomic_inc(&pdev->rx_ring.refill_ref_cnt); |
| |
| /* Initialize the Rx refill retry timer */ |
| adf_os_timer_init(pdev->osdev, &pdev->rx_ring.refill_retry_timer, |
| htt_rx_ring_refill_retry, (void *)pdev, |
| ADF_DEFERRABLE_TIMER); |
| |
| pdev->rx_ring.fill_cnt = 0; |
| #ifdef DEBUG_DMA_DONE |
| pdev->rx_ring.dbg_ring_idx = 0; |
| pdev->rx_ring.dbg_refill_cnt = 0; |
| pdev->rx_ring.dbg_sync_success = 0; |
| #endif |
| #ifdef HTT_RX_RESTORE |
| pdev->rx_ring.rx_reset = 0; |
| pdev->rx_ring.htt_rx_restore = 0; |
| #endif |
| #ifdef DEBUG_RX_RING_BUFFER |
| pdev->rx_buff_list = adf_os_mem_alloc(pdev->osdev, |
| HTT_RX_RING_BUFF_DBG_LIST * |
| sizeof(struct rx_buf_debug)); |
| if (!pdev->rx_buff_list) |
| adf_os_print("HTT: debug RX buffer allocation failed\n"); |
| else |
| adf_os_mem_set(pdev->rx_buff_list, 0, HTT_RX_RING_BUFF_DBG_LIST * |
| sizeof(struct rx_buf_debug)); |
| #endif |
| htt_rx_ring_fill_n(pdev, pdev->rx_ring.fill_level); |
| |
| if (pdev->cfg.is_full_reorder_offload) { |
| adf_os_print("HTT: full reorder offload enabled\n"); |
| htt_rx_amsdu_pop = htt_rx_amsdu_rx_in_order_pop_ll; |
| htt_rx_frag_pop = htt_rx_amsdu_rx_in_order_pop_ll; |
| htt_rx_mpdu_desc_list_next = htt_rx_in_ord_mpdu_desc_list_next_ll; |
| } else { |
| htt_rx_amsdu_pop = htt_rx_amsdu_pop_ll; |
| htt_rx_frag_pop = htt_rx_amsdu_pop_ll; |
| htt_rx_mpdu_desc_list_next = htt_rx_mpdu_desc_list_next_ll; |
| } |
| |
| if (VOS_MONITOR_MODE == vos_get_conparam()) |
| htt_rx_amsdu_pop = htt_rx_mon_amsdu_rx_in_order_pop_ll; |
| |
| htt_rx_offload_msdu_cnt = htt_rx_offload_msdu_cnt_ll; |
| htt_rx_offload_msdu_pop = htt_rx_offload_msdu_pop_ll; |
| htt_rx_mpdu_desc_retry = htt_rx_mpdu_desc_retry_ll; |
| htt_rx_mpdu_desc_seq_num = htt_rx_mpdu_desc_seq_num_ll; |
| htt_rx_mpdu_desc_pn = htt_rx_mpdu_desc_pn_ll; |
| htt_rx_mpdu_desc_tid = htt_rx_mpdu_desc_tid_ll; |
| htt_rx_msdu_desc_completes_mpdu = htt_rx_msdu_desc_completes_mpdu_ll; |
| htt_rx_msdu_first_msdu_flag = htt_rx_msdu_first_msdu_flag_ll; |
| htt_rx_msdu_has_wlan_mcast_flag = htt_rx_msdu_has_wlan_mcast_flag_ll; |
| htt_rx_msdu_is_wlan_mcast = htt_rx_msdu_is_wlan_mcast_ll; |
| htt_rx_msdu_is_frag = htt_rx_msdu_is_frag_ll; |
| htt_rx_msdu_desc_retrieve = htt_rx_msdu_desc_retrieve_ll; |
| htt_rx_mpdu_is_encrypted = htt_rx_mpdu_is_encrypted_ll; |
| htt_rx_msdu_desc_key_id = htt_rx_msdu_desc_key_id_ll; |
| htt_rx_msdu_chan_info_present = htt_rx_msdu_chan_info_present_ll; |
| htt_rx_msdu_center_freq = htt_rx_msdu_center_freq_ll; |
| } else { |
| pdev->rx_ring.size = HTT_RX_RING_SIZE_MIN; |
| HTT_ASSERT2(ADF_OS_IS_PWR2(pdev->rx_ring.size)); |
| pdev->rx_ring.size_mask = pdev->rx_ring.size - 1; |
| |
| /* host can force ring base address if it wish to do so */ |
| pdev->rx_ring.base_paddr = 0; |
| htt_rx_amsdu_pop = htt_rx_amsdu_pop_hl; |
| if (VOS_MONITOR_MODE == vos_get_conparam()) |
| htt_rx_amsdu_pop = htt_rx_mon_amsdu_pop_hl; |
| htt_rx_frag_pop = htt_rx_frag_pop_hl; |
| htt_rx_offload_msdu_cnt = htt_rx_offload_msdu_cnt_hl; |
| htt_rx_offload_msdu_pop = htt_rx_offload_msdu_pop_hl; |
| htt_rx_mpdu_desc_list_next = htt_rx_mpdu_desc_list_next_hl; |
| htt_rx_mpdu_desc_retry = htt_rx_mpdu_desc_retry_hl; |
| htt_rx_mpdu_desc_seq_num = htt_rx_mpdu_desc_seq_num_hl; |
| htt_rx_mpdu_desc_pn = htt_rx_mpdu_desc_pn_hl; |
| htt_rx_mpdu_desc_tid = htt_rx_mpdu_desc_tid_hl; |
| htt_rx_msdu_desc_completes_mpdu = htt_rx_msdu_desc_completes_mpdu_hl; |
| htt_rx_msdu_first_msdu_flag = htt_rx_msdu_first_msdu_flag_hl; |
| htt_rx_msdu_has_wlan_mcast_flag = htt_rx_msdu_has_wlan_mcast_flag_hl; |
| htt_rx_msdu_is_wlan_mcast = htt_rx_msdu_is_wlan_mcast_hl; |
| htt_rx_msdu_is_frag = htt_rx_msdu_is_frag_hl; |
| htt_rx_msdu_desc_retrieve = htt_rx_msdu_desc_retrieve_hl; |
| htt_rx_mpdu_is_encrypted = htt_rx_mpdu_is_encrypted_hl; |
| htt_rx_msdu_desc_key_id = htt_rx_msdu_desc_key_id_hl; |
| htt_rx_msdu_chan_info_present = htt_rx_msdu_chan_info_present_hl; |
| htt_rx_msdu_center_freq = htt_rx_msdu_center_freq_hl; |
| |
| /* |
| * HL case, the rx descriptor can be different sizes for |
| * different sub-types of RX_IND messages, e.g. for the |
| * initial vs. interior vs. final MSDUs within a PPDU. |
| * The size of each RX_IND message's rx desc is read from |
| * a field within the RX_IND message itself. |
| * In the meantime, until the rx_desc_size_hl variable is |
| * set to its real value based on the RX_IND message, |
| * initialize it to a reasonable value (zero). |
| */ |
| pdev->rx_desc_size_hl = 0; |
| } |
| return 0; /* success */ |
| |
| fail3: |
| adf_os_mem_free_consistent( |
| pdev->osdev, |
| pdev->rx_ring.size * sizeof(u_int32_t), |
| pdev->rx_ring.buf.paddrs_ring, |
| pdev->rx_ring.base_paddr, |
| adf_os_get_dma_mem_context((&pdev->rx_ring.buf), memctx)); |
| |
| fail2: |
| if (pdev->cfg.is_full_reorder_offload) { |
| adf_os_mem_free_consistent( |
| pdev->osdev, |
| sizeof(u_int32_t), |
| pdev->rx_ring.target_idx.vaddr, |
| pdev->rx_ring.target_idx.paddr, |
| adf_os_get_dma_mem_context((&pdev->rx_ring.target_idx), memctx)); |
| htt_rx_hash_deinit(pdev); |
| } else { |
| adf_os_mem_free(pdev->rx_ring.buf.netbufs_ring); |
| } |
| |
| fail1: |
| return 1; /* failure */ |
| } |
| |
| #ifdef IPA_UC_OFFLOAD |
| int htt_rx_ipa_uc_attach(struct htt_pdev_t *pdev, |
| unsigned int rx_ind_ring_elements) |
| { |
| /* Allocate RX indication ring */ |
| /* RX IND ring element |
| * 4bytes: pointer |
| * 2bytes: VDEV ID |
| * 2bytes: length */ |
| pdev->ipa_uc_rx_rsc.rx_ind_ring_base.vaddr = |
| adf_os_mem_alloc_consistent(pdev->osdev, |
| rx_ind_ring_elements * sizeof(struct ipa_uc_rx_ring_elem_t), |
| &pdev->ipa_uc_rx_rsc.rx_ind_ring_base.paddr, |
| adf_os_get_dma_mem_context( |
| (&pdev->ipa_uc_rx_rsc.rx_ind_ring_base), memctx)); |
| if (!pdev->ipa_uc_rx_rsc.rx_ind_ring_base.vaddr) { |
| adf_os_print("%s: RX IND RING alloc fail", __func__); |
| return -1; |
| } |
| |
| /* RX indication ring size, by bytes */ |
| pdev->ipa_uc_rx_rsc.rx_ind_ring_size = rx_ind_ring_elements * |
| sizeof(struct ipa_uc_rx_ring_elem_t); |
| |
| /* Allocate RX process done index */ |
| pdev->ipa_uc_rx_rsc.rx_ipa_prc_done_idx.vaddr = |
| adf_os_mem_alloc_consistent(pdev->osdev, |
| 4, |
| &pdev->ipa_uc_rx_rsc.rx_ipa_prc_done_idx.paddr, |
| adf_os_get_dma_mem_context( |
| (&pdev->ipa_uc_rx_rsc.rx_ipa_prc_done_idx), memctx)); |
| if (!pdev->ipa_uc_rx_rsc.rx_ipa_prc_done_idx.vaddr) { |
| adf_os_print("%s: RX PROC DONE IND alloc fail", __func__); |
| adf_os_mem_free_consistent(pdev->osdev, |
| pdev->ipa_uc_rx_rsc.rx_ind_ring_size, |
| pdev->ipa_uc_rx_rsc.rx_ind_ring_base.vaddr, |
| pdev->ipa_uc_rx_rsc.rx_ind_ring_base.paddr, |
| adf_os_get_dma_mem_context( |
| (&pdev->ipa_uc_rx_rsc.rx_ind_ring_base), memctx)); |
| return -2; |
| } |
| |
| return 0; |
| } |
| |
| int htt_rx_ipa_uc_detach(struct htt_pdev_t *pdev) |
| { |
| if (pdev->ipa_uc_rx_rsc.rx_ind_ring_base.vaddr) { |
| adf_os_mem_free_consistent(pdev->osdev, |
| pdev->ipa_uc_rx_rsc.rx_ind_ring_size, |
| pdev->ipa_uc_rx_rsc.rx_ind_ring_base.vaddr, |
| pdev->ipa_uc_rx_rsc.rx_ind_ring_base.paddr, |
| adf_os_get_dma_mem_context( |
| (&pdev->ipa_uc_rx_rsc.rx_ind_ring_base), memctx)); |
| } |
| |
| if (pdev->ipa_uc_rx_rsc.rx_ipa_prc_done_idx.vaddr) { |
| adf_os_mem_free_consistent(pdev->osdev, |
| 4, |
| pdev->ipa_uc_rx_rsc.rx_ipa_prc_done_idx.vaddr, |
| pdev->ipa_uc_rx_rsc.rx_ipa_prc_done_idx.paddr, |
| adf_os_get_dma_mem_context( |
| (&pdev->ipa_uc_rx_rsc.rx_ipa_prc_done_idx), memctx)); |
| } |
| |
| return 0; |
| } |
| #endif /* IPA_UC_OFFLOAD */ |
| |
| /** |
| * htt_register_rx_pkt_dump_callback() - registers callback to |
| * get rx pkt status and call callback to do rx packet dump |
| * |
| * @pdev: htt pdev handle |
| * @callback: callback to get rx pkt status and |
| * call callback to do rx packet dump |
| * |
| * This function is used to register the callback to get |
| * rx pkt status and call callback to do rx packet dump |
| * |
| * Return: None |
| * |
| */ |
| void htt_register_rx_pkt_dump_callback(struct htt_pdev_t *pdev, |
| tp_rx_pkt_dump_cb callback) |
| { |
| if (!pdev) { |
| adf_os_print("%s: htt pdev is NULL, rx packet status callback register unsuccessful\n", |
| __func__); |
| return; |
| } |
| pdev->rx_pkt_dump_cb = callback; |
| } |
| |
| /** |
| * htt_deregister_rx_pkt_dump_callback() - deregisters callback to |
| * get rx pkt status and call callback to do rx packet dump |
| * |
| * @pdev: htt pdev handle |
| * |
| * This function is used to deregister the callback to get |
| * rx pkt status and call callback to do rx packet dump |
| * |
| * Return: None |
| * |
| */ |
| void htt_deregister_rx_pkt_dump_callback(struct htt_pdev_t *pdev) |
| { |
| if (!pdev) { |
| adf_os_print("%s: htt pdev is NULL, rx packet status callback deregister unsuccessful\n", |
| __func__); |
| return; |
| } |
| pdev->rx_pkt_dump_cb = NULL; |
| } |
| |