
IP multicast check for IPv4 was failing as the big endian conversion was not being done and wrong values were being checked. Do ntohs conversion on macros to check if packet is mcast packet. In IPv6 check, the word is being checked instead of first byte. Check only first byte when checking for ipv6 multicast address. Change-Id: I686cdef7957778f9c60f2dc919421d39c4b8b71c CRs-Fixed: 3824229
6325 lines
158 KiB
C
6325 lines
158 KiB
C
/*
|
|
* Copyright (c) 2014-2021 The Linux Foundation. All rights reserved.
|
|
* Copyright (c) 2021-2024 Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/**
|
|
* DOC: qdf_nbuf.c
|
|
* QCA driver framework(QDF) network buffer management APIs
|
|
*/
|
|
#include <linux/hashtable.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/version.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/module.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/inetdevice.h>
|
|
#include <qdf_atomic.h>
|
|
#include <qdf_debugfs.h>
|
|
#include <qdf_lock.h>
|
|
#include <qdf_mem.h>
|
|
#include <qdf_module.h>
|
|
#include <qdf_nbuf.h>
|
|
#include <qdf_status.h>
|
|
#include "qdf_str.h"
|
|
#include <qdf_trace.h>
|
|
#include "qdf_tracker.h"
|
|
#include <qdf_types.h>
|
|
#include <net/ieee80211_radiotap.h>
|
|
#include <pld_common.h>
|
|
#include <qdf_crypto.h>
|
|
#include <linux/igmp.h>
|
|
#include <net/mld.h>
|
|
|
|
#if defined(FEATURE_TSO)
|
|
#include <net/ipv6.h>
|
|
#include <linux/ipv6.h>
|
|
#include <linux/tcp.h>
|
|
#include <linux/if_vlan.h>
|
|
#include <linux/ip.h>
|
|
#endif /* FEATURE_TSO */
|
|
|
|
#ifdef IPA_OFFLOAD
|
|
#include <i_qdf_ipa_wdi3.h>
|
|
#endif /* IPA_OFFLOAD */
|
|
#include "qdf_ssr_driver_dump.h"
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0)
|
|
|
|
#define qdf_nbuf_users_inc atomic_inc
|
|
#define qdf_nbuf_users_dec atomic_dec
|
|
#define qdf_nbuf_users_set atomic_set
|
|
#define qdf_nbuf_users_read atomic_read
|
|
#else
|
|
#define qdf_nbuf_users_inc refcount_inc
|
|
#define qdf_nbuf_users_dec refcount_dec
|
|
#define qdf_nbuf_users_set refcount_set
|
|
#define qdf_nbuf_users_read refcount_read
|
|
#endif /* KERNEL_VERSION(4, 13, 0) */
|
|
|
|
#define IEEE80211_RADIOTAP_VHT_BW_20 0
|
|
#define IEEE80211_RADIOTAP_VHT_BW_40 1
|
|
#define IEEE80211_RADIOTAP_VHT_BW_80 2
|
|
#define IEEE80211_RADIOTAP_VHT_BW_160 3
|
|
|
|
#define RADIOTAP_VHT_BW_20 0
|
|
#define RADIOTAP_VHT_BW_40 1
|
|
#define RADIOTAP_VHT_BW_80 4
|
|
#define RADIOTAP_VHT_BW_160 11
|
|
|
|
/* tx status */
|
|
#define RADIOTAP_TX_STATUS_FAIL 1
|
|
#define RADIOTAP_TX_STATUS_NOACK 2
|
|
|
|
/* channel number to freq conversion */
|
|
#define CHANNEL_NUM_14 14
|
|
#define CHANNEL_NUM_15 15
|
|
#define CHANNEL_NUM_27 27
|
|
#define CHANNEL_NUM_35 35
|
|
#define CHANNEL_NUM_182 182
|
|
#define CHANNEL_NUM_197 197
|
|
#define CHANNEL_FREQ_2484 2484
|
|
#define CHANNEL_FREQ_2407 2407
|
|
#define CHANNEL_FREQ_2512 2512
|
|
#define CHANNEL_FREQ_5000 5000
|
|
#define CHANNEL_FREQ_4000 4000
|
|
#define CHANNEL_FREQ_5150 5150
|
|
#define FREQ_MULTIPLIER_CONST_5MHZ 5
|
|
#define FREQ_MULTIPLIER_CONST_20MHZ 20
|
|
#define RADIOTAP_5G_SPECTRUM_CHANNEL 0x0100
|
|
#define RADIOTAP_2G_SPECTRUM_CHANNEL 0x0080
|
|
#define RADIOTAP_CCK_CHANNEL 0x0020
|
|
#define RADIOTAP_OFDM_CHANNEL 0x0040
|
|
|
|
#ifdef FEATURE_NBUFF_REPLENISH_TIMER
|
|
#include <qdf_mc_timer.h>
|
|
|
|
struct qdf_track_timer {
|
|
qdf_mc_timer_t track_timer;
|
|
qdf_atomic_t alloc_fail_cnt;
|
|
};
|
|
|
|
static struct qdf_track_timer alloc_track_timer;
|
|
|
|
#define QDF_NBUF_ALLOC_EXPIRE_TIMER_MS 5000
|
|
#define QDF_NBUF_ALLOC_EXPIRE_CNT_THRESHOLD 50
|
|
#endif
|
|
|
|
#ifdef NBUF_MEMORY_DEBUG
|
|
/* SMMU crash indication*/
|
|
static qdf_atomic_t smmu_crashed;
|
|
/* Number of nbuf not added to history*/
|
|
unsigned long g_histroy_add_drop;
|
|
#endif
|
|
|
|
/* Packet Counter */
|
|
static uint32_t nbuf_tx_mgmt[QDF_NBUF_TX_PKT_STATE_MAX];
|
|
static uint32_t nbuf_tx_data[QDF_NBUF_TX_PKT_STATE_MAX];
|
|
#ifdef QDF_NBUF_GLOBAL_COUNT
|
|
#define NBUF_DEBUGFS_NAME "nbuf_counters"
|
|
static qdf_atomic_t nbuf_count;
|
|
#endif
|
|
|
|
#if defined(NBUF_MEMORY_DEBUG) || defined(QDF_NBUF_GLOBAL_COUNT)
|
|
static bool is_initial_mem_debug_disabled;
|
|
#endif
|
|
|
|
/**
|
|
* __qdf_nbuf_get_ip_offset() - Get IPV4/V6 header offset
|
|
* @data: Pointer to network data buffer
|
|
*
|
|
* Get the IP header offset in case of 8021Q and 8021AD
|
|
* tag is present in L2 header.
|
|
*
|
|
* Return: IP header offset
|
|
*/
|
|
static inline uint8_t __qdf_nbuf_get_ip_offset(uint8_t *data)
|
|
{
|
|
uint16_t ether_type;
|
|
|
|
ether_type = *(uint16_t *)(data +
|
|
QDF_NBUF_TRAC_ETH_TYPE_OFFSET);
|
|
|
|
if (unlikely(ether_type == QDF_SWAP_U16(QDF_ETH_TYPE_8021Q)))
|
|
return QDF_NBUF_TRAC_VLAN_IP_OFFSET;
|
|
else if (unlikely(ether_type == QDF_SWAP_U16(QDF_ETH_TYPE_8021AD)))
|
|
return QDF_NBUF_TRAC_DOUBLE_VLAN_IP_OFFSET;
|
|
|
|
return QDF_NBUF_TRAC_IP_OFFSET;
|
|
}
|
|
|
|
/**
|
|
* __qdf_nbuf_get_ether_type() - Get the ether type
|
|
* @data: Pointer to network data buffer
|
|
*
|
|
* Get the ether type in case of 8021Q and 8021AD tag
|
|
* is present in L2 header, e.g for the returned ether type
|
|
* value, if IPV4 data ether type 0x0800, return 0x0008.
|
|
*
|
|
* Return ether type.
|
|
*/
|
|
static inline uint16_t __qdf_nbuf_get_ether_type(uint8_t *data)
|
|
{
|
|
uint16_t ether_type;
|
|
|
|
ether_type = *(uint16_t *)(data +
|
|
QDF_NBUF_TRAC_ETH_TYPE_OFFSET);
|
|
|
|
if (unlikely(ether_type == QDF_SWAP_U16(QDF_ETH_TYPE_8021Q)))
|
|
ether_type = *(uint16_t *)(data +
|
|
QDF_NBUF_TRAC_VLAN_ETH_TYPE_OFFSET);
|
|
else if (unlikely(ether_type == QDF_SWAP_U16(QDF_ETH_TYPE_8021AD)))
|
|
ether_type = *(uint16_t *)(data +
|
|
QDF_NBUF_TRAC_DOUBLE_VLAN_ETH_TYPE_OFFSET);
|
|
|
|
return ether_type;
|
|
}
|
|
|
|
void qdf_nbuf_tx_desc_count_display(void)
|
|
{
|
|
qdf_debug("Current Snapshot of the Driver:");
|
|
qdf_debug("Data Packets:");
|
|
qdf_debug("HDD %d TXRX_Q %d TXRX %d HTT %d",
|
|
nbuf_tx_data[QDF_NBUF_TX_PKT_HDD] -
|
|
(nbuf_tx_data[QDF_NBUF_TX_PKT_TXRX] +
|
|
nbuf_tx_data[QDF_NBUF_TX_PKT_TXRX_ENQUEUE] -
|
|
nbuf_tx_data[QDF_NBUF_TX_PKT_TXRX_DEQUEUE]),
|
|
nbuf_tx_data[QDF_NBUF_TX_PKT_TXRX_ENQUEUE] -
|
|
nbuf_tx_data[QDF_NBUF_TX_PKT_TXRX_DEQUEUE],
|
|
nbuf_tx_data[QDF_NBUF_TX_PKT_TXRX] -
|
|
nbuf_tx_data[QDF_NBUF_TX_PKT_HTT],
|
|
nbuf_tx_data[QDF_NBUF_TX_PKT_HTT] -
|
|
nbuf_tx_data[QDF_NBUF_TX_PKT_HTC]);
|
|
qdf_debug(" HTC %d HIF %d CE %d TX_COMP %d",
|
|
nbuf_tx_data[QDF_NBUF_TX_PKT_HTC] -
|
|
nbuf_tx_data[QDF_NBUF_TX_PKT_HIF],
|
|
nbuf_tx_data[QDF_NBUF_TX_PKT_HIF] -
|
|
nbuf_tx_data[QDF_NBUF_TX_PKT_CE],
|
|
nbuf_tx_data[QDF_NBUF_TX_PKT_CE] -
|
|
nbuf_tx_data[QDF_NBUF_TX_PKT_FREE],
|
|
nbuf_tx_data[QDF_NBUF_TX_PKT_FREE]);
|
|
qdf_debug("Mgmt Packets:");
|
|
qdf_debug("TXRX_Q %d TXRX %d HTT %d HTC %d HIF %d CE %d TX_COMP %d",
|
|
nbuf_tx_mgmt[QDF_NBUF_TX_PKT_TXRX_ENQUEUE] -
|
|
nbuf_tx_mgmt[QDF_NBUF_TX_PKT_TXRX_DEQUEUE],
|
|
nbuf_tx_mgmt[QDF_NBUF_TX_PKT_TXRX] -
|
|
nbuf_tx_mgmt[QDF_NBUF_TX_PKT_HTT],
|
|
nbuf_tx_mgmt[QDF_NBUF_TX_PKT_HTT] -
|
|
nbuf_tx_mgmt[QDF_NBUF_TX_PKT_HTC],
|
|
nbuf_tx_mgmt[QDF_NBUF_TX_PKT_HTC] -
|
|
nbuf_tx_mgmt[QDF_NBUF_TX_PKT_HIF],
|
|
nbuf_tx_mgmt[QDF_NBUF_TX_PKT_HIF] -
|
|
nbuf_tx_mgmt[QDF_NBUF_TX_PKT_CE],
|
|
nbuf_tx_mgmt[QDF_NBUF_TX_PKT_CE] -
|
|
nbuf_tx_mgmt[QDF_NBUF_TX_PKT_FREE],
|
|
nbuf_tx_mgmt[QDF_NBUF_TX_PKT_FREE]);
|
|
}
|
|
qdf_export_symbol(qdf_nbuf_tx_desc_count_display);
|
|
|
|
/**
|
|
* qdf_nbuf_tx_desc_count_update() - Updates the layer packet counter
|
|
* @packet_type : packet type either mgmt/data
|
|
* @current_state : layer at which the packet currently present
|
|
*
|
|
* Return: none
|
|
*/
|
|
static inline void qdf_nbuf_tx_desc_count_update(uint8_t packet_type,
|
|
uint8_t current_state)
|
|
{
|
|
switch (packet_type) {
|
|
case QDF_NBUF_TX_PKT_MGMT_TRACK:
|
|
nbuf_tx_mgmt[current_state]++;
|
|
break;
|
|
case QDF_NBUF_TX_PKT_DATA_TRACK:
|
|
nbuf_tx_data[current_state]++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void qdf_nbuf_tx_desc_count_clear(void)
|
|
{
|
|
memset(nbuf_tx_mgmt, 0, sizeof(nbuf_tx_mgmt));
|
|
memset(nbuf_tx_data, 0, sizeof(nbuf_tx_data));
|
|
}
|
|
qdf_export_symbol(qdf_nbuf_tx_desc_count_clear);
|
|
|
|
void qdf_nbuf_set_state(qdf_nbuf_t nbuf, uint8_t current_state)
|
|
{
|
|
/*
|
|
* Only Mgmt, Data Packets are tracked. WMI messages
|
|
* such as scan commands are not tracked
|
|
*/
|
|
uint8_t packet_type;
|
|
|
|
packet_type = QDF_NBUF_CB_TX_PACKET_TRACK(nbuf);
|
|
|
|
if ((packet_type != QDF_NBUF_TX_PKT_DATA_TRACK) &&
|
|
(packet_type != QDF_NBUF_TX_PKT_MGMT_TRACK)) {
|
|
return;
|
|
}
|
|
QDF_NBUF_CB_TX_PACKET_STATE(nbuf) = current_state;
|
|
qdf_nbuf_tx_desc_count_update(packet_type,
|
|
current_state);
|
|
}
|
|
qdf_export_symbol(qdf_nbuf_set_state);
|
|
|
|
#ifdef FEATURE_NBUFF_REPLENISH_TIMER
|
|
/**
|
|
* __qdf_nbuf_start_replenish_timer() - Start alloc fail replenish timer
|
|
*
|
|
* This function starts the alloc fail replenish timer.
|
|
*
|
|
* Return: void
|
|
*/
|
|
static inline void __qdf_nbuf_start_replenish_timer(void)
|
|
{
|
|
qdf_atomic_inc(&alloc_track_timer.alloc_fail_cnt);
|
|
if (qdf_mc_timer_get_current_state(&alloc_track_timer.track_timer) !=
|
|
QDF_TIMER_STATE_RUNNING)
|
|
qdf_mc_timer_start(&alloc_track_timer.track_timer,
|
|
QDF_NBUF_ALLOC_EXPIRE_TIMER_MS);
|
|
}
|
|
|
|
/**
|
|
* __qdf_nbuf_stop_replenish_timer() - Stop alloc fail replenish timer
|
|
*
|
|
* This function stops the alloc fail replenish timer.
|
|
*
|
|
* Return: void
|
|
*/
|
|
static inline void __qdf_nbuf_stop_replenish_timer(void)
|
|
{
|
|
if (qdf_atomic_read(&alloc_track_timer.alloc_fail_cnt) == 0)
|
|
return;
|
|
|
|
qdf_atomic_set(&alloc_track_timer.alloc_fail_cnt, 0);
|
|
if (qdf_mc_timer_get_current_state(&alloc_track_timer.track_timer) ==
|
|
QDF_TIMER_STATE_RUNNING)
|
|
qdf_mc_timer_stop(&alloc_track_timer.track_timer);
|
|
}
|
|
|
|
/**
|
|
* qdf_replenish_expire_handler() - Replenish expire handler
|
|
* @arg: unused callback argument
|
|
*
|
|
* This function triggers when the alloc fail replenish timer expires.
|
|
*
|
|
* Return: void
|
|
*/
|
|
static void qdf_replenish_expire_handler(void *arg)
|
|
{
|
|
if (qdf_atomic_read(&alloc_track_timer.alloc_fail_cnt) >
|
|
QDF_NBUF_ALLOC_EXPIRE_CNT_THRESHOLD) {
|
|
qdf_print("ERROR: NBUF allocation timer expired Fail count %d",
|
|
qdf_atomic_read(&alloc_track_timer.alloc_fail_cnt));
|
|
|
|
/* Error handling here */
|
|
}
|
|
}
|
|
|
|
void __qdf_nbuf_init_replenish_timer(void)
|
|
{
|
|
qdf_mc_timer_init(&alloc_track_timer.track_timer, QDF_TIMER_TYPE_SW,
|
|
qdf_replenish_expire_handler, NULL);
|
|
}
|
|
|
|
void __qdf_nbuf_deinit_replenish_timer(void)
|
|
{
|
|
__qdf_nbuf_stop_replenish_timer();
|
|
qdf_mc_timer_destroy(&alloc_track_timer.track_timer);
|
|
}
|
|
|
|
void qdf_nbuf_stop_replenish_timer(void)
|
|
{
|
|
__qdf_nbuf_stop_replenish_timer();
|
|
}
|
|
#else
|
|
|
|
static inline void __qdf_nbuf_start_replenish_timer(void) {}
|
|
static inline void __qdf_nbuf_stop_replenish_timer(void) {}
|
|
void qdf_nbuf_stop_replenish_timer(void)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
/* globals do not need to be initialized to NULL/0 */
|
|
qdf_nbuf_trace_update_t qdf_trace_update_cb;
|
|
qdf_nbuf_free_t nbuf_free_cb;
|
|
|
|
#ifdef QDF_NBUF_GLOBAL_COUNT
|
|
|
|
int __qdf_nbuf_count_get(void)
|
|
{
|
|
return qdf_atomic_read(&nbuf_count);
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_count_get);
|
|
|
|
void __qdf_nbuf_count_inc(qdf_nbuf_t nbuf)
|
|
{
|
|
int num_nbuf = 1;
|
|
qdf_nbuf_t ext_list;
|
|
|
|
if (qdf_likely(is_initial_mem_debug_disabled))
|
|
return;
|
|
|
|
ext_list = qdf_nbuf_get_ext_list(nbuf);
|
|
|
|
/* Take care to account for frag_list */
|
|
while (ext_list) {
|
|
++num_nbuf;
|
|
ext_list = qdf_nbuf_queue_next(ext_list);
|
|
}
|
|
|
|
qdf_atomic_add(num_nbuf, &nbuf_count);
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_count_inc);
|
|
|
|
void __qdf_nbuf_count_dec(__qdf_nbuf_t nbuf)
|
|
{
|
|
qdf_nbuf_t ext_list;
|
|
int num_nbuf;
|
|
|
|
if (qdf_likely(is_initial_mem_debug_disabled))
|
|
return;
|
|
|
|
if (qdf_nbuf_get_users(nbuf) > 1)
|
|
return;
|
|
|
|
num_nbuf = 1;
|
|
|
|
/* Take care to account for frag_list */
|
|
ext_list = qdf_nbuf_get_ext_list(nbuf);
|
|
while (ext_list) {
|
|
if (qdf_nbuf_get_users(ext_list) == 1)
|
|
++num_nbuf;
|
|
ext_list = qdf_nbuf_queue_next(ext_list);
|
|
}
|
|
|
|
qdf_atomic_sub(num_nbuf, &nbuf_count);
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_count_dec);
|
|
#endif
|
|
|
|
#ifdef NBUF_FRAG_MEMORY_DEBUG
|
|
void qdf_nbuf_frag_count_inc(qdf_nbuf_t nbuf)
|
|
{
|
|
qdf_nbuf_t ext_list;
|
|
uint32_t num_nr_frags;
|
|
uint32_t total_num_nr_frags;
|
|
|
|
if (qdf_likely(is_initial_mem_debug_disabled))
|
|
return;
|
|
|
|
num_nr_frags = qdf_nbuf_get_nr_frags(nbuf);
|
|
qdf_assert_always(num_nr_frags <= QDF_NBUF_MAX_FRAGS);
|
|
|
|
total_num_nr_frags = num_nr_frags;
|
|
|
|
/* Take into account the frags attached to frag_list */
|
|
ext_list = qdf_nbuf_get_ext_list(nbuf);
|
|
while (ext_list) {
|
|
num_nr_frags = qdf_nbuf_get_nr_frags(ext_list);
|
|
qdf_assert_always(num_nr_frags <= QDF_NBUF_MAX_FRAGS);
|
|
total_num_nr_frags += num_nr_frags;
|
|
ext_list = qdf_nbuf_queue_next(ext_list);
|
|
}
|
|
|
|
qdf_frag_count_inc(total_num_nr_frags);
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_frag_count_inc);
|
|
|
|
void qdf_nbuf_frag_count_dec(qdf_nbuf_t nbuf)
|
|
{
|
|
qdf_nbuf_t ext_list;
|
|
uint32_t num_nr_frags;
|
|
uint32_t total_num_nr_frags;
|
|
|
|
if (qdf_likely(is_initial_mem_debug_disabled))
|
|
return;
|
|
|
|
if (qdf_nbuf_get_users(nbuf) > 1)
|
|
return;
|
|
|
|
num_nr_frags = qdf_nbuf_get_nr_frags(nbuf);
|
|
qdf_assert_always(num_nr_frags <= QDF_NBUF_MAX_FRAGS);
|
|
|
|
total_num_nr_frags = num_nr_frags;
|
|
|
|
/* Take into account the frags attached to frag_list */
|
|
ext_list = qdf_nbuf_get_ext_list(nbuf);
|
|
while (ext_list) {
|
|
if (qdf_nbuf_get_users(ext_list) == 1) {
|
|
num_nr_frags = qdf_nbuf_get_nr_frags(ext_list);
|
|
qdf_assert_always(num_nr_frags <= QDF_NBUF_MAX_FRAGS);
|
|
total_num_nr_frags += num_nr_frags;
|
|
}
|
|
ext_list = qdf_nbuf_queue_next(ext_list);
|
|
}
|
|
|
|
qdf_frag_count_dec(total_num_nr_frags);
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_frag_count_dec);
|
|
|
|
#endif
|
|
|
|
static inline void
|
|
qdf_nbuf_set_defaults(struct sk_buff *skb, int align, int reserve)
|
|
{
|
|
unsigned long offset;
|
|
|
|
memset(skb->cb, 0x0, sizeof(skb->cb));
|
|
skb->dev = NULL;
|
|
|
|
/*
|
|
* The default is for netbuf fragments to be interpreted
|
|
* as wordstreams rather than bytestreams.
|
|
*/
|
|
QDF_NBUF_CB_TX_EXTRA_FRAG_WORDSTR_EFRAG(skb) = 1;
|
|
QDF_NBUF_CB_TX_EXTRA_FRAG_WORDSTR_NBUF(skb) = 1;
|
|
|
|
/*
|
|
* XXX:how about we reserve first then align
|
|
* Align & make sure that the tail & data are adjusted properly
|
|
*/
|
|
|
|
if (align) {
|
|
offset = ((unsigned long)skb->data) % align;
|
|
if (offset)
|
|
skb_reserve(skb, align - offset);
|
|
}
|
|
|
|
/*
|
|
* NOTE:alloc doesn't take responsibility if reserve unaligns the data
|
|
* pointer
|
|
*/
|
|
skb_reserve(skb, reserve);
|
|
qdf_nbuf_count_inc(skb);
|
|
}
|
|
|
|
#if defined(CONFIG_WIFI_EMULATION_WIFI_3_0) && defined(BUILD_X86) && \
|
|
!defined(QCA_WIFI_QCN9000)
|
|
struct sk_buff *__qdf_nbuf_alloc(qdf_device_t osdev, size_t size, int reserve,
|
|
int align, int prio, const char *func,
|
|
uint32_t line)
|
|
{
|
|
struct sk_buff *skb;
|
|
uint32_t lowmem_alloc_tries = 0;
|
|
|
|
if (align)
|
|
size += (align - 1);
|
|
|
|
realloc:
|
|
skb = dev_alloc_skb(size);
|
|
|
|
if (skb)
|
|
goto skb_alloc;
|
|
|
|
skb = pld_nbuf_pre_alloc(size);
|
|
|
|
if (!skb) {
|
|
qdf_rl_nofl_err("NBUF alloc failed %zuB @ %s:%d",
|
|
size, func, line);
|
|
return NULL;
|
|
}
|
|
|
|
skb_alloc:
|
|
/* Hawkeye M2M emulation cannot handle memory addresses below 0x50000040
|
|
* Though we are trying to reserve low memory upfront to prevent this,
|
|
* we sometimes see SKBs allocated from low memory.
|
|
*/
|
|
if (virt_to_phys(qdf_nbuf_data(skb)) < 0x50000040) {
|
|
lowmem_alloc_tries++;
|
|
if (lowmem_alloc_tries > 100) {
|
|
qdf_nofl_err("NBUF alloc failed %zuB @ %s:%d",
|
|
size, func, line);
|
|
return NULL;
|
|
} else {
|
|
/* Not freeing to make sure it
|
|
* will not get allocated again
|
|
*/
|
|
goto realloc;
|
|
}
|
|
}
|
|
|
|
qdf_nbuf_set_defaults(skb, align, reserve);
|
|
|
|
return skb;
|
|
}
|
|
#else
|
|
|
|
#ifdef QCA_DP_NBUF_FAST_RECYCLE_CHECK
|
|
struct sk_buff *__qdf_nbuf_alloc(qdf_device_t osdev, size_t size, int reserve,
|
|
int align, int prio, const char *func,
|
|
uint32_t line)
|
|
{
|
|
return __qdf_nbuf_frag_alloc(osdev, size, reserve, align, prio, func,
|
|
line);
|
|
}
|
|
|
|
#else
|
|
struct sk_buff *__qdf_nbuf_alloc(qdf_device_t osdev, size_t size, int reserve,
|
|
int align, int prio, const char *func,
|
|
uint32_t line)
|
|
{
|
|
struct sk_buff *skb;
|
|
int flags = GFP_KERNEL;
|
|
|
|
if (align)
|
|
size += (align - 1);
|
|
|
|
if (in_interrupt() || irqs_disabled() || in_atomic())
|
|
flags = GFP_ATOMIC;
|
|
|
|
skb = alloc_skb(size, flags);
|
|
|
|
if (skb)
|
|
goto skb_alloc;
|
|
|
|
skb = pld_nbuf_pre_alloc(size);
|
|
|
|
if (!skb) {
|
|
qdf_rl_nofl_err("NBUF alloc failed %zuB @ %s:%d",
|
|
size, func, line);
|
|
__qdf_nbuf_start_replenish_timer();
|
|
return NULL;
|
|
}
|
|
|
|
__qdf_nbuf_stop_replenish_timer();
|
|
|
|
skb_alloc:
|
|
qdf_nbuf_set_defaults(skb, align, reserve);
|
|
|
|
return skb;
|
|
}
|
|
#endif
|
|
|
|
#endif
|
|
qdf_export_symbol(__qdf_nbuf_alloc);
|
|
|
|
struct sk_buff *__qdf_nbuf_frag_alloc(qdf_device_t osdev, size_t size,
|
|
int reserve, int align, int prio,
|
|
const char *func, uint32_t line)
|
|
{
|
|
struct sk_buff *skb;
|
|
int flags = GFP_KERNEL & ~__GFP_DIRECT_RECLAIM;
|
|
bool atomic = false;
|
|
|
|
if (align)
|
|
size += (align - 1);
|
|
|
|
if (in_interrupt() || irqs_disabled() || in_atomic()) {
|
|
atomic = true;
|
|
flags = GFP_ATOMIC;
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)
|
|
/*
|
|
* Observed that kcompactd burns out CPU to make order-3 page.
|
|
*__netdev_alloc_skb has 4k page fallback option just in case of
|
|
* failing high order page allocation so we don't need to be
|
|
* hard. Make kcompactd rest in piece.
|
|
*/
|
|
flags = flags & ~__GFP_KSWAPD_RECLAIM;
|
|
#endif
|
|
}
|
|
|
|
skb = __netdev_alloc_skb(NULL, size, flags);
|
|
if (skb)
|
|
goto skb_alloc;
|
|
|
|
/* 32k page frag alloc failed, try page slab allocation */
|
|
if (likely(!atomic))
|
|
flags |= __GFP_DIRECT_RECLAIM;
|
|
|
|
skb = alloc_skb(size, flags);
|
|
if (skb)
|
|
goto skb_alloc;
|
|
|
|
skb = pld_nbuf_pre_alloc(size);
|
|
|
|
if (!skb) {
|
|
qdf_rl_nofl_err("NBUF alloc failed %zuB @ %s:%d",
|
|
size, func, line);
|
|
__qdf_nbuf_start_replenish_timer();
|
|
return NULL;
|
|
}
|
|
|
|
__qdf_nbuf_stop_replenish_timer();
|
|
|
|
skb_alloc:
|
|
qdf_nbuf_set_defaults(skb, align, reserve);
|
|
|
|
return skb;
|
|
}
|
|
|
|
qdf_export_symbol(__qdf_nbuf_frag_alloc);
|
|
|
|
__qdf_nbuf_t __qdf_nbuf_alloc_no_recycler(size_t size, int reserve, int align,
|
|
const char *func, uint32_t line)
|
|
{
|
|
qdf_nbuf_t nbuf;
|
|
unsigned long offset;
|
|
|
|
if (align)
|
|
size += (align - 1);
|
|
|
|
nbuf = alloc_skb(size, GFP_ATOMIC);
|
|
if (!nbuf)
|
|
goto ret_nbuf;
|
|
|
|
memset(nbuf->cb, 0x0, sizeof(nbuf->cb));
|
|
|
|
skb_reserve(nbuf, reserve);
|
|
|
|
if (align) {
|
|
offset = ((unsigned long)nbuf->data) % align;
|
|
if (offset)
|
|
skb_reserve(nbuf, align - offset);
|
|
}
|
|
|
|
qdf_nbuf_count_inc(nbuf);
|
|
|
|
ret_nbuf:
|
|
return nbuf;
|
|
}
|
|
|
|
qdf_export_symbol(__qdf_nbuf_alloc_no_recycler);
|
|
|
|
void __qdf_nbuf_free(struct sk_buff *skb)
|
|
{
|
|
if (pld_nbuf_pre_alloc_free(skb))
|
|
return;
|
|
|
|
qdf_nbuf_frag_count_dec(skb);
|
|
|
|
qdf_nbuf_count_dec(skb);
|
|
if (nbuf_free_cb)
|
|
nbuf_free_cb(skb);
|
|
else
|
|
dev_kfree_skb_any(skb);
|
|
}
|
|
|
|
qdf_export_symbol(__qdf_nbuf_free);
|
|
|
|
__qdf_nbuf_t __qdf_nbuf_clone(__qdf_nbuf_t skb)
|
|
{
|
|
qdf_nbuf_t skb_new = NULL;
|
|
|
|
skb_new = skb_clone(skb, GFP_ATOMIC);
|
|
if (skb_new) {
|
|
qdf_nbuf_frag_count_inc(skb_new);
|
|
qdf_nbuf_count_inc(skb_new);
|
|
}
|
|
return skb_new;
|
|
}
|
|
|
|
qdf_export_symbol(__qdf_nbuf_clone);
|
|
|
|
struct sk_buff *
|
|
__qdf_nbuf_page_frag_alloc(qdf_device_t osdev, size_t size, int reserve,
|
|
int align, __qdf_frag_cache_t *pf_cache,
|
|
const char *func, uint32_t line)
|
|
{
|
|
struct sk_buff *skb;
|
|
qdf_frag_t frag_data;
|
|
size_t orig_size = size;
|
|
int flags = GFP_KERNEL;
|
|
|
|
if (align)
|
|
size += (align - 1);
|
|
|
|
size += NET_SKB_PAD;
|
|
size += SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
|
|
size = SKB_DATA_ALIGN(size);
|
|
|
|
if (in_interrupt() || irqs_disabled() || in_atomic())
|
|
flags = GFP_ATOMIC;
|
|
|
|
frag_data = page_frag_alloc(pf_cache, size, flags);
|
|
if (!frag_data) {
|
|
qdf_rl_nofl_err("page frag alloc failed %zuB @ %s:%d",
|
|
size, func, line);
|
|
return __qdf_nbuf_alloc(osdev, orig_size, reserve, align, 0,
|
|
func, line);
|
|
}
|
|
|
|
skb = build_skb(frag_data, size);
|
|
if (skb) {
|
|
skb_reserve(skb, NET_SKB_PAD);
|
|
goto skb_alloc;
|
|
}
|
|
|
|
/* Free the data allocated from pf_cache */
|
|
page_frag_free(frag_data);
|
|
|
|
size = orig_size + align - 1;
|
|
|
|
skb = pld_nbuf_pre_alloc(size);
|
|
if (!skb) {
|
|
qdf_rl_nofl_err("NBUF alloc failed %zuB @ %s:%d",
|
|
size, func, line);
|
|
__qdf_nbuf_start_replenish_timer();
|
|
return NULL;
|
|
}
|
|
|
|
__qdf_nbuf_stop_replenish_timer();
|
|
|
|
skb_alloc:
|
|
qdf_nbuf_set_defaults(skb, align, reserve);
|
|
|
|
return skb;
|
|
}
|
|
|
|
qdf_export_symbol(__qdf_nbuf_page_frag_alloc);
|
|
|
|
#ifdef QCA_DP_TX_NBUF_LIST_FREE
|
|
void
|
|
__qdf_nbuf_dev_kfree_list(__qdf_nbuf_queue_head_t *nbuf_queue_head)
|
|
{
|
|
dev_kfree_skb_list_fast(nbuf_queue_head);
|
|
}
|
|
#else
|
|
void
|
|
__qdf_nbuf_dev_kfree_list(__qdf_nbuf_queue_head_t *nbuf_queue_head)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
qdf_export_symbol(__qdf_nbuf_dev_kfree_list);
|
|
|
|
#ifdef NBUF_MEMORY_DEBUG
|
|
struct qdf_nbuf_event {
|
|
qdf_nbuf_t nbuf;
|
|
char func[QDF_MEM_FUNC_NAME_SIZE];
|
|
uint32_t line;
|
|
enum qdf_nbuf_event_type type;
|
|
uint64_t timestamp;
|
|
qdf_dma_addr_t iova;
|
|
};
|
|
|
|
#ifndef QDF_NBUF_HISTORY_SIZE
|
|
#define QDF_NBUF_HISTORY_SIZE 4096
|
|
#endif
|
|
static qdf_atomic_t qdf_nbuf_history_index;
|
|
static struct qdf_nbuf_event qdf_nbuf_history[QDF_NBUF_HISTORY_SIZE];
|
|
|
|
void qdf_nbuf_ssr_register_region(void)
|
|
{
|
|
qdf_ssr_driver_dump_register_region("qdf_nbuf_history",
|
|
qdf_nbuf_history,
|
|
sizeof(qdf_nbuf_history));
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_ssr_register_region);
|
|
|
|
void qdf_nbuf_ssr_unregister_region(void)
|
|
{
|
|
qdf_ssr_driver_dump_unregister_region("qdf_nbuf_history");
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_ssr_unregister_region);
|
|
|
|
static int32_t qdf_nbuf_circular_index_next(qdf_atomic_t *index, int size)
|
|
{
|
|
int32_t next = qdf_atomic_inc_return(index);
|
|
|
|
if (next == size)
|
|
qdf_atomic_sub(size, index);
|
|
|
|
return next % size;
|
|
}
|
|
|
|
void
|
|
qdf_nbuf_history_add(qdf_nbuf_t nbuf, const char *func, uint32_t line,
|
|
enum qdf_nbuf_event_type type)
|
|
{
|
|
int32_t idx = qdf_nbuf_circular_index_next(&qdf_nbuf_history_index,
|
|
QDF_NBUF_HISTORY_SIZE);
|
|
struct qdf_nbuf_event *event = &qdf_nbuf_history[idx];
|
|
|
|
if (qdf_atomic_read(&smmu_crashed)) {
|
|
g_histroy_add_drop++;
|
|
return;
|
|
}
|
|
|
|
event->nbuf = nbuf;
|
|
qdf_str_lcopy(event->func, func, QDF_MEM_FUNC_NAME_SIZE);
|
|
event->line = line;
|
|
event->type = type;
|
|
event->timestamp = qdf_get_log_timestamp();
|
|
if (type == QDF_NBUF_MAP || type == QDF_NBUF_UNMAP ||
|
|
type == QDF_NBUF_SMMU_MAP || type == QDF_NBUF_SMMU_UNMAP)
|
|
event->iova = QDF_NBUF_CB_PADDR(nbuf);
|
|
else
|
|
event->iova = 0;
|
|
}
|
|
|
|
void qdf_set_smmu_fault_state(bool smmu_fault_state)
|
|
{
|
|
qdf_atomic_set(&smmu_crashed, smmu_fault_state);
|
|
if (!smmu_fault_state)
|
|
g_histroy_add_drop = 0;
|
|
}
|
|
qdf_export_symbol(qdf_set_smmu_fault_state);
|
|
#endif /* NBUF_MEMORY_DEBUG */
|
|
|
|
#ifdef NBUF_SMMU_MAP_UNMAP_DEBUG
|
|
#define qdf_nbuf_smmu_map_tracker_bits 11 /* 2048 buckets */
|
|
qdf_tracker_declare(qdf_nbuf_smmu_map_tracker, qdf_nbuf_smmu_map_tracker_bits,
|
|
"nbuf map-no-unmap events", "nbuf map", "nbuf unmap");
|
|
|
|
static void qdf_nbuf_smmu_map_tracking_init(void)
|
|
{
|
|
qdf_tracker_init(&qdf_nbuf_smmu_map_tracker);
|
|
}
|
|
|
|
static void qdf_nbuf_smmu_map_tracking_deinit(void)
|
|
{
|
|
qdf_tracker_deinit(&qdf_nbuf_smmu_map_tracker);
|
|
}
|
|
|
|
static QDF_STATUS
|
|
qdf_nbuf_track_smmu_map(qdf_nbuf_t nbuf, const char *func, uint32_t line)
|
|
{
|
|
if (is_initial_mem_debug_disabled)
|
|
return QDF_STATUS_SUCCESS;
|
|
|
|
return qdf_tracker_track(&qdf_nbuf_smmu_map_tracker, nbuf, func, line);
|
|
}
|
|
|
|
static void
|
|
qdf_nbuf_untrack_smmu_map(qdf_nbuf_t nbuf, const char *func, uint32_t line)
|
|
{
|
|
if (is_initial_mem_debug_disabled)
|
|
return;
|
|
|
|
qdf_nbuf_history_add(nbuf, func, line, QDF_NBUF_SMMU_UNMAP);
|
|
qdf_tracker_untrack(&qdf_nbuf_smmu_map_tracker, nbuf, func, line);
|
|
}
|
|
|
|
void qdf_nbuf_map_check_for_smmu_leaks(void)
|
|
{
|
|
qdf_tracker_check_for_leaks(&qdf_nbuf_smmu_map_tracker);
|
|
}
|
|
|
|
#ifdef IPA_OFFLOAD
|
|
QDF_STATUS qdf_nbuf_smmu_map_debug(qdf_nbuf_t nbuf,
|
|
uint8_t hdl,
|
|
uint8_t num_buffers,
|
|
qdf_mem_info_t *info,
|
|
const char *func,
|
|
uint32_t line)
|
|
{
|
|
QDF_STATUS status;
|
|
|
|
status = qdf_nbuf_track_smmu_map(nbuf, func, line);
|
|
if (QDF_IS_STATUS_ERROR(status))
|
|
return status;
|
|
|
|
status = __qdf_ipa_wdi_create_smmu_mapping(hdl, num_buffers, info);
|
|
|
|
if (QDF_IS_STATUS_ERROR(status)) {
|
|
qdf_nbuf_untrack_smmu_map(nbuf, func, line);
|
|
} else {
|
|
if (!is_initial_mem_debug_disabled)
|
|
qdf_nbuf_history_add(nbuf, func, line, QDF_NBUF_MAP);
|
|
qdf_net_buf_debug_update_smmu_map_node(nbuf, info->iova,
|
|
info->pa, func, line);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_smmu_map_debug);
|
|
|
|
QDF_STATUS qdf_nbuf_smmu_unmap_debug(qdf_nbuf_t nbuf,
|
|
uint8_t hdl,
|
|
uint8_t num_buffers,
|
|
qdf_mem_info_t *info,
|
|
const char *func,
|
|
uint32_t line)
|
|
{
|
|
QDF_STATUS status;
|
|
|
|
qdf_nbuf_untrack_smmu_map(nbuf, func, line);
|
|
status = __qdf_ipa_wdi_release_smmu_mapping(hdl, num_buffers, info);
|
|
qdf_net_buf_debug_update_smmu_unmap_node(nbuf, info->iova,
|
|
info->pa, func, line);
|
|
return status;
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_smmu_unmap_debug);
|
|
#endif /* IPA_OFFLOAD */
|
|
|
|
static void qdf_nbuf_panic_on_free_if_smmu_mapped(qdf_nbuf_t nbuf,
|
|
const char *func,
|
|
uint32_t line)
|
|
{
|
|
char map_func[QDF_TRACKER_FUNC_SIZE];
|
|
uint32_t map_line;
|
|
|
|
if (!qdf_tracker_lookup(&qdf_nbuf_smmu_map_tracker, nbuf,
|
|
&map_func, &map_line))
|
|
return;
|
|
|
|
QDF_MEMDEBUG_PANIC("Nbuf freed @ %s:%u while mapped from %s:%u",
|
|
func, line, map_func, map_line);
|
|
}
|
|
|
|
static inline void qdf_net_buf_update_smmu_params(QDF_NBUF_TRACK *p_node)
|
|
{
|
|
p_node->smmu_unmap_line_num = 0;
|
|
p_node->is_nbuf_smmu_mapped = false;
|
|
p_node->smmu_map_line_num = 0;
|
|
p_node->smmu_map_func_name[0] = '\0';
|
|
p_node->smmu_unmap_func_name[0] = '\0';
|
|
p_node->smmu_unmap_iova_addr = 0;
|
|
p_node->smmu_unmap_pa_addr = 0;
|
|
p_node->smmu_map_iova_addr = 0;
|
|
p_node->smmu_map_pa_addr = 0;
|
|
}
|
|
#else /* !NBUF_SMMU_MAP_UNMAP_DEBUG */
|
|
#ifdef NBUF_MEMORY_DEBUG
|
|
static void qdf_nbuf_smmu_map_tracking_init(void)
|
|
{
|
|
}
|
|
|
|
static void qdf_nbuf_smmu_map_tracking_deinit(void)
|
|
{
|
|
}
|
|
|
|
static void qdf_nbuf_panic_on_free_if_smmu_mapped(qdf_nbuf_t nbuf,
|
|
const char *func,
|
|
uint32_t line)
|
|
{
|
|
}
|
|
|
|
static inline void qdf_net_buf_update_smmu_params(QDF_NBUF_TRACK *p_node)
|
|
{
|
|
}
|
|
#endif /* NBUF_MEMORY_DEBUG */
|
|
|
|
#ifdef IPA_OFFLOAD
|
|
QDF_STATUS qdf_nbuf_smmu_map_debug(qdf_nbuf_t nbuf,
|
|
uint8_t hdl,
|
|
uint8_t num_buffers,
|
|
qdf_mem_info_t *info,
|
|
const char *func,
|
|
uint32_t line)
|
|
{
|
|
return __qdf_ipa_wdi_create_smmu_mapping(hdl, num_buffers, info);
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_smmu_map_debug);
|
|
|
|
QDF_STATUS qdf_nbuf_smmu_unmap_debug(qdf_nbuf_t nbuf,
|
|
uint8_t hdl,
|
|
uint8_t num_buffers,
|
|
qdf_mem_info_t *info,
|
|
const char *func,
|
|
uint32_t line)
|
|
{
|
|
return __qdf_ipa_wdi_release_smmu_mapping(hdl, num_buffers, info);
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_smmu_unmap_debug);
|
|
#endif /* IPA_OFFLOAD */
|
|
#endif /* NBUF_SMMU_MAP_UNMAP_DEBUG */
|
|
|
|
#ifdef NBUF_MAP_UNMAP_DEBUG
|
|
#define qdf_nbuf_map_tracker_bits 11 /* 2048 buckets */
|
|
qdf_tracker_declare(qdf_nbuf_map_tracker, qdf_nbuf_map_tracker_bits,
|
|
"nbuf map-no-unmap events", "nbuf map", "nbuf unmap");
|
|
|
|
static void qdf_nbuf_map_tracking_init(void)
|
|
{
|
|
qdf_tracker_init(&qdf_nbuf_map_tracker);
|
|
}
|
|
|
|
static void qdf_nbuf_map_tracking_deinit(void)
|
|
{
|
|
qdf_tracker_deinit(&qdf_nbuf_map_tracker);
|
|
}
|
|
|
|
static QDF_STATUS
|
|
qdf_nbuf_track_map(qdf_nbuf_t nbuf, const char *func, uint32_t line)
|
|
{
|
|
if (is_initial_mem_debug_disabled)
|
|
return QDF_STATUS_SUCCESS;
|
|
|
|
return qdf_tracker_track(&qdf_nbuf_map_tracker, nbuf, func, line);
|
|
}
|
|
|
|
static void
|
|
qdf_nbuf_untrack_map(qdf_nbuf_t nbuf, const char *func, uint32_t line)
|
|
{
|
|
if (is_initial_mem_debug_disabled)
|
|
return;
|
|
|
|
qdf_nbuf_history_add(nbuf, func, line, QDF_NBUF_UNMAP);
|
|
qdf_tracker_untrack(&qdf_nbuf_map_tracker, nbuf, func, line);
|
|
}
|
|
|
|
void qdf_nbuf_map_check_for_leaks(void)
|
|
{
|
|
qdf_tracker_check_for_leaks(&qdf_nbuf_map_tracker);
|
|
}
|
|
|
|
QDF_STATUS qdf_nbuf_map_debug(qdf_device_t osdev,
|
|
qdf_nbuf_t buf,
|
|
qdf_dma_dir_t dir,
|
|
const char *func,
|
|
uint32_t line)
|
|
{
|
|
QDF_STATUS status;
|
|
|
|
status = qdf_nbuf_track_map(buf, func, line);
|
|
if (QDF_IS_STATUS_ERROR(status))
|
|
return status;
|
|
|
|
status = __qdf_nbuf_map(osdev, buf, dir);
|
|
if (QDF_IS_STATUS_ERROR(status)) {
|
|
qdf_nbuf_untrack_map(buf, func, line);
|
|
} else {
|
|
if (!is_initial_mem_debug_disabled)
|
|
qdf_nbuf_history_add(buf, func, line, QDF_NBUF_MAP);
|
|
qdf_net_buf_debug_update_map_node(buf, func, line);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_map_debug);
|
|
|
|
void qdf_nbuf_unmap_debug(qdf_device_t osdev,
|
|
qdf_nbuf_t buf,
|
|
qdf_dma_dir_t dir,
|
|
const char *func,
|
|
uint32_t line)
|
|
{
|
|
qdf_nbuf_untrack_map(buf, func, line);
|
|
__qdf_nbuf_unmap_single(osdev, buf, dir);
|
|
qdf_net_buf_debug_update_unmap_node(buf, func, line);
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_unmap_debug);
|
|
|
|
QDF_STATUS qdf_nbuf_map_single_debug(qdf_device_t osdev,
|
|
qdf_nbuf_t buf,
|
|
qdf_dma_dir_t dir,
|
|
const char *func,
|
|
uint32_t line)
|
|
{
|
|
QDF_STATUS status;
|
|
|
|
status = qdf_nbuf_track_map(buf, func, line);
|
|
if (QDF_IS_STATUS_ERROR(status))
|
|
return status;
|
|
|
|
status = __qdf_nbuf_map_single(osdev, buf, dir);
|
|
if (QDF_IS_STATUS_ERROR(status)) {
|
|
qdf_nbuf_untrack_map(buf, func, line);
|
|
} else {
|
|
if (!is_initial_mem_debug_disabled)
|
|
qdf_nbuf_history_add(buf, func, line, QDF_NBUF_MAP);
|
|
qdf_net_buf_debug_update_map_node(buf, func, line);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_map_single_debug);
|
|
|
|
void qdf_nbuf_unmap_single_debug(qdf_device_t osdev,
|
|
qdf_nbuf_t buf,
|
|
qdf_dma_dir_t dir,
|
|
const char *func,
|
|
uint32_t line)
|
|
{
|
|
qdf_nbuf_untrack_map(buf, func, line);
|
|
__qdf_nbuf_unmap_single(osdev, buf, dir);
|
|
qdf_net_buf_debug_update_unmap_node(buf, func, line);
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_unmap_single_debug);
|
|
|
|
QDF_STATUS qdf_nbuf_map_nbytes_debug(qdf_device_t osdev,
|
|
qdf_nbuf_t buf,
|
|
qdf_dma_dir_t dir,
|
|
int nbytes,
|
|
const char *func,
|
|
uint32_t line)
|
|
{
|
|
QDF_STATUS status;
|
|
|
|
status = qdf_nbuf_track_map(buf, func, line);
|
|
if (QDF_IS_STATUS_ERROR(status))
|
|
return status;
|
|
|
|
status = __qdf_nbuf_map_nbytes(osdev, buf, dir, nbytes);
|
|
if (QDF_IS_STATUS_ERROR(status)) {
|
|
qdf_nbuf_untrack_map(buf, func, line);
|
|
} else {
|
|
if (!is_initial_mem_debug_disabled)
|
|
qdf_nbuf_history_add(buf, func, line, QDF_NBUF_MAP);
|
|
qdf_net_buf_debug_update_map_node(buf, func, line);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_map_nbytes_debug);
|
|
|
|
void qdf_nbuf_unmap_nbytes_debug(qdf_device_t osdev,
|
|
qdf_nbuf_t buf,
|
|
qdf_dma_dir_t dir,
|
|
int nbytes,
|
|
const char *func,
|
|
uint32_t line)
|
|
{
|
|
qdf_nbuf_untrack_map(buf, func, line);
|
|
__qdf_nbuf_unmap_nbytes(osdev, buf, dir, nbytes);
|
|
qdf_net_buf_debug_update_unmap_node(buf, func, line);
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_unmap_nbytes_debug);
|
|
|
|
QDF_STATUS qdf_nbuf_map_nbytes_single_debug(qdf_device_t osdev,
|
|
qdf_nbuf_t buf,
|
|
qdf_dma_dir_t dir,
|
|
int nbytes,
|
|
const char *func,
|
|
uint32_t line)
|
|
{
|
|
QDF_STATUS status;
|
|
|
|
status = qdf_nbuf_track_map(buf, func, line);
|
|
if (QDF_IS_STATUS_ERROR(status))
|
|
return status;
|
|
|
|
status = __qdf_nbuf_map_nbytes_single(osdev, buf, dir, nbytes);
|
|
if (QDF_IS_STATUS_ERROR(status)) {
|
|
qdf_nbuf_untrack_map(buf, func, line);
|
|
} else {
|
|
if (!is_initial_mem_debug_disabled)
|
|
qdf_nbuf_history_add(buf, func, line, QDF_NBUF_MAP);
|
|
qdf_net_buf_debug_update_map_node(buf, func, line);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_map_nbytes_single_debug);
|
|
|
|
void qdf_nbuf_unmap_nbytes_single_debug(qdf_device_t osdev,
|
|
qdf_nbuf_t buf,
|
|
qdf_dma_dir_t dir,
|
|
int nbytes,
|
|
const char *func,
|
|
uint32_t line)
|
|
{
|
|
qdf_nbuf_untrack_map(buf, func, line);
|
|
__qdf_nbuf_unmap_nbytes_single(osdev, buf, dir, nbytes);
|
|
qdf_net_buf_debug_update_unmap_node(buf, func, line);
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_unmap_nbytes_single_debug);
|
|
|
|
void qdf_nbuf_unmap_nbytes_single_paddr_debug(qdf_device_t osdev,
|
|
qdf_nbuf_t buf,
|
|
qdf_dma_addr_t phy_addr,
|
|
qdf_dma_dir_t dir, int nbytes,
|
|
const char *func, uint32_t line)
|
|
{
|
|
qdf_nbuf_untrack_map(buf, func, line);
|
|
__qdf_record_nbuf_nbytes(__qdf_nbuf_get_end_offset(buf), dir, false);
|
|
__qdf_mem_unmap_nbytes_single(osdev, phy_addr, dir, nbytes);
|
|
qdf_net_buf_debug_update_unmap_node(buf, func, line);
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_unmap_nbytes_single_paddr_debug);
|
|
|
|
static void qdf_nbuf_panic_on_free_if_mapped(qdf_nbuf_t nbuf,
|
|
const char *func,
|
|
uint32_t line)
|
|
{
|
|
char map_func[QDF_TRACKER_FUNC_SIZE];
|
|
uint32_t map_line;
|
|
|
|
if (!qdf_tracker_lookup(&qdf_nbuf_map_tracker, nbuf,
|
|
&map_func, &map_line))
|
|
return;
|
|
|
|
QDF_MEMDEBUG_PANIC("Nbuf freed @ %s:%u while mapped from %s:%u",
|
|
func, line, map_func, map_line);
|
|
}
|
|
#else
|
|
static inline void qdf_nbuf_map_tracking_init(void)
|
|
{
|
|
}
|
|
|
|
static inline void qdf_nbuf_map_tracking_deinit(void)
|
|
{
|
|
}
|
|
|
|
static inline void qdf_nbuf_panic_on_free_if_mapped(qdf_nbuf_t nbuf,
|
|
const char *func,
|
|
uint32_t line)
|
|
{
|
|
}
|
|
#endif /* NBUF_MAP_UNMAP_DEBUG */
|
|
|
|
#ifdef QDF_OS_DEBUG
|
|
QDF_STATUS
|
|
__qdf_nbuf_map(qdf_device_t osdev, struct sk_buff *skb, qdf_dma_dir_t dir)
|
|
{
|
|
struct skb_shared_info *sh = skb_shinfo(skb);
|
|
|
|
qdf_assert((dir == QDF_DMA_TO_DEVICE)
|
|
|| (dir == QDF_DMA_FROM_DEVICE));
|
|
|
|
/*
|
|
* Assume there's only a single fragment.
|
|
* To support multiple fragments, it would be necessary to change
|
|
* qdf_nbuf_t to be a separate object that stores meta-info
|
|
* (including the bus address for each fragment) and a pointer
|
|
* to the underlying sk_buff.
|
|
*/
|
|
qdf_assert(sh->nr_frags == 0);
|
|
|
|
return __qdf_nbuf_map_single(osdev, skb, dir);
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_map);
|
|
|
|
#else
|
|
QDF_STATUS
|
|
__qdf_nbuf_map(qdf_device_t osdev, struct sk_buff *skb, qdf_dma_dir_t dir)
|
|
{
|
|
return __qdf_nbuf_map_single(osdev, skb, dir);
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_map);
|
|
#endif
|
|
|
|
void
|
|
__qdf_nbuf_unmap(qdf_device_t osdev, struct sk_buff *skb,
|
|
qdf_dma_dir_t dir)
|
|
{
|
|
qdf_assert((dir == QDF_DMA_TO_DEVICE)
|
|
|| (dir == QDF_DMA_FROM_DEVICE));
|
|
|
|
/*
|
|
* Assume there's a single fragment.
|
|
* If this is not true, the assertion in __qdf_nbuf_map will catch it.
|
|
*/
|
|
__qdf_nbuf_unmap_single(osdev, skb, dir);
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_unmap);
|
|
|
|
#if defined(A_SIMOS_DEVHOST) || defined(HIF_USB) || defined(HIF_SDIO)
|
|
QDF_STATUS
|
|
__qdf_nbuf_map_single(qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir)
|
|
{
|
|
qdf_dma_addr_t paddr;
|
|
|
|
QDF_NBUF_CB_PADDR(buf) = paddr = (uintptr_t)buf->data;
|
|
BUILD_BUG_ON(sizeof(paddr) < sizeof(buf->data));
|
|
BUILD_BUG_ON(sizeof(QDF_NBUF_CB_PADDR(buf)) < sizeof(buf->data));
|
|
return QDF_STATUS_SUCCESS;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_map_single);
|
|
#else
|
|
QDF_STATUS
|
|
__qdf_nbuf_map_single(qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir)
|
|
{
|
|
qdf_dma_addr_t paddr;
|
|
|
|
/* assume that the OS only provides a single fragment */
|
|
QDF_NBUF_CB_PADDR(buf) = paddr =
|
|
dma_map_single(osdev->dev, buf->data,
|
|
skb_end_pointer(buf) - buf->data,
|
|
__qdf_dma_dir_to_os(dir));
|
|
__qdf_record_nbuf_nbytes(
|
|
__qdf_nbuf_get_end_offset(buf), dir, true);
|
|
return dma_mapping_error(osdev->dev, paddr)
|
|
? QDF_STATUS_E_FAILURE
|
|
: QDF_STATUS_SUCCESS;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_map_single);
|
|
#endif
|
|
|
|
#if defined(A_SIMOS_DEVHOST) || defined(HIF_USB) || defined(HIF_SDIO)
|
|
void __qdf_nbuf_unmap_single(qdf_device_t osdev, qdf_nbuf_t buf,
|
|
qdf_dma_dir_t dir)
|
|
{
|
|
}
|
|
#else
|
|
void __qdf_nbuf_unmap_single(qdf_device_t osdev, qdf_nbuf_t buf,
|
|
qdf_dma_dir_t dir)
|
|
{
|
|
if (QDF_NBUF_CB_PADDR(buf)) {
|
|
__qdf_record_nbuf_nbytes(
|
|
__qdf_nbuf_get_end_offset(buf), dir, false);
|
|
dma_unmap_single(osdev->dev, QDF_NBUF_CB_PADDR(buf),
|
|
skb_end_pointer(buf) - buf->data,
|
|
__qdf_dma_dir_to_os(dir));
|
|
}
|
|
}
|
|
#endif
|
|
qdf_export_symbol(__qdf_nbuf_unmap_single);
|
|
|
|
QDF_STATUS
|
|
__qdf_nbuf_set_rx_cksum(struct sk_buff *skb, qdf_nbuf_rx_cksum_t *cksum)
|
|
{
|
|
switch (cksum->l4_result) {
|
|
case QDF_NBUF_RX_CKSUM_NONE:
|
|
skb->ip_summed = CHECKSUM_NONE;
|
|
break;
|
|
case QDF_NBUF_RX_CKSUM_TCP_UDP_UNNECESSARY:
|
|
skb->ip_summed = CHECKSUM_UNNECESSARY;
|
|
skb->csum_level = cksum->csum_level;
|
|
break;
|
|
case QDF_NBUF_RX_CKSUM_TCP_UDP_HW:
|
|
skb->ip_summed = CHECKSUM_PARTIAL;
|
|
skb->csum = cksum->val;
|
|
break;
|
|
default:
|
|
pr_err("Unknown checksum type\n");
|
|
qdf_assert(0);
|
|
return QDF_STATUS_E_NOSUPPORT;
|
|
}
|
|
return QDF_STATUS_SUCCESS;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_set_rx_cksum);
|
|
|
|
qdf_nbuf_tx_cksum_t __qdf_nbuf_get_tx_cksum(struct sk_buff *skb)
|
|
{
|
|
switch (skb->ip_summed) {
|
|
case CHECKSUM_NONE:
|
|
return QDF_NBUF_TX_CKSUM_NONE;
|
|
case CHECKSUM_PARTIAL:
|
|
return QDF_NBUF_TX_CKSUM_TCP_UDP;
|
|
case CHECKSUM_COMPLETE:
|
|
return QDF_NBUF_TX_CKSUM_TCP_UDP_IP;
|
|
default:
|
|
return QDF_NBUF_TX_CKSUM_NONE;
|
|
}
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_get_tx_cksum);
|
|
|
|
uint8_t __qdf_nbuf_get_tid(struct sk_buff *skb)
|
|
{
|
|
return skb->priority;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_get_tid);
|
|
|
|
void __qdf_nbuf_set_tid(struct sk_buff *skb, uint8_t tid)
|
|
{
|
|
skb->priority = tid;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_set_tid);
|
|
|
|
uint8_t __qdf_nbuf_get_exemption_type(struct sk_buff *skb)
|
|
{
|
|
return QDF_NBUF_EXEMPT_NO_EXEMPTION;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_get_exemption_type);
|
|
|
|
void __qdf_nbuf_reg_trace_cb(qdf_nbuf_trace_update_t cb_func_ptr)
|
|
{
|
|
qdf_trace_update_cb = cb_func_ptr;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_reg_trace_cb);
|
|
|
|
enum qdf_proto_subtype
|
|
__qdf_nbuf_data_get_dhcp_subtype(uint8_t *data)
|
|
{
|
|
enum qdf_proto_subtype subtype = QDF_PROTO_INVALID;
|
|
|
|
if ((data[QDF_DHCP_OPTION53_OFFSET] == QDF_DHCP_OPTION53) &&
|
|
(data[QDF_DHCP_OPTION53_LENGTH_OFFSET] ==
|
|
QDF_DHCP_OPTION53_LENGTH)) {
|
|
|
|
switch (data[QDF_DHCP_OPTION53_STATUS_OFFSET]) {
|
|
case QDF_DHCP_DISCOVER:
|
|
subtype = QDF_PROTO_DHCP_DISCOVER;
|
|
break;
|
|
case QDF_DHCP_REQUEST:
|
|
subtype = QDF_PROTO_DHCP_REQUEST;
|
|
break;
|
|
case QDF_DHCP_OFFER:
|
|
subtype = QDF_PROTO_DHCP_OFFER;
|
|
break;
|
|
case QDF_DHCP_ACK:
|
|
subtype = QDF_PROTO_DHCP_ACK;
|
|
break;
|
|
case QDF_DHCP_NAK:
|
|
subtype = QDF_PROTO_DHCP_NACK;
|
|
break;
|
|
case QDF_DHCP_RELEASE:
|
|
subtype = QDF_PROTO_DHCP_RELEASE;
|
|
break;
|
|
case QDF_DHCP_INFORM:
|
|
subtype = QDF_PROTO_DHCP_INFORM;
|
|
break;
|
|
case QDF_DHCP_DECLINE:
|
|
subtype = QDF_PROTO_DHCP_DECLINE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return subtype;
|
|
}
|
|
|
|
#define EAPOL_WPA_KEY_INFO_ACK BIT(7)
|
|
#define EAPOL_WPA_KEY_INFO_MIC BIT(8)
|
|
#define EAPOL_WPA_KEY_INFO_ENCR_KEY_DATA BIT(12) /* IEEE 802.11i/RSN only */
|
|
|
|
/**
|
|
* __qdf_nbuf_data_get_eapol_key() - Get EAPOL key
|
|
* @data: Pointer to EAPOL packet data buffer
|
|
*
|
|
* We can distinguish M1/M3 from M2/M4 by the ack bit in the keyinfo field
|
|
* The ralationship between the ack bit and EAPOL type is as follows:
|
|
*
|
|
* EAPOL type | M1 M2 M3 M4
|
|
* --------------------------------------
|
|
* Ack | 1 0 1 0
|
|
* --------------------------------------
|
|
*
|
|
* Then, we can differentiate M1 from M3, M2 from M4 by below methods:
|
|
* M2/M4: by keyDataLength or Nonce value being 0 for M4.
|
|
* M1/M3: by the mic/encrKeyData bit in the keyinfo field.
|
|
*
|
|
* Return: subtype of the EAPOL packet.
|
|
*/
|
|
static inline enum qdf_proto_subtype
|
|
__qdf_nbuf_data_get_eapol_key(uint8_t *data)
|
|
{
|
|
uint16_t key_info, key_data_length;
|
|
enum qdf_proto_subtype subtype;
|
|
uint64_t *key_nonce;
|
|
|
|
key_info = qdf_ntohs((uint16_t)(*(uint16_t *)
|
|
(data + EAPOL_KEY_INFO_OFFSET)));
|
|
|
|
key_data_length = qdf_ntohs((uint16_t)(*(uint16_t *)
|
|
(data + EAPOL_KEY_DATA_LENGTH_OFFSET)));
|
|
key_nonce = (uint64_t *)(data + EAPOL_WPA_KEY_NONCE_OFFSET);
|
|
|
|
if (key_info & EAPOL_WPA_KEY_INFO_ACK)
|
|
if (key_info &
|
|
(EAPOL_WPA_KEY_INFO_MIC | EAPOL_WPA_KEY_INFO_ENCR_KEY_DATA))
|
|
subtype = QDF_PROTO_EAPOL_M3;
|
|
else
|
|
subtype = QDF_PROTO_EAPOL_M1;
|
|
else
|
|
if (key_data_length == 0 ||
|
|
!((*key_nonce) || (*(key_nonce + 1)) ||
|
|
(*(key_nonce + 2)) || (*(key_nonce + 3))))
|
|
subtype = QDF_PROTO_EAPOL_M4;
|
|
else
|
|
subtype = QDF_PROTO_EAPOL_M2;
|
|
|
|
return subtype;
|
|
}
|
|
|
|
/**
|
|
* __qdf_nbuf_data_get_exp_msg_type() - Get EAP expanded msg type
|
|
* @data: Pointer to EAPOL packet data buffer
|
|
* @code: EAP code
|
|
*
|
|
* Return: subtype of the EAPOL packet.
|
|
*/
|
|
static inline enum qdf_proto_subtype
|
|
__qdf_nbuf_data_get_exp_msg_type(uint8_t *data, uint8_t code)
|
|
{
|
|
uint8_t msg_type;
|
|
uint8_t opcode = *(data + EAP_EXP_MSG_OPCODE_OFFSET);
|
|
|
|
switch (opcode) {
|
|
case WSC_START:
|
|
return QDF_PROTO_EAP_WSC_START;
|
|
case WSC_ACK:
|
|
return QDF_PROTO_EAP_WSC_ACK;
|
|
case WSC_NACK:
|
|
return QDF_PROTO_EAP_WSC_NACK;
|
|
case WSC_MSG:
|
|
msg_type = *(data + EAP_EXP_MSG_TYPE_OFFSET);
|
|
switch (msg_type) {
|
|
case EAP_EXP_TYPE_M1:
|
|
return QDF_PROTO_EAP_M1;
|
|
case EAP_EXP_TYPE_M2:
|
|
return QDF_PROTO_EAP_M2;
|
|
case EAP_EXP_TYPE_M3:
|
|
return QDF_PROTO_EAP_M3;
|
|
case EAP_EXP_TYPE_M4:
|
|
return QDF_PROTO_EAP_M4;
|
|
case EAP_EXP_TYPE_M5:
|
|
return QDF_PROTO_EAP_M5;
|
|
case EAP_EXP_TYPE_M6:
|
|
return QDF_PROTO_EAP_M6;
|
|
case EAP_EXP_TYPE_M7:
|
|
return QDF_PROTO_EAP_M7;
|
|
case EAP_EXP_TYPE_M8:
|
|
return QDF_PROTO_EAP_M8;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case WSC_DONE:
|
|
return QDF_PROTO_EAP_WSC_DONE;
|
|
case WSC_FRAG_ACK:
|
|
return QDF_PROTO_EAP_WSC_FRAG_ACK;
|
|
default:
|
|
break;
|
|
}
|
|
switch (code) {
|
|
case QDF_EAP_REQUEST:
|
|
return QDF_PROTO_EAP_REQUEST;
|
|
case QDF_EAP_RESPONSE:
|
|
return QDF_PROTO_EAP_RESPONSE;
|
|
default:
|
|
return QDF_PROTO_INVALID;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* __qdf_nbuf_data_get_eap_type() - Get EAP type
|
|
* @data: Pointer to EAPOL packet data buffer
|
|
* @code: EAP code
|
|
*
|
|
* Return: subtype of the EAPOL packet.
|
|
*/
|
|
static inline enum qdf_proto_subtype
|
|
__qdf_nbuf_data_get_eap_type(uint8_t *data, uint8_t code)
|
|
{
|
|
uint8_t type = *(data + EAP_TYPE_OFFSET);
|
|
|
|
switch (type) {
|
|
case EAP_PACKET_TYPE_EXP:
|
|
return __qdf_nbuf_data_get_exp_msg_type(data, code);
|
|
case EAP_PACKET_TYPE_ID:
|
|
switch (code) {
|
|
case QDF_EAP_REQUEST:
|
|
return QDF_PROTO_EAP_REQ_ID;
|
|
case QDF_EAP_RESPONSE:
|
|
return QDF_PROTO_EAP_RSP_ID;
|
|
default:
|
|
return QDF_PROTO_INVALID;
|
|
}
|
|
default:
|
|
switch (code) {
|
|
case QDF_EAP_REQUEST:
|
|
return QDF_PROTO_EAP_REQUEST;
|
|
case QDF_EAP_RESPONSE:
|
|
return QDF_PROTO_EAP_RESPONSE;
|
|
default:
|
|
return QDF_PROTO_INVALID;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* __qdf_nbuf_data_get_eap_code() - Get EAPOL code
|
|
* @data: Pointer to EAPOL packet data buffer
|
|
*
|
|
* Return: subtype of the EAPOL packet.
|
|
*/
|
|
static inline enum qdf_proto_subtype
|
|
__qdf_nbuf_data_get_eap_code(uint8_t *data)
|
|
{
|
|
uint8_t code = *(data + EAP_CODE_OFFSET);
|
|
|
|
switch (code) {
|
|
case QDF_EAP_REQUEST:
|
|
case QDF_EAP_RESPONSE:
|
|
return __qdf_nbuf_data_get_eap_type(data, code);
|
|
case QDF_EAP_SUCCESS:
|
|
return QDF_PROTO_EAP_SUCCESS;
|
|
case QDF_EAP_FAILURE:
|
|
return QDF_PROTO_EAP_FAILURE;
|
|
case QDF_EAP_INITIATE:
|
|
return QDF_PROTO_EAP_INITIATE;
|
|
case QDF_EAP_FINISH:
|
|
return QDF_PROTO_EAP_FINISH;
|
|
default:
|
|
return QDF_PROTO_INVALID;
|
|
}
|
|
}
|
|
|
|
enum qdf_proto_subtype
|
|
__qdf_nbuf_data_get_eapol_subtype(uint8_t *data)
|
|
{
|
|
uint8_t pkt_type = *(data + EAPOL_PACKET_TYPE_OFFSET);
|
|
|
|
switch (pkt_type) {
|
|
case EAPOL_PACKET_TYPE_EAP:
|
|
return __qdf_nbuf_data_get_eap_code(data);
|
|
case EAPOL_PACKET_TYPE_START:
|
|
return QDF_PROTO_EAPOL_START;
|
|
case EAPOL_PACKET_TYPE_LOGOFF:
|
|
return QDF_PROTO_EAPOL_LOGOFF;
|
|
case EAPOL_PACKET_TYPE_KEY:
|
|
return __qdf_nbuf_data_get_eapol_key(data);
|
|
case EAPOL_PACKET_TYPE_ASF:
|
|
return QDF_PROTO_EAPOL_ASF;
|
|
default:
|
|
return QDF_PROTO_INVALID;
|
|
}
|
|
}
|
|
|
|
qdf_export_symbol(__qdf_nbuf_data_get_eapol_subtype);
|
|
|
|
enum qdf_proto_subtype
|
|
__qdf_nbuf_data_get_arp_subtype(uint8_t *data)
|
|
{
|
|
uint16_t subtype;
|
|
enum qdf_proto_subtype proto_subtype = QDF_PROTO_INVALID;
|
|
|
|
subtype = (uint16_t)(*(uint16_t *)
|
|
(data + ARP_SUB_TYPE_OFFSET));
|
|
|
|
switch (QDF_SWAP_U16(subtype)) {
|
|
case ARP_REQUEST:
|
|
proto_subtype = QDF_PROTO_ARP_REQ;
|
|
break;
|
|
case ARP_RESPONSE:
|
|
proto_subtype = QDF_PROTO_ARP_RES;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return proto_subtype;
|
|
}
|
|
|
|
enum qdf_proto_subtype
|
|
__qdf_nbuf_data_get_icmp_subtype(uint8_t *data)
|
|
{
|
|
uint8_t subtype;
|
|
enum qdf_proto_subtype proto_subtype = QDF_PROTO_INVALID;
|
|
|
|
subtype = (uint8_t)(*(uint8_t *)
|
|
(data + ICMP_SUBTYPE_OFFSET));
|
|
|
|
switch (subtype) {
|
|
case ICMP_REQUEST:
|
|
proto_subtype = QDF_PROTO_ICMP_REQ;
|
|
break;
|
|
case ICMP_RESPONSE:
|
|
proto_subtype = QDF_PROTO_ICMP_RES;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return proto_subtype;
|
|
}
|
|
|
|
enum qdf_proto_subtype
|
|
__qdf_nbuf_data_get_icmpv6_subtype(uint8_t *data)
|
|
{
|
|
uint8_t subtype;
|
|
enum qdf_proto_subtype proto_subtype = QDF_PROTO_INVALID;
|
|
|
|
subtype = (uint8_t)(*(uint8_t *)
|
|
(data + ICMPV6_SUBTYPE_OFFSET));
|
|
|
|
switch (subtype) {
|
|
case ICMPV6_REQUEST:
|
|
proto_subtype = QDF_PROTO_ICMPV6_REQ;
|
|
break;
|
|
case ICMPV6_RESPONSE:
|
|
proto_subtype = QDF_PROTO_ICMPV6_RES;
|
|
break;
|
|
case ICMPV6_RS:
|
|
proto_subtype = QDF_PROTO_ICMPV6_RS;
|
|
break;
|
|
case ICMPV6_RA:
|
|
proto_subtype = QDF_PROTO_ICMPV6_RA;
|
|
break;
|
|
case ICMPV6_NS:
|
|
proto_subtype = QDF_PROTO_ICMPV6_NS;
|
|
break;
|
|
case ICMPV6_NA:
|
|
proto_subtype = QDF_PROTO_ICMPV6_NA;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return proto_subtype;
|
|
}
|
|
|
|
bool
|
|
__qdf_nbuf_is_ipv4_last_fragment(struct sk_buff *skb)
|
|
{
|
|
if (((ntohs(ip_hdr(skb)->frag_off) & ~IP_OFFSET) & IP_MF) == 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
__qdf_nbuf_is_ipv4_fragment(struct sk_buff *skb)
|
|
{
|
|
if (ntohs(ip_hdr(skb)->frag_off) & IP_MF)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
__qdf_nbuf_data_set_ipv4_tos(uint8_t *data, uint8_t tos)
|
|
{
|
|
*(uint8_t *)(data + QDF_NBUF_TRAC_IPV4_TOS_OFFSET) = tos;
|
|
}
|
|
|
|
uint8_t
|
|
__qdf_nbuf_data_get_ipv4_tos(uint8_t *data)
|
|
{
|
|
uint8_t tos;
|
|
|
|
tos = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_TRAC_IPV4_TOS_OFFSET));
|
|
return tos;
|
|
}
|
|
|
|
uint8_t
|
|
__qdf_nbuf_data_get_ipv4_proto(uint8_t *data)
|
|
{
|
|
uint8_t proto_type;
|
|
|
|
proto_type = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_TRAC_IPV4_PROTO_TYPE_OFFSET));
|
|
return proto_type;
|
|
}
|
|
|
|
uint8_t
|
|
__qdf_nbuf_data_get_ipv6_tc(uint8_t *data)
|
|
{
|
|
struct ipv6hdr *hdr;
|
|
|
|
hdr = (struct ipv6hdr *)(data + QDF_NBUF_TRAC_IPV6_OFFSET);
|
|
return ip6_tclass(ip6_flowinfo(hdr));
|
|
}
|
|
|
|
void
|
|
__qdf_nbuf_data_set_ipv6_tc(uint8_t *data, uint8_t tc)
|
|
{
|
|
struct ipv6hdr *hdr;
|
|
|
|
hdr = (struct ipv6hdr *)(data + QDF_NBUF_TRAC_IPV6_OFFSET);
|
|
ip6_flow_hdr(hdr, tc, ip6_flowlabel(hdr));
|
|
}
|
|
|
|
uint8_t
|
|
__qdf_nbuf_data_get_ipv6_proto(uint8_t *data)
|
|
{
|
|
uint8_t proto_type;
|
|
|
|
proto_type = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_TRAC_IPV6_PROTO_TYPE_OFFSET));
|
|
return proto_type;
|
|
}
|
|
|
|
bool __qdf_nbuf_data_is_ipv4_pkt(uint8_t *data)
|
|
{
|
|
uint16_t ether_type;
|
|
|
|
ether_type = (uint16_t)(*(uint16_t *)(data +
|
|
QDF_NBUF_TRAC_ETH_TYPE_OFFSET));
|
|
|
|
if (ether_type == QDF_SWAP_U16(QDF_NBUF_TRAC_IPV4_ETH_TYPE))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_data_is_ipv4_pkt);
|
|
|
|
bool __qdf_nbuf_data_is_ipv4_dhcp_pkt(uint8_t *data)
|
|
{
|
|
uint16_t sport;
|
|
uint16_t dport;
|
|
uint8_t ipv4_offset;
|
|
uint8_t ipv4_hdr_len;
|
|
struct iphdr *iphdr;
|
|
|
|
if (__qdf_nbuf_get_ether_type(data) !=
|
|
QDF_SWAP_U16(QDF_NBUF_TRAC_IPV4_ETH_TYPE))
|
|
return false;
|
|
|
|
ipv4_offset = __qdf_nbuf_get_ip_offset(data);
|
|
iphdr = (struct iphdr *)(data + ipv4_offset);
|
|
ipv4_hdr_len = iphdr->ihl * QDF_NBUF_IPV4_HDR_SIZE_UNIT;
|
|
|
|
sport = *(uint16_t *)(data + ipv4_offset + ipv4_hdr_len);
|
|
dport = *(uint16_t *)(data + ipv4_offset + ipv4_hdr_len +
|
|
sizeof(uint16_t));
|
|
|
|
if (((sport == QDF_SWAP_U16(QDF_NBUF_TRAC_DHCP_SRV_PORT)) &&
|
|
(dport == QDF_SWAP_U16(QDF_NBUF_TRAC_DHCP_CLI_PORT))) ||
|
|
((sport == QDF_SWAP_U16(QDF_NBUF_TRAC_DHCP_CLI_PORT)) &&
|
|
(dport == QDF_SWAP_U16(QDF_NBUF_TRAC_DHCP_SRV_PORT))))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_data_is_ipv4_dhcp_pkt);
|
|
|
|
/**
|
|
* qdf_is_eapol_type() - check if packet is EAPOL
|
|
* @type: Packet type
|
|
*
|
|
* This api is to check if frame is EAPOL packet type.
|
|
*
|
|
* Return: true if it is EAPOL frame
|
|
* false otherwise.
|
|
*/
|
|
#ifdef BIG_ENDIAN_HOST
|
|
static inline bool qdf_is_eapol_type(uint16_t type)
|
|
{
|
|
return (type == QDF_NBUF_TRAC_EAPOL_ETH_TYPE);
|
|
}
|
|
#else
|
|
static inline bool qdf_is_eapol_type(uint16_t type)
|
|
{
|
|
return (type == QDF_SWAP_U16(QDF_NBUF_TRAC_EAPOL_ETH_TYPE));
|
|
}
|
|
#endif
|
|
|
|
bool __qdf_nbuf_data_is_ipv4_eapol_pkt(uint8_t *data)
|
|
{
|
|
uint16_t ether_type;
|
|
|
|
ether_type = __qdf_nbuf_get_ether_type(data);
|
|
|
|
return qdf_is_eapol_type(ether_type);
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_data_is_ipv4_eapol_pkt);
|
|
|
|
bool __qdf_nbuf_is_ipv4_wapi_pkt(struct sk_buff *skb)
|
|
{
|
|
uint16_t ether_type;
|
|
|
|
ether_type = (uint16_t)(*(uint16_t *)(skb->data +
|
|
QDF_NBUF_TRAC_ETH_TYPE_OFFSET));
|
|
|
|
if (ether_type == QDF_SWAP_U16(QDF_NBUF_TRAC_WAPI_ETH_TYPE))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_is_ipv4_wapi_pkt);
|
|
|
|
/**
|
|
* qdf_nbuf_is_ipv6_vlan_pkt() - check whether packet is vlan IPV6
|
|
* @data: Pointer to network data buffer
|
|
*
|
|
* This api is for vlan header included ipv6 packet.
|
|
*
|
|
* Return: true if packet is vlan header included IPV6
|
|
* false otherwise.
|
|
*/
|
|
static bool qdf_nbuf_is_ipv6_vlan_pkt(uint8_t *data)
|
|
{
|
|
uint16_t ether_type;
|
|
|
|
ether_type = *(uint16_t *)(data + QDF_NBUF_TRAC_ETH_TYPE_OFFSET);
|
|
|
|
if (unlikely(ether_type == QDF_SWAP_U16(QDF_ETH_TYPE_8021Q))) {
|
|
ether_type = *(uint16_t *)(data +
|
|
QDF_NBUF_TRAC_VLAN_ETH_TYPE_OFFSET);
|
|
|
|
if (ether_type == QDF_SWAP_U16(QDF_NBUF_TRAC_IPV6_ETH_TYPE))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* qdf_nbuf_is_ipv4_vlan_pkt() - check whether packet is vlan IPV4
|
|
* @data: Pointer to network data buffer
|
|
*
|
|
* This api is for vlan header included ipv4 packet.
|
|
*
|
|
* Return: true if packet is vlan header included IPV4
|
|
* false otherwise.
|
|
*/
|
|
static bool qdf_nbuf_is_ipv4_vlan_pkt(uint8_t *data)
|
|
{
|
|
uint16_t ether_type;
|
|
|
|
ether_type = *(uint16_t *)(data + QDF_NBUF_TRAC_ETH_TYPE_OFFSET);
|
|
|
|
if (unlikely(ether_type == QDF_SWAP_U16(QDF_ETH_TYPE_8021Q))) {
|
|
ether_type = *(uint16_t *)(data +
|
|
QDF_NBUF_TRAC_VLAN_ETH_TYPE_OFFSET);
|
|
|
|
if (ether_type == QDF_SWAP_U16(QDF_NBUF_TRAC_IPV4_ETH_TYPE))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool __qdf_nbuf_data_is_ipv4_igmp_pkt(uint8_t *data)
|
|
{
|
|
uint8_t pkt_type;
|
|
|
|
if (__qdf_nbuf_data_is_ipv4_pkt(data)) {
|
|
pkt_type = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_TRAC_IPV4_PROTO_TYPE_OFFSET));
|
|
goto is_igmp;
|
|
}
|
|
|
|
if (qdf_nbuf_is_ipv4_vlan_pkt(data)) {
|
|
pkt_type = (uint8_t)(*(uint8_t *)(
|
|
data +
|
|
QDF_NBUF_TRAC_VLAN_IPV4_PROTO_TYPE_OFFSET));
|
|
goto is_igmp;
|
|
}
|
|
|
|
return false;
|
|
is_igmp:
|
|
if (pkt_type == QDF_NBUF_TRAC_IGMP_TYPE)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
qdf_export_symbol(__qdf_nbuf_data_is_ipv4_igmp_pkt);
|
|
|
|
bool __qdf_nbuf_data_is_ipv6_igmp_pkt(uint8_t *data)
|
|
{
|
|
uint8_t pkt_type;
|
|
uint8_t next_hdr;
|
|
|
|
if (__qdf_nbuf_data_is_ipv6_pkt(data)) {
|
|
pkt_type = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_TRAC_IPV6_PROTO_TYPE_OFFSET));
|
|
next_hdr = (uint8_t)(*(uint8_t *)(
|
|
data +
|
|
QDF_NBUF_TRAC_IPV6_OFFSET +
|
|
QDF_NBUF_TRAC_IPV6_HEADER_SIZE));
|
|
goto is_mld;
|
|
}
|
|
|
|
if (qdf_nbuf_is_ipv6_vlan_pkt(data)) {
|
|
pkt_type = (uint8_t)(*(uint8_t *)(
|
|
data +
|
|
QDF_NBUF_TRAC_VLAN_IPV6_PROTO_TYPE_OFFSET));
|
|
next_hdr = (uint8_t)(*(uint8_t *)(
|
|
data +
|
|
QDF_NBUF_TRAC_VLAN_IPV6_OFFSET +
|
|
QDF_NBUF_TRAC_IPV6_HEADER_SIZE));
|
|
goto is_mld;
|
|
}
|
|
|
|
return false;
|
|
is_mld:
|
|
if (pkt_type == QDF_NBUF_TRAC_ICMPV6_TYPE)
|
|
return true;
|
|
if ((pkt_type == QDF_NBUF_TRAC_HOPOPTS_TYPE) &&
|
|
(next_hdr == QDF_NBUF_TRAC_ICMPV6_TYPE))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
qdf_export_symbol(__qdf_nbuf_data_is_ipv6_igmp_pkt);
|
|
|
|
bool __qdf_nbuf_is_ipv4_igmp_leave_pkt(__qdf_nbuf_t buf)
|
|
{
|
|
qdf_ether_header_t *eh = NULL;
|
|
uint16_t ether_type;
|
|
uint8_t eth_hdr_size = sizeof(qdf_ether_header_t);
|
|
|
|
eh = (qdf_ether_header_t *)qdf_nbuf_data(buf);
|
|
ether_type = eh->ether_type;
|
|
|
|
if (ether_type == htons(ETH_P_8021Q)) {
|
|
struct vlan_ethhdr *veth =
|
|
(struct vlan_ethhdr *)qdf_nbuf_data(buf);
|
|
ether_type = veth->h_vlan_encapsulated_proto;
|
|
eth_hdr_size = sizeof(struct vlan_ethhdr);
|
|
}
|
|
|
|
if (ether_type == QDF_SWAP_U16(QDF_NBUF_TRAC_IPV4_ETH_TYPE)) {
|
|
struct iphdr *iph = NULL;
|
|
struct igmphdr *ih = NULL;
|
|
|
|
iph = (struct iphdr *)(qdf_nbuf_data(buf) + eth_hdr_size);
|
|
ih = (struct igmphdr *)((uint8_t *)iph + iph->ihl * 4);
|
|
switch (ih->type) {
|
|
case IGMP_HOST_LEAVE_MESSAGE:
|
|
return true;
|
|
case IGMPV3_HOST_MEMBERSHIP_REPORT:
|
|
{
|
|
struct igmpv3_report *ihv3 = (struct igmpv3_report *)ih;
|
|
struct igmpv3_grec *grec = NULL;
|
|
int num = 0;
|
|
int i = 0;
|
|
int len = 0;
|
|
int type = 0;
|
|
|
|
num = ntohs(ihv3->ngrec);
|
|
for (i = 0; i < num; i++) {
|
|
grec = (void *)((uint8_t *)(ihv3->grec) + len);
|
|
type = grec->grec_type;
|
|
if ((type == IGMPV3_MODE_IS_INCLUDE) ||
|
|
(type == IGMPV3_CHANGE_TO_INCLUDE))
|
|
return true;
|
|
|
|
len += sizeof(struct igmpv3_grec);
|
|
len += ntohs(grec->grec_nsrcs) * 4;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
qdf_export_symbol(__qdf_nbuf_is_ipv4_igmp_leave_pkt);
|
|
|
|
bool __qdf_nbuf_is_ipv6_igmp_leave_pkt(__qdf_nbuf_t buf)
|
|
{
|
|
qdf_ether_header_t *eh = NULL;
|
|
uint16_t ether_type;
|
|
uint8_t eth_hdr_size = sizeof(qdf_ether_header_t);
|
|
|
|
eh = (qdf_ether_header_t *)qdf_nbuf_data(buf);
|
|
ether_type = eh->ether_type;
|
|
|
|
if (ether_type == htons(ETH_P_8021Q)) {
|
|
struct vlan_ethhdr *veth =
|
|
(struct vlan_ethhdr *)qdf_nbuf_data(buf);
|
|
ether_type = veth->h_vlan_encapsulated_proto;
|
|
eth_hdr_size = sizeof(struct vlan_ethhdr);
|
|
}
|
|
|
|
if (ether_type == QDF_SWAP_U16(QDF_NBUF_TRAC_IPV6_ETH_TYPE)) {
|
|
struct ipv6hdr *ip6h = NULL;
|
|
struct icmp6hdr *icmp6h = NULL;
|
|
uint8_t nexthdr;
|
|
uint16_t frag_off = 0;
|
|
int offset;
|
|
qdf_nbuf_t buf_copy = NULL;
|
|
|
|
ip6h = (struct ipv6hdr *)(qdf_nbuf_data(buf) + eth_hdr_size);
|
|
if (ip6h->nexthdr != IPPROTO_HOPOPTS ||
|
|
ip6h->payload_len == 0)
|
|
return false;
|
|
|
|
buf_copy = qdf_nbuf_copy(buf);
|
|
if (qdf_likely(!buf_copy))
|
|
return false;
|
|
|
|
nexthdr = ip6h->nexthdr;
|
|
offset = ipv6_skip_exthdr(buf_copy,
|
|
eth_hdr_size + sizeof(*ip6h),
|
|
&nexthdr,
|
|
&frag_off);
|
|
qdf_nbuf_free(buf_copy);
|
|
if (offset < 0 || nexthdr != IPPROTO_ICMPV6)
|
|
return false;
|
|
|
|
icmp6h = (struct icmp6hdr *)(qdf_nbuf_data(buf) + offset);
|
|
|
|
switch (icmp6h->icmp6_type) {
|
|
case ICMPV6_MGM_REDUCTION:
|
|
return true;
|
|
case ICMPV6_MLD2_REPORT:
|
|
{
|
|
struct mld2_report *mh = NULL;
|
|
struct mld2_grec *grec = NULL;
|
|
int num = 0;
|
|
int i = 0;
|
|
int len = 0;
|
|
int type = -1;
|
|
|
|
mh = (struct mld2_report *)icmp6h;
|
|
num = ntohs(mh->mld2r_ngrec);
|
|
for (i = 0; i < num; i++) {
|
|
grec = (void *)(((uint8_t *)mh->mld2r_grec) +
|
|
len);
|
|
type = grec->grec_type;
|
|
if ((type == MLD2_MODE_IS_INCLUDE) ||
|
|
(type == MLD2_CHANGE_TO_INCLUDE))
|
|
return true;
|
|
else if (type == MLD2_BLOCK_OLD_SOURCES)
|
|
return true;
|
|
|
|
len += sizeof(struct mld2_grec);
|
|
len += ntohs(grec->grec_nsrcs) *
|
|
sizeof(struct in6_addr);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
qdf_export_symbol(__qdf_nbuf_is_ipv6_igmp_leave_pkt);
|
|
|
|
bool __qdf_nbuf_is_ipv4_tdls_pkt(struct sk_buff *skb)
|
|
{
|
|
uint16_t ether_type;
|
|
|
|
ether_type = *(uint16_t *)(skb->data +
|
|
QDF_NBUF_TRAC_ETH_TYPE_OFFSET);
|
|
|
|
if (ether_type == QDF_SWAP_U16(QDF_NBUF_TRAC_TDLS_ETH_TYPE))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_is_ipv4_tdls_pkt);
|
|
|
|
bool __qdf_nbuf_data_is_ipv4_arp_pkt(uint8_t *data)
|
|
{
|
|
uint16_t ether_type;
|
|
|
|
ether_type = __qdf_nbuf_get_ether_type(data);
|
|
|
|
if (ether_type == QDF_SWAP_U16(QDF_NBUF_TRAC_ARP_ETH_TYPE))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_data_is_ipv4_arp_pkt);
|
|
|
|
bool __qdf_nbuf_data_is_arp_req(uint8_t *data)
|
|
{
|
|
uint16_t op_code;
|
|
|
|
op_code = (uint16_t)(*(uint16_t *)(data +
|
|
QDF_NBUF_PKT_ARP_OPCODE_OFFSET));
|
|
|
|
if (op_code == QDF_SWAP_U16(QDF_NBUF_PKT_ARPOP_REQ))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool __qdf_nbuf_data_is_arp_rsp(uint8_t *data)
|
|
{
|
|
uint16_t op_code;
|
|
|
|
op_code = (uint16_t)(*(uint16_t *)(data +
|
|
QDF_NBUF_PKT_ARP_OPCODE_OFFSET));
|
|
|
|
if (op_code == QDF_SWAP_U16(QDF_NBUF_PKT_ARPOP_REPLY))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
uint32_t __qdf_nbuf_get_arp_src_ip(uint8_t *data)
|
|
{
|
|
uint32_t src_ip;
|
|
|
|
src_ip = (uint32_t)(*(uint32_t *)(data +
|
|
QDF_NBUF_PKT_ARP_SRC_IP_OFFSET));
|
|
|
|
return src_ip;
|
|
}
|
|
|
|
uint32_t __qdf_nbuf_get_arp_tgt_ip(uint8_t *data)
|
|
{
|
|
uint32_t tgt_ip;
|
|
|
|
tgt_ip = (uint32_t)(*(uint32_t *)(data +
|
|
QDF_NBUF_PKT_ARP_TGT_IP_OFFSET));
|
|
|
|
return tgt_ip;
|
|
}
|
|
|
|
uint8_t *__qdf_nbuf_get_dns_domain_name(uint8_t *data, uint32_t len)
|
|
{
|
|
uint8_t *domain_name;
|
|
|
|
domain_name = (uint8_t *)
|
|
(data + QDF_NBUF_PKT_DNS_NAME_OVER_UDP_OFFSET);
|
|
return domain_name;
|
|
}
|
|
|
|
bool __qdf_nbuf_data_is_dns_query(uint8_t *data)
|
|
{
|
|
uint16_t op_code;
|
|
uint16_t tgt_port;
|
|
|
|
tgt_port = (uint16_t)(*(uint16_t *)(data +
|
|
QDF_NBUF_PKT_DNS_DST_PORT_OFFSET));
|
|
/* Standard DNS query always happen on Dest Port 53. */
|
|
if (tgt_port == QDF_SWAP_U16(QDF_NBUF_PKT_DNS_STANDARD_PORT)) {
|
|
op_code = (uint16_t)(*(uint16_t *)(data +
|
|
QDF_NBUF_PKT_DNS_OVER_UDP_OPCODE_OFFSET));
|
|
if ((QDF_SWAP_U16(op_code) & QDF_NBUF_PKT_DNSOP_BITMAP) ==
|
|
QDF_NBUF_PKT_DNSOP_STANDARD_QUERY)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool __qdf_nbuf_data_is_dns_response(uint8_t *data)
|
|
{
|
|
uint16_t op_code;
|
|
uint16_t src_port;
|
|
|
|
src_port = (uint16_t)(*(uint16_t *)(data +
|
|
QDF_NBUF_PKT_DNS_SRC_PORT_OFFSET));
|
|
/* Standard DNS response always comes on Src Port 53. */
|
|
if (src_port == QDF_SWAP_U16(QDF_NBUF_PKT_DNS_STANDARD_PORT)) {
|
|
op_code = (uint16_t)(*(uint16_t *)(data +
|
|
QDF_NBUF_PKT_DNS_OVER_UDP_OPCODE_OFFSET));
|
|
|
|
if ((QDF_SWAP_U16(op_code) & QDF_NBUF_PKT_DNSOP_BITMAP) ==
|
|
QDF_NBUF_PKT_DNSOP_STANDARD_RESPONSE)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool __qdf_nbuf_data_is_tcp_fin(uint8_t *data)
|
|
{
|
|
uint8_t op_code;
|
|
|
|
op_code = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_PKT_TCP_OPCODE_OFFSET));
|
|
|
|
if (op_code == QDF_NBUF_PKT_TCPOP_FIN)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool __qdf_nbuf_data_is_tcp_fin_ack(uint8_t *data)
|
|
{
|
|
uint8_t op_code;
|
|
|
|
op_code = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_PKT_TCP_OPCODE_OFFSET));
|
|
|
|
if (op_code == QDF_NBUF_PKT_TCPOP_FIN_ACK)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool __qdf_nbuf_data_is_tcp_syn(uint8_t *data)
|
|
{
|
|
uint8_t op_code;
|
|
|
|
op_code = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_PKT_TCP_OPCODE_OFFSET));
|
|
|
|
if (op_code == QDF_NBUF_PKT_TCPOP_SYN)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool __qdf_nbuf_data_is_tcp_syn_ack(uint8_t *data)
|
|
{
|
|
uint8_t op_code;
|
|
|
|
op_code = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_PKT_TCP_OPCODE_OFFSET));
|
|
|
|
if (op_code == QDF_NBUF_PKT_TCPOP_SYN_ACK)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool __qdf_nbuf_data_is_tcp_rst(uint8_t *data)
|
|
{
|
|
uint8_t op_code;
|
|
|
|
op_code = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_PKT_TCP_OPCODE_OFFSET));
|
|
|
|
if (op_code == QDF_NBUF_PKT_TCPOP_RST)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool __qdf_nbuf_data_is_tcp_ack(uint8_t *data)
|
|
{
|
|
uint8_t op_code;
|
|
|
|
op_code = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_PKT_TCP_OPCODE_OFFSET));
|
|
|
|
if (op_code == QDF_NBUF_PKT_TCPOP_ACK)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
uint16_t __qdf_nbuf_data_get_tcp_src_port(uint8_t *data)
|
|
{
|
|
uint16_t src_port;
|
|
|
|
src_port = (uint16_t)(*(uint16_t *)(data +
|
|
QDF_NBUF_PKT_TCP_SRC_PORT_OFFSET));
|
|
|
|
return src_port;
|
|
}
|
|
|
|
uint16_t __qdf_nbuf_data_get_tcp_dst_port(uint8_t *data)
|
|
{
|
|
uint16_t tgt_port;
|
|
|
|
tgt_port = (uint16_t)(*(uint16_t *)(data +
|
|
QDF_NBUF_PKT_TCP_DST_PORT_OFFSET));
|
|
|
|
return tgt_port;
|
|
}
|
|
|
|
bool __qdf_nbuf_data_is_icmpv4_req(uint8_t *data)
|
|
{
|
|
uint8_t op_code;
|
|
|
|
op_code = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_PKT_ICMPv4_OPCODE_OFFSET));
|
|
|
|
if (op_code == QDF_NBUF_PKT_ICMPv4OP_REQ)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool __qdf_nbuf_data_is_icmpv4_rsp(uint8_t *data)
|
|
{
|
|
uint8_t op_code;
|
|
|
|
op_code = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_PKT_ICMPv4_OPCODE_OFFSET));
|
|
|
|
if (op_code == QDF_NBUF_PKT_ICMPv4OP_REPLY)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool __qdf_nbuf_data_is_icmpv4_redirect(uint8_t *data)
|
|
{
|
|
uint8_t op_code;
|
|
|
|
op_code = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_PKT_ICMPv4_OPCODE_OFFSET));
|
|
|
|
if (op_code == QDF_NBUF_PKT_ICMPV4_REDIRECT)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
qdf_export_symbol(__qdf_nbuf_data_is_icmpv4_redirect);
|
|
|
|
bool __qdf_nbuf_data_is_icmpv6_redirect(uint8_t *data)
|
|
{
|
|
uint8_t subtype;
|
|
|
|
subtype = (uint8_t)(*(uint8_t *)(data + ICMPV6_SUBTYPE_OFFSET));
|
|
|
|
if (subtype == ICMPV6_REDIRECT)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
qdf_export_symbol(__qdf_nbuf_data_is_icmpv6_redirect);
|
|
|
|
uint32_t __qdf_nbuf_get_icmpv4_src_ip(uint8_t *data)
|
|
{
|
|
uint32_t src_ip;
|
|
|
|
src_ip = (uint32_t)(*(uint32_t *)(data +
|
|
QDF_NBUF_PKT_ICMPv4_SRC_IP_OFFSET));
|
|
|
|
return src_ip;
|
|
}
|
|
|
|
uint32_t __qdf_nbuf_get_icmpv4_tgt_ip(uint8_t *data)
|
|
{
|
|
uint32_t tgt_ip;
|
|
|
|
tgt_ip = (uint32_t)(*(uint32_t *)(data +
|
|
QDF_NBUF_PKT_ICMPv4_TGT_IP_OFFSET));
|
|
|
|
return tgt_ip;
|
|
}
|
|
|
|
bool __qdf_nbuf_data_is_ipv6_pkt(uint8_t *data)
|
|
{
|
|
uint16_t ether_type;
|
|
|
|
ether_type = (uint16_t)(*(uint16_t *)(data +
|
|
QDF_NBUF_TRAC_ETH_TYPE_OFFSET));
|
|
|
|
if (ether_type == QDF_SWAP_U16(QDF_NBUF_TRAC_IPV6_ETH_TYPE))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_data_is_ipv6_pkt);
|
|
|
|
bool __qdf_nbuf_data_is_ipv6_dhcp_pkt(uint8_t *data)
|
|
{
|
|
uint16_t sport;
|
|
uint16_t dport;
|
|
uint8_t ipv6_offset;
|
|
|
|
if (!__qdf_nbuf_data_is_ipv6_pkt(data))
|
|
return false;
|
|
|
|
ipv6_offset = __qdf_nbuf_get_ip_offset(data);
|
|
sport = *(uint16_t *)(data + ipv6_offset +
|
|
QDF_NBUF_TRAC_IPV6_HEADER_SIZE);
|
|
dport = *(uint16_t *)(data + ipv6_offset +
|
|
QDF_NBUF_TRAC_IPV6_HEADER_SIZE +
|
|
sizeof(uint16_t));
|
|
|
|
if (((sport == QDF_SWAP_U16(QDF_NBUF_TRAC_DHCP6_SRV_PORT)) &&
|
|
(dport == QDF_SWAP_U16(QDF_NBUF_TRAC_DHCP6_CLI_PORT))) ||
|
|
((sport == QDF_SWAP_U16(QDF_NBUF_TRAC_DHCP6_CLI_PORT)) &&
|
|
(dport == QDF_SWAP_U16(QDF_NBUF_TRAC_DHCP6_SRV_PORT))))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_data_is_ipv6_dhcp_pkt);
|
|
|
|
bool __qdf_nbuf_data_is_ipv6_mdns_pkt(uint8_t *data)
|
|
{
|
|
uint16_t sport;
|
|
uint16_t dport;
|
|
|
|
sport = *(uint16_t *)(data + QDF_NBUF_TRAC_IPV6_OFFSET +
|
|
QDF_NBUF_TRAC_IPV6_HEADER_SIZE);
|
|
dport = *(uint16_t *)(data + QDF_NBUF_TRAC_IPV6_OFFSET +
|
|
QDF_NBUF_TRAC_IPV6_HEADER_SIZE +
|
|
sizeof(uint16_t));
|
|
|
|
if (sport == QDF_SWAP_U16(QDF_NBUF_TRAC_MDNS_SRC_N_DST_PORT) &&
|
|
dport == sport)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
qdf_export_symbol(__qdf_nbuf_data_is_ipv6_mdns_pkt);
|
|
|
|
bool __qdf_nbuf_data_is_ipv4_mcast_pkt(uint8_t *data)
|
|
{
|
|
if (__qdf_nbuf_data_is_ipv4_pkt(data)) {
|
|
uint32_t *dst_addr =
|
|
(uint32_t *)(data + QDF_NBUF_TRAC_IPV4_DEST_ADDR_OFFSET);
|
|
|
|
/*
|
|
* Check first word of the IPV4 address and if it is
|
|
* equal to 0xE then it represents multicast IP.
|
|
*/
|
|
if ((*dst_addr &
|
|
QDF_SWAP_U32(QDF_NBUF_TRAC_IPV4_ADDR_BCAST_MASK)) ==
|
|
QDF_SWAP_U32(QDF_NBUF_TRAC_IPV4_ADDR_MCAST_MASK))
|
|
return true;
|
|
else
|
|
return false;
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
bool __qdf_nbuf_data_is_ipv6_mcast_pkt(uint8_t *data)
|
|
{
|
|
if (__qdf_nbuf_data_is_ipv6_pkt(data)) {
|
|
uint16_t *dst_addr;
|
|
|
|
dst_addr = (uint16_t *)
|
|
(data + QDF_NBUF_TRAC_IPV6_DEST_ADDR_OFFSET);
|
|
|
|
/*
|
|
* Check first byte of the IP address and if it
|
|
* 0xFF then it is a IPV6 mcast packet.
|
|
*/
|
|
if ((*dst_addr & QDF_SWAP_U16(QDF_NBUF_TRAC_IPV6_DEST_ADDR)) ==
|
|
QDF_SWAP_U16(QDF_NBUF_TRAC_IPV6_DEST_ADDR))
|
|
return true;
|
|
else
|
|
return false;
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
bool __qdf_nbuf_data_is_icmp_pkt(uint8_t *data)
|
|
{
|
|
if (__qdf_nbuf_data_is_ipv4_pkt(data)) {
|
|
uint8_t pkt_type;
|
|
|
|
pkt_type = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_TRAC_IPV4_PROTO_TYPE_OFFSET));
|
|
|
|
if (pkt_type == QDF_NBUF_TRAC_ICMP_TYPE)
|
|
return true;
|
|
else
|
|
return false;
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
qdf_export_symbol(__qdf_nbuf_data_is_icmp_pkt);
|
|
|
|
bool __qdf_nbuf_data_is_icmpv6_pkt(uint8_t *data)
|
|
{
|
|
if (__qdf_nbuf_data_is_ipv6_pkt(data)) {
|
|
uint8_t pkt_type;
|
|
|
|
pkt_type = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_TRAC_IPV6_PROTO_TYPE_OFFSET));
|
|
|
|
if (pkt_type == QDF_NBUF_TRAC_ICMPV6_TYPE)
|
|
return true;
|
|
else
|
|
return false;
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
qdf_export_symbol(__qdf_nbuf_data_is_icmpv6_pkt);
|
|
|
|
bool __qdf_nbuf_data_is_ipv4_udp_pkt(uint8_t *data)
|
|
{
|
|
if (__qdf_nbuf_data_is_ipv4_pkt(data)) {
|
|
uint8_t pkt_type;
|
|
|
|
pkt_type = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_TRAC_IPV4_PROTO_TYPE_OFFSET));
|
|
|
|
if (pkt_type == QDF_NBUF_TRAC_UDP_TYPE)
|
|
return true;
|
|
else
|
|
return false;
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
bool __qdf_nbuf_data_is_ipv4_tcp_pkt(uint8_t *data)
|
|
{
|
|
if (__qdf_nbuf_data_is_ipv4_pkt(data)) {
|
|
uint8_t pkt_type;
|
|
|
|
pkt_type = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_TRAC_IPV4_PROTO_TYPE_OFFSET));
|
|
|
|
if (pkt_type == QDF_NBUF_TRAC_TCP_TYPE)
|
|
return true;
|
|
else
|
|
return false;
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
bool __qdf_nbuf_data_is_ipv6_udp_pkt(uint8_t *data)
|
|
{
|
|
if (__qdf_nbuf_data_is_ipv6_pkt(data)) {
|
|
uint8_t pkt_type;
|
|
|
|
pkt_type = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_TRAC_IPV6_PROTO_TYPE_OFFSET));
|
|
|
|
if (pkt_type == QDF_NBUF_TRAC_UDP_TYPE)
|
|
return true;
|
|
else
|
|
return false;
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
bool __qdf_nbuf_data_is_ipv6_tcp_pkt(uint8_t *data)
|
|
{
|
|
if (__qdf_nbuf_data_is_ipv6_pkt(data)) {
|
|
uint8_t pkt_type;
|
|
|
|
pkt_type = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_TRAC_IPV6_PROTO_TYPE_OFFSET));
|
|
|
|
if (pkt_type == QDF_NBUF_TRAC_TCP_TYPE)
|
|
return true;
|
|
else
|
|
return false;
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
bool __qdf_nbuf_is_bcast_pkt(qdf_nbuf_t nbuf)
|
|
{
|
|
struct ethhdr *eh = (struct ethhdr *)qdf_nbuf_data(nbuf);
|
|
return qdf_is_macaddr_broadcast((struct qdf_mac_addr *)eh->h_dest);
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_is_bcast_pkt);
|
|
|
|
bool __qdf_nbuf_is_mcast_replay(qdf_nbuf_t nbuf)
|
|
{
|
|
struct sk_buff *skb = (struct sk_buff *)nbuf;
|
|
struct ethhdr *eth = eth_hdr(skb);
|
|
|
|
if (qdf_likely(skb->pkt_type != PACKET_MULTICAST))
|
|
return false;
|
|
|
|
if (qdf_unlikely(ether_addr_equal(eth->h_source, skb->dev->dev_addr)))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool __qdf_nbuf_is_arp_local(struct sk_buff *skb)
|
|
{
|
|
struct arphdr *arp;
|
|
struct in_ifaddr **ifap = NULL;
|
|
struct in_ifaddr *ifa = NULL;
|
|
struct in_device *in_dev;
|
|
unsigned char *arp_ptr;
|
|
__be32 tip;
|
|
|
|
arp = (struct arphdr *)skb->data;
|
|
if (arp->ar_op == htons(ARPOP_REQUEST)) {
|
|
/* if fail to acquire rtnl lock, assume it's local arp */
|
|
if (!rtnl_trylock())
|
|
return true;
|
|
|
|
in_dev = __in_dev_get_rtnl(skb->dev);
|
|
if (in_dev) {
|
|
for (ifap = &in_dev->ifa_list; (ifa = *ifap) != NULL;
|
|
ifap = &ifa->ifa_next) {
|
|
if (!strcmp(skb->dev->name, ifa->ifa_label))
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ifa && ifa->ifa_local) {
|
|
arp_ptr = (unsigned char *)(arp + 1);
|
|
arp_ptr += (skb->dev->addr_len + 4 +
|
|
skb->dev->addr_len);
|
|
memcpy(&tip, arp_ptr, 4);
|
|
qdf_debug("ARP packet: local IP: %x dest IP: %x",
|
|
ifa->ifa_local, tip);
|
|
if (ifa->ifa_local == tip) {
|
|
rtnl_unlock();
|
|
return true;
|
|
}
|
|
}
|
|
rtnl_unlock();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* __qdf_nbuf_data_get_tcp_hdr_len() - get TCP header length
|
|
* @data: pointer to data of network buffer
|
|
* @tcp_hdr_len_offset: bytes offset for tcp header length of ethernet packets
|
|
*
|
|
* Return: TCP header length in unit of byte
|
|
*/
|
|
static inline
|
|
uint8_t __qdf_nbuf_data_get_tcp_hdr_len(uint8_t *data,
|
|
uint8_t tcp_hdr_len_offset)
|
|
{
|
|
uint8_t tcp_hdr_len;
|
|
|
|
tcp_hdr_len =
|
|
*((uint8_t *)(data + tcp_hdr_len_offset));
|
|
|
|
tcp_hdr_len = ((tcp_hdr_len & QDF_NBUF_PKT_TCP_HDR_LEN_MASK) >>
|
|
QDF_NBUF_PKT_TCP_HDR_LEN_LSB) *
|
|
QDF_NBUF_PKT_TCP_HDR_LEN_UNIT;
|
|
|
|
return tcp_hdr_len;
|
|
}
|
|
|
|
bool __qdf_nbuf_is_ipv4_v6_pure_tcp_ack(struct sk_buff *skb)
|
|
{
|
|
bool is_tcp_ack = false;
|
|
uint8_t op_code, tcp_hdr_len;
|
|
uint16_t ip_payload_len;
|
|
uint8_t *data = skb->data;
|
|
|
|
/*
|
|
* If packet length > TCP ACK max length or it's nonlinearized,
|
|
* then it must not be TCP ACK.
|
|
*/
|
|
if (qdf_nbuf_len(skb) > QDF_NBUF_PKT_TCP_ACK_MAX_LEN ||
|
|
qdf_nbuf_is_nonlinear(skb))
|
|
return false;
|
|
|
|
if (qdf_nbuf_is_ipv4_tcp_pkt(skb)) {
|
|
ip_payload_len =
|
|
QDF_SWAP_U16(*((uint16_t *)(data +
|
|
QDF_NBUF_TRAC_IPV4_TOTAL_LEN_OFFSET)))
|
|
- QDF_NBUF_TRAC_IPV4_HEADER_SIZE;
|
|
|
|
tcp_hdr_len = __qdf_nbuf_data_get_tcp_hdr_len(
|
|
data,
|
|
QDF_NBUF_PKT_IPV4_TCP_HDR_LEN_OFFSET);
|
|
|
|
op_code = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_PKT_IPV4_TCP_OPCODE_OFFSET));
|
|
|
|
if (ip_payload_len == tcp_hdr_len &&
|
|
op_code == QDF_NBUF_PKT_TCPOP_ACK)
|
|
is_tcp_ack = true;
|
|
|
|
} else if (qdf_nbuf_is_ipv6_tcp_pkt(skb)) {
|
|
ip_payload_len =
|
|
QDF_SWAP_U16(*((uint16_t *)(data +
|
|
QDF_NBUF_TRAC_IPV6_PAYLOAD_LEN_OFFSET)));
|
|
|
|
tcp_hdr_len = __qdf_nbuf_data_get_tcp_hdr_len(
|
|
data,
|
|
QDF_NBUF_PKT_IPV6_TCP_HDR_LEN_OFFSET);
|
|
op_code = (uint8_t)(*(uint8_t *)(data +
|
|
QDF_NBUF_PKT_IPV6_TCP_OPCODE_OFFSET));
|
|
|
|
if (ip_payload_len == tcp_hdr_len &&
|
|
op_code == QDF_NBUF_PKT_TCPOP_ACK)
|
|
is_tcp_ack = true;
|
|
}
|
|
|
|
return is_tcp_ack;
|
|
}
|
|
|
|
#ifdef QCA_DP_NBUF_FAST_RECYCLE_CHECK
|
|
bool qdf_nbuf_fast_xmit(qdf_nbuf_t nbuf)
|
|
{
|
|
return nbuf->fast_xmit;
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_fast_xmit);
|
|
|
|
void qdf_nbuf_set_fast_xmit(qdf_nbuf_t nbuf, int value)
|
|
{
|
|
nbuf->fast_xmit = value;
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_set_fast_xmit);
|
|
#else
|
|
bool qdf_nbuf_fast_xmit(qdf_nbuf_t nbuf)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_fast_xmit);
|
|
|
|
void qdf_nbuf_set_fast_xmit(qdf_nbuf_t nbuf, int value)
|
|
{
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_set_fast_xmit);
|
|
#endif
|
|
|
|
#ifdef NBUF_MEMORY_DEBUG
|
|
|
|
static spinlock_t g_qdf_net_buf_track_lock[QDF_NET_BUF_TRACK_MAX_SIZE];
|
|
|
|
static QDF_NBUF_TRACK *gp_qdf_net_buf_track_tbl[QDF_NET_BUF_TRACK_MAX_SIZE];
|
|
static struct kmem_cache *nbuf_tracking_cache;
|
|
static QDF_NBUF_TRACK *qdf_net_buf_track_free_list;
|
|
static spinlock_t qdf_net_buf_track_free_list_lock;
|
|
static uint32_t qdf_net_buf_track_free_list_count;
|
|
static uint32_t qdf_net_buf_track_used_list_count;
|
|
static uint32_t qdf_net_buf_track_max_used;
|
|
static uint32_t qdf_net_buf_track_max_free;
|
|
static uint32_t qdf_net_buf_track_max_allocated;
|
|
static uint32_t qdf_net_buf_track_fail_count;
|
|
|
|
/**
|
|
* update_max_used() - update qdf_net_buf_track_max_used tracking variable
|
|
*
|
|
* tracks the max number of network buffers that the wlan driver was tracking
|
|
* at any one time.
|
|
*
|
|
* Return: none
|
|
*/
|
|
static inline void update_max_used(void)
|
|
{
|
|
int sum;
|
|
|
|
if (qdf_net_buf_track_max_used <
|
|
qdf_net_buf_track_used_list_count)
|
|
qdf_net_buf_track_max_used = qdf_net_buf_track_used_list_count;
|
|
sum = qdf_net_buf_track_free_list_count +
|
|
qdf_net_buf_track_used_list_count;
|
|
if (qdf_net_buf_track_max_allocated < sum)
|
|
qdf_net_buf_track_max_allocated = sum;
|
|
}
|
|
|
|
/**
|
|
* update_max_free() - update qdf_net_buf_track_free_list_count
|
|
*
|
|
* tracks the max number tracking buffers kept in the freelist.
|
|
*
|
|
* Return: none
|
|
*/
|
|
static inline void update_max_free(void)
|
|
{
|
|
if (qdf_net_buf_track_max_free <
|
|
qdf_net_buf_track_free_list_count)
|
|
qdf_net_buf_track_max_free = qdf_net_buf_track_free_list_count;
|
|
}
|
|
|
|
/**
|
|
* qdf_nbuf_track_alloc() - allocate a cookie to track nbufs allocated by wlan
|
|
*
|
|
* This function pulls from a freelist if possible and uses kmem_cache_alloc.
|
|
* This function also ads fexibility to adjust the allocation and freelist
|
|
* scheems.
|
|
*
|
|
* Return: a pointer to an unused QDF_NBUF_TRACK structure may not be zeroed.
|
|
*/
|
|
static QDF_NBUF_TRACK *qdf_nbuf_track_alloc(void)
|
|
{
|
|
int flags = GFP_KERNEL;
|
|
unsigned long irq_flag;
|
|
QDF_NBUF_TRACK *new_node = NULL;
|
|
|
|
spin_lock_irqsave(&qdf_net_buf_track_free_list_lock, irq_flag);
|
|
qdf_net_buf_track_used_list_count++;
|
|
if (qdf_net_buf_track_free_list) {
|
|
new_node = qdf_net_buf_track_free_list;
|
|
qdf_net_buf_track_free_list =
|
|
qdf_net_buf_track_free_list->p_next;
|
|
qdf_net_buf_track_free_list_count--;
|
|
}
|
|
update_max_used();
|
|
spin_unlock_irqrestore(&qdf_net_buf_track_free_list_lock, irq_flag);
|
|
|
|
if (new_node)
|
|
return new_node;
|
|
|
|
if (in_interrupt() || irqs_disabled() || in_atomic())
|
|
flags = GFP_ATOMIC;
|
|
|
|
return kmem_cache_alloc(nbuf_tracking_cache, flags);
|
|
}
|
|
|
|
/* FREEQ_POOLSIZE initial and minimum desired freelist poolsize */
|
|
#define FREEQ_POOLSIZE 2048
|
|
|
|
/**
|
|
* qdf_nbuf_track_free() - free the nbuf tracking cookie.
|
|
* @node: nbuf tracking node
|
|
*
|
|
* Matches calls to qdf_nbuf_track_alloc.
|
|
* Either frees the tracking cookie to kernel or an internal
|
|
* freelist based on the size of the freelist.
|
|
*
|
|
* Return: none
|
|
*/
|
|
static void qdf_nbuf_track_free(QDF_NBUF_TRACK *node)
|
|
{
|
|
unsigned long irq_flag;
|
|
|
|
if (!node)
|
|
return;
|
|
|
|
/* Try to shrink the freelist if free_list_count > than FREEQ_POOLSIZE
|
|
* only shrink the freelist if it is bigger than twice the number of
|
|
* nbufs in use. If the driver is stalling in a consistent bursty
|
|
* fashion, this will keep 3/4 of thee allocations from the free list
|
|
* while also allowing the system to recover memory as less frantic
|
|
* traffic occurs.
|
|
*/
|
|
|
|
spin_lock_irqsave(&qdf_net_buf_track_free_list_lock, irq_flag);
|
|
|
|
qdf_net_buf_track_used_list_count--;
|
|
if (qdf_net_buf_track_free_list_count > FREEQ_POOLSIZE &&
|
|
(qdf_net_buf_track_free_list_count >
|
|
qdf_net_buf_track_used_list_count << 1)) {
|
|
kmem_cache_free(nbuf_tracking_cache, node);
|
|
} else {
|
|
node->p_next = qdf_net_buf_track_free_list;
|
|
qdf_net_buf_track_free_list = node;
|
|
qdf_net_buf_track_free_list_count++;
|
|
}
|
|
update_max_free();
|
|
spin_unlock_irqrestore(&qdf_net_buf_track_free_list_lock, irq_flag);
|
|
}
|
|
|
|
/**
|
|
* qdf_nbuf_track_prefill() - prefill the nbuf tracking cookie freelist
|
|
*
|
|
* Removes a 'warmup time' characteristic of the freelist. Prefilling
|
|
* the freelist first makes it performant for the first iperf udp burst
|
|
* as well as steady state.
|
|
*
|
|
* Return: None
|
|
*/
|
|
static void qdf_nbuf_track_prefill(void)
|
|
{
|
|
int i;
|
|
QDF_NBUF_TRACK *node, *head;
|
|
|
|
/* prepopulate the freelist */
|
|
head = NULL;
|
|
for (i = 0; i < FREEQ_POOLSIZE; i++) {
|
|
node = qdf_nbuf_track_alloc();
|
|
if (!node)
|
|
continue;
|
|
node->p_next = head;
|
|
head = node;
|
|
}
|
|
while (head) {
|
|
node = head->p_next;
|
|
qdf_nbuf_track_free(head);
|
|
head = node;
|
|
}
|
|
|
|
/* prefilled buffers should not count as used */
|
|
qdf_net_buf_track_max_used = 0;
|
|
}
|
|
|
|
/**
|
|
* qdf_nbuf_track_memory_manager_create() - manager for nbuf tracking cookies
|
|
*
|
|
* This initializes the memory manager for the nbuf tracking cookies. Because
|
|
* these cookies are all the same size and only used in this feature, we can
|
|
* use a kmem_cache to provide tracking as well as to speed up allocations.
|
|
* To avoid the overhead of allocating and freeing the buffers (including SLUB
|
|
* features) a freelist is prepopulated here.
|
|
*
|
|
* Return: None
|
|
*/
|
|
static void qdf_nbuf_track_memory_manager_create(void)
|
|
{
|
|
spin_lock_init(&qdf_net_buf_track_free_list_lock);
|
|
nbuf_tracking_cache = kmem_cache_create("qdf_nbuf_tracking_cache",
|
|
sizeof(QDF_NBUF_TRACK),
|
|
0, 0, NULL);
|
|
|
|
qdf_nbuf_track_prefill();
|
|
}
|
|
|
|
/**
|
|
* qdf_nbuf_track_memory_manager_destroy() - manager for nbuf tracking cookies
|
|
*
|
|
* Empty the freelist and print out usage statistics when it is no longer
|
|
* needed. Also the kmem_cache should be destroyed here so that it can warn if
|
|
* any nbuf tracking cookies were leaked.
|
|
*
|
|
* Return: None
|
|
*/
|
|
static void qdf_nbuf_track_memory_manager_destroy(void)
|
|
{
|
|
QDF_NBUF_TRACK *node, *tmp;
|
|
unsigned long irq_flag;
|
|
|
|
spin_lock_irqsave(&qdf_net_buf_track_free_list_lock, irq_flag);
|
|
node = qdf_net_buf_track_free_list;
|
|
|
|
if (qdf_net_buf_track_max_used > FREEQ_POOLSIZE * 4)
|
|
qdf_print("%s: unexpectedly large max_used count %d",
|
|
__func__, qdf_net_buf_track_max_used);
|
|
|
|
if (qdf_net_buf_track_max_used < qdf_net_buf_track_max_allocated)
|
|
qdf_print("%s: %d unused trackers were allocated",
|
|
__func__,
|
|
qdf_net_buf_track_max_allocated -
|
|
qdf_net_buf_track_max_used);
|
|
|
|
if (qdf_net_buf_track_free_list_count > FREEQ_POOLSIZE &&
|
|
qdf_net_buf_track_free_list_count > 3*qdf_net_buf_track_max_used/4)
|
|
qdf_print("%s: check freelist shrinking functionality",
|
|
__func__);
|
|
|
|
QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_INFO,
|
|
"%s: %d residual freelist size",
|
|
__func__, qdf_net_buf_track_free_list_count);
|
|
|
|
QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_INFO,
|
|
"%s: %d max freelist size observed",
|
|
__func__, qdf_net_buf_track_max_free);
|
|
|
|
QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_INFO,
|
|
"%s: %d max buffers used observed",
|
|
__func__, qdf_net_buf_track_max_used);
|
|
|
|
QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_INFO,
|
|
"%s: %d max buffers allocated observed",
|
|
__func__, qdf_net_buf_track_max_allocated);
|
|
|
|
while (node) {
|
|
tmp = node;
|
|
node = node->p_next;
|
|
kmem_cache_free(nbuf_tracking_cache, tmp);
|
|
qdf_net_buf_track_free_list_count--;
|
|
}
|
|
|
|
if (qdf_net_buf_track_free_list_count != 0)
|
|
qdf_info("%d unfreed tracking memory lost in freelist",
|
|
qdf_net_buf_track_free_list_count);
|
|
|
|
if (qdf_net_buf_track_used_list_count != 0)
|
|
qdf_info("%d unfreed tracking memory still in use",
|
|
qdf_net_buf_track_used_list_count);
|
|
|
|
spin_unlock_irqrestore(&qdf_net_buf_track_free_list_lock, irq_flag);
|
|
kmem_cache_destroy(nbuf_tracking_cache);
|
|
qdf_net_buf_track_free_list = NULL;
|
|
}
|
|
|
|
void qdf_net_buf_debug_init(void)
|
|
{
|
|
uint32_t i;
|
|
|
|
is_initial_mem_debug_disabled = qdf_mem_debug_config_get();
|
|
|
|
if (is_initial_mem_debug_disabled)
|
|
return;
|
|
|
|
qdf_atomic_set(&qdf_nbuf_history_index, -1);
|
|
|
|
qdf_nbuf_map_tracking_init();
|
|
qdf_nbuf_smmu_map_tracking_init();
|
|
qdf_nbuf_track_memory_manager_create();
|
|
|
|
for (i = 0; i < QDF_NET_BUF_TRACK_MAX_SIZE; i++) {
|
|
gp_qdf_net_buf_track_tbl[i] = NULL;
|
|
spin_lock_init(&g_qdf_net_buf_track_lock[i]);
|
|
}
|
|
}
|
|
qdf_export_symbol(qdf_net_buf_debug_init);
|
|
|
|
void qdf_net_buf_debug_exit(void)
|
|
{
|
|
uint32_t i;
|
|
uint32_t count = 0;
|
|
unsigned long irq_flag;
|
|
QDF_NBUF_TRACK *p_node;
|
|
QDF_NBUF_TRACK *p_prev;
|
|
|
|
if (is_initial_mem_debug_disabled)
|
|
return;
|
|
|
|
for (i = 0; i < QDF_NET_BUF_TRACK_MAX_SIZE; i++) {
|
|
spin_lock_irqsave(&g_qdf_net_buf_track_lock[i], irq_flag);
|
|
p_node = gp_qdf_net_buf_track_tbl[i];
|
|
while (p_node) {
|
|
p_prev = p_node;
|
|
p_node = p_node->p_next;
|
|
count++;
|
|
qdf_info("SKB buf memory Leak@ Func %s, @Line %d, size %zu, nbuf %pK",
|
|
p_prev->func_name, p_prev->line_num,
|
|
p_prev->size, p_prev->net_buf);
|
|
qdf_info("SKB leak map %s, line %d, unmap %s line %d mapped=%d",
|
|
p_prev->map_func_name,
|
|
p_prev->map_line_num,
|
|
p_prev->unmap_func_name,
|
|
p_prev->unmap_line_num,
|
|
p_prev->is_nbuf_mapped);
|
|
qdf_nbuf_track_free(p_prev);
|
|
}
|
|
spin_unlock_irqrestore(&g_qdf_net_buf_track_lock[i], irq_flag);
|
|
}
|
|
|
|
qdf_nbuf_track_memory_manager_destroy();
|
|
qdf_nbuf_map_tracking_deinit();
|
|
qdf_nbuf_smmu_map_tracking_deinit();
|
|
|
|
#ifdef CONFIG_HALT_KMEMLEAK
|
|
if (count) {
|
|
qdf_err("%d SKBs leaked .. please fix the SKB leak", count);
|
|
QDF_BUG(0);
|
|
}
|
|
#endif
|
|
}
|
|
qdf_export_symbol(qdf_net_buf_debug_exit);
|
|
|
|
/**
|
|
* qdf_net_buf_debug_hash() - hash network buffer pointer
|
|
* @net_buf: network buffer
|
|
*
|
|
* Return: hash value
|
|
*/
|
|
static uint32_t qdf_net_buf_debug_hash(qdf_nbuf_t net_buf)
|
|
{
|
|
uint32_t i;
|
|
|
|
i = (uint32_t) (((uintptr_t) net_buf) >> 4);
|
|
i += (uint32_t) (((uintptr_t) net_buf) >> 14);
|
|
i &= (QDF_NET_BUF_TRACK_MAX_SIZE - 1);
|
|
|
|
return i;
|
|
}
|
|
|
|
/**
|
|
* qdf_net_buf_debug_look_up() - look up network buffer in debug hash table
|
|
* @net_buf: network buffer
|
|
*
|
|
* Return: If skb is found in hash table then return pointer to network buffer
|
|
* else return %NULL
|
|
*/
|
|
static QDF_NBUF_TRACK *qdf_net_buf_debug_look_up(qdf_nbuf_t net_buf)
|
|
{
|
|
uint32_t i;
|
|
QDF_NBUF_TRACK *p_node;
|
|
|
|
i = qdf_net_buf_debug_hash(net_buf);
|
|
p_node = gp_qdf_net_buf_track_tbl[i];
|
|
|
|
while (p_node) {
|
|
if (p_node->net_buf == net_buf)
|
|
return p_node;
|
|
p_node = p_node->p_next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void qdf_net_buf_debug_add_node(qdf_nbuf_t net_buf, size_t size,
|
|
const char *func_name, uint32_t line_num)
|
|
{
|
|
uint32_t i;
|
|
unsigned long irq_flag;
|
|
QDF_NBUF_TRACK *p_node;
|
|
QDF_NBUF_TRACK *new_node;
|
|
|
|
if (is_initial_mem_debug_disabled)
|
|
return;
|
|
|
|
new_node = qdf_nbuf_track_alloc();
|
|
|
|
i = qdf_net_buf_debug_hash(net_buf);
|
|
spin_lock_irqsave(&g_qdf_net_buf_track_lock[i], irq_flag);
|
|
|
|
p_node = qdf_net_buf_debug_look_up(net_buf);
|
|
|
|
if (p_node) {
|
|
qdf_print("Double allocation of skb ! Already allocated from %pK %s %d current alloc from %pK %s %d",
|
|
p_node->net_buf, p_node->func_name, p_node->line_num,
|
|
net_buf, func_name, line_num);
|
|
qdf_nbuf_track_free(new_node);
|
|
} else {
|
|
p_node = new_node;
|
|
if (p_node) {
|
|
p_node->net_buf = net_buf;
|
|
qdf_str_lcopy(p_node->func_name, func_name,
|
|
QDF_MEM_FUNC_NAME_SIZE);
|
|
p_node->line_num = line_num;
|
|
p_node->is_nbuf_mapped = false;
|
|
p_node->map_line_num = 0;
|
|
p_node->unmap_line_num = 0;
|
|
p_node->map_func_name[0] = '\0';
|
|
p_node->unmap_func_name[0] = '\0';
|
|
p_node->size = size;
|
|
p_node->time = qdf_get_log_timestamp();
|
|
qdf_net_buf_update_smmu_params(p_node);
|
|
qdf_mem_skb_inc(size);
|
|
p_node->p_next = gp_qdf_net_buf_track_tbl[i];
|
|
gp_qdf_net_buf_track_tbl[i] = p_node;
|
|
} else {
|
|
qdf_net_buf_track_fail_count++;
|
|
qdf_print(
|
|
"Mem alloc failed ! Could not track skb from %s %d of size %zu",
|
|
func_name, line_num, size);
|
|
}
|
|
}
|
|
|
|
spin_unlock_irqrestore(&g_qdf_net_buf_track_lock[i], irq_flag);
|
|
}
|
|
qdf_export_symbol(qdf_net_buf_debug_add_node);
|
|
|
|
void qdf_net_buf_debug_update_node(qdf_nbuf_t net_buf, const char *func_name,
|
|
uint32_t line_num)
|
|
{
|
|
uint32_t i;
|
|
unsigned long irq_flag;
|
|
QDF_NBUF_TRACK *p_node;
|
|
|
|
if (is_initial_mem_debug_disabled)
|
|
return;
|
|
|
|
i = qdf_net_buf_debug_hash(net_buf);
|
|
spin_lock_irqsave(&g_qdf_net_buf_track_lock[i], irq_flag);
|
|
|
|
p_node = qdf_net_buf_debug_look_up(net_buf);
|
|
|
|
if (p_node) {
|
|
qdf_str_lcopy(p_node->func_name, kbasename(func_name),
|
|
QDF_MEM_FUNC_NAME_SIZE);
|
|
p_node->line_num = line_num;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&g_qdf_net_buf_track_lock[i], irq_flag);
|
|
}
|
|
|
|
qdf_export_symbol(qdf_net_buf_debug_update_node);
|
|
|
|
void qdf_net_buf_debug_update_map_node(qdf_nbuf_t net_buf,
|
|
const char *func_name,
|
|
uint32_t line_num)
|
|
{
|
|
uint32_t i;
|
|
unsigned long irq_flag;
|
|
QDF_NBUF_TRACK *p_node;
|
|
|
|
if (is_initial_mem_debug_disabled)
|
|
return;
|
|
|
|
i = qdf_net_buf_debug_hash(net_buf);
|
|
spin_lock_irqsave(&g_qdf_net_buf_track_lock[i], irq_flag);
|
|
|
|
p_node = qdf_net_buf_debug_look_up(net_buf);
|
|
|
|
if (p_node) {
|
|
qdf_str_lcopy(p_node->map_func_name, func_name,
|
|
QDF_MEM_FUNC_NAME_SIZE);
|
|
p_node->map_line_num = line_num;
|
|
p_node->is_nbuf_mapped = true;
|
|
}
|
|
spin_unlock_irqrestore(&g_qdf_net_buf_track_lock[i], irq_flag);
|
|
}
|
|
|
|
#ifdef NBUF_SMMU_MAP_UNMAP_DEBUG
|
|
void qdf_net_buf_debug_update_smmu_map_node(qdf_nbuf_t nbuf,
|
|
unsigned long iova,
|
|
unsigned long pa,
|
|
const char *func,
|
|
uint32_t line)
|
|
{
|
|
uint32_t i;
|
|
unsigned long irq_flag;
|
|
QDF_NBUF_TRACK *p_node;
|
|
|
|
if (is_initial_mem_debug_disabled)
|
|
return;
|
|
|
|
i = qdf_net_buf_debug_hash(nbuf);
|
|
spin_lock_irqsave(&g_qdf_net_buf_track_lock[i], irq_flag);
|
|
|
|
p_node = qdf_net_buf_debug_look_up(nbuf);
|
|
|
|
if (p_node) {
|
|
qdf_str_lcopy(p_node->smmu_map_func_name, func,
|
|
QDF_MEM_FUNC_NAME_SIZE);
|
|
p_node->smmu_map_line_num = line;
|
|
p_node->is_nbuf_smmu_mapped = true;
|
|
p_node->smmu_map_iova_addr = iova;
|
|
p_node->smmu_map_pa_addr = pa;
|
|
}
|
|
spin_unlock_irqrestore(&g_qdf_net_buf_track_lock[i], irq_flag);
|
|
}
|
|
|
|
void qdf_net_buf_debug_update_smmu_unmap_node(qdf_nbuf_t nbuf,
|
|
unsigned long iova,
|
|
unsigned long pa,
|
|
const char *func,
|
|
uint32_t line)
|
|
{
|
|
uint32_t i;
|
|
unsigned long irq_flag;
|
|
QDF_NBUF_TRACK *p_node;
|
|
|
|
if (is_initial_mem_debug_disabled)
|
|
return;
|
|
|
|
i = qdf_net_buf_debug_hash(nbuf);
|
|
spin_lock_irqsave(&g_qdf_net_buf_track_lock[i], irq_flag);
|
|
|
|
p_node = qdf_net_buf_debug_look_up(nbuf);
|
|
|
|
if (p_node) {
|
|
qdf_str_lcopy(p_node->smmu_unmap_func_name, func,
|
|
QDF_MEM_FUNC_NAME_SIZE);
|
|
p_node->smmu_unmap_line_num = line;
|
|
p_node->is_nbuf_smmu_mapped = false;
|
|
p_node->smmu_unmap_iova_addr = iova;
|
|
p_node->smmu_unmap_pa_addr = pa;
|
|
}
|
|
spin_unlock_irqrestore(&g_qdf_net_buf_track_lock[i], irq_flag);
|
|
}
|
|
#endif
|
|
|
|
void qdf_net_buf_debug_update_unmap_node(qdf_nbuf_t net_buf,
|
|
const char *func_name,
|
|
uint32_t line_num)
|
|
{
|
|
uint32_t i;
|
|
unsigned long irq_flag;
|
|
QDF_NBUF_TRACK *p_node;
|
|
|
|
if (is_initial_mem_debug_disabled)
|
|
return;
|
|
|
|
i = qdf_net_buf_debug_hash(net_buf);
|
|
spin_lock_irqsave(&g_qdf_net_buf_track_lock[i], irq_flag);
|
|
|
|
p_node = qdf_net_buf_debug_look_up(net_buf);
|
|
|
|
if (p_node) {
|
|
qdf_str_lcopy(p_node->unmap_func_name, func_name,
|
|
QDF_MEM_FUNC_NAME_SIZE);
|
|
p_node->unmap_line_num = line_num;
|
|
p_node->is_nbuf_mapped = false;
|
|
}
|
|
spin_unlock_irqrestore(&g_qdf_net_buf_track_lock[i], irq_flag);
|
|
}
|
|
|
|
void qdf_net_buf_debug_delete_node(qdf_nbuf_t net_buf)
|
|
{
|
|
uint32_t i;
|
|
QDF_NBUF_TRACK *p_head;
|
|
QDF_NBUF_TRACK *p_node = NULL;
|
|
unsigned long irq_flag;
|
|
QDF_NBUF_TRACK *p_prev;
|
|
|
|
if (is_initial_mem_debug_disabled)
|
|
return;
|
|
|
|
i = qdf_net_buf_debug_hash(net_buf);
|
|
spin_lock_irqsave(&g_qdf_net_buf_track_lock[i], irq_flag);
|
|
|
|
p_head = gp_qdf_net_buf_track_tbl[i];
|
|
|
|
/* Unallocated SKB */
|
|
if (!p_head)
|
|
goto done;
|
|
|
|
p_node = p_head;
|
|
/* Found at head of the table */
|
|
if (p_head->net_buf == net_buf) {
|
|
gp_qdf_net_buf_track_tbl[i] = p_node->p_next;
|
|
goto done;
|
|
}
|
|
|
|
/* Search in collision list */
|
|
while (p_node) {
|
|
p_prev = p_node;
|
|
p_node = p_node->p_next;
|
|
if ((p_node) && (p_node->net_buf == net_buf)) {
|
|
p_prev->p_next = p_node->p_next;
|
|
break;
|
|
}
|
|
}
|
|
|
|
done:
|
|
spin_unlock_irqrestore(&g_qdf_net_buf_track_lock[i], irq_flag);
|
|
|
|
if (p_node) {
|
|
qdf_mem_skb_dec(p_node->size);
|
|
qdf_nbuf_track_free(p_node);
|
|
} else {
|
|
if (qdf_net_buf_track_fail_count) {
|
|
qdf_print("Untracked net_buf free: %pK with tracking failures count: %u",
|
|
net_buf, qdf_net_buf_track_fail_count);
|
|
} else
|
|
QDF_MEMDEBUG_PANIC("Unallocated buffer ! Double free of net_buf %pK ?",
|
|
net_buf);
|
|
}
|
|
}
|
|
qdf_export_symbol(qdf_net_buf_debug_delete_node);
|
|
|
|
void qdf_net_buf_debug_acquire_skb(qdf_nbuf_t net_buf,
|
|
const char *func_name, uint32_t line_num)
|
|
{
|
|
qdf_nbuf_t ext_list = qdf_nbuf_get_ext_list(net_buf);
|
|
|
|
if (is_initial_mem_debug_disabled)
|
|
return;
|
|
|
|
while (ext_list) {
|
|
/*
|
|
* Take care to add if it is Jumbo packet connected using
|
|
* frag_list
|
|
*/
|
|
qdf_nbuf_t next;
|
|
|
|
next = qdf_nbuf_queue_next(ext_list);
|
|
qdf_net_buf_debug_add_node(ext_list, 0, func_name, line_num);
|
|
ext_list = next;
|
|
}
|
|
qdf_net_buf_debug_add_node(net_buf, 0, func_name, line_num);
|
|
}
|
|
qdf_export_symbol(qdf_net_buf_debug_acquire_skb);
|
|
|
|
void qdf_net_buf_debug_release_skb(qdf_nbuf_t net_buf)
|
|
{
|
|
qdf_nbuf_t ext_list;
|
|
|
|
if (is_initial_mem_debug_disabled)
|
|
return;
|
|
|
|
ext_list = qdf_nbuf_get_ext_list(net_buf);
|
|
while (ext_list) {
|
|
/*
|
|
* Take care to free if it is Jumbo packet connected using
|
|
* frag_list
|
|
*/
|
|
qdf_nbuf_t next;
|
|
|
|
next = qdf_nbuf_queue_next(ext_list);
|
|
|
|
if (qdf_nbuf_get_users(ext_list) > 1) {
|
|
ext_list = next;
|
|
continue;
|
|
}
|
|
|
|
qdf_net_buf_debug_delete_node(ext_list);
|
|
ext_list = next;
|
|
}
|
|
|
|
if (qdf_nbuf_get_users(net_buf) > 1)
|
|
return;
|
|
|
|
qdf_net_buf_debug_delete_node(net_buf);
|
|
}
|
|
qdf_export_symbol(qdf_net_buf_debug_release_skb);
|
|
|
|
qdf_nbuf_t qdf_nbuf_alloc_debug(qdf_device_t osdev, qdf_size_t size,
|
|
int reserve, int align, int prio,
|
|
const char *func, uint32_t line)
|
|
{
|
|
qdf_nbuf_t nbuf;
|
|
|
|
if (is_initial_mem_debug_disabled)
|
|
return __qdf_nbuf_alloc(osdev, size,
|
|
reserve, align,
|
|
prio, func, line);
|
|
|
|
nbuf = __qdf_nbuf_alloc(osdev, size, reserve, align, prio, func, line);
|
|
|
|
/* Store SKB in internal QDF tracking table */
|
|
if (qdf_likely(nbuf)) {
|
|
qdf_net_buf_debug_add_node(nbuf, size, func, line);
|
|
qdf_nbuf_history_add(nbuf, func, line, QDF_NBUF_ALLOC);
|
|
} else {
|
|
qdf_nbuf_history_add(nbuf, func, line, QDF_NBUF_ALLOC_FAILURE);
|
|
}
|
|
|
|
return nbuf;
|
|
}
|
|
qdf_export_symbol(qdf_nbuf_alloc_debug);
|
|
|
|
qdf_nbuf_t qdf_nbuf_frag_alloc_debug(qdf_device_t osdev, qdf_size_t size,
|
|
int reserve, int align, int prio,
|
|
const char *func, uint32_t line)
|
|
{
|
|
qdf_nbuf_t nbuf;
|
|
|
|
if (is_initial_mem_debug_disabled)
|
|
return __qdf_nbuf_frag_alloc(osdev, size,
|
|
reserve, align,
|
|
prio, func, line);
|
|
|
|
nbuf = __qdf_nbuf_frag_alloc(osdev, size, reserve, align, prio,
|
|
func, line);
|
|
|
|
/* Store SKB in internal QDF tracking table */
|
|
if (qdf_likely(nbuf)) {
|
|
qdf_net_buf_debug_add_node(nbuf, size, func, line);
|
|
qdf_nbuf_history_add(nbuf, func, line, QDF_NBUF_ALLOC);
|
|
} else {
|
|
qdf_nbuf_history_add(nbuf, func, line, QDF_NBUF_ALLOC_FAILURE);
|
|
}
|
|
|
|
return nbuf;
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_frag_alloc_debug);
|
|
|
|
qdf_nbuf_t qdf_nbuf_alloc_no_recycler_debug(size_t size, int reserve, int align,
|
|
const char *func, uint32_t line)
|
|
{
|
|
qdf_nbuf_t nbuf;
|
|
|
|
if (is_initial_mem_debug_disabled)
|
|
return __qdf_nbuf_alloc_no_recycler(size, reserve, align, func,
|
|
line);
|
|
|
|
nbuf = __qdf_nbuf_alloc_no_recycler(size, reserve, align, func, line);
|
|
|
|
/* Store SKB in internal QDF tracking table */
|
|
if (qdf_likely(nbuf)) {
|
|
qdf_net_buf_debug_add_node(nbuf, size, func, line);
|
|
qdf_nbuf_history_add(nbuf, func, line, QDF_NBUF_ALLOC);
|
|
} else {
|
|
qdf_nbuf_history_add(nbuf, func, line, QDF_NBUF_ALLOC_FAILURE);
|
|
}
|
|
|
|
return nbuf;
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_alloc_no_recycler_debug);
|
|
|
|
void qdf_nbuf_free_debug(qdf_nbuf_t nbuf, const char *func, uint32_t line)
|
|
{
|
|
qdf_nbuf_t ext_list;
|
|
qdf_frag_t p_frag;
|
|
uint32_t num_nr_frags;
|
|
uint32_t idx = 0;
|
|
|
|
if (qdf_unlikely(!nbuf))
|
|
return;
|
|
|
|
if (is_initial_mem_debug_disabled)
|
|
goto free_buf;
|
|
|
|
if (qdf_nbuf_get_users(nbuf) > 1)
|
|
goto free_buf;
|
|
|
|
/* Remove SKB from internal QDF tracking table */
|
|
qdf_nbuf_panic_on_free_if_smmu_mapped(nbuf, func, line);
|
|
qdf_nbuf_panic_on_free_if_mapped(nbuf, func, line);
|
|
qdf_net_buf_debug_delete_node(nbuf);
|
|
qdf_nbuf_history_add(nbuf, func, line, QDF_NBUF_FREE);
|
|
|
|
/* Take care to delete the debug entries for frags */
|
|
num_nr_frags = qdf_nbuf_get_nr_frags(nbuf);
|
|
|
|
qdf_assert_always(num_nr_frags <= QDF_NBUF_MAX_FRAGS);
|
|
|
|
while (idx < num_nr_frags) {
|
|
p_frag = qdf_nbuf_get_frag_addr(nbuf, idx);
|
|
if (qdf_likely(p_frag))
|
|
qdf_frag_debug_refcount_dec(p_frag, func, line);
|
|
idx++;
|
|
}
|
|
|
|
/*
|
|
* Take care to update the debug entries for frag_list and also
|
|
* for the frags attached to frag_list
|
|
*/
|
|
ext_list = qdf_nbuf_get_ext_list(nbuf);
|
|
while (ext_list) {
|
|
if (qdf_nbuf_get_users(ext_list) == 1) {
|
|
qdf_nbuf_panic_on_free_if_smmu_mapped(ext_list, func,
|
|
line);
|
|
qdf_nbuf_panic_on_free_if_mapped(ext_list, func, line);
|
|
idx = 0;
|
|
num_nr_frags = qdf_nbuf_get_nr_frags(ext_list);
|
|
qdf_assert_always(num_nr_frags <= QDF_NBUF_MAX_FRAGS);
|
|
while (idx < num_nr_frags) {
|
|
p_frag = qdf_nbuf_get_frag_addr(ext_list, idx);
|
|
if (qdf_likely(p_frag))
|
|
qdf_frag_debug_refcount_dec(p_frag,
|
|
func, line);
|
|
idx++;
|
|
}
|
|
qdf_net_buf_debug_delete_node(ext_list);
|
|
}
|
|
|
|
ext_list = qdf_nbuf_queue_next(ext_list);
|
|
}
|
|
|
|
free_buf:
|
|
__qdf_nbuf_free(nbuf);
|
|
}
|
|
qdf_export_symbol(qdf_nbuf_free_debug);
|
|
|
|
struct sk_buff *__qdf_nbuf_alloc_simple(qdf_device_t osdev, size_t size,
|
|
const char *func, uint32_t line)
|
|
{
|
|
struct sk_buff *skb;
|
|
int flags = GFP_KERNEL;
|
|
|
|
if (in_interrupt() || irqs_disabled() || in_atomic()) {
|
|
flags = GFP_ATOMIC;
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)
|
|
/*
|
|
* Observed that kcompactd burns out CPU to make order-3 page.
|
|
*__netdev_alloc_skb has 4k page fallback option just in case of
|
|
* failing high order page allocation so we don't need to be
|
|
* hard. Make kcompactd rest in piece.
|
|
*/
|
|
flags = flags & ~__GFP_KSWAPD_RECLAIM;
|
|
#endif
|
|
}
|
|
|
|
skb = __netdev_alloc_skb(NULL, size, flags);
|
|
|
|
|
|
if (qdf_likely(is_initial_mem_debug_disabled)) {
|
|
if (qdf_likely(skb))
|
|
qdf_nbuf_count_inc(skb);
|
|
} else {
|
|
if (qdf_likely(skb)) {
|
|
qdf_nbuf_count_inc(skb);
|
|
qdf_net_buf_debug_add_node(skb, size, func, line);
|
|
qdf_nbuf_history_add(skb, func, line, QDF_NBUF_ALLOC);
|
|
} else {
|
|
qdf_nbuf_history_add(skb, func, line, QDF_NBUF_ALLOC_FAILURE);
|
|
}
|
|
}
|
|
|
|
|
|
return skb;
|
|
}
|
|
|
|
qdf_export_symbol(__qdf_nbuf_alloc_simple);
|
|
|
|
void qdf_nbuf_free_debug_simple(qdf_nbuf_t nbuf, const char *func,
|
|
uint32_t line)
|
|
{
|
|
if (qdf_likely(nbuf)) {
|
|
if (is_initial_mem_debug_disabled) {
|
|
dev_kfree_skb_any(nbuf);
|
|
} else {
|
|
qdf_nbuf_free_debug(nbuf, func, line);
|
|
}
|
|
}
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_free_debug_simple);
|
|
|
|
qdf_nbuf_t qdf_nbuf_clone_debug(qdf_nbuf_t buf, const char *func, uint32_t line)
|
|
{
|
|
uint32_t num_nr_frags;
|
|
uint32_t idx = 0;
|
|
qdf_nbuf_t ext_list;
|
|
qdf_frag_t p_frag;
|
|
|
|
qdf_nbuf_t cloned_buf = __qdf_nbuf_clone(buf);
|
|
|
|
if (is_initial_mem_debug_disabled)
|
|
return cloned_buf;
|
|
|
|
if (qdf_unlikely(!cloned_buf))
|
|
return NULL;
|
|
|
|
/* Take care to update the debug entries for frags */
|
|
num_nr_frags = qdf_nbuf_get_nr_frags(cloned_buf);
|
|
|
|
qdf_assert_always(num_nr_frags <= QDF_NBUF_MAX_FRAGS);
|
|
|
|
while (idx < num_nr_frags) {
|
|
p_frag = qdf_nbuf_get_frag_addr(cloned_buf, idx);
|
|
if (qdf_likely(p_frag))
|
|
qdf_frag_debug_refcount_inc(p_frag, func, line);
|
|
idx++;
|
|
}
|
|
|
|
/* Take care to update debug entries for frags attached to frag_list */
|
|
ext_list = qdf_nbuf_get_ext_list(cloned_buf);
|
|
while (ext_list) {
|
|
idx = 0;
|
|
num_nr_frags = qdf_nbuf_get_nr_frags(ext_list);
|
|
|
|
qdf_assert_always(num_nr_frags <= QDF_NBUF_MAX_FRAGS);
|
|
|
|
while (idx < num_nr_frags) {
|
|
p_frag = qdf_nbuf_get_frag_addr(ext_list, idx);
|
|
if (qdf_likely(p_frag))
|
|
qdf_frag_debug_refcount_inc(p_frag, func, line);
|
|
idx++;
|
|
}
|
|
ext_list = qdf_nbuf_queue_next(ext_list);
|
|
}
|
|
|
|
/* Store SKB in internal QDF tracking table */
|
|
qdf_net_buf_debug_add_node(cloned_buf, 0, func, line);
|
|
qdf_nbuf_history_add(cloned_buf, func, line, QDF_NBUF_ALLOC_CLONE);
|
|
|
|
return cloned_buf;
|
|
}
|
|
qdf_export_symbol(qdf_nbuf_clone_debug);
|
|
|
|
qdf_nbuf_t
|
|
qdf_nbuf_page_frag_alloc_debug(qdf_device_t osdev, qdf_size_t size, int reserve,
|
|
int align, __qdf_frag_cache_t *pf_cache,
|
|
const char *func, uint32_t line)
|
|
{
|
|
qdf_nbuf_t nbuf;
|
|
|
|
if (is_initial_mem_debug_disabled)
|
|
return __qdf_nbuf_page_frag_alloc(osdev, size, reserve, align,
|
|
pf_cache, func, line);
|
|
|
|
nbuf = __qdf_nbuf_page_frag_alloc(osdev, size, reserve, align,
|
|
pf_cache, func, line);
|
|
|
|
/* Store SKB in internal QDF tracking table */
|
|
if (qdf_likely(nbuf)) {
|
|
qdf_net_buf_debug_add_node(nbuf, size, func, line);
|
|
qdf_nbuf_history_add(nbuf, func, line, QDF_NBUF_ALLOC);
|
|
} else {
|
|
qdf_nbuf_history_add(nbuf, func, line, QDF_NBUF_ALLOC_FAILURE);
|
|
}
|
|
|
|
return nbuf;
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_page_frag_alloc_debug);
|
|
|
|
qdf_nbuf_t qdf_nbuf_copy_debug(qdf_nbuf_t buf, const char *func, uint32_t line)
|
|
{
|
|
qdf_nbuf_t copied_buf = __qdf_nbuf_copy(buf);
|
|
|
|
if (is_initial_mem_debug_disabled)
|
|
return copied_buf;
|
|
|
|
if (qdf_unlikely(!copied_buf))
|
|
return NULL;
|
|
|
|
/* Store SKB in internal QDF tracking table */
|
|
qdf_net_buf_debug_add_node(copied_buf, 0, func, line);
|
|
qdf_nbuf_history_add(copied_buf, func, line, QDF_NBUF_ALLOC_COPY);
|
|
|
|
return copied_buf;
|
|
}
|
|
qdf_export_symbol(qdf_nbuf_copy_debug);
|
|
|
|
qdf_nbuf_t
|
|
qdf_nbuf_copy_expand_debug(qdf_nbuf_t buf, int headroom, int tailroom,
|
|
const char *func, uint32_t line)
|
|
{
|
|
qdf_nbuf_t copied_buf = __qdf_nbuf_copy_expand(buf, headroom, tailroom);
|
|
|
|
if (qdf_unlikely(!copied_buf))
|
|
return NULL;
|
|
|
|
if (is_initial_mem_debug_disabled)
|
|
return copied_buf;
|
|
|
|
/* Store SKB in internal QDF tracking table */
|
|
qdf_net_buf_debug_add_node(copied_buf, 0, func, line);
|
|
qdf_nbuf_history_add(copied_buf, func, line,
|
|
QDF_NBUF_ALLOC_COPY_EXPAND);
|
|
|
|
return copied_buf;
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_copy_expand_debug);
|
|
|
|
qdf_nbuf_t
|
|
qdf_nbuf_unshare_debug(qdf_nbuf_t buf, const char *func_name,
|
|
uint32_t line_num)
|
|
{
|
|
qdf_nbuf_t unshared_buf;
|
|
qdf_frag_t p_frag;
|
|
uint32_t num_nr_frags;
|
|
uint32_t idx = 0;
|
|
qdf_nbuf_t ext_list, next;
|
|
|
|
if (is_initial_mem_debug_disabled)
|
|
return __qdf_nbuf_unshare(buf);
|
|
|
|
/* Not a shared buffer, nothing to do */
|
|
if (!qdf_nbuf_is_cloned(buf))
|
|
return buf;
|
|
|
|
if (qdf_nbuf_get_users(buf) > 1)
|
|
goto unshare_buf;
|
|
|
|
/* Take care to delete the debug entries for frags */
|
|
num_nr_frags = qdf_nbuf_get_nr_frags(buf);
|
|
|
|
while (idx < num_nr_frags) {
|
|
p_frag = qdf_nbuf_get_frag_addr(buf, idx);
|
|
if (qdf_likely(p_frag))
|
|
qdf_frag_debug_refcount_dec(p_frag, func_name,
|
|
line_num);
|
|
idx++;
|
|
}
|
|
|
|
qdf_net_buf_debug_delete_node(buf);
|
|
|
|
/* Take care of jumbo packet connected using frag_list and frags */
|
|
ext_list = qdf_nbuf_get_ext_list(buf);
|
|
while (ext_list) {
|
|
idx = 0;
|
|
next = qdf_nbuf_queue_next(ext_list);
|
|
num_nr_frags = qdf_nbuf_get_nr_frags(ext_list);
|
|
|
|
if (qdf_nbuf_get_users(ext_list) > 1) {
|
|
ext_list = next;
|
|
continue;
|
|
}
|
|
|
|
while (idx < num_nr_frags) {
|
|
p_frag = qdf_nbuf_get_frag_addr(ext_list, idx);
|
|
if (qdf_likely(p_frag))
|
|
qdf_frag_debug_refcount_dec(p_frag, func_name,
|
|
line_num);
|
|
idx++;
|
|
}
|
|
|
|
qdf_net_buf_debug_delete_node(ext_list);
|
|
ext_list = next;
|
|
}
|
|
|
|
unshare_buf:
|
|
unshared_buf = __qdf_nbuf_unshare(buf);
|
|
|
|
if (qdf_likely(unshared_buf))
|
|
qdf_net_buf_debug_add_node(unshared_buf, 0, func_name,
|
|
line_num);
|
|
|
|
return unshared_buf;
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_unshare_debug);
|
|
|
|
void
|
|
qdf_nbuf_dev_kfree_list_debug(__qdf_nbuf_queue_head_t *nbuf_queue_head,
|
|
const char *func, uint32_t line)
|
|
{
|
|
qdf_nbuf_t buf;
|
|
|
|
if (qdf_nbuf_queue_empty(nbuf_queue_head))
|
|
return;
|
|
|
|
if (is_initial_mem_debug_disabled)
|
|
return __qdf_nbuf_dev_kfree_list(nbuf_queue_head);
|
|
|
|
while ((buf = qdf_nbuf_queue_head_dequeue(nbuf_queue_head)) != NULL)
|
|
qdf_nbuf_free_debug(buf, func, line);
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_dev_kfree_list_debug);
|
|
#endif /* NBUF_MEMORY_DEBUG */
|
|
|
|
#if defined(QCA_DP_NBUF_FAST_PPEDS)
|
|
#if defined(NBUF_MEMORY_DEBUG)
|
|
struct sk_buff *__qdf_nbuf_alloc_ppe_ds(qdf_device_t osdev, size_t size,
|
|
const char *func, uint32_t line)
|
|
{
|
|
struct sk_buff *skb;
|
|
int flags = GFP_KERNEL;
|
|
|
|
if (in_interrupt() || irqs_disabled() || in_atomic()) {
|
|
flags = GFP_ATOMIC;
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)
|
|
/*
|
|
* Observed that kcompactd burns out CPU to make order-3
|
|
* page.__netdev_alloc_skb has 4k page fallback option
|
|
* just in case of
|
|
* failing high order page allocation so we don't need
|
|
* to be hard. Make kcompactd rest in piece.
|
|
*/
|
|
flags = flags & ~__GFP_KSWAPD_RECLAIM;
|
|
#endif
|
|
}
|
|
skb = __netdev_alloc_skb_no_skb_reset(NULL, size, flags);
|
|
if (qdf_likely(is_initial_mem_debug_disabled)) {
|
|
if (qdf_likely(skb))
|
|
qdf_nbuf_count_inc(skb);
|
|
} else {
|
|
if (qdf_likely(skb)) {
|
|
qdf_nbuf_count_inc(skb);
|
|
qdf_net_buf_debug_add_node(skb, size, func, line);
|
|
qdf_nbuf_history_add(skb, func, line,
|
|
QDF_NBUF_ALLOC);
|
|
} else {
|
|
qdf_nbuf_history_add(skb, func, line,
|
|
QDF_NBUF_ALLOC_FAILURE);
|
|
}
|
|
}
|
|
return skb;
|
|
}
|
|
#else
|
|
struct sk_buff *__qdf_nbuf_alloc_ppe_ds(qdf_device_t osdev, size_t size,
|
|
const char *func, uint32_t line)
|
|
{
|
|
struct sk_buff *skb;
|
|
int flags = GFP_KERNEL;
|
|
|
|
if (in_interrupt() || irqs_disabled() || in_atomic()) {
|
|
flags = GFP_ATOMIC;
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)
|
|
/*
|
|
* Observed that kcompactd burns out CPU to make order-3
|
|
* page.__netdev_alloc_skb has 4k page fallback option
|
|
* just in case of
|
|
* failing high order page allocation so we don't need
|
|
* to be hard. Make kcompactd rest in piece.
|
|
*/
|
|
flags = flags & ~__GFP_KSWAPD_RECLAIM;
|
|
#endif
|
|
}
|
|
skb = __netdev_alloc_skb_no_skb_reset(NULL, size, flags);
|
|
if (qdf_likely(skb))
|
|
qdf_nbuf_count_inc(skb);
|
|
|
|
return skb;
|
|
}
|
|
#endif
|
|
qdf_export_symbol(__qdf_nbuf_alloc_ppe_ds);
|
|
#endif
|
|
|
|
#if defined(FEATURE_TSO)
|
|
|
|
/**
|
|
* struct qdf_tso_cmn_seg_info_t - TSO common info structure
|
|
*
|
|
* @ethproto: ethernet type of the msdu
|
|
* @ip_tcp_hdr_len: ip + tcp length for the msdu
|
|
* @l2_len: L2 length for the msdu
|
|
* @eit_hdr: pointer to EIT header
|
|
* @eit_hdr_len: EIT header length for the msdu
|
|
* @eit_hdr_dma_map_addr: dma addr for EIT header
|
|
* @tcphdr: pointer to tcp header
|
|
* @ipv4_csum_en: ipv4 checksum enable
|
|
* @tcp_ipv4_csum_en: TCP ipv4 checksum enable
|
|
* @tcp_ipv6_csum_en: TCP ipv6 checksum enable
|
|
* @ip_id: IP id
|
|
* @tcp_seq_num: TCP sequence number
|
|
*
|
|
* This structure holds the TSO common info that is common
|
|
* across all the TCP segments of the jumbo packet.
|
|
*/
|
|
struct qdf_tso_cmn_seg_info_t {
|
|
uint16_t ethproto;
|
|
uint16_t ip_tcp_hdr_len;
|
|
uint16_t l2_len;
|
|
uint8_t *eit_hdr;
|
|
uint32_t eit_hdr_len;
|
|
qdf_dma_addr_t eit_hdr_dma_map_addr;
|
|
struct tcphdr *tcphdr;
|
|
uint16_t ipv4_csum_en;
|
|
uint16_t tcp_ipv4_csum_en;
|
|
uint16_t tcp_ipv6_csum_en;
|
|
uint16_t ip_id;
|
|
uint32_t tcp_seq_num;
|
|
};
|
|
|
|
/**
|
|
* qdf_nbuf_adj_tso_frag() - adjustment for buffer address of tso fragment
|
|
* @skb: network buffer
|
|
*
|
|
* Return: byte offset length of 8 bytes aligned.
|
|
*/
|
|
#ifdef FIX_TXDMA_LIMITATION
|
|
static uint8_t qdf_nbuf_adj_tso_frag(struct sk_buff *skb)
|
|
{
|
|
uint32_t eit_hdr_len;
|
|
uint8_t *eit_hdr;
|
|
uint8_t byte_8_align_offset;
|
|
|
|
eit_hdr = skb->data;
|
|
eit_hdr_len = (skb_transport_header(skb)
|
|
- skb_mac_header(skb)) + tcp_hdrlen(skb);
|
|
byte_8_align_offset = ((unsigned long)(eit_hdr) + eit_hdr_len) & 0x7L;
|
|
if (qdf_unlikely(byte_8_align_offset)) {
|
|
TSO_DEBUG("%pK,Len %d %d",
|
|
eit_hdr, eit_hdr_len, byte_8_align_offset);
|
|
if (unlikely(skb_headroom(skb) < byte_8_align_offset)) {
|
|
TSO_DEBUG("[%d]Insufficient headroom,[%pK],[%pK],[%d]",
|
|
__LINE__, skb->head, skb->data,
|
|
byte_8_align_offset);
|
|
return 0;
|
|
}
|
|
qdf_nbuf_push_head(skb, byte_8_align_offset);
|
|
qdf_mem_move(skb->data,
|
|
skb->data + byte_8_align_offset,
|
|
eit_hdr_len);
|
|
skb->len -= byte_8_align_offset;
|
|
skb->mac_header -= byte_8_align_offset;
|
|
skb->network_header -= byte_8_align_offset;
|
|
skb->transport_header -= byte_8_align_offset;
|
|
}
|
|
return byte_8_align_offset;
|
|
}
|
|
#else
|
|
static uint8_t qdf_nbuf_adj_tso_frag(struct sk_buff *skb)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_WLAN_SYSFS_MEM_STATS
|
|
void qdf_record_nbuf_nbytes(
|
|
uint32_t nbytes, qdf_dma_dir_t dir, bool is_mapped)
|
|
{
|
|
__qdf_record_nbuf_nbytes(nbytes, dir, is_mapped);
|
|
}
|
|
|
|
qdf_export_symbol(qdf_record_nbuf_nbytes);
|
|
|
|
#endif /* CONFIG_WLAN_SYSFS_MEM_STATS */
|
|
|
|
/**
|
|
* qdf_nbuf_tso_map_frag() - Map TSO segment
|
|
* @osdev: qdf device handle
|
|
* @tso_frag_vaddr: addr of tso fragment
|
|
* @nbytes: number of bytes
|
|
* @dir: direction
|
|
*
|
|
* Map TSO segment and for MCL record the amount of memory mapped
|
|
*
|
|
* Return: DMA address of mapped TSO fragment in success and
|
|
* NULL in case of DMA mapping failure
|
|
*/
|
|
static inline qdf_dma_addr_t qdf_nbuf_tso_map_frag(
|
|
qdf_device_t osdev, void *tso_frag_vaddr,
|
|
uint32_t nbytes, qdf_dma_dir_t dir)
|
|
{
|
|
qdf_dma_addr_t tso_frag_paddr = 0;
|
|
|
|
tso_frag_paddr = dma_map_single(osdev->dev, tso_frag_vaddr,
|
|
nbytes, __qdf_dma_dir_to_os(dir));
|
|
if (unlikely(dma_mapping_error(osdev->dev, tso_frag_paddr))) {
|
|
qdf_err("DMA mapping error!");
|
|
qdf_assert_always(0);
|
|
return 0;
|
|
}
|
|
qdf_record_nbuf_nbytes(nbytes, dir, true);
|
|
return tso_frag_paddr;
|
|
}
|
|
|
|
/**
|
|
* qdf_nbuf_tso_unmap_frag() - Unmap TSO segment
|
|
* @osdev: qdf device handle
|
|
* @tso_frag_paddr: DMA addr of tso fragment
|
|
* @dir: direction
|
|
* @nbytes: number of bytes
|
|
*
|
|
* Unmap TSO segment and for MCL record the amount of memory mapped
|
|
*
|
|
* Return: None
|
|
*/
|
|
static inline void qdf_nbuf_tso_unmap_frag(
|
|
qdf_device_t osdev, qdf_dma_addr_t tso_frag_paddr,
|
|
uint32_t nbytes, qdf_dma_dir_t dir)
|
|
{
|
|
qdf_record_nbuf_nbytes(nbytes, dir, false);
|
|
dma_unmap_single(osdev->dev, tso_frag_paddr,
|
|
nbytes, __qdf_dma_dir_to_os(dir));
|
|
}
|
|
|
|
/**
|
|
* __qdf_nbuf_get_tso_cmn_seg_info() - get TSO common
|
|
* information
|
|
* @osdev: qdf device handle
|
|
* @skb: skb buffer
|
|
* @tso_info: Parameters common to all segments
|
|
*
|
|
* Get the TSO information that is common across all the TCP
|
|
* segments of the jumbo packet
|
|
*
|
|
* Return: 0 - success 1 - failure
|
|
*/
|
|
static uint8_t __qdf_nbuf_get_tso_cmn_seg_info(qdf_device_t osdev,
|
|
struct sk_buff *skb,
|
|
struct qdf_tso_cmn_seg_info_t *tso_info)
|
|
{
|
|
/* Get ethernet type and ethernet header length */
|
|
tso_info->ethproto = vlan_get_protocol(skb);
|
|
|
|
/* Determine whether this is an IPv4 or IPv6 packet */
|
|
if (tso_info->ethproto == htons(ETH_P_IP)) { /* IPv4 */
|
|
/* for IPv4, get the IP ID and enable TCP and IP csum */
|
|
struct iphdr *ipv4_hdr = ip_hdr(skb);
|
|
|
|
tso_info->ip_id = ntohs(ipv4_hdr->id);
|
|
tso_info->ipv4_csum_en = 1;
|
|
tso_info->tcp_ipv4_csum_en = 1;
|
|
if (qdf_unlikely(ipv4_hdr->protocol != IPPROTO_TCP)) {
|
|
qdf_err("TSO IPV4 proto 0x%x not TCP",
|
|
ipv4_hdr->protocol);
|
|
return 1;
|
|
}
|
|
} else if (tso_info->ethproto == htons(ETH_P_IPV6)) { /* IPv6 */
|
|
/* for IPv6, enable TCP csum. No IP ID or IP csum */
|
|
tso_info->tcp_ipv6_csum_en = 1;
|
|
} else {
|
|
qdf_err("TSO: ethertype 0x%x is not supported!",
|
|
tso_info->ethproto);
|
|
return 1;
|
|
}
|
|
tso_info->l2_len = (skb_network_header(skb) - skb_mac_header(skb));
|
|
tso_info->tcphdr = tcp_hdr(skb);
|
|
tso_info->tcp_seq_num = ntohl(tcp_hdr(skb)->seq);
|
|
/* get pointer to the ethernet + IP + TCP header and their length */
|
|
tso_info->eit_hdr = skb->data;
|
|
tso_info->eit_hdr_len = (skb_transport_header(skb)
|
|
- skb_mac_header(skb)) + tcp_hdrlen(skb);
|
|
tso_info->eit_hdr_dma_map_addr = qdf_nbuf_tso_map_frag(
|
|
osdev, tso_info->eit_hdr,
|
|
tso_info->eit_hdr_len,
|
|
QDF_DMA_TO_DEVICE);
|
|
if (qdf_unlikely(!tso_info->eit_hdr_dma_map_addr))
|
|
return 1;
|
|
|
|
if (tso_info->ethproto == htons(ETH_P_IP)) {
|
|
/* include IPv4 header length for IPV4 (total length) */
|
|
tso_info->ip_tcp_hdr_len =
|
|
tso_info->eit_hdr_len - tso_info->l2_len;
|
|
} else if (tso_info->ethproto == htons(ETH_P_IPV6)) {
|
|
/* exclude IPv6 header length for IPv6 (payload length) */
|
|
tso_info->ip_tcp_hdr_len = tcp_hdrlen(skb);
|
|
}
|
|
/*
|
|
* The length of the payload (application layer data) is added to
|
|
* tso_info->ip_tcp_hdr_len before passing it on to the msdu link ext
|
|
* descriptor.
|
|
*/
|
|
|
|
TSO_DEBUG("%s seq# %u eit hdr len %u l2 len %u skb len %u\n", __func__,
|
|
tso_info->tcp_seq_num,
|
|
tso_info->eit_hdr_len,
|
|
tso_info->l2_len,
|
|
skb->len);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* __qdf_nbuf_fill_tso_cmn_seg_info() - Init function for each TSO nbuf segment
|
|
*
|
|
* @curr_seg: Segment whose contents are initialized
|
|
* @tso_cmn_info: Parameters common to all segments
|
|
*
|
|
* Return: None
|
|
*/
|
|
static inline void __qdf_nbuf_fill_tso_cmn_seg_info(
|
|
struct qdf_tso_seg_elem_t *curr_seg,
|
|
struct qdf_tso_cmn_seg_info_t *tso_cmn_info)
|
|
{
|
|
/* Initialize the flags to 0 */
|
|
memset(&curr_seg->seg, 0x0, sizeof(curr_seg->seg));
|
|
|
|
/*
|
|
* The following fields remain the same across all segments of
|
|
* a jumbo packet
|
|
*/
|
|
curr_seg->seg.tso_flags.tso_enable = 1;
|
|
curr_seg->seg.tso_flags.ipv4_checksum_en =
|
|
tso_cmn_info->ipv4_csum_en;
|
|
curr_seg->seg.tso_flags.tcp_ipv6_checksum_en =
|
|
tso_cmn_info->tcp_ipv6_csum_en;
|
|
curr_seg->seg.tso_flags.tcp_ipv4_checksum_en =
|
|
tso_cmn_info->tcp_ipv4_csum_en;
|
|
curr_seg->seg.tso_flags.tcp_flags_mask = 0x1FF;
|
|
|
|
/* The following fields change for the segments */
|
|
curr_seg->seg.tso_flags.ip_id = tso_cmn_info->ip_id;
|
|
tso_cmn_info->ip_id++;
|
|
|
|
curr_seg->seg.tso_flags.syn = tso_cmn_info->tcphdr->syn;
|
|
curr_seg->seg.tso_flags.rst = tso_cmn_info->tcphdr->rst;
|
|
curr_seg->seg.tso_flags.ack = tso_cmn_info->tcphdr->ack;
|
|
curr_seg->seg.tso_flags.urg = tso_cmn_info->tcphdr->urg;
|
|
curr_seg->seg.tso_flags.ece = tso_cmn_info->tcphdr->ece;
|
|
curr_seg->seg.tso_flags.cwr = tso_cmn_info->tcphdr->cwr;
|
|
|
|
curr_seg->seg.tso_flags.tcp_seq_num = tso_cmn_info->tcp_seq_num;
|
|
|
|
/*
|
|
* First fragment for each segment always contains the ethernet,
|
|
* IP and TCP header
|
|
*/
|
|
curr_seg->seg.tso_frags[0].vaddr = tso_cmn_info->eit_hdr;
|
|
curr_seg->seg.tso_frags[0].length = tso_cmn_info->eit_hdr_len;
|
|
curr_seg->seg.total_len = curr_seg->seg.tso_frags[0].length;
|
|
curr_seg->seg.tso_frags[0].paddr = tso_cmn_info->eit_hdr_dma_map_addr;
|
|
|
|
TSO_DEBUG("%s %d eit hdr %pK eit_hdr_len %d tcp_seq_num %u tso_info->total_len %u\n",
|
|
__func__, __LINE__, tso_cmn_info->eit_hdr,
|
|
tso_cmn_info->eit_hdr_len,
|
|
curr_seg->seg.tso_flags.tcp_seq_num,
|
|
curr_seg->seg.total_len);
|
|
qdf_tso_seg_dbg_record(curr_seg, TSOSEG_LOC_FILLCMNSEG);
|
|
}
|
|
|
|
uint32_t __qdf_nbuf_get_tso_info(qdf_device_t osdev, struct sk_buff *skb,
|
|
struct qdf_tso_info_t *tso_info)
|
|
{
|
|
/* common across all segments */
|
|
struct qdf_tso_cmn_seg_info_t tso_cmn_info;
|
|
/* segment specific */
|
|
void *tso_frag_vaddr;
|
|
qdf_dma_addr_t tso_frag_paddr = 0;
|
|
uint32_t num_seg = 0;
|
|
struct qdf_tso_seg_elem_t *curr_seg;
|
|
struct qdf_tso_num_seg_elem_t *total_num_seg;
|
|
skb_frag_t *frag = NULL;
|
|
uint32_t tso_frag_len = 0; /* tso segment's fragment length*/
|
|
uint32_t skb_frag_len = 0; /* skb's fragment length (contiguous memory)*/
|
|
uint32_t skb_proc = skb->len; /* bytes of skb pending processing */
|
|
uint32_t tso_seg_size = skb_shinfo(skb)->gso_size;
|
|
int j = 0; /* skb fragment index */
|
|
uint8_t byte_8_align_offset;
|
|
|
|
memset(&tso_cmn_info, 0x0, sizeof(tso_cmn_info));
|
|
total_num_seg = tso_info->tso_num_seg_list;
|
|
curr_seg = tso_info->tso_seg_list;
|
|
total_num_seg->num_seg.tso_cmn_num_seg = 0;
|
|
|
|
byte_8_align_offset = qdf_nbuf_adj_tso_frag(skb);
|
|
|
|
if (qdf_unlikely(__qdf_nbuf_get_tso_cmn_seg_info(osdev,
|
|
skb, &tso_cmn_info))) {
|
|
qdf_warn("TSO: error getting common segment info");
|
|
return 0;
|
|
}
|
|
|
|
/* length of the first chunk of data in the skb */
|
|
skb_frag_len = skb_headlen(skb);
|
|
|
|
/* the 0th tso segment's 0th fragment always contains the EIT header */
|
|
/* update the remaining skb fragment length and TSO segment length */
|
|
skb_frag_len -= tso_cmn_info.eit_hdr_len;
|
|
skb_proc -= tso_cmn_info.eit_hdr_len;
|
|
|
|
/* get the address to the next tso fragment */
|
|
tso_frag_vaddr = skb->data +
|
|
tso_cmn_info.eit_hdr_len +
|
|
byte_8_align_offset;
|
|
/* get the length of the next tso fragment */
|
|
tso_frag_len = min(skb_frag_len, tso_seg_size);
|
|
|
|
if (tso_frag_len != 0) {
|
|
tso_frag_paddr = qdf_nbuf_tso_map_frag(
|
|
osdev, tso_frag_vaddr, tso_frag_len,
|
|
QDF_DMA_TO_DEVICE);
|
|
if (qdf_unlikely(!tso_frag_paddr))
|
|
return 0;
|
|
}
|
|
|
|
TSO_DEBUG("%s[%d] skb frag len %d tso frag len %d\n", __func__,
|
|
__LINE__, skb_frag_len, tso_frag_len);
|
|
num_seg = tso_info->num_segs;
|
|
tso_info->num_segs = 0;
|
|
tso_info->is_tso = 1;
|
|
|
|
while (num_seg && curr_seg) {
|
|
int i = 1; /* tso fragment index */
|
|
uint8_t more_tso_frags = 1;
|
|
|
|
curr_seg->seg.num_frags = 0;
|
|
tso_info->num_segs++;
|
|
total_num_seg->num_seg.tso_cmn_num_seg++;
|
|
|
|
__qdf_nbuf_fill_tso_cmn_seg_info(curr_seg,
|
|
&tso_cmn_info);
|
|
|
|
/* If TCP PSH flag is set, set it in the last or only segment */
|
|
if (num_seg == 1)
|
|
curr_seg->seg.tso_flags.psh = tso_cmn_info.tcphdr->psh;
|
|
|
|
if (unlikely(skb_proc == 0))
|
|
return tso_info->num_segs;
|
|
|
|
curr_seg->seg.tso_flags.ip_len = tso_cmn_info.ip_tcp_hdr_len;
|
|
curr_seg->seg.tso_flags.l2_len = tso_cmn_info.l2_len;
|
|
/* frag len is added to ip_len in while loop below*/
|
|
|
|
curr_seg->seg.num_frags++;
|
|
|
|
while (more_tso_frags) {
|
|
if (tso_frag_len != 0) {
|
|
curr_seg->seg.tso_frags[i].vaddr =
|
|
tso_frag_vaddr;
|
|
curr_seg->seg.tso_frags[i].length =
|
|
tso_frag_len;
|
|
curr_seg->seg.total_len += tso_frag_len;
|
|
curr_seg->seg.tso_flags.ip_len += tso_frag_len;
|
|
curr_seg->seg.num_frags++;
|
|
skb_proc = skb_proc - tso_frag_len;
|
|
|
|
/* increment the TCP sequence number */
|
|
|
|
tso_cmn_info.tcp_seq_num += tso_frag_len;
|
|
curr_seg->seg.tso_frags[i].paddr =
|
|
tso_frag_paddr;
|
|
|
|
qdf_assert_always(curr_seg->seg.tso_frags[i].paddr);
|
|
}
|
|
|
|
TSO_DEBUG("%s[%d] frag %d frag len %d total_len %u vaddr %pK\n",
|
|
__func__, __LINE__,
|
|
i,
|
|
tso_frag_len,
|
|
curr_seg->seg.total_len,
|
|
curr_seg->seg.tso_frags[i].vaddr);
|
|
|
|
/* if there is no more data left in the skb */
|
|
if (!skb_proc)
|
|
return tso_info->num_segs;
|
|
|
|
/* get the next payload fragment information */
|
|
/* check if there are more fragments in this segment */
|
|
if (tso_frag_len < tso_seg_size) {
|
|
more_tso_frags = 1;
|
|
if (tso_frag_len != 0) {
|
|
tso_seg_size = tso_seg_size -
|
|
tso_frag_len;
|
|
i++;
|
|
if (curr_seg->seg.num_frags ==
|
|
FRAG_NUM_MAX) {
|
|
more_tso_frags = 0;
|
|
/*
|
|
* reset i and the tso
|
|
* payload size
|
|
*/
|
|
i = 1;
|
|
tso_seg_size =
|
|
skb_shinfo(skb)->
|
|
gso_size;
|
|
}
|
|
}
|
|
} else {
|
|
more_tso_frags = 0;
|
|
/* reset i and the tso payload size */
|
|
i = 1;
|
|
tso_seg_size = skb_shinfo(skb)->gso_size;
|
|
}
|
|
|
|
/* if the next fragment is contiguous */
|
|
if ((tso_frag_len != 0) && (tso_frag_len < skb_frag_len)) {
|
|
tso_frag_vaddr = tso_frag_vaddr + tso_frag_len;
|
|
skb_frag_len = skb_frag_len - tso_frag_len;
|
|
tso_frag_len = min(skb_frag_len, tso_seg_size);
|
|
|
|
} else { /* the next fragment is not contiguous */
|
|
if (skb_shinfo(skb)->nr_frags == 0) {
|
|
qdf_info("TSO: nr_frags == 0!");
|
|
qdf_assert(0);
|
|
return 0;
|
|
}
|
|
if (j >= skb_shinfo(skb)->nr_frags) {
|
|
qdf_info("TSO: nr_frags %d j %d",
|
|
skb_shinfo(skb)->nr_frags, j);
|
|
qdf_assert(0);
|
|
return 0;
|
|
}
|
|
frag = &skb_shinfo(skb)->frags[j];
|
|
skb_frag_len = skb_frag_size(frag);
|
|
tso_frag_len = min(skb_frag_len, tso_seg_size);
|
|
tso_frag_vaddr = skb_frag_address_safe(frag);
|
|
j++;
|
|
}
|
|
|
|
TSO_DEBUG("%s[%d] skb frag len %d tso frag %d len tso_seg_size %d\n",
|
|
__func__, __LINE__, skb_frag_len, tso_frag_len,
|
|
tso_seg_size);
|
|
|
|
if (!(tso_frag_vaddr)) {
|
|
TSO_DEBUG("%s: Fragment virtual addr is NULL",
|
|
__func__);
|
|
return 0;
|
|
}
|
|
|
|
tso_frag_paddr = qdf_nbuf_tso_map_frag(
|
|
osdev, tso_frag_vaddr,
|
|
tso_frag_len,
|
|
QDF_DMA_TO_DEVICE);
|
|
if (qdf_unlikely(!tso_frag_paddr))
|
|
return 0;
|
|
}
|
|
TSO_DEBUG("%s tcp_seq_num: %u", __func__,
|
|
curr_seg->seg.tso_flags.tcp_seq_num);
|
|
num_seg--;
|
|
/* if TCP FIN flag was set, set it in the last segment */
|
|
if (!num_seg)
|
|
curr_seg->seg.tso_flags.fin = tso_cmn_info.tcphdr->fin;
|
|
|
|
qdf_tso_seg_dbg_record(curr_seg, TSOSEG_LOC_GETINFO);
|
|
curr_seg = curr_seg->next;
|
|
}
|
|
return tso_info->num_segs;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_get_tso_info);
|
|
|
|
void __qdf_nbuf_unmap_tso_segment(qdf_device_t osdev,
|
|
struct qdf_tso_seg_elem_t *tso_seg,
|
|
bool is_last_seg)
|
|
{
|
|
uint32_t num_frags = 0;
|
|
|
|
if (tso_seg->seg.num_frags > 0)
|
|
num_frags = tso_seg->seg.num_frags - 1;
|
|
|
|
/*Num of frags in a tso seg cannot be less than 2 */
|
|
if (num_frags < 1) {
|
|
/*
|
|
* If Num of frags is 1 in a tso seg but is_last_seg true,
|
|
* this may happen when qdf_nbuf_get_tso_info failed,
|
|
* do dma unmap for the 0th frag in this seg.
|
|
*/
|
|
if (is_last_seg && tso_seg->seg.num_frags == 1)
|
|
goto last_seg_free_first_frag;
|
|
|
|
qdf_assert(0);
|
|
qdf_err("ERROR: num of frags in a tso segment is %d",
|
|
(num_frags + 1));
|
|
return;
|
|
}
|
|
|
|
while (num_frags) {
|
|
/*Do dma unmap the tso seg except the 0th frag */
|
|
if (0 == tso_seg->seg.tso_frags[num_frags].paddr) {
|
|
qdf_err("ERROR: TSO seg frag %d mapped physical address is NULL",
|
|
num_frags);
|
|
qdf_assert(0);
|
|
return;
|
|
}
|
|
qdf_nbuf_tso_unmap_frag(
|
|
osdev,
|
|
tso_seg->seg.tso_frags[num_frags].paddr,
|
|
tso_seg->seg.tso_frags[num_frags].length,
|
|
QDF_DMA_TO_DEVICE);
|
|
tso_seg->seg.tso_frags[num_frags].paddr = 0;
|
|
num_frags--;
|
|
qdf_tso_seg_dbg_record(tso_seg, TSOSEG_LOC_UNMAPTSO);
|
|
}
|
|
|
|
last_seg_free_first_frag:
|
|
if (is_last_seg) {
|
|
/*Do dma unmap for the tso seg 0th frag */
|
|
if (0 == tso_seg->seg.tso_frags[0].paddr) {
|
|
qdf_err("ERROR: TSO seg frag 0 mapped physical address is NULL");
|
|
qdf_assert(0);
|
|
return;
|
|
}
|
|
qdf_nbuf_tso_unmap_frag(osdev,
|
|
tso_seg->seg.tso_frags[0].paddr,
|
|
tso_seg->seg.tso_frags[0].length,
|
|
QDF_DMA_TO_DEVICE);
|
|
tso_seg->seg.tso_frags[0].paddr = 0;
|
|
qdf_tso_seg_dbg_record(tso_seg, TSOSEG_LOC_UNMAPLAST);
|
|
}
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_unmap_tso_segment);
|
|
|
|
size_t __qdf_nbuf_get_tcp_payload_len(struct sk_buff *skb)
|
|
{
|
|
size_t packet_len;
|
|
|
|
packet_len = skb->len -
|
|
((skb_transport_header(skb) - skb_mac_header(skb)) +
|
|
tcp_hdrlen(skb));
|
|
|
|
return packet_len;
|
|
}
|
|
|
|
qdf_export_symbol(__qdf_nbuf_get_tcp_payload_len);
|
|
|
|
#ifndef BUILD_X86
|
|
uint32_t __qdf_nbuf_get_tso_num_seg(struct sk_buff *skb)
|
|
{
|
|
uint32_t tso_seg_size = skb_shinfo(skb)->gso_size;
|
|
uint32_t remainder, num_segs = 0;
|
|
uint8_t skb_nr_frags = skb_shinfo(skb)->nr_frags;
|
|
uint8_t frags_per_tso = 0;
|
|
uint32_t skb_frag_len = 0;
|
|
uint32_t eit_hdr_len = (skb_transport_header(skb)
|
|
- skb_mac_header(skb)) + tcp_hdrlen(skb);
|
|
skb_frag_t *frag = NULL;
|
|
int j = 0;
|
|
uint32_t temp_num_seg = 0;
|
|
|
|
/* length of the first chunk of data in the skb minus eit header*/
|
|
skb_frag_len = skb_headlen(skb) - eit_hdr_len;
|
|
|
|
/* Calculate num of segs for skb's first chunk of data*/
|
|
remainder = skb_frag_len % tso_seg_size;
|
|
num_segs = skb_frag_len / tso_seg_size;
|
|
/*
|
|
* Remainder non-zero and nr_frags zero implies end of skb data.
|
|
* In that case, one more tso seg is required to accommodate
|
|
* remaining data, hence num_segs++. If nr_frags is non-zero,
|
|
* then remaining data will be accommodated while doing the calculation
|
|
* for nr_frags data. Hence, frags_per_tso++.
|
|
*/
|
|
if (remainder) {
|
|
if (!skb_nr_frags)
|
|
num_segs++;
|
|
else
|
|
frags_per_tso++;
|
|
}
|
|
|
|
while (skb_nr_frags) {
|
|
if (j >= skb_shinfo(skb)->nr_frags) {
|
|
qdf_info("TSO: nr_frags %d j %d",
|
|
skb_shinfo(skb)->nr_frags, j);
|
|
qdf_assert(0);
|
|
return 0;
|
|
}
|
|
/*
|
|
* Calculate the number of tso seg for nr_frags data:
|
|
* Get the length of each frag in skb_frag_len, add to
|
|
* remainder.Get the number of segments by dividing it to
|
|
* tso_seg_size and calculate the new remainder.
|
|
* Decrement the nr_frags value and keep
|
|
* looping all the skb_fragments.
|
|
*/
|
|
frag = &skb_shinfo(skb)->frags[j];
|
|
skb_frag_len = skb_frag_size(frag);
|
|
temp_num_seg = num_segs;
|
|
remainder += skb_frag_len;
|
|
num_segs += remainder / tso_seg_size;
|
|
remainder = remainder % tso_seg_size;
|
|
skb_nr_frags--;
|
|
if (remainder) {
|
|
if (num_segs > temp_num_seg)
|
|
frags_per_tso = 0;
|
|
/*
|
|
* increment the tso per frags whenever remainder is
|
|
* positive. If frags_per_tso reaches the (max-1),
|
|
* [First frags always have EIT header, therefore max-1]
|
|
* increment the num_segs as no more data can be
|
|
* accommodated in the curr tso seg. Reset the remainder
|
|
* and frags per tso and keep looping.
|
|
*/
|
|
frags_per_tso++;
|
|
if (frags_per_tso == FRAG_NUM_MAX - 1) {
|
|
num_segs++;
|
|
frags_per_tso = 0;
|
|
remainder = 0;
|
|
}
|
|
/*
|
|
* If this is the last skb frag and still remainder is
|
|
* non-zero(frags_per_tso is not reached to the max-1)
|
|
* then increment the num_segs to take care of the
|
|
* remaining length.
|
|
*/
|
|
if (!skb_nr_frags && remainder) {
|
|
num_segs++;
|
|
frags_per_tso = 0;
|
|
}
|
|
} else {
|
|
/* Whenever remainder is 0, reset the frags_per_tso. */
|
|
frags_per_tso = 0;
|
|
}
|
|
j++;
|
|
}
|
|
|
|
return num_segs;
|
|
}
|
|
#elif !defined(QCA_WIFI_QCN9000)
|
|
uint32_t __qdf_nbuf_get_tso_num_seg(struct sk_buff *skb)
|
|
{
|
|
uint32_t i, gso_size, tmp_len, num_segs = 0;
|
|
skb_frag_t *frag = NULL;
|
|
|
|
/*
|
|
* Check if the head SKB or any of frags are allocated in < 0x50000000
|
|
* region which cannot be accessed by Target
|
|
*/
|
|
if (virt_to_phys(skb->data) < 0x50000040) {
|
|
TSO_DEBUG("%s %d: Invalid Address nr_frags = %d, paddr = %pK \n",
|
|
__func__, __LINE__, skb_shinfo(skb)->nr_frags,
|
|
virt_to_phys(skb->data));
|
|
goto fail;
|
|
|
|
}
|
|
|
|
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
|
|
frag = &skb_shinfo(skb)->frags[i];
|
|
|
|
if (!frag)
|
|
goto fail;
|
|
|
|
if (virt_to_phys(skb_frag_address_safe(frag)) < 0x50000040)
|
|
goto fail;
|
|
}
|
|
|
|
|
|
gso_size = skb_shinfo(skb)->gso_size;
|
|
tmp_len = skb->len - ((skb_transport_header(skb) - skb_mac_header(skb))
|
|
+ tcp_hdrlen(skb));
|
|
while (tmp_len) {
|
|
num_segs++;
|
|
if (tmp_len > gso_size)
|
|
tmp_len -= gso_size;
|
|
else
|
|
break;
|
|
}
|
|
|
|
return num_segs;
|
|
|
|
/*
|
|
* Do not free this frame, just do socket level accounting
|
|
* so that this is not reused.
|
|
*/
|
|
fail:
|
|
if (skb->sk)
|
|
atomic_sub(skb->truesize, &(skb->sk->sk_wmem_alloc));
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
uint32_t __qdf_nbuf_get_tso_num_seg(struct sk_buff *skb)
|
|
{
|
|
uint32_t i, gso_size, tmp_len, num_segs = 0;
|
|
skb_frag_t *frag = NULL;
|
|
|
|
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
|
|
frag = &skb_shinfo(skb)->frags[i];
|
|
|
|
if (!frag)
|
|
goto fail;
|
|
}
|
|
|
|
gso_size = skb_shinfo(skb)->gso_size;
|
|
tmp_len = skb->len - ((skb_transport_header(skb) - skb_mac_header(skb))
|
|
+ tcp_hdrlen(skb));
|
|
while (tmp_len) {
|
|
num_segs++;
|
|
if (tmp_len > gso_size)
|
|
tmp_len -= gso_size;
|
|
else
|
|
break;
|
|
}
|
|
|
|
return num_segs;
|
|
|
|
/*
|
|
* Do not free this frame, just do socket level accounting
|
|
* so that this is not reused.
|
|
*/
|
|
fail:
|
|
if (skb->sk)
|
|
atomic_sub(skb->truesize, &(skb->sk->sk_wmem_alloc));
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
qdf_export_symbol(__qdf_nbuf_get_tso_num_seg);
|
|
|
|
#endif /* FEATURE_TSO */
|
|
|
|
void __qdf_dmaaddr_to_32s(qdf_dma_addr_t dmaaddr,
|
|
uint32_t *lo, uint32_t *hi)
|
|
{
|
|
if (sizeof(dmaaddr) > sizeof(uint32_t)) {
|
|
*lo = lower_32_bits(dmaaddr);
|
|
*hi = upper_32_bits(dmaaddr);
|
|
} else {
|
|
*lo = dmaaddr;
|
|
*hi = 0;
|
|
}
|
|
}
|
|
|
|
qdf_export_symbol(__qdf_dmaaddr_to_32s);
|
|
|
|
struct sk_buff *__qdf_nbuf_inc_users(struct sk_buff *skb)
|
|
{
|
|
qdf_nbuf_users_inc(&skb->users);
|
|
return skb;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_inc_users);
|
|
|
|
int __qdf_nbuf_get_users(struct sk_buff *skb)
|
|
{
|
|
return qdf_nbuf_users_read(&skb->users);
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_get_users);
|
|
|
|
void __qdf_nbuf_ref(struct sk_buff *skb)
|
|
{
|
|
skb_get(skb);
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_ref);
|
|
|
|
int __qdf_nbuf_shared(struct sk_buff *skb)
|
|
{
|
|
return skb_shared(skb);
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_shared);
|
|
|
|
QDF_STATUS
|
|
__qdf_nbuf_dmamap_create(qdf_device_t osdev, __qdf_dma_map_t *dmap)
|
|
{
|
|
QDF_STATUS error = QDF_STATUS_SUCCESS;
|
|
/*
|
|
* driver can tell its SG capability, it must be handled.
|
|
* Bounce buffers if they are there
|
|
*/
|
|
(*dmap) = kzalloc(sizeof(struct __qdf_dma_map), GFP_KERNEL);
|
|
if (!(*dmap))
|
|
error = QDF_STATUS_E_NOMEM;
|
|
|
|
return error;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_dmamap_create);
|
|
|
|
void
|
|
__qdf_nbuf_dmamap_destroy(qdf_device_t osdev, __qdf_dma_map_t dmap)
|
|
{
|
|
kfree(dmap);
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_dmamap_destroy);
|
|
|
|
#ifdef QDF_OS_DEBUG
|
|
QDF_STATUS
|
|
__qdf_nbuf_map_nbytes(
|
|
qdf_device_t osdev,
|
|
struct sk_buff *skb,
|
|
qdf_dma_dir_t dir,
|
|
int nbytes)
|
|
{
|
|
struct skb_shared_info *sh = skb_shinfo(skb);
|
|
|
|
qdf_assert((dir == QDF_DMA_TO_DEVICE) || (dir == QDF_DMA_FROM_DEVICE));
|
|
|
|
/*
|
|
* Assume there's only a single fragment.
|
|
* To support multiple fragments, it would be necessary to change
|
|
* adf_nbuf_t to be a separate object that stores meta-info
|
|
* (including the bus address for each fragment) and a pointer
|
|
* to the underlying sk_buff.
|
|
*/
|
|
qdf_assert(sh->nr_frags == 0);
|
|
|
|
return __qdf_nbuf_map_nbytes_single(osdev, skb, dir, nbytes);
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_map_nbytes);
|
|
#else
|
|
QDF_STATUS
|
|
__qdf_nbuf_map_nbytes(
|
|
qdf_device_t osdev,
|
|
struct sk_buff *skb,
|
|
qdf_dma_dir_t dir,
|
|
int nbytes)
|
|
{
|
|
return __qdf_nbuf_map_nbytes_single(osdev, skb, dir, nbytes);
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_map_nbytes);
|
|
#endif
|
|
void
|
|
__qdf_nbuf_unmap_nbytes(
|
|
qdf_device_t osdev,
|
|
struct sk_buff *skb,
|
|
qdf_dma_dir_t dir,
|
|
int nbytes)
|
|
{
|
|
qdf_assert((dir == QDF_DMA_TO_DEVICE) || (dir == QDF_DMA_FROM_DEVICE));
|
|
|
|
/*
|
|
* Assume there's a single fragment.
|
|
* If this is not true, the assertion in __adf_nbuf_map will catch it.
|
|
*/
|
|
__qdf_nbuf_unmap_nbytes_single(osdev, skb, dir, nbytes);
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_unmap_nbytes);
|
|
|
|
void
|
|
__qdf_nbuf_dma_map_info(__qdf_dma_map_t bmap, qdf_dmamap_info_t *sg)
|
|
{
|
|
qdf_assert(bmap->mapped);
|
|
qdf_assert(bmap->nsegs <= QDF_MAX_SCATTER);
|
|
|
|
memcpy(sg->dma_segs, bmap->seg, bmap->nsegs *
|
|
sizeof(struct __qdf_segment));
|
|
sg->nsegs = bmap->nsegs;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_dma_map_info);
|
|
|
|
#if defined(__QDF_SUPPORT_FRAG_MEM)
|
|
void
|
|
__qdf_nbuf_frag_info(struct sk_buff *skb, qdf_sglist_t *sg)
|
|
{
|
|
qdf_assert(skb);
|
|
sg->sg_segs[0].vaddr = skb->data;
|
|
sg->sg_segs[0].len = skb->len;
|
|
sg->nsegs = 1;
|
|
|
|
for (int i = 1; i <= sh->nr_frags; i++) {
|
|
skb_frag_t *f = &sh->frags[i - 1];
|
|
|
|
sg->sg_segs[i].vaddr = (uint8_t *)(page_address(f->page) +
|
|
f->page_offset);
|
|
sg->sg_segs[i].len = f->size;
|
|
|
|
qdf_assert(i < QDF_MAX_SGLIST);
|
|
}
|
|
sg->nsegs += i;
|
|
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_frag_info);
|
|
#else
|
|
#ifdef QDF_OS_DEBUG
|
|
void
|
|
__qdf_nbuf_frag_info(struct sk_buff *skb, qdf_sglist_t *sg)
|
|
{
|
|
|
|
struct skb_shared_info *sh = skb_shinfo(skb);
|
|
|
|
qdf_assert(skb);
|
|
sg->sg_segs[0].vaddr = skb->data;
|
|
sg->sg_segs[0].len = skb->len;
|
|
sg->nsegs = 1;
|
|
|
|
qdf_assert(sh->nr_frags == 0);
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_frag_info);
|
|
#else
|
|
void
|
|
__qdf_nbuf_frag_info(struct sk_buff *skb, qdf_sglist_t *sg)
|
|
{
|
|
sg->sg_segs[0].vaddr = skb->data;
|
|
sg->sg_segs[0].len = skb->len;
|
|
sg->nsegs = 1;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_frag_info);
|
|
#endif
|
|
#endif
|
|
uint32_t
|
|
__qdf_nbuf_get_frag_size(__qdf_nbuf_t nbuf, uint32_t cur_frag)
|
|
{
|
|
struct skb_shared_info *sh = skb_shinfo(nbuf);
|
|
const skb_frag_t *frag = sh->frags + cur_frag;
|
|
|
|
return skb_frag_size(frag);
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_get_frag_size);
|
|
|
|
#ifdef A_SIMOS_DEVHOST
|
|
QDF_STATUS __qdf_nbuf_frag_map(
|
|
qdf_device_t osdev, __qdf_nbuf_t nbuf,
|
|
int offset, qdf_dma_dir_t dir, int cur_frag)
|
|
{
|
|
int32_t paddr, frag_len;
|
|
|
|
QDF_NBUF_CB_PADDR(nbuf) = paddr = nbuf->data;
|
|
return QDF_STATUS_SUCCESS;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_frag_map);
|
|
#else
|
|
QDF_STATUS __qdf_nbuf_frag_map(
|
|
qdf_device_t osdev, __qdf_nbuf_t nbuf,
|
|
int offset, qdf_dma_dir_t dir, int cur_frag)
|
|
{
|
|
dma_addr_t paddr, frag_len;
|
|
struct skb_shared_info *sh = skb_shinfo(nbuf);
|
|
const skb_frag_t *frag = sh->frags + cur_frag;
|
|
|
|
frag_len = skb_frag_size(frag);
|
|
|
|
QDF_NBUF_CB_TX_EXTRA_FRAG_PADDR(nbuf) = paddr =
|
|
skb_frag_dma_map(osdev->dev, frag, offset, frag_len,
|
|
__qdf_dma_dir_to_os(dir));
|
|
return dma_mapping_error(osdev->dev, paddr) ?
|
|
QDF_STATUS_E_FAULT : QDF_STATUS_SUCCESS;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_frag_map);
|
|
#endif
|
|
void
|
|
__qdf_nbuf_dmamap_set_cb(__qdf_dma_map_t dmap, void *cb, void *arg)
|
|
{
|
|
return;
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_dmamap_set_cb);
|
|
|
|
/**
|
|
* __qdf_nbuf_sync_single_for_cpu() - nbuf sync
|
|
* @osdev: os device
|
|
* @buf: sk buff
|
|
* @dir: direction
|
|
*
|
|
* Return: none
|
|
*/
|
|
#if defined(A_SIMOS_DEVHOST)
|
|
static void __qdf_nbuf_sync_single_for_cpu(
|
|
qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir)
|
|
{
|
|
return;
|
|
}
|
|
#else
|
|
static void __qdf_nbuf_sync_single_for_cpu(
|
|
qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir)
|
|
{
|
|
if (0 == QDF_NBUF_CB_PADDR(buf)) {
|
|
qdf_err("ERROR: NBUF mapped physical address is NULL");
|
|
return;
|
|
}
|
|
dma_sync_single_for_cpu(osdev->dev, QDF_NBUF_CB_PADDR(buf),
|
|
skb_end_offset(buf) - skb_headroom(buf),
|
|
__qdf_dma_dir_to_os(dir));
|
|
}
|
|
#endif
|
|
|
|
void
|
|
__qdf_nbuf_sync_for_cpu(qdf_device_t osdev,
|
|
struct sk_buff *skb, qdf_dma_dir_t dir)
|
|
{
|
|
qdf_assert(
|
|
(dir == QDF_DMA_TO_DEVICE) || (dir == QDF_DMA_FROM_DEVICE));
|
|
|
|
/*
|
|
* Assume there's a single fragment.
|
|
* If this is not true, the assertion in __adf_nbuf_map will catch it.
|
|
*/
|
|
__qdf_nbuf_sync_single_for_cpu(osdev, skb, dir);
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_sync_for_cpu);
|
|
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0))
|
|
/**
|
|
* qdf_nbuf_update_radiotap_vht_flags() - Update radiotap header VHT flags
|
|
* @rx_status: Pointer to rx_status.
|
|
* @rtap_buf: Buf to which VHT info has to be updated.
|
|
* @rtap_len: Current length of radiotap buffer
|
|
*
|
|
* Return: Length of radiotap after VHT flags updated.
|
|
*/
|
|
static unsigned int qdf_nbuf_update_radiotap_vht_flags(
|
|
struct mon_rx_status *rx_status,
|
|
int8_t *rtap_buf,
|
|
uint32_t rtap_len)
|
|
{
|
|
uint16_t vht_flags = 0;
|
|
struct mon_rx_user_status *rx_user_status = rx_status->rx_user_status;
|
|
|
|
rtap_len = qdf_align(rtap_len, 2);
|
|
|
|
/* IEEE80211_RADIOTAP_VHT u16, u8, u8, u8[4], u8, u8, u16 */
|
|
vht_flags |= IEEE80211_RADIOTAP_VHT_KNOWN_STBC |
|
|
IEEE80211_RADIOTAP_VHT_KNOWN_GI |
|
|
IEEE80211_RADIOTAP_VHT_KNOWN_LDPC_EXTRA_OFDM_SYM |
|
|
IEEE80211_RADIOTAP_VHT_KNOWN_BEAMFORMED |
|
|
IEEE80211_RADIOTAP_VHT_KNOWN_BANDWIDTH |
|
|
IEEE80211_RADIOTAP_VHT_KNOWN_GROUP_ID;
|
|
put_unaligned_le16(vht_flags, &rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
rtap_buf[rtap_len] |=
|
|
(rx_status->is_stbc ?
|
|
IEEE80211_RADIOTAP_VHT_FLAG_STBC : 0) |
|
|
(rx_status->sgi ? IEEE80211_RADIOTAP_VHT_FLAG_SGI : 0) |
|
|
(rx_status->ldpc ?
|
|
IEEE80211_RADIOTAP_VHT_FLAG_LDPC_EXTRA_OFDM_SYM : 0) |
|
|
(rx_status->beamformed ?
|
|
IEEE80211_RADIOTAP_VHT_FLAG_BEAMFORMED : 0);
|
|
rtap_len += 1;
|
|
|
|
if (!rx_user_status) {
|
|
switch (rx_status->vht_flag_values2) {
|
|
case IEEE80211_RADIOTAP_VHT_BW_20:
|
|
rtap_buf[rtap_len] = RADIOTAP_VHT_BW_20;
|
|
break;
|
|
case IEEE80211_RADIOTAP_VHT_BW_40:
|
|
rtap_buf[rtap_len] = RADIOTAP_VHT_BW_40;
|
|
break;
|
|
case IEEE80211_RADIOTAP_VHT_BW_80:
|
|
rtap_buf[rtap_len] = RADIOTAP_VHT_BW_80;
|
|
break;
|
|
case IEEE80211_RADIOTAP_VHT_BW_160:
|
|
rtap_buf[rtap_len] = RADIOTAP_VHT_BW_160;
|
|
break;
|
|
}
|
|
rtap_len += 1;
|
|
rtap_buf[rtap_len] = (rx_status->vht_flag_values3[0]);
|
|
rtap_len += 1;
|
|
rtap_buf[rtap_len] = (rx_status->vht_flag_values3[1]);
|
|
rtap_len += 1;
|
|
rtap_buf[rtap_len] = (rx_status->vht_flag_values3[2]);
|
|
rtap_len += 1;
|
|
rtap_buf[rtap_len] = (rx_status->vht_flag_values3[3]);
|
|
rtap_len += 1;
|
|
rtap_buf[rtap_len] = (rx_status->vht_flag_values4);
|
|
rtap_len += 1;
|
|
rtap_buf[rtap_len] = (rx_status->vht_flag_values5);
|
|
rtap_len += 1;
|
|
put_unaligned_le16(rx_status->vht_flag_values6,
|
|
&rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
} else {
|
|
switch (rx_user_status->vht_flag_values2) {
|
|
case IEEE80211_RADIOTAP_VHT_BW_20:
|
|
rtap_buf[rtap_len] = RADIOTAP_VHT_BW_20;
|
|
break;
|
|
case IEEE80211_RADIOTAP_VHT_BW_40:
|
|
rtap_buf[rtap_len] = RADIOTAP_VHT_BW_40;
|
|
break;
|
|
case IEEE80211_RADIOTAP_VHT_BW_80:
|
|
rtap_buf[rtap_len] = RADIOTAP_VHT_BW_80;
|
|
break;
|
|
case IEEE80211_RADIOTAP_VHT_BW_160:
|
|
rtap_buf[rtap_len] = RADIOTAP_VHT_BW_160;
|
|
break;
|
|
}
|
|
rtap_len += 1;
|
|
rtap_buf[rtap_len] = (rx_user_status->vht_flag_values3[0]);
|
|
rtap_len += 1;
|
|
rtap_buf[rtap_len] = (rx_user_status->vht_flag_values3[1]);
|
|
rtap_len += 1;
|
|
rtap_buf[rtap_len] = (rx_user_status->vht_flag_values3[2]);
|
|
rtap_len += 1;
|
|
rtap_buf[rtap_len] = (rx_user_status->vht_flag_values3[3]);
|
|
rtap_len += 1;
|
|
rtap_buf[rtap_len] = (rx_user_status->vht_flag_values4);
|
|
rtap_len += 1;
|
|
rtap_buf[rtap_len] = (rx_user_status->vht_flag_values5);
|
|
rtap_len += 1;
|
|
put_unaligned_le16(rx_user_status->vht_flag_values6,
|
|
&rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
}
|
|
|
|
return rtap_len;
|
|
}
|
|
|
|
/**
|
|
* qdf_nbuf_update_radiotap_he_flags() - Update radiotap header from rx_status
|
|
* @rx_status: Pointer to rx_status.
|
|
* @rtap_buf: buffer to which radiotap has to be updated
|
|
* @rtap_len: radiotap length
|
|
*
|
|
* API update high-efficiency (11ax) fields in the radiotap header
|
|
*
|
|
* Return: length of rtap_len updated.
|
|
*/
|
|
static unsigned int
|
|
qdf_nbuf_update_radiotap_he_flags(struct mon_rx_status *rx_status,
|
|
int8_t *rtap_buf, uint32_t rtap_len)
|
|
{
|
|
/*
|
|
* IEEE80211_RADIOTAP_HE u16, u16, u16, u16, u16, u16
|
|
* Enable all "known" HE radiotap flags for now
|
|
*/
|
|
struct mon_rx_user_status *rx_user_status = rx_status->rx_user_status;
|
|
|
|
rtap_len = qdf_align(rtap_len, 2);
|
|
|
|
if (!rx_user_status) {
|
|
put_unaligned_le16(rx_status->he_data1, &rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
put_unaligned_le16(rx_status->he_data2, &rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
put_unaligned_le16(rx_status->he_data3, &rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
put_unaligned_le16(rx_status->he_data4, &rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
put_unaligned_le16(rx_status->he_data5, &rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
put_unaligned_le16(rx_status->he_data6, &rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
} else {
|
|
put_unaligned_le16(rx_user_status->he_data1 |
|
|
rx_status->he_data1, &rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
put_unaligned_le16(rx_user_status->he_data2 |
|
|
rx_status->he_data2, &rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
put_unaligned_le16(rx_user_status->he_data3 |
|
|
rx_status->he_data3, &rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
put_unaligned_le16(rx_user_status->he_data4 |
|
|
rx_status->he_data4, &rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
put_unaligned_le16(rx_user_status->he_data5 |
|
|
rx_status->he_data5, &rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
put_unaligned_le16(rx_user_status->he_data6 |
|
|
rx_status->he_data6, &rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
}
|
|
|
|
return rtap_len;
|
|
}
|
|
|
|
|
|
/**
|
|
* qdf_nbuf_update_radiotap_he_mu_flags() - update he-mu radiotap flags
|
|
* @rx_status: Pointer to rx_status.
|
|
* @rtap_buf: buffer to which radiotap has to be updated
|
|
* @rtap_len: radiotap length
|
|
*
|
|
* API update HE-MU fields in the radiotap header
|
|
*
|
|
* Return: length of rtap_len updated.
|
|
*/
|
|
static unsigned int
|
|
qdf_nbuf_update_radiotap_he_mu_flags(struct mon_rx_status *rx_status,
|
|
int8_t *rtap_buf, uint32_t rtap_len)
|
|
{
|
|
struct mon_rx_user_status *rx_user_status = rx_status->rx_user_status;
|
|
|
|
rtap_len = qdf_align(rtap_len, 2);
|
|
|
|
/*
|
|
* IEEE80211_RADIOTAP_HE_MU u16, u16, u8[4]
|
|
* Enable all "known" he-mu radiotap flags for now
|
|
*/
|
|
|
|
if (!rx_user_status) {
|
|
put_unaligned_le16(rx_status->he_flags1, &rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
put_unaligned_le16(rx_status->he_flags2, &rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
rtap_buf[rtap_len] = rx_status->he_RU[0];
|
|
rtap_len += 1;
|
|
|
|
rtap_buf[rtap_len] = rx_status->he_RU[1];
|
|
rtap_len += 1;
|
|
|
|
rtap_buf[rtap_len] = rx_status->he_RU[2];
|
|
rtap_len += 1;
|
|
|
|
rtap_buf[rtap_len] = rx_status->he_RU[3];
|
|
rtap_len += 1;
|
|
} else {
|
|
put_unaligned_le16(rx_user_status->he_flags1,
|
|
&rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
put_unaligned_le16(rx_user_status->he_flags2,
|
|
&rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
rtap_buf[rtap_len] = rx_user_status->he_RU[0];
|
|
rtap_len += 1;
|
|
|
|
rtap_buf[rtap_len] = rx_user_status->he_RU[1];
|
|
rtap_len += 1;
|
|
|
|
rtap_buf[rtap_len] = rx_user_status->he_RU[2];
|
|
rtap_len += 1;
|
|
|
|
rtap_buf[rtap_len] = rx_user_status->he_RU[3];
|
|
rtap_len += 1;
|
|
qdf_debug("he_flags %x %x he-RU %x %x %x %x",
|
|
rx_user_status->he_flags1,
|
|
rx_user_status->he_flags2, rx_user_status->he_RU[0],
|
|
rx_user_status->he_RU[1], rx_user_status->he_RU[2],
|
|
rx_user_status->he_RU[3]);
|
|
}
|
|
|
|
return rtap_len;
|
|
}
|
|
|
|
/**
|
|
* qdf_nbuf_update_radiotap_he_mu_other_flags() - update he_mu_other flags
|
|
* @rx_status: Pointer to rx_status.
|
|
* @rtap_buf: buffer to which radiotap has to be updated
|
|
* @rtap_len: radiotap length
|
|
*
|
|
* API update he-mu-other fields in the radiotap header
|
|
*
|
|
* Return: length of rtap_len updated.
|
|
*/
|
|
static unsigned int
|
|
qdf_nbuf_update_radiotap_he_mu_other_flags(struct mon_rx_status *rx_status,
|
|
int8_t *rtap_buf, uint32_t rtap_len)
|
|
{
|
|
struct mon_rx_user_status *rx_user_status = rx_status->rx_user_status;
|
|
|
|
rtap_len = qdf_align(rtap_len, 2);
|
|
|
|
/*
|
|
* IEEE80211_RADIOTAP_HE-MU-OTHER u16, u16, u8, u8
|
|
* Enable all "known" he-mu-other radiotap flags for now
|
|
*/
|
|
if (!rx_user_status) {
|
|
put_unaligned_le16(rx_status->he_per_user_1,
|
|
&rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
put_unaligned_le16(rx_status->he_per_user_2,
|
|
&rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
rtap_buf[rtap_len] = rx_status->he_per_user_position;
|
|
rtap_len += 1;
|
|
|
|
rtap_buf[rtap_len] = rx_status->he_per_user_known;
|
|
rtap_len += 1;
|
|
} else {
|
|
put_unaligned_le16(rx_user_status->he_per_user_1,
|
|
&rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
put_unaligned_le16(rx_user_status->he_per_user_2,
|
|
&rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
rtap_buf[rtap_len] = rx_user_status->he_per_user_position;
|
|
rtap_len += 1;
|
|
|
|
rtap_buf[rtap_len] = rx_user_status->he_per_user_known;
|
|
rtap_len += 1;
|
|
}
|
|
|
|
return rtap_len;
|
|
}
|
|
|
|
/**
|
|
* qdf_nbuf_update_radiotap_usig_flags() - Update radiotap header with USIG data
|
|
* from rx_status
|
|
* @rx_status: Pointer to rx_status.
|
|
* @rtap_buf: buffer to which radiotap has to be updated
|
|
* @rtap_len: radiotap length
|
|
*
|
|
* API update Extra High Throughput (11be) fields in the radiotap header
|
|
*
|
|
* Return: length of rtap_len updated.
|
|
*/
|
|
static unsigned int
|
|
qdf_nbuf_update_radiotap_usig_flags(struct mon_rx_status *rx_status,
|
|
int8_t *rtap_buf, uint32_t rtap_len)
|
|
{
|
|
/*
|
|
* IEEE80211_RADIOTAP_USIG:
|
|
* u32, u32, u32
|
|
*/
|
|
rtap_len = qdf_align(rtap_len, 4);
|
|
|
|
put_unaligned_le32(rx_status->usig_common, &rtap_buf[rtap_len]);
|
|
rtap_len += 4;
|
|
|
|
put_unaligned_le32(rx_status->usig_value, &rtap_buf[rtap_len]);
|
|
rtap_len += 4;
|
|
|
|
put_unaligned_le32(rx_status->usig_mask, &rtap_buf[rtap_len]);
|
|
rtap_len += 4;
|
|
|
|
qdf_rl_debug("U-SIG data %x %x %x",
|
|
rx_status->usig_common, rx_status->usig_value,
|
|
rx_status->usig_mask);
|
|
|
|
return rtap_len;
|
|
}
|
|
|
|
/**
|
|
* qdf_nbuf_update_radiotap_eht_flags() - Update radiotap header with EHT data
|
|
* from rx_status
|
|
* @rx_status: Pointer to rx_status.
|
|
* @rtap_buf: buffer to which radiotap has to be updated
|
|
* @rtap_len: radiotap length
|
|
*
|
|
* API update Extra High Throughput (11be) fields in the radiotap header
|
|
*
|
|
* Return: length of rtap_len updated.
|
|
*/
|
|
static unsigned int
|
|
qdf_nbuf_update_radiotap_eht_flags(struct mon_rx_status *rx_status,
|
|
int8_t *rtap_buf, uint32_t rtap_len)
|
|
{
|
|
struct mon_rx_user_status *rx_user_status = rx_status->rx_user_status;
|
|
/*
|
|
* IEEE80211_RADIOTAP_EHT:
|
|
* u32, u32, u32, u32, u32, u32, u32, u16, [u32, u32, u32]
|
|
*/
|
|
rtap_len = qdf_align(rtap_len, 4);
|
|
|
|
if (!rx_user_status) {
|
|
put_unaligned_le32(rx_status->eht_known, &rtap_buf[rtap_len]);
|
|
rtap_len += 4;
|
|
|
|
put_unaligned_le32(rx_status->eht_data[0], &rtap_buf[rtap_len]);
|
|
rtap_len += 4;
|
|
|
|
put_unaligned_le32(rx_status->eht_data[1], &rtap_buf[rtap_len]);
|
|
rtap_len += 4;
|
|
} else {
|
|
put_unaligned_le32(rx_status->eht_known |
|
|
rx_user_status->eht_known,
|
|
&rtap_buf[rtap_len]);
|
|
rtap_len += 4;
|
|
|
|
put_unaligned_le32(rx_status->eht_data[0] |
|
|
rx_user_status->eht_data[0],
|
|
&rtap_buf[rtap_len]);
|
|
rtap_len += 4;
|
|
|
|
put_unaligned_le32(rx_status->eht_data[1] |
|
|
rx_user_status->eht_data[1],
|
|
&rtap_buf[rtap_len]);
|
|
rtap_len += 4;
|
|
}
|
|
|
|
put_unaligned_le32(rx_status->eht_data[2], &rtap_buf[rtap_len]);
|
|
rtap_len += 4;
|
|
|
|
put_unaligned_le32(rx_status->eht_data[3], &rtap_buf[rtap_len]);
|
|
rtap_len += 4;
|
|
|
|
put_unaligned_le32(rx_status->eht_data[4], &rtap_buf[rtap_len]);
|
|
rtap_len += 4;
|
|
|
|
put_unaligned_le32(rx_status->eht_data[5], &rtap_buf[rtap_len]);
|
|
rtap_len += 4;
|
|
|
|
if (!rx_user_status) {
|
|
qdf_rl_debug("EHT data %x %x %x %x %x %x %x",
|
|
rx_status->eht_known, rx_status->eht_data[0],
|
|
rx_status->eht_data[1], rx_status->eht_data[2],
|
|
rx_status->eht_data[3], rx_status->eht_data[4],
|
|
rx_status->eht_data[5]);
|
|
} else {
|
|
put_unaligned_le32(rx_user_status->eht_user_info, &rtap_buf[rtap_len]);
|
|
rtap_len += 4;
|
|
|
|
qdf_rl_debug("EHT data %x %x %x %x %x %x %x",
|
|
rx_status->eht_known | rx_user_status->eht_known,
|
|
rx_status->eht_data[0] |
|
|
rx_user_status->eht_data[0],
|
|
rx_status->eht_data[1] |
|
|
rx_user_status->eht_data[1],
|
|
rx_status->eht_data[2], rx_status->eht_data[3],
|
|
rx_status->eht_data[4], rx_status->eht_data[5]);
|
|
}
|
|
|
|
return rtap_len;
|
|
}
|
|
|
|
#define IEEE80211_RADIOTAP_TX_STATUS 0
|
|
#define IEEE80211_RADIOTAP_RETRY_COUNT 1
|
|
#define IEEE80211_RADIOTAP_EXTENSION2 2
|
|
uint8_t ATH_OUI[] = {0x00, 0x03, 0x7f}; /* Atheros OUI */
|
|
|
|
/**
|
|
* qdf_nbuf_update_radiotap_ampdu_flags() - Update radiotap header ampdu flags
|
|
* @rx_status: Pointer to rx_status.
|
|
* @rtap_buf: Buf to which AMPDU info has to be updated.
|
|
* @rtap_len: Current length of radiotap buffer
|
|
*
|
|
* Return: Length of radiotap after AMPDU flags updated.
|
|
*/
|
|
static unsigned int qdf_nbuf_update_radiotap_ampdu_flags(
|
|
struct mon_rx_status *rx_status,
|
|
uint8_t *rtap_buf,
|
|
uint32_t rtap_len)
|
|
{
|
|
/*
|
|
* IEEE80211_RADIOTAP_AMPDU_STATUS u32 u16 u8 u8
|
|
* First 32 bits of AMPDU represents the reference number
|
|
*/
|
|
|
|
uint32_t ampdu_reference_num = rx_status->ppdu_id;
|
|
uint16_t ampdu_flags = 0;
|
|
uint16_t ampdu_reserved_flags = 0;
|
|
|
|
rtap_len = qdf_align(rtap_len, 4);
|
|
|
|
put_unaligned_le32(ampdu_reference_num, &rtap_buf[rtap_len]);
|
|
rtap_len += 4;
|
|
put_unaligned_le16(ampdu_flags, &rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
put_unaligned_le16(ampdu_reserved_flags, &rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
return rtap_len;
|
|
}
|
|
|
|
#ifdef DP_MON_RSSI_IN_DBM
|
|
#define QDF_MON_STATUS_GET_RSSI_IN_DBM(rx_status) \
|
|
(rx_status->rssi_comb)
|
|
#else
|
|
#ifdef QCA_RSSI_DB2DBM
|
|
#define QDF_MON_STATUS_GET_RSSI_IN_DBM(rx_status) \
|
|
(((rx_status)->rssi_dbm_conv_support) ? \
|
|
((rx_status)->rssi_comb + (rx_status)->rssi_offset) :\
|
|
((rx_status)->rssi_comb + (rx_status)->chan_noise_floor))
|
|
#else
|
|
#define QDF_MON_STATUS_GET_RSSI_IN_DBM(rx_status) \
|
|
(rx_status->rssi_comb + rx_status->chan_noise_floor)
|
|
#endif
|
|
#endif
|
|
|
|
/**
|
|
* qdf_nbuf_update_radiotap_tx_flags() - Update radiotap header tx flags
|
|
* @rx_status: Pointer to rx_status.
|
|
* @rtap_buf: Buf to which tx info has to be updated.
|
|
* @rtap_len: Current length of radiotap buffer
|
|
*
|
|
* Return: Length of radiotap after tx flags updated.
|
|
*/
|
|
static unsigned int qdf_nbuf_update_radiotap_tx_flags(
|
|
struct mon_rx_status *rx_status,
|
|
uint8_t *rtap_buf,
|
|
uint32_t rtap_len)
|
|
{
|
|
/*
|
|
* IEEE80211_RADIOTAP_TX_FLAGS u16
|
|
*/
|
|
|
|
uint16_t tx_flags = 0;
|
|
|
|
rtap_len = qdf_align(rtap_len, 2);
|
|
|
|
switch (rx_status->tx_status) {
|
|
case RADIOTAP_TX_STATUS_FAIL:
|
|
tx_flags |= IEEE80211_RADIOTAP_F_TX_FAIL;
|
|
break;
|
|
case RADIOTAP_TX_STATUS_NOACK:
|
|
tx_flags |= IEEE80211_RADIOTAP_F_TX_NOACK;
|
|
break;
|
|
}
|
|
put_unaligned_le16(tx_flags, &rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
return rtap_len;
|
|
}
|
|
|
|
unsigned int qdf_nbuf_update_radiotap(struct mon_rx_status *rx_status,
|
|
qdf_nbuf_t nbuf, uint32_t headroom_sz)
|
|
{
|
|
uint8_t rtap_buf[RADIOTAP_HEADER_LEN] = {0};
|
|
struct ieee80211_radiotap_header *rthdr =
|
|
(struct ieee80211_radiotap_header *)rtap_buf;
|
|
uint32_t rtap_hdr_len = sizeof(struct ieee80211_radiotap_header);
|
|
uint32_t rtap_len = rtap_hdr_len;
|
|
uint8_t length = rtap_len;
|
|
struct qdf_radiotap_vendor_ns_ath *radiotap_vendor_ns_ath;
|
|
struct qdf_radiotap_ext2 *rtap_ext2;
|
|
struct mon_rx_user_status *rx_user_status = rx_status->rx_user_status;
|
|
|
|
/* per user info */
|
|
qdf_le32_t *it_present;
|
|
uint32_t it_present_val;
|
|
bool radiotap_ext1_hdr_present = false;
|
|
|
|
it_present = &rthdr->it_present;
|
|
|
|
/* Adding Extended Header space */
|
|
if (rx_status->add_rtap_ext || rx_status->add_rtap_ext2 ||
|
|
rx_status->usig_flags || rx_status->eht_flags) {
|
|
rtap_hdr_len += RADIOTAP_HEADER_EXT_LEN;
|
|
rtap_len = rtap_hdr_len;
|
|
radiotap_ext1_hdr_present = true;
|
|
}
|
|
|
|
length = rtap_len;
|
|
|
|
/* IEEE80211_RADIOTAP_TSFT __le64 microseconds*/
|
|
it_present_val = (1 << IEEE80211_RADIOTAP_TSFT);
|
|
put_unaligned_le64(rx_status->tsft, &rtap_buf[rtap_len]);
|
|
rtap_len += 8;
|
|
|
|
/* IEEE80211_RADIOTAP_FLAGS u8 */
|
|
it_present_val |= (1 << IEEE80211_RADIOTAP_FLAGS);
|
|
|
|
if (rx_status->rs_fcs_err)
|
|
rx_status->rtap_flags |= IEEE80211_RADIOTAP_F_BADFCS;
|
|
|
|
rtap_buf[rtap_len] = rx_status->rtap_flags;
|
|
rtap_len += 1;
|
|
|
|
/* IEEE80211_RADIOTAP_RATE u8 500kb/s */
|
|
if (!rx_status->ht_flags && !rx_status->vht_flags &&
|
|
!rx_status->he_flags && !rx_status->eht_flags) {
|
|
it_present_val |= (1 << IEEE80211_RADIOTAP_RATE);
|
|
rtap_buf[rtap_len] = rx_status->rate;
|
|
} else
|
|
rtap_buf[rtap_len] = 0;
|
|
rtap_len += 1;
|
|
|
|
/* IEEE80211_RADIOTAP_CHANNEL 2 x __le16 MHz, bitmap */
|
|
it_present_val |= (1 << IEEE80211_RADIOTAP_CHANNEL);
|
|
put_unaligned_le16(rx_status->chan_freq, &rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
/* Channel flags. */
|
|
if (rx_status->chan_freq > CHANNEL_FREQ_5150)
|
|
rx_status->chan_flags = RADIOTAP_5G_SPECTRUM_CHANNEL;
|
|
else
|
|
rx_status->chan_flags = RADIOTAP_2G_SPECTRUM_CHANNEL;
|
|
if (rx_status->cck_flag)
|
|
rx_status->chan_flags |= RADIOTAP_CCK_CHANNEL;
|
|
if (rx_status->ofdm_flag)
|
|
rx_status->chan_flags |= RADIOTAP_OFDM_CHANNEL;
|
|
put_unaligned_le16(rx_status->chan_flags, &rtap_buf[rtap_len]);
|
|
rtap_len += 2;
|
|
|
|
/* IEEE80211_RADIOTAP_DBM_ANTSIGNAL s8 decibels from one milliwatt
|
|
* (dBm)
|
|
*/
|
|
it_present_val |= (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL);
|
|
/*
|
|
* rssi_comb is int dB, need to convert it to dBm.
|
|
* normalize value to noise floor of -96 dBm
|
|
*/
|
|
rtap_buf[rtap_len] = QDF_MON_STATUS_GET_RSSI_IN_DBM(rx_status);
|
|
rtap_len += 1;
|
|
|
|
/* RX signal noise floor */
|
|
it_present_val |= (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE);
|
|
rtap_buf[rtap_len] = (uint8_t)rx_status->chan_noise_floor;
|
|
rtap_len += 1;
|
|
|
|
/* IEEE80211_RADIOTAP_ANTENNA u8 antenna index */
|
|
it_present_val |= (1 << IEEE80211_RADIOTAP_ANTENNA);
|
|
rtap_buf[rtap_len] = rx_status->nr_ant;
|
|
rtap_len += 1;
|
|
|
|
if ((rtap_len - length) > RADIOTAP_FIXED_HEADER_LEN) {
|
|
qdf_print("length is greater than RADIOTAP_FIXED_HEADER_LEN");
|
|
return 0;
|
|
}
|
|
|
|
/* update tx flags for pkt capture*/
|
|
if (rx_status->add_rtap_ext) {
|
|
it_present_val |=
|
|
cpu_to_le32(1 << IEEE80211_RADIOTAP_TX_FLAGS);
|
|
rtap_len = qdf_nbuf_update_radiotap_tx_flags(rx_status,
|
|
rtap_buf,
|
|
rtap_len);
|
|
|
|
if ((rtap_len - length) > RADIOTAP_TX_FLAGS_LEN) {
|
|
qdf_print("length is greater than RADIOTAP_TX_FLAGS_LEN");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (rx_status->ht_flags) {
|
|
length = rtap_len;
|
|
/* IEEE80211_RADIOTAP_VHT u8, u8, u8 */
|
|
it_present_val |= (1 << IEEE80211_RADIOTAP_MCS);
|
|
rtap_buf[rtap_len] = IEEE80211_RADIOTAP_MCS_HAVE_BW |
|
|
IEEE80211_RADIOTAP_MCS_HAVE_MCS |
|
|
IEEE80211_RADIOTAP_MCS_HAVE_GI;
|
|
rtap_len += 1;
|
|
|
|
if (rx_status->sgi)
|
|
rtap_buf[rtap_len] |= IEEE80211_RADIOTAP_MCS_SGI;
|
|
if (rx_status->bw)
|
|
rtap_buf[rtap_len] |= IEEE80211_RADIOTAP_MCS_BW_40;
|
|
else
|
|
rtap_buf[rtap_len] |= IEEE80211_RADIOTAP_MCS_BW_20;
|
|
rtap_len += 1;
|
|
|
|
rtap_buf[rtap_len] = rx_status->ht_mcs;
|
|
rtap_len += 1;
|
|
|
|
if ((rtap_len - length) > RADIOTAP_HT_FLAGS_LEN) {
|
|
qdf_print("length is greater than RADIOTAP_HT_FLAGS_LEN");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (rx_status->rs_flags & IEEE80211_AMPDU_FLAG) {
|
|
/* IEEE80211_RADIOTAP_AMPDU_STATUS u32 u16 u8 u8 */
|
|
it_present_val |= (1 << IEEE80211_RADIOTAP_AMPDU_STATUS);
|
|
rtap_len = qdf_nbuf_update_radiotap_ampdu_flags(rx_status,
|
|
rtap_buf,
|
|
rtap_len);
|
|
}
|
|
|
|
if (rx_status->vht_flags) {
|
|
length = rtap_len;
|
|
/* IEEE80211_RADIOTAP_VHT u16, u8, u8, u8[4], u8, u8, u16 */
|
|
it_present_val |= (1 << IEEE80211_RADIOTAP_VHT);
|
|
rtap_len = qdf_nbuf_update_radiotap_vht_flags(rx_status,
|
|
rtap_buf,
|
|
rtap_len);
|
|
|
|
if ((rtap_len - length) > RADIOTAP_VHT_FLAGS_LEN) {
|
|
qdf_print("length is greater than RADIOTAP_VHT_FLAGS_LEN");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (rx_status->he_flags) {
|
|
length = rtap_len;
|
|
/* IEEE80211_RADIOTAP_HE */
|
|
it_present_val |= (1 << IEEE80211_RADIOTAP_HE);
|
|
rtap_len = qdf_nbuf_update_radiotap_he_flags(rx_status,
|
|
rtap_buf,
|
|
rtap_len);
|
|
|
|
if ((rtap_len - length) > RADIOTAP_HE_FLAGS_LEN) {
|
|
qdf_print("length is greater than RADIOTAP_HE_FLAGS_LEN");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (rx_status->he_mu_flags) {
|
|
length = rtap_len;
|
|
/* IEEE80211_RADIOTAP_HE-MU */
|
|
it_present_val |= (1 << IEEE80211_RADIOTAP_HE_MU);
|
|
rtap_len = qdf_nbuf_update_radiotap_he_mu_flags(rx_status,
|
|
rtap_buf,
|
|
rtap_len);
|
|
|
|
if ((rtap_len - length) > RADIOTAP_HE_MU_FLAGS_LEN) {
|
|
qdf_print("length is greater than RADIOTAP_HE_MU_FLAGS_LEN");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (rx_status->he_mu_other_flags) {
|
|
length = rtap_len;
|
|
/* IEEE80211_RADIOTAP_HE-MU-OTHER */
|
|
it_present_val |= (1 << IEEE80211_RADIOTAP_HE_MU_OTHER);
|
|
rtap_len =
|
|
qdf_nbuf_update_radiotap_he_mu_other_flags(rx_status,
|
|
rtap_buf,
|
|
rtap_len);
|
|
|
|
if ((rtap_len - length) > RADIOTAP_HE_MU_OTHER_FLAGS_LEN) {
|
|
qdf_print("length is greater than RADIOTAP_HE_MU_OTHER_FLAGS_LEN");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
rtap_len = qdf_align(rtap_len, 2);
|
|
/*
|
|
* Radiotap Vendor Namespace
|
|
*/
|
|
it_present_val |= (1 << IEEE80211_RADIOTAP_VENDOR_NAMESPACE);
|
|
radiotap_vendor_ns_ath = (struct qdf_radiotap_vendor_ns_ath *)
|
|
(rtap_buf + rtap_len);
|
|
/*
|
|
* Copy Atheros OUI - 3 bytes (4th byte is 0)
|
|
*/
|
|
qdf_mem_copy(radiotap_vendor_ns_ath->hdr.oui, ATH_OUI, sizeof(ATH_OUI));
|
|
/*
|
|
* Name space selector = 0
|
|
* We only will have one namespace for now
|
|
*/
|
|
radiotap_vendor_ns_ath->hdr.selector = 0;
|
|
radiotap_vendor_ns_ath->hdr.skip_length = cpu_to_le16(
|
|
sizeof(*radiotap_vendor_ns_ath) -
|
|
sizeof(radiotap_vendor_ns_ath->hdr));
|
|
radiotap_vendor_ns_ath->device_id = cpu_to_le32(rx_status->device_id);
|
|
radiotap_vendor_ns_ath->lsig = cpu_to_le32(rx_status->l_sig_a_info);
|
|
radiotap_vendor_ns_ath->lsig_b = cpu_to_le32(rx_status->l_sig_b_info);
|
|
radiotap_vendor_ns_ath->ppdu_start_timestamp =
|
|
cpu_to_le32(rx_status->ppdu_timestamp);
|
|
rtap_len += sizeof(*radiotap_vendor_ns_ath);
|
|
|
|
/* Move to next it_present */
|
|
if (radiotap_ext1_hdr_present) {
|
|
it_present_val |= (1 << IEEE80211_RADIOTAP_EXT);
|
|
put_unaligned_le32(it_present_val, it_present);
|
|
it_present_val = 0;
|
|
it_present++;
|
|
}
|
|
|
|
/* Add Extension to Radiotap Header & corresponding data */
|
|
if (rx_status->add_rtap_ext) {
|
|
it_present_val |= (1 << IEEE80211_RADIOTAP_TX_STATUS);
|
|
it_present_val |= (1 << IEEE80211_RADIOTAP_RETRY_COUNT);
|
|
|
|
rtap_buf[rtap_len] = rx_status->tx_status;
|
|
rtap_len += 1;
|
|
rtap_buf[rtap_len] = rx_status->tx_retry_cnt;
|
|
rtap_len += 1;
|
|
}
|
|
|
|
/* Add Extension2 to Radiotap Header */
|
|
if (rx_status->add_rtap_ext2) {
|
|
it_present_val |= (1 << IEEE80211_RADIOTAP_EXTENSION2);
|
|
|
|
rtap_ext2 = (struct qdf_radiotap_ext2 *)(rtap_buf + rtap_len);
|
|
rtap_ext2->ppdu_id = rx_status->ppdu_id;
|
|
rtap_ext2->prev_ppdu_id = rx_status->prev_ppdu_id;
|
|
if (!rx_user_status) {
|
|
rtap_ext2->tid = rx_status->tid;
|
|
rtap_ext2->start_seq = rx_status->start_seq;
|
|
qdf_mem_copy(rtap_ext2->ba_bitmap,
|
|
rx_status->ba_bitmap,
|
|
8 * (sizeof(uint32_t)));
|
|
} else {
|
|
uint8_t ba_bitmap_sz = rx_user_status->ba_bitmap_sz;
|
|
|
|
/* set default bitmap sz if not set */
|
|
ba_bitmap_sz = ba_bitmap_sz ? ba_bitmap_sz : 8;
|
|
rtap_ext2->tid = rx_user_status->tid;
|
|
rtap_ext2->start_seq = rx_user_status->start_seq;
|
|
qdf_mem_copy(rtap_ext2->ba_bitmap,
|
|
rx_user_status->ba_bitmap,
|
|
ba_bitmap_sz * (sizeof(uint32_t)));
|
|
}
|
|
|
|
rtap_len += sizeof(*rtap_ext2);
|
|
}
|
|
|
|
if (rx_status->usig_flags) {
|
|
length = rtap_len;
|
|
/* IEEE80211_RADIOTAP_USIG */
|
|
it_present_val |= (1 << IEEE80211_RADIOTAP_EXT1_USIG);
|
|
rtap_len = qdf_nbuf_update_radiotap_usig_flags(rx_status,
|
|
rtap_buf,
|
|
rtap_len);
|
|
|
|
if ((rtap_len - length) > RADIOTAP_EHT_FLAGS_LEN) {
|
|
qdf_print("length is greater than RADIOTAP_EHT_FLAGS_LEN");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (rx_status->eht_flags) {
|
|
length = rtap_len;
|
|
/* IEEE80211_RADIOTAP_EHT */
|
|
it_present_val |= (1 << IEEE80211_RADIOTAP_EXT1_EHT);
|
|
rtap_len = qdf_nbuf_update_radiotap_eht_flags(rx_status,
|
|
rtap_buf,
|
|
rtap_len);
|
|
|
|
if ((rtap_len - length) > RADIOTAP_EHT_FLAGS_LEN) {
|
|
qdf_print("length is greater than RADIOTAP_EHT_FLAGS_LEN");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
put_unaligned_le32(it_present_val, it_present);
|
|
rthdr->it_len = cpu_to_le16(rtap_len);
|
|
|
|
if (headroom_sz < rtap_len) {
|
|
qdf_debug("DEBUG: Not enough space to update radiotap");
|
|
return 0;
|
|
}
|
|
|
|
qdf_nbuf_push_head(nbuf, rtap_len);
|
|
qdf_mem_copy(qdf_nbuf_data(nbuf), rtap_buf, rtap_len);
|
|
return rtap_len;
|
|
}
|
|
#else
|
|
static unsigned int qdf_nbuf_update_radiotap_vht_flags(
|
|
struct mon_rx_status *rx_status,
|
|
int8_t *rtap_buf,
|
|
uint32_t rtap_len)
|
|
{
|
|
qdf_err("ERROR: struct ieee80211_radiotap_header not supported");
|
|
return 0;
|
|
}
|
|
|
|
unsigned int qdf_nbuf_update_radiotap_he_flags(struct mon_rx_status *rx_status,
|
|
int8_t *rtap_buf, uint32_t rtap_len)
|
|
{
|
|
qdf_err("ERROR: struct ieee80211_radiotap_header not supported");
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int qdf_nbuf_update_radiotap_ampdu_flags(
|
|
struct mon_rx_status *rx_status,
|
|
uint8_t *rtap_buf,
|
|
uint32_t rtap_len)
|
|
{
|
|
qdf_err("ERROR: struct ieee80211_radiotap_header not supported");
|
|
return 0;
|
|
}
|
|
|
|
unsigned int qdf_nbuf_update_radiotap(struct mon_rx_status *rx_status,
|
|
qdf_nbuf_t nbuf, uint32_t headroom_sz)
|
|
{
|
|
qdf_err("ERROR: struct ieee80211_radiotap_header not supported");
|
|
return 0;
|
|
}
|
|
#endif
|
|
qdf_export_symbol(qdf_nbuf_update_radiotap);
|
|
|
|
void __qdf_nbuf_reg_free_cb(qdf_nbuf_free_t cb_func_ptr)
|
|
{
|
|
nbuf_free_cb = cb_func_ptr;
|
|
}
|
|
|
|
qdf_export_symbol(__qdf_nbuf_reg_free_cb);
|
|
|
|
void qdf_nbuf_classify_pkt(struct sk_buff *skb)
|
|
{
|
|
struct ethhdr *eh = (struct ethhdr *)skb->data;
|
|
|
|
/* check destination mac address is broadcast/multicast */
|
|
if (is_broadcast_ether_addr((uint8_t *)eh))
|
|
QDF_NBUF_CB_SET_BCAST(skb);
|
|
else if (is_multicast_ether_addr((uint8_t *)eh))
|
|
QDF_NBUF_CB_SET_MCAST(skb);
|
|
|
|
if (qdf_nbuf_is_ipv4_arp_pkt(skb))
|
|
QDF_NBUF_CB_GET_PACKET_TYPE(skb) =
|
|
QDF_NBUF_CB_PACKET_TYPE_ARP;
|
|
else if (qdf_nbuf_is_ipv4_dhcp_pkt(skb))
|
|
QDF_NBUF_CB_GET_PACKET_TYPE(skb) =
|
|
QDF_NBUF_CB_PACKET_TYPE_DHCP;
|
|
else if (qdf_nbuf_is_ipv4_eapol_pkt(skb))
|
|
QDF_NBUF_CB_GET_PACKET_TYPE(skb) =
|
|
QDF_NBUF_CB_PACKET_TYPE_EAPOL;
|
|
else if (qdf_nbuf_is_ipv4_wapi_pkt(skb))
|
|
QDF_NBUF_CB_GET_PACKET_TYPE(skb) =
|
|
QDF_NBUF_CB_PACKET_TYPE_WAPI;
|
|
}
|
|
qdf_export_symbol(qdf_nbuf_classify_pkt);
|
|
|
|
void __qdf_nbuf_init(__qdf_nbuf_t nbuf)
|
|
{
|
|
qdf_nbuf_users_set(&nbuf->users, 1);
|
|
nbuf->data = nbuf->head + NET_SKB_PAD;
|
|
skb_reset_tail_pointer(nbuf);
|
|
}
|
|
qdf_export_symbol(__qdf_nbuf_init);
|
|
|
|
#ifdef WLAN_FEATURE_FASTPATH
|
|
void qdf_nbuf_init_fast(qdf_nbuf_t nbuf)
|
|
{
|
|
qdf_nbuf_users_set(&nbuf->users, 1);
|
|
skb_reset_tail_pointer(nbuf);
|
|
}
|
|
qdf_export_symbol(qdf_nbuf_init_fast);
|
|
#endif /* WLAN_FEATURE_FASTPATH */
|
|
|
|
|
|
#ifdef QDF_NBUF_GLOBAL_COUNT
|
|
void __qdf_nbuf_mod_init(void)
|
|
{
|
|
is_initial_mem_debug_disabled = qdf_mem_debug_config_get();
|
|
qdf_atomic_init(&nbuf_count);
|
|
qdf_debugfs_create_atomic(NBUF_DEBUGFS_NAME, S_IRUSR, NULL, &nbuf_count);
|
|
}
|
|
|
|
void __qdf_nbuf_mod_exit(void)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0))
|
|
QDF_STATUS __qdf_nbuf_move_frag_page_offset(__qdf_nbuf_t nbuf, uint8_t idx,
|
|
int offset)
|
|
{
|
|
unsigned int frag_offset;
|
|
skb_frag_t *frag;
|
|
|
|
if (qdf_unlikely(idx >= __qdf_nbuf_get_nr_frags(nbuf)))
|
|
return QDF_STATUS_E_FAILURE;
|
|
|
|
frag = &skb_shinfo(nbuf)->frags[idx];
|
|
frag_offset = skb_frag_off(frag);
|
|
|
|
frag_offset += offset;
|
|
skb_frag_off_set(frag, frag_offset);
|
|
|
|
__qdf_nbuf_trim_add_frag_size(nbuf, idx, -(offset), 0);
|
|
|
|
return QDF_STATUS_SUCCESS;
|
|
}
|
|
|
|
#else
|
|
QDF_STATUS __qdf_nbuf_move_frag_page_offset(__qdf_nbuf_t nbuf, uint8_t idx,
|
|
int offset)
|
|
{
|
|
uint16_t frag_offset;
|
|
skb_frag_t *frag;
|
|
|
|
if (qdf_unlikely(idx >= __qdf_nbuf_get_nr_frags(nbuf)))
|
|
return QDF_STATUS_E_FAILURE;
|
|
|
|
frag = &skb_shinfo(nbuf)->frags[idx];
|
|
frag_offset = frag->page_offset;
|
|
|
|
frag_offset += offset;
|
|
frag->page_offset = frag_offset;
|
|
|
|
__qdf_nbuf_trim_add_frag_size(nbuf, idx, -(offset), 0);
|
|
|
|
return QDF_STATUS_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
qdf_export_symbol(__qdf_nbuf_move_frag_page_offset);
|
|
|
|
void __qdf_nbuf_remove_frag(__qdf_nbuf_t nbuf,
|
|
uint16_t idx,
|
|
uint16_t truesize)
|
|
{
|
|
struct page *page;
|
|
uint16_t frag_len;
|
|
|
|
page = skb_frag_page(&skb_shinfo(nbuf)->frags[idx]);
|
|
|
|
if (qdf_unlikely(!page))
|
|
return;
|
|
|
|
frag_len = qdf_nbuf_get_frag_size_by_idx(nbuf, idx);
|
|
put_page(page);
|
|
nbuf->len -= frag_len;
|
|
nbuf->data_len -= frag_len;
|
|
nbuf->truesize -= truesize;
|
|
skb_shinfo(nbuf)->nr_frags--;
|
|
}
|
|
|
|
qdf_export_symbol(__qdf_nbuf_remove_frag);
|
|
|
|
void __qdf_nbuf_add_rx_frag(__qdf_frag_t buf, __qdf_nbuf_t nbuf,
|
|
int offset, int frag_len,
|
|
unsigned int truesize, bool take_frag_ref)
|
|
{
|
|
struct page *page;
|
|
int frag_offset;
|
|
uint8_t nr_frag;
|
|
|
|
nr_frag = __qdf_nbuf_get_nr_frags(nbuf);
|
|
qdf_assert_always(nr_frag < QDF_NBUF_MAX_FRAGS);
|
|
|
|
page = virt_to_head_page(buf);
|
|
frag_offset = buf - page_address(page);
|
|
|
|
skb_add_rx_frag(nbuf, nr_frag, page,
|
|
(frag_offset + offset),
|
|
frag_len, truesize);
|
|
|
|
if (unlikely(take_frag_ref)) {
|
|
qdf_frag_count_inc(QDF_NBUF_FRAG_DEBUG_COUNT_ONE);
|
|
skb_frag_ref(nbuf, nr_frag);
|
|
}
|
|
}
|
|
|
|
qdf_export_symbol(__qdf_nbuf_add_rx_frag);
|
|
|
|
#ifdef NBUF_FRAG_MEMORY_DEBUG
|
|
|
|
QDF_STATUS qdf_nbuf_move_frag_page_offset_debug(qdf_nbuf_t nbuf, uint8_t idx,
|
|
int offset, const char *func,
|
|
uint32_t line)
|
|
{
|
|
QDF_STATUS result;
|
|
qdf_frag_t p_fragp, n_fragp;
|
|
|
|
p_fragp = qdf_nbuf_get_frag_addr(nbuf, idx);
|
|
result = __qdf_nbuf_move_frag_page_offset(nbuf, idx, offset);
|
|
|
|
if (qdf_likely(is_initial_mem_debug_disabled))
|
|
return result;
|
|
|
|
n_fragp = qdf_nbuf_get_frag_addr(nbuf, idx);
|
|
|
|
/*
|
|
* Update frag address in frag debug tracker
|
|
* when frag offset is successfully changed in skb
|
|
*/
|
|
if (result == QDF_STATUS_SUCCESS)
|
|
qdf_frag_debug_update_addr(p_fragp, n_fragp, func, line);
|
|
|
|
return result;
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_move_frag_page_offset_debug);
|
|
|
|
void qdf_nbuf_add_rx_frag_debug(qdf_frag_t buf, qdf_nbuf_t nbuf,
|
|
int offset, int frag_len,
|
|
unsigned int truesize, bool take_frag_ref,
|
|
const char *func, uint32_t line)
|
|
{
|
|
qdf_frag_t fragp;
|
|
uint32_t num_nr_frags;
|
|
|
|
__qdf_nbuf_add_rx_frag(buf, nbuf, offset,
|
|
frag_len, truesize, take_frag_ref);
|
|
|
|
if (qdf_likely(is_initial_mem_debug_disabled))
|
|
return;
|
|
|
|
num_nr_frags = qdf_nbuf_get_nr_frags(nbuf);
|
|
|
|
qdf_assert_always(num_nr_frags <= QDF_NBUF_MAX_FRAGS);
|
|
|
|
fragp = qdf_nbuf_get_frag_addr(nbuf, num_nr_frags - 1);
|
|
|
|
/* Update frag address in frag debug tracking table */
|
|
if (fragp != buf && !take_frag_ref)
|
|
qdf_frag_debug_update_addr(buf, fragp, func, line);
|
|
|
|
/* Update frag refcount in frag debug tracking table */
|
|
qdf_frag_debug_refcount_inc(fragp, func, line);
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_add_rx_frag_debug);
|
|
|
|
void qdf_net_buf_debug_acquire_frag(qdf_nbuf_t buf, const char *func,
|
|
uint32_t line)
|
|
{
|
|
uint32_t num_nr_frags;
|
|
uint32_t idx = 0;
|
|
qdf_nbuf_t ext_list;
|
|
qdf_frag_t p_frag;
|
|
|
|
if (qdf_likely(is_initial_mem_debug_disabled))
|
|
return;
|
|
|
|
if (qdf_unlikely(!buf))
|
|
return;
|
|
|
|
/* Take care to update the refcount in the debug entries for frags */
|
|
num_nr_frags = qdf_nbuf_get_nr_frags(buf);
|
|
|
|
qdf_assert_always(num_nr_frags <= QDF_NBUF_MAX_FRAGS);
|
|
|
|
while (idx < num_nr_frags) {
|
|
p_frag = qdf_nbuf_get_frag_addr(buf, idx);
|
|
if (qdf_likely(p_frag))
|
|
qdf_frag_debug_refcount_inc(p_frag, func, line);
|
|
idx++;
|
|
}
|
|
|
|
/*
|
|
* Take care to update the refcount in the debug entries for the
|
|
* frags attached to frag_list
|
|
*/
|
|
ext_list = qdf_nbuf_get_ext_list(buf);
|
|
while (ext_list) {
|
|
idx = 0;
|
|
num_nr_frags = qdf_nbuf_get_nr_frags(ext_list);
|
|
|
|
qdf_assert_always(num_nr_frags <= QDF_NBUF_MAX_FRAGS);
|
|
|
|
while (idx < num_nr_frags) {
|
|
p_frag = qdf_nbuf_get_frag_addr(ext_list, idx);
|
|
if (qdf_likely(p_frag))
|
|
qdf_frag_debug_refcount_inc(p_frag, func, line);
|
|
idx++;
|
|
}
|
|
ext_list = qdf_nbuf_queue_next(ext_list);
|
|
}
|
|
}
|
|
|
|
qdf_export_symbol(qdf_net_buf_debug_acquire_frag);
|
|
|
|
void qdf_net_buf_debug_release_frag(qdf_nbuf_t buf, const char *func,
|
|
uint32_t line)
|
|
{
|
|
uint32_t num_nr_frags;
|
|
qdf_nbuf_t ext_list;
|
|
uint32_t idx = 0;
|
|
qdf_frag_t p_frag;
|
|
|
|
if (qdf_likely(is_initial_mem_debug_disabled))
|
|
return;
|
|
|
|
if (qdf_unlikely(!buf))
|
|
return;
|
|
|
|
/*
|
|
* Decrement refcount for frag debug nodes only when last user
|
|
* of nbuf calls this API so as to avoid decrementing refcount
|
|
* on every call expect the last one in case where nbuf has multiple
|
|
* users
|
|
*/
|
|
if (qdf_nbuf_get_users(buf) > 1)
|
|
return;
|
|
|
|
/* Take care to update the refcount in the debug entries for frags */
|
|
num_nr_frags = qdf_nbuf_get_nr_frags(buf);
|
|
|
|
qdf_assert_always(num_nr_frags <= QDF_NBUF_MAX_FRAGS);
|
|
|
|
while (idx < num_nr_frags) {
|
|
p_frag = qdf_nbuf_get_frag_addr(buf, idx);
|
|
if (qdf_likely(p_frag))
|
|
qdf_frag_debug_refcount_dec(p_frag, func, line);
|
|
idx++;
|
|
}
|
|
|
|
/* Take care to update debug entries for frags attached to frag_list */
|
|
ext_list = qdf_nbuf_get_ext_list(buf);
|
|
while (ext_list) {
|
|
if (qdf_nbuf_get_users(ext_list) == 1) {
|
|
idx = 0;
|
|
num_nr_frags = qdf_nbuf_get_nr_frags(ext_list);
|
|
qdf_assert_always(num_nr_frags <= QDF_NBUF_MAX_FRAGS);
|
|
while (idx < num_nr_frags) {
|
|
p_frag = qdf_nbuf_get_frag_addr(ext_list, idx);
|
|
if (qdf_likely(p_frag))
|
|
qdf_frag_debug_refcount_dec(p_frag,
|
|
func, line);
|
|
idx++;
|
|
}
|
|
}
|
|
ext_list = qdf_nbuf_queue_next(ext_list);
|
|
}
|
|
}
|
|
|
|
qdf_export_symbol(qdf_net_buf_debug_release_frag);
|
|
|
|
QDF_STATUS
|
|
qdf_nbuf_remove_frag_debug(qdf_nbuf_t nbuf,
|
|
uint16_t idx,
|
|
uint16_t truesize,
|
|
const char *func,
|
|
uint32_t line)
|
|
{
|
|
uint16_t num_frags;
|
|
qdf_frag_t frag;
|
|
|
|
if (qdf_unlikely(!nbuf))
|
|
return QDF_STATUS_E_INVAL;
|
|
|
|
num_frags = qdf_nbuf_get_nr_frags(nbuf);
|
|
if (idx >= num_frags)
|
|
return QDF_STATUS_E_INVAL;
|
|
|
|
if (qdf_likely(is_initial_mem_debug_disabled)) {
|
|
__qdf_nbuf_remove_frag(nbuf, idx, truesize);
|
|
return QDF_STATUS_SUCCESS;
|
|
}
|
|
|
|
frag = qdf_nbuf_get_frag_addr(nbuf, idx);
|
|
if (qdf_likely(frag))
|
|
qdf_frag_debug_refcount_dec(frag, func, line);
|
|
|
|
__qdf_nbuf_remove_frag(nbuf, idx, truesize);
|
|
|
|
return QDF_STATUS_SUCCESS;
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_remove_frag_debug);
|
|
|
|
#endif /* NBUF_FRAG_MEMORY_DEBUG */
|
|
|
|
qdf_nbuf_t qdf_get_nbuf_valid_frag(qdf_nbuf_t nbuf)
|
|
{
|
|
qdf_nbuf_t last_nbuf;
|
|
uint32_t num_frags;
|
|
|
|
if (qdf_unlikely(!nbuf))
|
|
return NULL;
|
|
|
|
num_frags = qdf_nbuf_get_nr_frags(nbuf);
|
|
|
|
/* Check nbuf has enough memory to store frag memory */
|
|
if (num_frags < QDF_NBUF_MAX_FRAGS)
|
|
return nbuf;
|
|
|
|
if (!__qdf_nbuf_has_fraglist(nbuf))
|
|
return NULL;
|
|
|
|
last_nbuf = __qdf_nbuf_get_last_frag_list_nbuf(nbuf);
|
|
if (qdf_unlikely(!last_nbuf))
|
|
return NULL;
|
|
|
|
num_frags = qdf_nbuf_get_nr_frags(last_nbuf);
|
|
if (num_frags < QDF_NBUF_MAX_FRAGS)
|
|
return last_nbuf;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
qdf_export_symbol(qdf_get_nbuf_valid_frag);
|
|
|
|
QDF_STATUS
|
|
qdf_nbuf_add_frag_debug(qdf_device_t osdev, qdf_frag_t buf,
|
|
qdf_nbuf_t nbuf, int offset,
|
|
int frag_len, unsigned int truesize,
|
|
bool take_frag_ref, unsigned int minsize,
|
|
const char *func, uint32_t line)
|
|
{
|
|
qdf_nbuf_t cur_nbuf;
|
|
qdf_nbuf_t this_nbuf;
|
|
|
|
cur_nbuf = nbuf;
|
|
this_nbuf = nbuf;
|
|
|
|
if (qdf_unlikely(!frag_len || !buf)) {
|
|
qdf_nofl_err("%s : %d frag[ buf[%pK] len[%d]] not valid\n",
|
|
func, line,
|
|
buf, frag_len);
|
|
return QDF_STATUS_E_INVAL;
|
|
}
|
|
|
|
this_nbuf = qdf_get_nbuf_valid_frag(this_nbuf);
|
|
|
|
if (this_nbuf) {
|
|
cur_nbuf = this_nbuf;
|
|
} else {
|
|
/* allocate a dummy mpdu buffer of 64 bytes headroom */
|
|
this_nbuf = qdf_nbuf_alloc(osdev, minsize, minsize, 4, false);
|
|
if (qdf_unlikely(!this_nbuf)) {
|
|
qdf_nofl_err("%s : %d no memory to allocate\n",
|
|
func, line);
|
|
return QDF_STATUS_E_NOMEM;
|
|
}
|
|
}
|
|
|
|
qdf_nbuf_add_rx_frag(buf, this_nbuf, offset, frag_len, truesize,
|
|
take_frag_ref);
|
|
|
|
if (this_nbuf != cur_nbuf) {
|
|
/* add new skb to frag list */
|
|
qdf_nbuf_append_ext_list(nbuf, this_nbuf,
|
|
qdf_nbuf_len(this_nbuf));
|
|
}
|
|
|
|
return QDF_STATUS_SUCCESS;
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_add_frag_debug);
|
|
|
|
#ifdef MEMORY_DEBUG
|
|
void qdf_nbuf_acquire_track_lock(uint32_t index,
|
|
unsigned long irq_flag)
|
|
{
|
|
spin_lock_irqsave(&g_qdf_net_buf_track_lock[index],
|
|
irq_flag);
|
|
}
|
|
|
|
void qdf_nbuf_release_track_lock(uint32_t index,
|
|
unsigned long irq_flag)
|
|
{
|
|
spin_unlock_irqrestore(&g_qdf_net_buf_track_lock[index],
|
|
irq_flag);
|
|
}
|
|
|
|
QDF_NBUF_TRACK *qdf_nbuf_get_track_tbl(uint32_t index)
|
|
{
|
|
return gp_qdf_net_buf_track_tbl[index];
|
|
}
|
|
#endif /* MEMORY_DEBUG */
|
|
|
|
#ifdef ENHANCED_OS_ABSTRACTION
|
|
void qdf_nbuf_set_timestamp(qdf_nbuf_t buf)
|
|
{
|
|
__qdf_nbuf_set_timestamp(buf);
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_set_timestamp);
|
|
|
|
uint64_t qdf_nbuf_get_timestamp(qdf_nbuf_t buf)
|
|
{
|
|
return __qdf_nbuf_get_timestamp(buf);
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_get_timestamp);
|
|
|
|
uint64_t qdf_nbuf_get_timestamp_us(qdf_nbuf_t buf)
|
|
{
|
|
return __qdf_nbuf_get_timestamp_us(buf);
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_get_timestamp_us);
|
|
|
|
uint64_t qdf_nbuf_get_timedelta_us(qdf_nbuf_t buf)
|
|
{
|
|
return __qdf_nbuf_get_timedelta_us(buf);
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_get_timedelta_us);
|
|
|
|
uint64_t qdf_nbuf_get_timedelta_ms(qdf_nbuf_t buf)
|
|
{
|
|
return __qdf_nbuf_get_timedelta_ms(buf);
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_get_timedelta_ms);
|
|
|
|
qdf_ktime_t qdf_nbuf_net_timedelta(qdf_ktime_t t)
|
|
{
|
|
return __qdf_nbuf_net_timedelta(t);
|
|
}
|
|
|
|
qdf_export_symbol(qdf_nbuf_net_timedelta);
|
|
#endif
|