blob: db8c8ab14075122229f1ae2d7916da87d4606396 [file] [log] [blame]
/**
Copyright (c) 2008 - 2013 Quantenna Communications Inc
All Rights Reserved
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#include <linux/version.h>
#ifndef AUTOCONF_INCLUDED
# include <linux/config.h>
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,1,0)
# include <linux/kconfig.h>
#else
# include <generated/autoconf.h>
#endif
#include <linux/version.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/dma-mapping.h>
#include <linux/stddef.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/syscalls.h>
#include <linux/file.h>
#include <linux/in.h>
#include <net/sock.h>
#include <net/sch_generic.h>
#include <linux/netlink.h>
#include <trace/ippkt.h>
#include "qdrv_pktlogger.h"
#include "qdrv_control.h"
#include "qdrv_debug.h"
#include "qtn/qdrv_bld.h"
#include "qdrv_netdebug_checksum.h"
#include "qdrv_netdebug_binary.h"
#include "qdrv_muc_stats.h"
#include <radar/radar.h>
#include <qtn/qtn_math.h>
#include <asm/board/soc.h>
#include <qtn/mproc_sync_base.h>
#include <common/ruby_mem.h>
#include <common/pktlogger/pktlogger_nl_common.h>
#include <qtn/qtn_bb_mutex.h>
#include <qtn/hardware_revision.h>
#include <qtn/emac_debug.h>
#include <qtn/ruby_cpumon.h>
#include <qtn/qtn_muc_stats_print.h>
#include <qtn/txbf_mbox.h>
struct qdrv_pktlogger *g_pktlogger_p = NULL;
void ruby_cpumon_get_cycles(uint64_t *sleep, uint64_t *awake){}
struct kmem_cache *
kmem_cache_find(const char *name)
{
return NULL;
}
void
kmem_cache_calc_usage(struct kmem_cache *cachep, unsigned int *p_tot,
unsigned int *p_cur, unsigned int *p_act, unsigned int *p_hwm){}
void *qdrv_pktlogger_alloc_buffer(char *description, int data_len)
{
void *data_p;
data_p = kzalloc(data_len, GFP_ATOMIC);
if (data_p == NULL) {
DBGPRINTF_LIMIT_E("%s():Failed for %s", __FUNCTION__, description);
return NULL;
}
return data_p;
}
void qdrv_pktlogger_free_buffer(void *data_buffer)
{
kfree(data_buffer);
}
void qdrv_pktlogger_hdr_init(struct qdrv_wlan *qw,
struct qdrv_pktlogger_hdr *hdr,
int rec_type,
int stats_len)
{
uint64_t tsf;
hdr->udpheader.dest = qw->pktlogger.dst_port;
hdr->udpheader.source = qw->pktlogger.src_port;
hdr->udpheader.len = htons(stats_len);
hdr->udpheader.check = 0;
hdr->type = rec_type;
IEEE80211_ADDR_COPY(hdr->src, qw->mac->mac_addr);
hdr->version = QDRV_NETDEBUG_CHECKSUM;
hdr->builddate = QDRV_BUILDDATE;
strncpy(hdr->buildstring, QDRV_BLD_NAME, QDRV_NETDEBUG_BUILDSTRING_SIZE - 1);
hdr->buildstring[QDRV_NETDEBUG_BUILDSTRING_SIZE - 1] = '\0';
hdr->timestamp = jiffies;
qw->ic.ic_get_tsf(&tsf);
hdr->tsf_lo = U64_LOW32(tsf);
hdr->tsf_hi = U64_HIGH32(tsf);
hdr->stats_len = stats_len - sizeof(*hdr);
hdr->opmode = (u_int8_t)qw->ic.ic_opmode;
hdr->platform = get_hardware_revision();
memset(hdr->padding, 0, sizeof(hdr->padding));
}
/*
* Remap the statistics structures if not already done.
* These are used by netdebug and ratedebug, and are never unmapped.
*/
static int qdrv_pktlogger_map(struct qdrv_wlan *qw)
{
struct qtn_stats_log *iw_stats_log;
iw_stats_log = (struct qtn_stats_log *)qw->mac->mac_sys_stats;
if (iw_stats_log == NULL) {
return -1;
}
if (qw->pktlogger.stats_uc_rx_ptr == NULL) {
qw->pktlogger.stats_uc_rx_ptr =
ioremap_nocache(muc_to_lhost((u32)iw_stats_log->rx_muc_stats),
sizeof(struct muc_rx_stats));
}
if (qw->pktlogger.stats_uc_rx_bf_ptr == NULL) {
qw->pktlogger.stats_uc_rx_bf_ptr =
ioremap_nocache(muc_to_lhost((u32)iw_stats_log->rx_muc_bf_stats),
sizeof(struct muc_rx_bf_stats));
}
if (qw->pktlogger.stats_uc_rx_rate_ptr == NULL) {
qw->pktlogger.stats_uc_rx_rate_ptr =
ioremap_nocache(muc_to_lhost((u32)iw_stats_log->rx_muc_rates),
sizeof(struct muc_rx_rates));
}
if (qw->pktlogger.stats_uc_tx_ptr == NULL) {
qw->pktlogger.stats_uc_tx_ptr =
ioremap_nocache(muc_to_lhost((u32)iw_stats_log->tx_muc_stats),
sizeof(struct muc_tx_stats));
}
if (qw->pktlogger.stats_uc_tx_rate_ptr == NULL) {
qw->pktlogger.stats_uc_tx_rate_ptr =
ioremap_nocache(muc_to_lhost((u32)iw_stats_log->tx_muc_rates),
sizeof(struct qtn_rate_tx_stats_per_sec));
}
if (qw->pktlogger.stats_uc_su_rates_read_ptr == NULL) {
qw->pktlogger.stats_uc_su_rates_read_ptr =
ioremap_nocache(muc_to_lhost((u32)iw_stats_log->muc_su_rate_stats_read),
sizeof(uint32_t));
}
if (qw->pktlogger.stats_uc_mu_rates_read_ptr == NULL) {
qw->pktlogger.stats_uc_mu_rates_read_ptr =
ioremap_nocache(muc_to_lhost((u32)iw_stats_log->muc_mu_rate_stats_read),
sizeof(uint32_t));
}
if (qw->pktlogger.stats_uc_scs_cnt == NULL) {
qw->pktlogger.stats_uc_scs_cnt =
ioremap_nocache(muc_to_lhost((u32)iw_stats_log->scs_cnt),
sizeof(struct qdrv_scs_cnt));
}
if (qw->pktlogger.netdev_q_ptr_w == NULL) {
struct ieee80211vap *vap = TAILQ_FIRST(&qw->ic.ic_vaps);
if (vap) {
if (vap->iv_dev != NULL) {
struct netdev_queue *ndq = netdev_get_tx_queue(vap->iv_dev, 0);
if (ndq != NULL) {
qw->pktlogger.netdev_q_ptr_w = ndq;
}
}
}
}
if (qw->pktlogger.netdev_q_ptr_e == NULL) {
if (qw->pktlogger.dev != NULL) {
struct netdev_queue *ndq = netdev_get_tx_queue(qw->pktlogger.dev, 0);
if (ndq != NULL) {
qw->pktlogger.netdev_q_ptr_e = ndq;
}
}
}
return 0;
}
/* record Auc status pointer */
static void qdrv_pktlogger_set_auc_status_ptr(struct qdrv_wlan *qw)
{
#if 0
#if !defined(CONFIG_TOPAZ_PCIE_HOST)
const bool fw_no_mu = qw->sp->fw_no_mu;
const struct qtn_auc_stat_field auc_field_default[] = {
#include <qtn/qtn_auc_stats_fields.default.h>
};
const struct qtn_auc_stat_field auc_field_nomu[] = {
#include <qtn/qtn_auc_stats_fields.nomu.h>
};
const struct qtn_auc_stat_field *auc_field = fw_no_mu ? auc_field_nomu : auc_field_default;
const size_t nstats = fw_no_mu ? ARRAY_SIZE(auc_field_nomu) : ARRAY_SIZE(auc_field_default);
unsigned int i;
for (i = 0; i < nstats; i++) {
const uintptr_t addr = auc_field[i].addr;
const char *const name = auc_field[i].name;
if (__in_mem_range(addr, TOPAZ_AUC_DMEM_ADDR, TOPAZ_AUC_DMEM_SIZE)) {
if (strcmp(name, "sleep") == 0) {
if (qw->pktlogger.stats_auc_sleep_p == NULL)
qw->pktlogger.stats_auc_sleep_p = (uint32_t *)addr;
} else if (strcmp(name, "jiffies") == 0) {
if (qw->pktlogger.stats_auc_jiffies_p == NULL)
qw->pktlogger.stats_auc_jiffies_p = (uint32_t *)addr;
} else if (strcmp(name, "IRQ_0") == 0) {
if (qw->pktlogger.stats_auc_intr_p == NULL)
qw->pktlogger.stats_auc_intr_p = (uint32_t *)addr;
} else if (strcmp(name, "task_alive_counters[0]") == 0) {
if (qw->pktlogger.stats_auc_dbg_p == NULL)
qw->pktlogger.stats_auc_dbg_p = (struct auc_dbg_counters *)addr;
}
}
}
#endif
#endif
}
int qdrv_pktlogger_set(struct qdrv_wlan *qw, const char *param, const char *value)
{
int ret = 0;
struct net_device *ndev;
if (strcmp(param, "dstmac") == 0) {
ret = qdrv_parse_mac(value, qw->pktlogger.dst_addr);
} else if (strcmp(param, "dstip") == 0) {
ret = iputil_v4_pton(value, &qw->pktlogger.dst_ip);
} else if (strcmp(param, "srcip") == 0) {
ret = iputil_v4_pton(value, &qw->pktlogger.src_ip);
} else if (strcmp(param, "dstport") == 0) {
unsigned portnum;
if ((sscanf(value, "%u", &portnum) != 1)) {
printk("invalid portnum %s\n", value);
ret = -1;
} else {
qw->pktlogger.dst_port = htons(portnum);
}
} else if (strcmp(param, "wifimac") == 0) {
ret = qdrv_parse_mac(value, qw->pktlogger.recv_addr);
} else if (strcmp(param, "interface") == 0) {
ndev = dev_get_by_name(&init_net, value);
if (ndev) {
qw->pktlogger.dev = ndev;
dev_put(ndev);
}
} else {
printk("%s is not a valid parameter\n", param);
return -1;
}
return ret;
}
void qdrv_pktlogger_show(struct qdrv_wlan *qw)
{
uint8_t *addr_p;
struct qdrv_pktlogger_types_tbl *tbl = NULL;
int i;
addr_p = (uint8_t*)&qw->pktlogger.dst_ip;
printk("Dst IP: %pI4:%u\n", addr_p, ntohs(qw->pktlogger.dst_port));
addr_p = (uint8_t*)&qw->pktlogger.src_ip;
printk("Src IP: %pI4:%u\n", addr_p, ntohs(qw->pktlogger.src_port));
addr_p = (uint8_t*)qw->pktlogger.dst_addr;
printk("Dst MAC: %pM\n", addr_p);
addr_p = (uint8_t*)qw->pktlogger.src_addr;
printk("Src MAC: %pM\n", addr_p);
addr_p = (uint8_t*)qw->pktlogger.recv_addr;
printk("Wifi MAC: %pM\n", addr_p);
printk("Max IP frag len: %u\n", qw->pktlogger.maxfraglen);
printk("Device name: %s\n", qw->pktlogger.dev->name);
printk("Packet id: %u\n", qw->pktlogger.ip_id);
printk("Queue len: %u\n", qw->pktlogger.queue_len);
printk("Queued: %u\n", qw->pktlogger.stats.pkt_queued);
printk("Requeued: %u\n", qw->pktlogger.stats.pkt_requeued);
printk("Dropped: %u\n", qw->pktlogger.stats.pkt_dropped);
printk("Failed: %u\n", qw->pktlogger.stats.pkt_failed);
printk("Queue send: %u\n", qw->pktlogger.stats.queue_send);
printk("\nType Enabled Interval\n");
for (i = 0; i < QDRV_NETDEBUG_TYPE_MAX; i++) {
tbl = qdrv_pktlogger_get_tbls_by_id(i);
if (tbl) {
printk("%-10s%5d%9d\n", tbl->name,
!!(qw->pktlogger.flag & BIT(i)), tbl->interval);
}
}
}
/* RSSI monitor and MuC kill signal gen */
static void qdrv_pktlogger_gen_muc_kill(struct qdrv_wlan *qw,
struct qtn_rx_stats *rx_stats)
{
#ifdef QDRV_FEATURE_KILL_MUC
int ant;
static int rssi_zero_cnt[4] = {0, 0, 0, 0};
if ( qw->flags_ext & QDRV_WLAN_MUC_KILLED) {
return;
}
for (ant=0; ant < 4; ant++) {
if (rx_stats->num_pkts >= 0 &&
rx_stats->avg_rssi[ant] == 0) {
rssi_zero_cnt[ant]++;
} else {
rssi_zero_cnt[ant] = 0;
}
if (rssi_zero_cnt[ant] >= 30) {
printk("Killing MuC based on low RSSI\n");
qdrv_hostlink_killmuc(qw);
qw->flags_ext |= QDRV_WLAN_MUC_KILLED;
}
}
#if 0
/* Test feature */
if (rx_stats->sys_temp > 6000000) {
qdrv_hostlink_killmuc(qw);
qw->flags_ext |= QDRV_WLAN_MUC_KILLED;
}
#endif
return;
#endif
}
static void
qdrv_pktlogger_slab_prepare(struct qdrv_wlan *qw, struct qdrv_slab_watch *p_out)
{
struct qdrv_pktlogger *p_pktlogger = &qw->pktlogger;
struct kmem_cache *p_cache = NULL;
#define CACHE(x)\
p_cache = p_pktlogger->qmeminfo.caches[QDRV_SLAB_IDX_SIZE_##x]; \
if (p_cache) { \
kmem_cache_calc_usage(p_cache, &p_out->stat_size_tot_alloc_##x, \
&p_out->stat_size_cur_alloc_##x, &p_out->stat_size_act_alloc_##x, \
&p_out->stat_size_hwm_alloc_##x); \
}
#define ZACHE(y)\
p_cache = p_pktlogger->qmeminfo.caches[QDRV_SLAB_IDX_##y]; \
if (p_cache) { \
kmem_cache_calc_usage(p_cache, &p_out->stat_tot_alloc_##y, \
&p_out->stat_cur_alloc_##y, &p_out->stat_act_alloc_##y, \
&p_out->stat_hwm_alloc_##y); \
}
#include "qdrv_slab_watch.h"
#undef CACHE
#undef ZACHE
}
/*
* Fill memory stats structure
*/
static void
qdrv_pktlogger_netdebug_mem_stats_prepare(struct qdrv_mem_stats *stats)
{
struct sysinfo si;
si_meminfo(&si);
memset(stats, 0, sizeof(*stats));
stats->mem_free = si.freeram;
stats->mem_slab_reclaimable = global_page_state(NR_SLAB_RECLAIMABLE);
stats->mem_slab_unreclaimable = global_page_state(NR_SLAB_UNRECLAIMABLE);
stats->mem_anon = global_page_state(NR_ANON_PAGES);
stats->mem_mapped = global_page_state(NR_FILE_MAPPED);
stats->mem_cached = global_page_state(NR_FILE_PAGES);
}
/*
* Usual Evm value range is from -5.x to -28.x
* Need to decode it into signed integer in pktlogger
*/
static void
qdrv_pktlogger_insert_evms( struct qdrv_netdebug_stats *stats )
{
int iter;
for (iter = 0; iter < QDRV_NUM_RF_STREAMS; iter++) {
if (stats->stats_phy_rx.last_rssi_evm[iter] != MUC_PHY_ERR_SUM_NOT_AVAIL) {
stats->stats_evm.rx_evm_val[iter] = stats->stats_phy_rx.last_rssi_evm[iter];
} else {
stats->stats_evm.rx_evm_val[iter] = 0;
}
}
}
static void
qdrv_pktlogger_insert_pd_vol(struct qdrv_wlan *qw, struct qdrv_netdebug_stats *stats)
{
char *cmd = NULL;
dma_addr_t cmd_dma;
char calcmd[4] = {51, 0, 4, 0};
cmd = qdrv_hostlink_alloc_coherent(NULL, QDRV_CMD_LENGTH, &cmd_dma, GFP_ATOMIC);
if (cmd == NULL) {
DBGPRINTF_E("Failed allocate %d bytes for cmd\n", QDRV_CMD_LENGTH);
return;
}
memcpy(cmd, calcmd, sizeof(calcmd));
qdrv_hostlink_msg_calcmd(qw, sizeof(calcmd), cmd_dma);
stats->stats_pd_vol.tx_pd_vol[0] = cmd[6] << 8 | cmd[5];
stats->stats_pd_vol.tx_pd_vol[1] = cmd[9] << 8 | cmd[8];
stats->stats_pd_vol.tx_pd_vol[2] = cmd[12] << 8 | cmd[11];
stats->stats_pd_vol.tx_pd_vol[3] = cmd[15] << 8 | cmd[14];
qdrv_hostlink_free_coherent(NULL, QDRV_CMD_LENGTH, cmd, cmd_dma);
}
static void qdrv_pktlogger_netdebug_misc_stats_prepare(struct qdrv_wlan *qw, struct qdrv_misc_stats *stats)
{
static uint64_t last_awake = 0;
uint64_t this_awake = 0;
uint32_t diff_awake = 0;
ruby_cpumon_get_cycles(NULL, &this_awake);
diff_awake = this_awake - last_awake;
last_awake = this_awake;
stats->cpuawake = diff_awake;
}
static void qdrv_pktlogger_get_tqe_stats(struct qdrv_tqe_stats *tqe_stats)
{
tqe_stats->emac0_outc = readl(TOPAZ_TQE_OUTPORT_EMAC0_CNT);
tqe_stats->emac1_outc = readl(TOPAZ_TQE_OUTPORT_EMAC1_CNT);
tqe_stats->wmac_outc = readl(TOPAZ_TQE_OUTPORT_WMAC_CNT);
tqe_stats->lhost_outc = readl(TOPAZ_TQE_OUTPORT_LHOST_CNT);
tqe_stats->muc_outc = readl(TOPAZ_TQE_OUTPORT_MUC_CNT);
tqe_stats->dsp_outc = readl(TOPAZ_TQE_OUTPORT_DSP_CNT);
tqe_stats->auc_outc = readl(TOPAZ_TQE_OUTPORT_AUC_CNT);
tqe_stats->pcie_outc = readl(TOPAZ_TQE_OUTPORT_PCIE_CNT);
tqe_stats->drop = readl(TOPAZ_TQE_DROP_CNT);
tqe_stats->emac0_drop= readl(TOPAZ_TQE_DROP_EMAC0_CNT);
tqe_stats->emac1_drop = readl(TOPAZ_TQE_DROP_EMAC1_CNT);
tqe_stats->wmac_drop = readl(TOPAZ_TQE_DROP_WMAC_CNT);
tqe_stats->lhost_drop = readl(TOPAZ_TQE_DROP_LHOST_CNT);
tqe_stats->muc_drop = readl(TOPAZ_TQE_DROP_MUC_CNT);
tqe_stats->dsp_drop = readl(TOPAZ_TQE_DROP_DSP_CNT);
tqe_stats->auc_drop = readl(TOPAZ_TQE_DROP_AUC_CNT);
tqe_stats->pcie_drop = readl(TOPAZ_TQE_DROP_PCIE_CNT);
}
static void qdrv_pktlogger_get_hbm_stats(struct qdrv_hbm_stats *hbm_stats,
struct qdrv_hbm_stats_oth *hbm_stats_oth)
{
int pool;
int master;
uint32_t *statp = (uint32_t *)hbm_stats;
int req = TOPAZ_HBM_BUF_EMAC_RX_COUNT + TOPAZ_HBM_BUF_WMAC_RX_COUNT;
int rel = 0;
COMPILE_TIME_ASSERT(sizeof(*hbm_stats) ==
TOPAZ_HBM_POOL_COUNT * TOPAZ_HBM_MASTER_COUNT * 2 * sizeof(uint32_t));
for (master = 0; master < TOPAZ_HBM_MASTER_COUNT; ++master) {
for (pool = 0; pool < TOPAZ_HBM_POOL_COUNT; ++pool) {
*statp = readl(TOPAZ_HBM_POOL_REQUEST_CNT(master, pool));
req += *statp;
statp++;
}
}
for (master = 0; master < TOPAZ_HBM_MASTER_COUNT; ++master) {
for (pool = 0; pool < TOPAZ_HBM_POOL_COUNT; ++pool) {
*statp = readl(TOPAZ_HBM_POOL_RELEASE_CNT(master, pool));
rel += *statp;
statp++;
}
}
hbm_stats_oth->hbm_req = req;
hbm_stats_oth->hbm_rel = rel;
hbm_stats_oth->hbm_diff = req - rel;
hbm_stats_oth->hbm_overflow = readl(TOPAZ_HBM_OVERFLOW_CNT);
hbm_stats_oth->hbm_underflow = readl(TOPAZ_HBM_UNDERFLOW_CNT);
}
static void qdrv_pktlogger_netdebug_dsp_mu_stats_prepare(
struct dsp_mu_stats* stats_dsp_mu)
{
volatile struct qtn_txbf_mbox* txbf_mbox = qtn_txbf_mbox_get();
int i;
memset(stats_dsp_mu, 0, sizeof(*stats_dsp_mu));
for (i = 0; i < ARRAY_SIZE(txbf_mbox->mu_grp_qmat); i++) {
if (txbf_mbox->mu_grp_qmat[i].grp_id != 0) {
stats_dsp_mu->mu_u0_aid[i] = txbf_mbox->mu_grp_qmat[i].u0_aid;
stats_dsp_mu->mu_u1_aid[i] = txbf_mbox->mu_grp_qmat[i].u1_aid;
stats_dsp_mu->mu_rank[i] = txbf_mbox->mu_grp_qmat[i].rank;
}
}
}
/*
* Gather statistics and send to the configured target
*/
void qdrv_pktlogger_netdebug_stats_send(unsigned long data)
{
struct qdrv_wlan *qw = (struct qdrv_wlan *)data;
struct qdrv_mac *mac = qw->mac;
struct qdrv_netdebug_stats *stats;
struct qtn_stats_log *iw_stats_log;
struct muc_rx_rates *muc_rx_rates_p;
struct qdrv_mem_stats mem_stats;
struct qdrv_misc_stats misc_stats;
int i;
int curr_index;
void *data_buff;
struct qdrv_pktlogger_types_tbl *tbl = NULL;
struct muc_rx_rates *rx_rates_prev = qw->pktlogger.rx_rate_pre;
struct muc_rx_rates *rx_rates_curr = qw->pktlogger.rx_rate_cur;
tbl = qdrv_pktlogger_get_tbls_by_id(QDRV_NETDEBUG_TYPE_STATS);
if (!tbl) {
return;
}
#if QDRV_NETDEBUG_ETH_DEV_STATS_ENABLED
struct net_device_stats *dev_stats;
#endif
qdrv_pktlogger_flush_data(qw);
muc_rx_rates_p = (struct muc_rx_rates *)qw->pktlogger.stats_uc_rx_rate_ptr;
data_buff = qdrv_pktlogger_alloc_buffer("net", sizeof(*stats));
if (data_buff == NULL) {
return;
}
stats = (struct qdrv_netdebug_stats*) data_buff;
qdrv_pktlogger_hdr_init(qw, &stats->ndb_hdr, QDRV_NETDEBUG_TYPE_STATS,
sizeof(*stats));
/* Gather statistics */
memcpy(&stats->stats_wlan_rx, &qw->rx_stats, sizeof(stats->stats_wlan_rx));
memcpy(&stats->stats_wlan_tx, &qw->tx_stats, sizeof(stats->stats_wlan_tx));
memcpy(&stats->stats_wlan_sm, &qw->sm_stats, sizeof(stats->stats_wlan_sm));
memcpy(&stats->stats_scs_cnt, qw->pktlogger.stats_uc_scs_cnt, sizeof(stats->stats_scs_cnt));
iw_stats_log = (struct qtn_stats_log *)mac->mac_sys_stats;
/* Use the current entry en to the log, making sure it has not already been sent */
curr_index = (iw_stats_log->curr_buff - 1 + NUM_LOG_BUFFS) % NUM_LOG_BUFFS;
memcpy(&stats->stats_phy_rx, &iw_stats_log->stat_buffs[curr_index].rx_phy_stats,
sizeof(stats->stats_phy_rx));
memcpy(&stats->stats_phy_tx, &iw_stats_log->stat_buffs[curr_index].tx_phy_stats,
sizeof(stats->stats_phy_tx));
/*
* Show the nss and mcs in a field with using XYY format.
* X means nss (1 ~ 4)
* YY means mcs (0~ 76)
*/
stats->stats_phy_rx.last_rx_mcs =
(stats->stats_phy_rx.last_rx_mcs & QTN_STATS_MCS_RATE_MASK) +
MS(stats->stats_phy_rx.last_rx_mcs, QTN_PHY_STATS_MCS_NSS) * 100;
stats->stats_phy_tx.last_tx_mcs =
(stats->stats_phy_tx.last_tx_mcs & QTN_STATS_MCS_RATE_MASK) +
MS(stats->stats_phy_tx.last_tx_mcs, QTN_PHY_STATS_MCS_NSS) * 100;
if (qw->pktlogger.stats_auc_sleep_p)
stats->stats_auc_intr_count.sleep = *qw->pktlogger.stats_auc_sleep_p;
if (qw->pktlogger.stats_auc_jiffies_p)
stats->stats_auc_intr_count.jiffies = *qw->pktlogger.stats_auc_jiffies_p;
if (qw->pktlogger.stats_auc_intr_p)
memcpy(stats->stats_auc_intr_count.aucirq, qw->pktlogger.stats_auc_intr_p,
sizeof(stats->stats_auc_intr_count.aucirq));
if (qw->pktlogger.stats_auc_dbg_p)
memcpy(&stats->stats_auc_debug_counts, qw->pktlogger.stats_auc_dbg_p,
sizeof(stats->stats_auc_debug_counts));
qdrv_pktlogger_get_tqe_stats(&stats->stats_tqe);
memcpy(&stats->stats_cgq, &qw->cgq_stats, sizeof(stats->stats_cgq));
qdrv_pktlogger_get_hbm_stats(&stats->stats_hbm, &stats->stats_hbm_oth);
if (qdrv_muc_stats_get_display_choice(&iw_stats_log->stat_buffs[curr_index],
&qw->ic) == QDRV_MUC_STATS_SHOW_EVM) {
qdrv_pktlogger_insert_evms(stats);
} else {
memset(stats->stats_evm.rx_evm_val, 0, sizeof(stats->stats_evm.rx_evm_val));
}
qdrv_pktlogger_insert_pd_vol(qw, stats);
/* uc_rx_stats are in DMEM, IO doesnt work */
memcpy(&stats->stats_muc_rx, qw->pktlogger.stats_uc_rx_ptr, sizeof(stats->stats_muc_rx));
memcpy(&stats->stats_muc_rx_bf, qw->pktlogger.stats_uc_rx_bf_ptr, sizeof(stats->stats_muc_rx_bf));
memcpy(&stats->stats_muc_tx, qw->pktlogger.stats_uc_tx_ptr, sizeof(stats->stats_muc_tx));
trace_ippkt_dropped(TRACE_IPPKT_DROP_RSN_MUC_RX_AGG_TIMEOUT,
stats->stats_muc_rx.agg_timeout, 1);
trace_ippkt_dropped(TRACE_IPPKT_DROP_RSN_MUC_RX_AGG_EMPTY,
stats->stats_muc_rx.agg_evict_empty, 1);
/* Queueing stats on the LHost - within QDisc struct (one per tx queue). */
if (qw->pktlogger.netdev_q_ptr_e != NULL) {
struct Qdisc *qd = qw->pktlogger.netdev_q_ptr_e->qdisc;
if (qd != NULL) {
stats->stats_qdisc.eth_sent = qd->bstats.packets;
stats->stats_qdisc.eth_dropped = qd->qstats.drops;
}
}
if (qw->pktlogger.netdev_q_ptr_w != NULL) {
struct Qdisc *qd = qw->pktlogger.netdev_q_ptr_w->qdisc;
if (qd != NULL) {
stats->stats_qdisc.wifi_sent = qd->bstats.packets;
stats->stats_qdisc.wifi_dropped = qd->qstats.drops;
}
}
if (qw->pktlogger.dev && (strncmp(qw->pktlogger.dev->name, "eth", 3) == 0)) {
if (qw->pktlogger.dev_emac0)
stats->stats_emac.rx_emac0_dma_missed =
qtn_eth_rx_lost_get(qw->pktlogger.dev_emac0);
if (qw->pktlogger.dev_emac1)
stats->stats_emac.rx_emac1_dma_missed =
qtn_eth_rx_lost_get(qw->pktlogger.dev_emac1);
}
/*
* There are too many 32 bit rate fields to fit in the debug packet so use 16 bit
* fields and take the difference from the previous value (otherwise it could wrap
* every few seconds).
*/
memcpy(rx_rates_curr, muc_rx_rates_p, sizeof(*rx_rates_curr));
for (i = 0; i < ARRAY_SIZE(stats->rates_muc_rx.rx_mcs); i++) {
stats->rates_muc_rx.rx_mcs[i] =
(uint16_t) (rx_rates_curr->rx_mcs[i] - rx_rates_prev->rx_mcs[i]);
}
for (i = 0; i < ARRAY_SIZE(stats->rates_muc_rx_11ac.rx_11ac_mcs); i++) {
stats->rates_muc_rx_11ac.rx_11ac_mcs[i] =
(uint16_t) (rx_rates_curr->rx_mcs_11ac[i] - rx_rates_prev->rx_mcs_11ac[i]);
}
qw->pktlogger.rx_rate_pre = rx_rates_curr;
qw->pktlogger.rx_rate_cur = rx_rates_prev;
#if QDRV_NETDEBUG_ETH_DEV_STATS_ENABLED
if (qw->pktlogger.dev &&
qw->pktlogger.dev->netdev_ops->ndo_get_stats) {
dev_stats = qw->pktlogger.dev->netdev_ops->ndo_get_stats(qw->pktlogger.dev);
memcpy(&stats->stats_eth, dev_stats, sizeof(stats->stats_eth));
}
#endif
/* Prepare memory statistics */
qdrv_pktlogger_netdebug_mem_stats_prepare(&mem_stats);
memcpy(&stats->stats_mem, &mem_stats, sizeof(stats->stats_mem));
qdrv_pktlogger_slab_prepare(qw, &stats->stats_slab);
qdrv_pktlogger_netdebug_misc_stats_prepare(qw, &misc_stats);
memcpy(&stats->stats_misc, &misc_stats, sizeof(stats->stats_misc));
memcpy(&stats->stats_csw, &qw->csw_stats, sizeof(stats->stats_csw));
qdrv_pktlogger_netdebug_dsp_mu_stats_prepare(&stats->stats_dsp_mu);
/* Invoke stat monitor to kill MuC possibly */
qdrv_pktlogger_gen_muc_kill(qw, &stats->stats_phy_rx);
qdrv_pktlogger_send(stats, sizeof(*stats));
/* refresh timer */
mod_timer(&qw->pktlogger.stats_timer, jiffies + (tbl->interval * HZ));
}
static void
qdrv_pktlogger_slab_init(struct qdrv_wlan *qw)
{
struct qdrv_pktlogger *p_pktlogger = &qw->pktlogger;
#define CACHE(x) p_pktlogger->qmeminfo.caches[QDRV_SLAB_IDX_SIZE_##x] = kmem_cache_find("size-"#x);
#define ZACHE(y) p_pktlogger->qmeminfo.caches[QDRV_SLAB_IDX_##y] = kmem_cache_find(#y);
#include "qdrv_slab_watch.h"
#undef CACHE
#undef ZACHE
}
static int qdrv_pktlogger_start_stats(struct qdrv_wlan *qw)
{
struct qdrv_pktlogger_types_tbl *tbl = NULL;
tbl = qdrv_pktlogger_get_tbls_by_id(QDRV_NETDEBUG_TYPE_STATS);
if (!tbl) {
return -1;
}
if (qdrv_pktlogger_map(qw) < 0) {
return -1;
}
qdrv_pktlogger_set_auc_status_ptr(qw);
qdrv_pktlogger_slab_init(qw);
del_timer(&qw->pktlogger.stats_timer);
init_timer(&qw->pktlogger.stats_timer);
qw->pktlogger.stats_timer.function = qdrv_pktlogger_netdebug_stats_send;
qw->pktlogger.stats_timer.data = (unsigned long)qw;
qw->pktlogger.stats_timer.expires = jiffies + (tbl->interval * HZ);
add_timer(&qw->pktlogger.stats_timer);
qw->pktlogger.flag |= BIT(QDRV_NETDEBUG_TYPE_STATS);
printk("Netdebug is enabled\n");
return 0;
}
static void qdrv_pktlogger_stop_stats(struct qdrv_wlan *qw)
{
/*
* Clear the netdev queue pointers so we can uninstall the qdisc and
* resinstall a new one without crashing.
*/
qw->pktlogger.netdev_q_ptr_w = NULL;
qw->pktlogger.netdev_q_ptr_e = NULL;
del_timer(&qw->pktlogger.stats_timer);
qw->pktlogger.flag &= (~BIT(QDRV_NETDEBUG_TYPE_STATS));
printk("Netdebug is disabled\n");
}
static void qdrv_pktlogger_send_iwevent(void *data, int len)
{
struct qdrv_wlan *qw;
struct qdrv_netdebug_iwevent* hdr;
int payloadlen;
if (g_pktlogger_p) {
qw = g_pktlogger_p->qw;
} else {
printk("Pktlogger is not ready\n");
return;
}
hdr = qdrv_pktlogger_alloc_buffer("iwevent", sizeof(*hdr));
if (hdr == NULL) {
return;
}
if (len > sizeof(hdr->iwevent_data))
len = sizeof(hdr->iwevent_data);
payloadlen = sizeof(struct qdrv_pktlogger_hdr) + len;
qdrv_pktlogger_hdr_init(qw, &hdr->ndb_hdr, QDRV_NETDEBUG_TYPE_IWEVENT, payloadlen);
memcpy(hdr->iwevent_data, data, len);
qdrv_pktlogger_send(hdr, payloadlen);
}
/* Netlink query for the pktlogger compressed structures. */
static int
qdrv_pktlogger_create_pktlogger_data(struct qdrv_wlan *qw, char *p_buf, int buf_len)
{
struct sk_buff *skb_out;
struct nlmsghdr *nlho;
void *p_pkt;
int res = -1;
skb_out = nlmsg_new(buf_len, GFP_KERNEL);
if (skb_out) {
nlho = nlmsg_put(skb_out, 0, 0, NLMSG_DONE, buf_len, 0);
NETLINK_CB(skb_out).dst_group = 0;
p_pkt = nlmsg_data(nlho);
memcpy(p_pkt, p_buf, buf_len);
res = nlmsg_unicast(g_pktlogger_p->netlink_socket, skb_out, 12345);
}
return res;
}
static int
qdrv_pktlogger_max_radio_supported(void)
{
return 1;
}
static int
qdrv_pktlogger_config_flag_enabled(struct pktlogger_nl_pktlog_config_t *p_cur_pktconfig)
{
return p_cur_pktconfig->flags & 0x1;
}
static void
qdrv_pktlogger_config_flag_enable(struct pktlogger_nl_pktlog_config_t *p_cur_pktconfig)
{
p_cur_pktconfig->flags |= 0x1;
}
static int
qdrv_pktlogger_runtime_flag_enabled(struct qdrv_wlan *qw, int type)
{
return qw->pktlogger.flag & BIT(type);
}
static int
qdrv_pktlogger_config_interval_sanitise(struct pktlogger_nl_pktlog_config_t *p_cur_pktconfig)
{
if (p_cur_pktconfig->rate > 180) {
return 180;
}
if (p_cur_pktconfig->rate < 1) {
return 1;
}
return p_cur_pktconfig->rate;
}
static void
qdrv_pktlogger_set_single(struct qdrv_wlan *qw, uint32_t radio_index, struct pktlogger_nl_pktlog_config_t *p_cur_pktconfig)
{
struct qdrv_pktlogger_types_tbl *tbl = NULL;
uint32_t this_type = p_cur_pktconfig->type;
tbl = qdrv_pktlogger_get_tbls_by_id(this_type);
if (tbl) {
int to_enable = qdrv_pktlogger_config_flag_enabled(p_cur_pktconfig);
int is_enabled = qdrv_pktlogger_runtime_flag_enabled(qw, this_type);
tbl->interval = qdrv_pktlogger_config_interval_sanitise(p_cur_pktconfig);
tbl->history = p_cur_pktconfig->history;
if (to_enable && !is_enabled) {
/* Enable - currently disabled */
if (tbl->start) {
printk("Enabling %s logger, period %d\n", tbl->name, tbl->interval);
tbl->start(qw);
}
} else if (!to_enable && is_enabled) {
/* Disable - currently enabled */
if (tbl->stop) {
printk("Disabling %s logger\n", tbl->name);
tbl->stop(qw);
}
}
}
}
static void
qdrv_pktlogger_set_config(struct qdrv_wlan *qw, struct pktlogger_nl_config_set_t *p_s_conf)
{
int this_radio_idx = 0;
struct pktlogger_nl_config_t *p_conf = &p_s_conf->config;
uint32_t rcontrol = p_conf->rcontrol;
while (rcontrol) {
if (rcontrol & 0x1) {
uint32_t radio_config_count;
struct pktlogger_nl_radio_config_t *p_cur_radio = &p_conf->per_radio[this_radio_idx];
int i = 0;
radio_config_count = p_cur_radio->pktlog_ver_cnt & 0xFF;
/* Configure the radio appropriately */
for (i = 0; i < radio_config_count && i < ARRAY_SIZE(p_cur_radio->pktlog_configs); i++) {
struct pktlogger_nl_pktlog_config_t *p_cur_pktconfig = &p_cur_radio->pktlog_configs[i];
qdrv_pktlogger_set_single(qw, this_radio_idx, p_cur_pktconfig);
}
}
/* Next radio */
rcontrol >>= 1;
this_radio_idx++;
if (this_radio_idx >= qdrv_pktlogger_max_radio_supported()) {
break;
}
}
}
static void
qdrv_pktlogger_get_one_config(struct qdrv_wlan *qw, struct pktlogger_nl_pktlog_config_t *p_cur_pktconfig, struct qdrv_pktlogger_types_tbl *tbl)
{
p_cur_pktconfig->type = tbl->id;
if (qw->pktlogger.flag & BIT(tbl->id)) {
qdrv_pktlogger_config_flag_enable(p_cur_pktconfig);
}
if (tbl->struct_vsize) {
if (tbl->struct_vsize == 0xFFFF) {
p_cur_pktconfig->flags |= 0x4;
} else {
p_cur_pktconfig->flags |= 0x2;
p_cur_pktconfig->struct_vsize = tbl->struct_vsize;
}
}
p_cur_pktconfig->struct_bsize = tbl->struct_bsize;
p_cur_pktconfig->history = tbl->history;
strncpy(&p_cur_pktconfig->name[0], &tbl->name[0], sizeof(p_cur_pktconfig->name));
p_cur_pktconfig->rate = tbl->interval;
}
static void
qdrv_pktlogger_get_config_one(struct qdrv_wlan *qw, struct pktlogger_nl_config_one_t *p_conf, uint32_t radio_index, uint32_t ptype)
{
struct pktlogger_nl_pktlog_config_t *p_cur_pktconfig = &p_conf->config;
struct qdrv_pktlogger_types_tbl *tbl = NULL;
memset(p_conf, 0, sizeof(*p_conf));
p_conf->radio_index = radio_index;
tbl = qdrv_pktlogger_get_tbls_by_id(ptype);
if (tbl) {
qdrv_pktlogger_get_one_config(qw, p_cur_pktconfig, tbl);
} else {
p_cur_pktconfig->flags = 0x8000;
}
}
static void
qdrv_pktlogger_get_config(struct qdrv_wlan *qw, struct pktlogger_nl_config_t *p_conf)
{
struct qdrv_pktlogger *p_pktlogger = &qw->pktlogger;
struct pktlogger_nl_radio_config_t *p_cur_radio;
struct pktlogger_nl_pktlog_config_t *p_cur_pktconfig;
int i = 0;
int pktcount = 0;
memset(p_conf, 0, sizeof(*p_conf));
p_conf->rev = 0;
p_conf->rcontrol = 0x1;
p_cur_radio = &p_conf->per_radio[0];
p_cur_radio->destip = p_pktlogger->dst_ip;
p_cur_radio->srcip = p_pktlogger->src_ip;
p_cur_radio->destport = p_pktlogger->dst_port;
p_cur_radio->srcport = p_pktlogger->src_port;
memcpy(&p_cur_radio->destmac[0], &p_pktlogger->dst_addr[0], sizeof(p_cur_radio->destmac));
memcpy(&p_cur_radio->srcmac[0], &p_pktlogger->src_addr[0], sizeof(p_cur_radio->srcmac));
strncpy(&p_cur_radio->radioname[0], qw->mac->vnet[0]->name, sizeof(p_cur_radio->radioname));
p_cur_pktconfig = &p_cur_radio->pktlog_configs[0];
for (i = 0; i < ARRAY_SIZE(p_cur_radio->pktlog_configs); i++) {
struct qdrv_pktlogger_types_tbl *tbl = NULL;
tbl = qdrv_pktlogger_get_tbls_by_id(i);
if (tbl) {
qdrv_pktlogger_get_one_config(qw, p_cur_pktconfig, tbl);
p_cur_pktconfig++;
pktcount++;
}
}
p_cur_radio->pktlog_ver_cnt = pktcount;
}
/* Netlink query for a single pktlogger configuration structure */
static struct sk_buff *
qdrv_pktlogger_create_config_query_one(struct qdrv_wlan *qw, struct pktlogger_nl_query_t *p_query)
{
struct sk_buff *skb_out;
struct nlmsghdr *nlho;
struct pktlogger_nl_query_t *p_outq;
struct pktlogger_nl_config_one_t *p_outconf;
int msg_size = sizeof(*p_outq) + sizeof(*p_outconf);
/* Ensure sanity of input */
if (p_query->arg1 >= qdrv_pktlogger_max_radio_supported()) {
return NULL;
}
//printk("Create config\n");
skb_out = nlmsg_new(msg_size, GFP_KERNEL);
if (skb_out) {
nlho = nlmsg_put(skb_out, 0, 0, NLMSG_DONE, msg_size, 0);
NETLINK_CB(skb_out).dst_group = 0;
p_outq = nlmsg_data(nlho);
p_outconf = (struct pktlogger_nl_config_one_t *)&p_outq->data[0];
memcpy(p_outq, p_query, sizeof(*p_outq));
p_outq->hdr.mlen = sizeof(*p_outconf);
qdrv_pktlogger_get_config_one(qw, p_outconf, p_query->arg1, p_query->arg2);
}
return skb_out;
}
/* Netlink query for the pktlogger config for all radios */
static struct sk_buff *
qdrv_pktlogger_create_config_query(struct qdrv_wlan *qw, struct pktlogger_nl_query_t *p_query)
{
struct sk_buff *skb_out;
struct nlmsghdr *nlho;
struct pktlogger_nl_query_t *p_outq;
struct pktlogger_nl_config_t *p_outconf;
int msg_size = sizeof(*p_outq) + sizeof(*p_outconf);
skb_out = nlmsg_new(msg_size, GFP_KERNEL);
if (skb_out) {
nlho = nlmsg_put(skb_out, 0, 0, NLMSG_DONE, msg_size, 0);
NETLINK_CB(skb_out).dst_group = 0;
p_outq = nlmsg_data(nlho);
p_outconf = (struct pktlogger_nl_config_t *)&p_outq->data[0];
memcpy(p_outq, p_query, sizeof(*p_outq));
p_outq->hdr.mlen = sizeof(*p_outconf);
qdrv_pktlogger_get_config(qw, p_outconf);
}
return skb_out;
}
/* Netlink query for the pktlogger compressed structures. */
static struct sk_buff *
qdrv_pktlogger_create_struct_query(struct qdrv_wlan *qw, struct pktlogger_nl_query_t *p_query)
{
struct sk_buff *skb_out;
struct nlmsghdr *nlho;
int msg_size = sizeof(pktlogger_structs) + sizeof(*p_query);
struct pktlogger_nl_query_t *p_outq;
void *p_structstart;
skb_out = nlmsg_new(msg_size, GFP_KERNEL);
if (skb_out) {
nlho = nlmsg_put(skb_out, 0, 0, NLMSG_DONE, msg_size, 0);
NETLINK_CB(skb_out).dst_group = 0;
p_outq = nlmsg_data(nlho);
p_structstart = &p_outq->data[0];
memcpy(p_outq, p_query, sizeof(*p_outq));
p_outq->hdr.mlen = sizeof(pktlogger_structs);
memcpy(p_structstart, pktlogger_structs, sizeof(pktlogger_structs));
}
return skb_out;
}
/* Generic query handler */
static void qdrv_pktlogger_netlink_query(struct qdrv_wlan *qw, struct nlmsghdr *nlh)
{
int pid;
int res = 0;
struct sk_buff *skb_out = NULL;
struct pktlogger_nl_query_t *p_query = (struct pktlogger_nl_query_t *)nlmsg_data(nlh);
pid = nlh->nlmsg_pid;
switch (p_query->query_num) {
case PKTLOGGER_QUERY_STRUCT:
skb_out = qdrv_pktlogger_create_struct_query(qw, p_query);
break;
case PKTLOGGER_QUERY_CONFIG:
skb_out = qdrv_pktlogger_create_config_query(qw, p_query);
break;
case PKTLOGGER_QUERY_CONFIG_ONE:
skb_out = qdrv_pktlogger_create_config_query_one(qw, p_query);
break;
default:
printk("Unknown query (%d)\n", p_query->query_num);
break;
}
if (skb_out) {
res = nlmsg_unicast(g_pktlogger_p->netlink_socket, skb_out, pid);
}
if (res < 0)
printk("Error sending msg to uspace\n");
}
static void
qdrv_pktlogger_netlink_config_one(struct qdrv_wlan *qw, struct nlmsghdr *nlh)
{
struct pktlogger_nl_config_oneset_t *p_oconfig = (struct pktlogger_nl_config_oneset_t *)nlmsg_data(nlh);
struct pktlogger_nl_pktlog_config_t *p_config = &p_oconfig->config.config;
qdrv_pktlogger_set_single(qw, p_oconfig->config.radio_index, p_config);
}
static void
qdrv_pktlogger_netlink_config(struct qdrv_wlan *qw, struct nlmsghdr *nlh)
{
struct pktlogger_nl_config_set_t *p_config = (struct pktlogger_nl_config_set_t *)nlmsg_data(nlh);
qdrv_pktlogger_set_config(qw, p_config);
}
/* Pktlogger netlink message incoming */
static void qdrv_pktlogger_netlink_msg(struct qdrv_wlan *qw, struct nlmsghdr *nlh)
{
struct pktlogger_nl_hdr_t *p_hdr = (struct pktlogger_nl_hdr_t *)nlmsg_data(nlh);
if (p_hdr->magic != PKTLOGGER_MSG_MAGIC) {
printk("Invalid magic in pktlogger netlink msg\n");
return;
}
switch(p_hdr->mtype) {
case PKTLOGGER_NETLINK_MTYPE_QUERY:
qdrv_pktlogger_netlink_query(qw, nlh);
break;
case PKTLOGGER_NETLINK_MTYPE_CONFIG:
qdrv_pktlogger_netlink_config(qw, nlh);
break;
case PKTLOGGER_NETLINK_MTYPE_CONFIG_ONE:
qdrv_pktlogger_netlink_config_one(qw, nlh);
break;
default:
printk("Unknown msg type %d\n", p_hdr->mtype);
break;
}
}
static void qdrv_pktlogger_recv_msg(struct sk_buff *skb)
{
struct nlmsghdr *nlh = (struct nlmsghdr*)skb->data;
DBGPRINTF(DBG_LL_DEBUG, QDRV_LF_ALL,
"%s line %d Netlink received pid:%d, size:%d, type:%d\n",
__FUNCTION__, __LINE__, nlh->nlmsg_pid, nlh->nlmsg_len, nlh->nlmsg_type);
switch (nlh->nlmsg_type) {
case QDRV_NETDEBUG_TYPE_IWEVENT:
if (g_pktlogger_p->flag & BIT(QDRV_NETDEBUG_TYPE_IWEVENT)) {
qdrv_pktlogger_send_iwevent(skb->data + sizeof(struct nlmsghdr),
nlh->nlmsg_len);
}
break;
case QDRV_NETDEBUG_TYPE_SYSMSG:
if (g_pktlogger_p->flag & BIT(QDRV_NETDEBUG_TYPE_SYSMSG)) {
qdrv_control_sysmsg_send(g_pktlogger_p->qw,
(char *)(skb->data + sizeof(struct nlmsghdr)),
nlh->nlmsg_len, 0);
}
break;
case QDRV_NETDEBUG_TYPE_PKTLOGGER:
qdrv_pktlogger_netlink_msg(g_pktlogger_p->qw, nlh);
break;
default:
printk("%s line %d Netlink Invalid type %d\n",
__FUNCTION__, __LINE__, nlh->nlmsg_type);
break;
}
}
static int qdrv_pktlogger_start_netlink(struct qdrv_wlan *qw)
{
qw->pktlogger.netlink_ref++;
if (qw->pktlogger.netlink_socket == NULL) {
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0))
struct netlink_kernel_cfg cfg = {
.input = qdrv_pktlogger_recv_msg
};
qw->pktlogger.netlink_socket = netlink_kernel_create(&init_net,
QDRV_NETLINK_PKTLOGGER, &cfg);
#else
qw->pktlogger.netlink_socket = netlink_kernel_create(&init_net,
QDRV_NETLINK_PKTLOGGER, 0, qdrv_pktlogger_recv_msg, NULL, THIS_MODULE);
#endif
if (qw->pktlogger.netlink_socket == NULL) {
DBGPRINTF_E("Error creating netlink socket.\n");
return -1;
}
}
return 0;
}
static void qdrv_pktlogger_stop_netlink(struct qdrv_wlan *qw)
{
qw->pktlogger.netlink_ref--;
if ((qw->pktlogger.netlink_ref == 0) && qw->pktlogger.netlink_socket) {
netlink_kernel_release(qw->pktlogger.netlink_socket);
qw->pktlogger.netlink_socket = NULL;
}
}
static int qdrv_pktlogger_start_iwevent(struct qdrv_wlan *qw)
{
struct qdrv_pktlogger_types_tbl *tbl = NULL;
if ((qw->pktlogger.flag & BIT(QDRV_NETDEBUG_TYPE_IWEVENT)) == 0) {
tbl = qdrv_pktlogger_get_tbls_by_id(QDRV_NETDEBUG_TYPE_IWEVENT);
if (!tbl) {
return -1;
}
qw->pktlogger.flag |= BIT(QDRV_NETDEBUG_TYPE_IWEVENT);
return qdrv_pktlogger_start_netlink(qw);
} else {
return 0;
}
}
static void qdrv_pktlogger_stop_iwevent(struct qdrv_wlan *qw)
{
if ((qw->pktlogger.flag & BIT(QDRV_NETDEBUG_TYPE_IWEVENT))) {
qw->pktlogger.flag &= (~BIT(QDRV_NETDEBUG_TYPE_IWEVENT));
qdrv_pktlogger_stop_netlink(qw);
}
}
static int qdrv_pktlogger_start_sysmsg(struct qdrv_wlan *qw)
{
struct qdrv_pktlogger_types_tbl *tbl = NULL;
if ((qw->pktlogger.flag & BIT(QDRV_NETDEBUG_TYPE_SYSMSG)) == 0) {
tbl = qdrv_pktlogger_get_tbls_by_id(QDRV_NETDEBUG_TYPE_SYSMSG);
if (!tbl) {
return -1;
}
qw->pktlogger.flag |= BIT(QDRV_NETDEBUG_TYPE_SYSMSG);
del_timer(&qw->pktlogger.sysmsg_timer);
init_timer(&qw->pktlogger.sysmsg_timer);
qw->pktlogger.sysmsg_timer.function = qdrv_control_sysmsg_timer;
qw->pktlogger.sysmsg_timer.data = (unsigned long)qw;
mod_timer(&qw->pktlogger.sysmsg_timer, jiffies +
(tbl->interval * HZ));
return qdrv_pktlogger_start_netlink(qw);
} else {
return 0;
}
}
static void qdrv_pktlogger_stop_sysmsg(struct qdrv_wlan *qw)
{
if ((qw->pktlogger.flag & BIT(QDRV_NETDEBUG_TYPE_SYSMSG))) {
qw->pktlogger.flag &= (~BIT(QDRV_NETDEBUG_TYPE_SYSMSG));
qdrv_control_sysmsg_send(qw, NULL, 0, 1);
del_timer(&qw->pktlogger.sysmsg_timer);
qdrv_pktlogger_stop_netlink(qw);
}
}
static void qdrv_pktlogger_ratedebug_send(unsigned long data)
{
struct qdrv_wlan *qw = (struct qdrv_wlan *)data;
void *databuf;
struct qdrv_netdebug_rate *rate_stats;
struct qtn_stats_log *iw_stats_log = (struct qtn_stats_log *)qw->mac->mac_sys_stats;
struct muc_rx_rates *muc_rx_rates_p =
(struct muc_rx_rates *)qw->pktlogger.stats_uc_rx_rate_ptr;
struct qtn_rate_tx_stats_per_sec *tx_rate_stats =
(struct qtn_rate_tx_stats_per_sec *)qw->pktlogger.stats_uc_tx_rate_ptr;
uint32_t *su_rates_read_ptr = qw->pktlogger.stats_uc_su_rates_read_ptr;
uint32_t *mu_rates_read_ptr = qw->pktlogger.stats_uc_mu_rates_read_ptr;
struct muc_rx_rates *rx_rates_prev = qw->pktlogger.rx_ratelog_pre;
struct muc_rx_rates *rx_rates_curr = qw->pktlogger.rx_ratelog_cur;
uint32_t curr_index;
int i;
int rate_entry = 0;
struct qdrv_pktlogger_types_tbl *tbl =
qdrv_pktlogger_get_tbls_by_id(QDRV_NETDEBUG_TYPE_RATE);
if (!tbl) {
return;
}
databuf = qdrv_pktlogger_alloc_buffer("rate", sizeof(*rate_stats));
if (databuf == NULL) {
return;
}
rate_stats = (struct qdrv_netdebug_rate *) databuf;
qdrv_pktlogger_hdr_init(qw, &rate_stats->ndb_hdr, QDRV_NETDEBUG_TYPE_RATE,
sizeof(*rate_stats));
/*
* Copy assuming all rate adaptions for this second collected. The
* parser will need to check the sequence numbers to detect missed data.
*/
memcpy(&rate_stats->rate_su_tx_stats, tx_rate_stats->stats_su,
sizeof(rate_stats->rate_su_tx_stats));
memcpy(&rate_stats->rate_mu_tx_stats, tx_rate_stats->stats_mu,
sizeof(rate_stats->rate_mu_tx_stats));
/* Tell MUC the rate tx stats can be re-written */
*su_rates_read_ptr = 1;
*mu_rates_read_ptr = 1;
/*
* Get rates from the MuC and diff from previous values. Copy as many rates as
* possible, starting from the last rate to prefer higher rates.
*/
memcpy(rx_rates_curr, muc_rx_rates_p, sizeof(*rx_rates_curr));
for (i = ARRAY_SIZE(rx_rates_curr->rx_mcs_11ac) - 1; i >= 0; i--) {
if (rate_entry >= (ARRAY_SIZE(rate_stats->rate_gen_stats.rx_mcs)))
break;
if (rx_rates_curr->rx_mcs_11ac[i] == rx_rates_prev->rx_mcs_11ac[i])
continue;
rate_stats->rate_gen_stats.rx_mcs_rates[rate_entry] =
(uint16_t)((QTN_PHY_STATS_MODE_11AC << 8) | i);
rate_stats->rate_gen_stats.rx_mcs[rate_entry] =
rx_rates_curr->rx_mcs_11ac[i] - rx_rates_prev->rx_mcs_11ac[i];
rate_entry++;
}
for (i = ARRAY_SIZE(rx_rates_curr->rx_mcs) - 1; i >= 0; i--) {
if (rate_entry >= (ARRAY_SIZE(rate_stats->rate_gen_stats.rx_mcs)))
break;
if (rx_rates_curr->rx_mcs[i] == rx_rates_prev->rx_mcs[i])
continue;
rate_stats->rate_gen_stats.rx_mcs_rates[rate_entry] = i;
rate_stats->rate_gen_stats.rx_mcs[rate_entry] =
rx_rates_curr->rx_mcs[i] - rx_rates_prev->rx_mcs[i];
rate_entry++;
}
qw->pktlogger.rx_ratelog_pre = rx_rates_curr;
qw->pktlogger.rx_ratelog_cur = rx_rates_prev;
/* Find the current index into the phy stats */
curr_index = (iw_stats_log->curr_buff - 1 + NUM_LOG_BUFFS) % NUM_LOG_BUFFS;
if ((iw_stats_log->stat_buffs[curr_index].tstamp & 0x01) == 0) {
u_int32_t *p_evms = &rate_stats->rate_gen_stats.rx_evm[0];
int32_t evm_int, evm_frac;
for (i = 0; i < QDRV_NUM_RF_STREAMS; i++) {
convert_evm_db(iw_stats_log->stat_buffs[curr_index].rx_phy_stats.last_rssi_evm[i],
iw_stats_log->stat_buffs[curr_index].rx_phy_stats.last_rxsym,
&evm_int,
&evm_frac);
*p_evms++ = (evm_frac & 0xffff) | (evm_int << 16);
}
} else {
memset(rate_stats->rate_gen_stats.rx_evm, 0,
sizeof(rate_stats->rate_gen_stats.rx_evm));
}
rate_stats->rate_gen_stats.rx_crc =
iw_stats_log->stat_buffs[curr_index].rx_phy_stats.cnt_mac_crc;
rate_stats->rate_gen_stats.rx_sp_errors =
iw_stats_log->stat_buffs[curr_index].rx_phy_stats.cnt_sp_fail;
rate_stats->rate_gen_stats.rx_lp_errors =
iw_stats_log->stat_buffs[curr_index].rx_phy_stats.cnt_lp_fail;
qdrv_pktlogger_send(rate_stats, sizeof(*rate_stats));
/* refresh timer */
mod_timer(&qw->pktlogger.rate_timer, jiffies + (tbl->interval * HZ));
}
static int qdrv_pktlogger_start_rate(struct qdrv_wlan *qw)
{
struct timer_list *timer = &qw->pktlogger.rate_timer;
struct qdrv_pktlogger_types_tbl *tbl =
qdrv_pktlogger_get_tbls_by_id(QDRV_NETDEBUG_TYPE_RATE);
del_timer(&qw->pktlogger.rate_timer);
if (!tbl) {
return -1;
}
if (qdrv_pktlogger_map(qw) < 0) {
return -1;
}
init_timer(timer);
timer->function = qdrv_pktlogger_ratedebug_send;
timer->data = (unsigned long)qw;
timer->expires = jiffies + (tbl->interval * HZ);
add_timer(timer);
/* Set the flag to clear old stats */
*qw->pktlogger.stats_uc_su_rates_read_ptr = 1;
*qw->pktlogger.stats_uc_mu_rates_read_ptr = 1;
qw->pktlogger.flag |= BIT(QDRV_NETDEBUG_TYPE_RATE);
printk("Ratedebug is enabled\n");
return 0;
}
static void qdrv_pktlogger_stop_rate(struct qdrv_wlan *qw)
{
del_timer(&qw->pktlogger.rate_timer);
qw->pktlogger.flag &= (~BIT(QDRV_NETDEBUG_TYPE_RATE));
printk("Ratedebug is disabled\n");
}
static void qdrv_pktlogger_mem_send(unsigned long data)
{
struct qdrv_wlan *qw = (struct qdrv_wlan *)data;
struct qdrv_netdebug_mem *stats;
struct qdrv_pktlogger_types_tbl *tbl = NULL;
u8 *p;
int i;
int j;
u32 *remap_addr;
u32 val;
void *data_buf;
tbl = qdrv_pktlogger_get_tbls_by_id(QDRV_NETDEBUG_TYPE_MEM);
if (!tbl) {
return;
}
data_buf = qdrv_pktlogger_alloc_buffer("mem", sizeof(*stats));
if (data_buf == NULL) {
return;
}
stats = (struct qdrv_netdebug_mem *) data_buf;
qdrv_pktlogger_hdr_init(qw, &stats->ndb_hdr, QDRV_NETDEBUG_TYPE_MEM,
sizeof(*stats));
/* copy from each location into the packet */
p = &stats->stvec_data[0];
/* BB registers can be registered into memdebug */
qtn_bb_mutex_enter(QTN_LHOST_SOC_CPU);
for (i = 0; i < qw->pktlogger.mem_wp_index; i++) {
memcpy(p, &qw->pktlogger.mem_wps[i].addr, sizeof(qw->pktlogger.mem_wps[i].addr));
p += sizeof(qw->pktlogger.mem_wps[i].addr);
memcpy(p, &qw->pktlogger.mem_wps[i].size, sizeof(qw->pktlogger.mem_wps[i].size));
p += sizeof(qw->pktlogger.mem_wps[i].size);
remap_addr = qw->pktlogger.mem_wps[i].remap_addr;
for (j = 0; j < qw->pktlogger.mem_wps[i].size; j++) {
val = *remap_addr;
memcpy(p, &val, sizeof(u32));
p += sizeof(u32);
remap_addr++;
}
}
qtn_bb_mutex_leave(QTN_LHOST_SOC_CPU);
/* send completed packet */
qdrv_pktlogger_send(stats, sizeof(*stats));
/* refresh timer */
mod_timer(&qw->pktlogger.mem_timer, jiffies + (tbl->interval * HZ));
}
static int qdrv_pktlogger_start_mem(struct qdrv_wlan *qw) {
struct timer_list *timer = &qw->pktlogger.mem_timer;
struct qdrv_pktlogger_types_tbl *tbl = NULL;
if (!qw->pktlogger.mem_wp_index) {
DBGPRINTF_E("no watchpoints defined! not starting\n");
return -1;
}
tbl = qdrv_pktlogger_get_tbls_by_id(QDRV_NETDEBUG_TYPE_MEM);
if (!tbl) {
return -1;
}
del_timer(timer);
init_timer(timer);
timer->function = qdrv_pktlogger_mem_send;
timer->data = (unsigned long) qw;
timer->expires = jiffies + (tbl->interval * HZ);
add_timer(timer);
qw->pktlogger.flag |= BIT(QDRV_NETDEBUG_TYPE_MEM);
return 0;
}
static void qdrv_pktlogger_stop_mem(struct qdrv_wlan *qw) {
int i;
del_timer(&qw->pktlogger.mem_timer);
for (i = 0; i < qw->pktlogger.mem_wp_index; i++) {
iounmap(qw->pktlogger.mem_wps[i].remap_addr);
}
qw->pktlogger.mem_wp_index = 0;
qw->pktlogger.flag &= (~BIT(QDRV_NETDEBUG_TYPE_MEM));
}
#ifdef CONFIG_QVSP
/*
* Forward VSP stats to the netdebug target
*/
static void qdrv_pktlogger_vspdebug_send(void *data, void *vsp_data, uint32_t size)
{
struct qdrv_wlan *qw = (struct qdrv_wlan *)data;
void *databuf;
struct qdrv_pktlogger_hdr *ndb_hdr;
databuf = qdrv_pktlogger_alloc_buffer("vsp", sizeof(struct qdrv_pktlogger_hdr) + size);
if (databuf == NULL) {
return;
}
ndb_hdr = (struct qdrv_pktlogger_hdr *)databuf;
qdrv_pktlogger_hdr_init(qw, ndb_hdr,
QDRV_NETDEBUG_TYPE_VSP, size + sizeof(struct qdrv_pktlogger_hdr));
memcpy(ndb_hdr + 1, vsp_data, size);
qdrv_pktlogger_send(databuf, size + sizeof(struct qdrv_pktlogger_hdr));
}
static int qdrv_pktlogger_start_vsp(struct qdrv_wlan *qw)
{
struct qdrv_pktlogger_types_tbl *tbl = NULL;
if (qw->qvsp == NULL) {
DBGPRINTF_E("VSP is not not initialised\n");
return -1;
}
tbl = qdrv_pktlogger_get_tbls_by_id(QDRV_NETDEBUG_TYPE_VSP);
if (!tbl) {
return -1;
}
qvsp_netdbg_init(qw->qvsp, &qdrv_pktlogger_vspdebug_send, tbl->interval);
qw->pktlogger.flag |= BIT(QDRV_NETDEBUG_TYPE_VSP);
printk("VSP netdebug is enabled\n");
return 0;
}
static void qdrv_pktlogger_stop_vsp(struct qdrv_wlan *qw)
{
if (qw->qvsp == NULL) {
DBGPRINTF_E("VSP is not not initialised\n");
return;
}
qvsp_netdbg_exit(qw->qvsp);
qw->pktlogger.flag &= (~BIT(QDRV_NETDEBUG_TYPE_VSP));
printk("VSP netdebug is disabled\n");
}
#endif
/*
* Push radar statistics out on every call
*/
static void qdrv_control_radar_stats_send(void *data,
int (*pulse_copy_iter)(void *dest, void *src, int pulse_indx),
void *pulse_buf, int num_pulses)
{
struct qdrv_wlan *qw = (struct qdrv_wlan *)data;
struct qdrv_radar_stats *stats;
int indx;
int num_bytes = 0;
stats = qdrv_pktlogger_alloc_buffer("radar", sizeof(*stats));
if (stats == NULL) {
radar_register_statcb(NULL, NULL);
return;
}
qdrv_pktlogger_hdr_init(qw, &stats->ndb_hdr, QDRV_NETDEBUG_TYPE_RADAR,
sizeof(*stats));
/* Max num of pulses is 175 based on 8 bytes per pulse */
if (num_pulses > QDRV_NETDEBUG_RADAR_MAXPULSE) {
stats->ndb_hdr.flags = QDRV_NETDEBUG_FLAGS_TRUNCATED;
num_pulses = QDRV_NETDEBUG_RADAR_MAXPULSE;
}
stats->numpulses = num_pulses;
/* Gather statistics */
for (indx=0;indx < num_pulses; indx++) {
num_bytes += pulse_copy_iter(
stats->pulseinfo + num_bytes, pulse_buf, indx);
}
int trimlen = QDRV_NETDEBUG_RADAR_PULSESIZE * QDRV_NETDEBUG_RADAR_MAXPULSE - num_bytes;
qdrv_pktlogger_send(stats, sizeof(*stats) - trimlen);
}
static int qdrv_pktlogger_start_radar(struct qdrv_wlan *qw)
{
int ret;
ret = radar_register_statcb(qdrv_control_radar_stats_send, (void*)qw);
qw->pktlogger.flag |= BIT(QDRV_NETDEBUG_TYPE_RADAR);
return ret;
}
static void qdrv_pktlogger_stop_radar(struct qdrv_wlan *qw)
{
radar_register_statcb(NULL, NULL);
qw->pktlogger.flag &= (~BIT(QDRV_NETDEBUG_TYPE_RADAR));
}
void add_per_node_phystat(struct qdrv_netdebug_phystats* stats,
int index, struct ieee80211_node *ni)
{
struct qdrv_netdebug_per_node_phystats* item =
&stats->per_node_stats[index];
memcpy(&item->node_macaddr, &ni->ni_macaddr, sizeof(item->node_macaddr));
memcpy(&item->per_node_phystats, ni->ni_shared_stats, sizeof(item->per_node_phystats));
}
void qdrv_pktlogger_phystats_send(unsigned long data)
{
static struct qtn_stats_log host_log;
static int last_tstamp = 0;
struct qdrv_wlan* qw = (struct qdrv_wlan *)data;
struct qdrv_mac* mac = qw ? qw->mac : NULL;
struct ieee80211_node_table* nt = qw ? &qw->ic.ic_sta : NULL;
struct ieee80211_node* ni;
struct qdrv_pktlogger_types_tbl *tbl = NULL;
int i;
tbl = qdrv_pktlogger_get_tbls_by_id(QDRV_NETDEBUG_TYPE_PHY_STATS);
if (!(tbl && mac && mac->mac_sys_stats && nt)) {
return;
}
memcpy(&host_log, mac->mac_sys_stats, sizeof(host_log));
/* Gather statistics */
/* Why +2:
* the "current" stat is the most recently _complete_ stat written
* item at current+1 is being written right now and probably not safe to read
* item at +2 is the oldest available stat in this circular buffer
*/
for ( i = (host_log.curr_buff + 2) % NUM_LOG_BUFFS;
i != host_log.curr_buff;
i = (i+1) % NUM_LOG_BUFFS) {
struct qtn_stats* curr_log_ptr = &host_log.stat_buffs[i];
if(curr_log_ptr->tstamp > last_tstamp) {
struct qdrv_netdebug_phystats* phystats;
/*
* we do not know how much nodes will be acquired so
* allocate room enough for the worst case
*/
int phystats_len = sizeof(*phystats) +
(QTN_NCIDX_MAX-1) * sizeof(phystats->per_node_stats[0]);
phystats = qdrv_pktlogger_alloc_buffer("phystats", phystats_len);
if (phystats != NULL) {
int node_index = 0;
IEEE80211_NODE_LOCK_IRQ(nt);
TAILQ_FOREACH(ni, &nt->nt_node, ni_list) {
add_per_node_phystat(phystats, node_index, ni);
node_index++;
}
IEEE80211_NODE_UNLOCK_IRQ(nt);
phystats->per_node_stats_count = node_index;
/* redefine phystats_len as now we know how much nodes do we have*/
phystats_len = sizeof(*phystats) +
(node_index-1) * sizeof(phystats->per_node_stats[0]);
qdrv_pktlogger_hdr_init(qw, &phystats->ndb_hdr,
QDRV_NETDEBUG_TYPE_PHY_STATS, phystats_len);
memcpy(&phystats->stats, curr_log_ptr, sizeof(phystats->stats));
qdrv_pktlogger_send(phystats, phystats_len);
}
last_tstamp = curr_log_ptr->tstamp;
}
}
/* refresh timer */
mod_timer(&qw->pktlogger.phy_stats_timer, jiffies + (tbl->interval * HZ));
}
static int qdrv_pktlogger_start_phy_stats(struct qdrv_wlan *qw)
{
struct timer_list *timer = &qw->pktlogger.phy_stats_timer;
struct qdrv_pktlogger_types_tbl *tbl =
qdrv_pktlogger_get_tbls_by_id(QDRV_NETDEBUG_TYPE_PHY_STATS);
del_timer(&qw->pktlogger.phy_stats_timer);
if (!tbl) {
printk("unable to find item at QDRV_NETDEBUG_TYPE_PHY_STATS\n");
return -1;
}
init_timer(timer);
timer->function = qdrv_pktlogger_phystats_send;
timer->data = (unsigned long)qw;
timer->expires = jiffies + (tbl->interval * HZ);
add_timer(timer);
/* Set the flag to clear old stats */
qw->pktlogger.flag |= BIT(QDRV_NETDEBUG_TYPE_PHY_STATS);
printk("phy_stats sending enabled\n");
return 0;
}
static void qdrv_pktlogger_stop_phy_stats(struct qdrv_wlan *qw)
{
del_timer(&qw->pktlogger.phy_stats_timer);
qw->pktlogger.flag &= (~BIT(QDRV_NETDEBUG_TYPE_PHY_STATS));
printk("phy_stats sending disabled\n");
}
void qdrv_pktlogger_dspstats_send(unsigned long data)
{
struct qdrv_wlan* qw = (struct qdrv_wlan *)data;
struct qdrv_mac* mac = qw ? qw->mac : NULL;
struct qdrv_pktlogger_types_tbl *tbl = NULL;
volatile struct qtn_txbf_mbox *txbf_mbox = qtn_txbf_mbox_get();
struct qdrv_netdebug_dspstats* dspstats;
tbl = qdrv_pktlogger_get_tbls_by_id(QDRV_NETDEBUG_TYPE_DSP_STATS);
if (!(tbl && mac)) {
return;
}
dspstats = qdrv_pktlogger_alloc_buffer("dspstats", sizeof(*dspstats));
if (dspstats != NULL) {
qdrv_pktlogger_hdr_init(qw, &dspstats->ndb_hdr,
QDRV_NETDEBUG_TYPE_DSP_STATS, sizeof(*dspstats));
memcpy(&dspstats->stats, (void *)&txbf_mbox->dsp_stats, sizeof(dspstats->stats));
qdrv_pktlogger_send(dspstats, sizeof(*dspstats));
}
/* refresh timer */
mod_timer(&qw->pktlogger.dsp_stats_timer, jiffies + (tbl->interval * HZ));
}
static int qdrv_pktlogger_start_dsp_stats(struct qdrv_wlan *qw)
{
struct timer_list *timer = &qw->pktlogger.dsp_stats_timer;
struct qdrv_pktlogger_types_tbl *tbl =
qdrv_pktlogger_get_tbls_by_id(QDRV_NETDEBUG_TYPE_DSP_STATS);
del_timer(&qw->pktlogger.dsp_stats_timer);
if (!tbl) {
printk("unable to find item at QDRV_NETDEBUG_TYPE_DSP_STATS\n");
return -1;
}
init_timer(timer);
timer->function = qdrv_pktlogger_dspstats_send;
timer->data = (unsigned long)qw;
timer->expires = jiffies + (tbl->interval * HZ);
add_timer(timer);
/* Set the flag to clear old stats */
qw->pktlogger.flag |= BIT(QDRV_NETDEBUG_TYPE_DSP_STATS);
printk("dsp_stats sending enabled\n");
return 0;
}
static void qdrv_pktlogger_stop_dsp_stats(struct qdrv_wlan *qw)
{
del_timer(&qw->pktlogger.dsp_stats_timer);
qw->pktlogger.flag &= (~BIT(QDRV_NETDEBUG_TYPE_DSP_STATS));
printk("dsp_stats sending disabled\n");
}
void qdrv_pktlogger_core_dump_send(unsigned long data)
{
struct qdrv_wlan* qw = (struct qdrv_wlan *)data;
struct qdrv_mac* mac = qw ? qw->mac : NULL;
struct qdrv_pktlogger_types_tbl *tbl = NULL;
struct qdrv_netdebug_core_dump *core_dump;
uint32_t len_copied = 0;
tbl = qdrv_pktlogger_get_tbls_by_id(QDRV_NETDEBUG_TYPE_CORE_DUMP);
if (!(tbl && mac)) {
return;
}
core_dump = qdrv_pktlogger_alloc_buffer(tbl->name, tbl->struct_bsize + tbl->struct_vsize);
if (core_dump) {
qdrv_copy_core_dump(core_dump->data, sizeof(core_dump->data), &len_copied);
if (!len_copied) {
qdrv_pktlogger_free_buffer(core_dump);
return;
}
qdrv_pktlogger_hdr_init(qw, &core_dump->ndb_hdr,
QDRV_NETDEBUG_TYPE_CORE_DUMP, sizeof(core_dump->ndb_hdr) + len_copied);
qdrv_pktlogger_send(core_dump, sizeof(*core_dump));
}
}
static int qdrv_pktlogger_start_core_dump(struct qdrv_wlan *qw)
{
struct qdrv_pktlogger_types_tbl *tbl =
qdrv_pktlogger_get_tbls_by_id(QDRV_NETDEBUG_TYPE_CORE_DUMP);
if (!tbl) {
printk("Unable to find item at QDRV_NETDEBUG_TYPE_CORE_DUMP\n");
return -1;
}
/* Set the flag to clear old stats */
qw->pktlogger.flag |= BIT(QDRV_NETDEBUG_TYPE_CORE_DUMP);
printk("core_dump sending enabled\n");
/*
* Send the core dump, if it exists; it would be sent only once when this type is enabled
* ('interval' is ignored)
*/
qdrv_pktlogger_core_dump_send((unsigned long) qw);
return 0;
}
static void qdrv_pktlogger_stop_core_dump(struct qdrv_wlan *qw)
{
qw->pktlogger.flag &= (~BIT(QDRV_NETDEBUG_TYPE_CORE_DUMP));
printk("core_dump sending disabled\n");
}
void qdrv_pktlogger_flush_data(struct qdrv_wlan *qw)
{
int enable = 1;
qdrv_hostlink_enable_flush_data(qw, enable);
}
struct qdrv_pktlogger_types_tbl qdrv_pktlogger_types_tbl_ent[] =
{
{QDRV_NETDEBUG_TYPE_STATS, "stats",
qdrv_pktlogger_start_stats, qdrv_pktlogger_stop_stats,
QDRV_PKTLOGGER_INTERVAL_DFLT_STATS,
sizeof(struct qdrv_netdebug_stats), 0, 0},
{QDRV_NETDEBUG_TYPE_RADAR, "radar",
qdrv_pktlogger_start_radar, qdrv_pktlogger_stop_radar,
QDRV_PKTLOGGER_INTERVAL_DFLT_NONE,
sizeof(struct qdrv_radar_stats), 0, 0},
{QDRV_NETDEBUG_TYPE_TXBF, "txbf", NULL, NULL,
QDRV_PKTLOGGER_INTERVAL_DFLT_NONE,
sizeof(struct qdrv_netdebug_txbf), 0, 0},
{QDRV_NETDEBUG_TYPE_IWEVENT, "iwevent",
qdrv_pktlogger_start_iwevent, qdrv_pktlogger_stop_iwevent,
QDRV_PKTLOGGER_INTERVAL_DFLT_NONE,
sizeof(struct qdrv_netdebug_iwevent), 0xFFFF, 0},
{QDRV_NETDEBUG_TYPE_SYSMSG, "sysmsg",
qdrv_pktlogger_start_sysmsg, qdrv_pktlogger_stop_sysmsg,
QDRV_PKTLOGGER_INTERVAL_DFLT_SYSMSG,
sizeof(struct qdrv_netdebug_sysmsg), 0xFFFF, 0},
{QDRV_NETDEBUG_TYPE_MEM, "mem",
qdrv_pktlogger_start_mem, qdrv_pktlogger_stop_mem,
QDRV_PKTLOGGER_INTERVAL_DFLT_MEM,
sizeof(struct qdrv_netdebug_mem), 0, 0},
{QDRV_NETDEBUG_TYPE_RATE, "rate",
qdrv_pktlogger_start_rate, qdrv_pktlogger_stop_rate,
QDRV_PKTLOGGER_INTERVAL_DFLT_RATE,
sizeof(struct qdrv_netdebug_rate), 0, 0},
#ifdef CONFIG_QVSP
{QDRV_NETDEBUG_TYPE_VSP, "vsp",
qdrv_pktlogger_start_vsp, qdrv_pktlogger_stop_vsp,
QDRV_PKTLOGGER_INTERVAL_DFLT_VSP,
0, 0, 0},
#endif
{QDRV_NETDEBUG_TYPE_PHY_STATS, "phy_stats",
qdrv_pktlogger_start_phy_stats, qdrv_pktlogger_stop_phy_stats,
QDRV_PKTLOGGER_INTERVAL_DFLT_PHY_STATS,
sizeof(struct qdrv_netdebug_phystats),
sizeof(struct qtn_node_shared_stats), 0},
{QDRV_NETDEBUG_TYPE_DSP_STATS, "dsp_stats",
qdrv_pktlogger_start_dsp_stats, qdrv_pktlogger_stop_dsp_stats,
QDRV_PKTLOGGER_INTERVAL_DFLT_DSP_STATS,
sizeof(struct qdrv_netdebug_dspstats), 0, 0},
{QDRV_NETDEBUG_TYPE_CORE_DUMP, "core_dump",
qdrv_pktlogger_start_core_dump, qdrv_pktlogger_stop_core_dump,
QDRV_PKTLOGGER_INTERVAL_DFLT_NONE,
sizeof(struct qdrv_netdebug_core_dump), 0, 0},
{-1, NULL, NULL, NULL, QDRV_PKTLOGGER_INTERVAL_DFLT_NONE, 0, 0}
};
struct qdrv_pktlogger_types_tbl *qdrv_pktlogger_get_tbls_by_id(int id)
{
struct qdrv_pktlogger_types_tbl * tbl_p = NULL;
int i;
for (i = 0; qdrv_pktlogger_types_tbl_ent[i].id > 0; i++) {
if (qdrv_pktlogger_types_tbl_ent[i].id == id) {
tbl_p = &qdrv_pktlogger_types_tbl_ent[i];
break;
}
}
return tbl_p;
}
static void qdrv_pktlogger_stop_all(struct qdrv_wlan *qw)
{
int index;
for (index = 0; qdrv_pktlogger_types_tbl_ent[index].name != NULL; index++) {
if (qdrv_pktlogger_types_tbl_ent[index].stop != NULL) {
qdrv_pktlogger_types_tbl_ent[index].stop(qw);
}
}
}
int qdrv_pktlogger_start_or_stop(struct qdrv_wlan *qw, const char *type,
int start, uint32_t interval)
{
int ret = 0;
int index;
if (!start && (strncmp(type, "all", strlen(type)) == 0)) {
qdrv_pktlogger_stop_all(qw);
return 0;
}
for (index = 0; qdrv_pktlogger_types_tbl_ent[index].name != NULL; index++) {
if (strncmp(type, qdrv_pktlogger_types_tbl_ent[index].name, strlen(type)) == 0) {
if (start) {
if (qdrv_pktlogger_types_tbl_ent[index].start != NULL) {
if (interval > 0) {
qdrv_pktlogger_types_tbl_ent[index].interval = interval;
}
ret = qdrv_pktlogger_types_tbl_ent[index].start(qw);
} else {
printk("No start command for log type %s\n", type);
ret = -1;
}
} else {
if (qdrv_pktlogger_types_tbl_ent[index].stop != NULL) {
qdrv_pktlogger_types_tbl_ent[index].stop(qw);
ret = 0;
} else {
printk("No stop command for log type %s\n", type);
ret = -1;
}
}
break;
}
}
if (qdrv_pktlogger_types_tbl_ent[index].name == NULL) {
printk("Log type %s is invalid\n", type);
ret = -1;
}
return ret;
}
static inline void qdrv_pktlogger_set_iphdr(struct iphdr *iphdr, u_int16_t id,
u_int16_t frag_off, int len)
{
iphdr->version = 4;
iphdr->ihl = 5;
iphdr->tos = 0;
iphdr->tot_len = htons(PKTLOGGER_IP_HEADER_LEN + len);
iphdr->id = htons(id);
iphdr->frag_off = htons(frag_off);
iphdr->ttl = PKTLOGGER_IP_TTL;
iphdr->protocol = IPPROTO_UDP;
put_unaligned_be32(g_pktlogger_p->src_ip, &iphdr->saddr);
put_unaligned_be32(g_pktlogger_p->dst_ip, &iphdr->daddr);
iphdr->check = ip_fast_csum((unsigned char *)iphdr, iphdr->ihl);
}
static inline void qdrv_pktlogger_set_etherhdr(struct ether_header *hdr)
{
IEEE80211_ADDR_COPY(hdr->ether_shost, g_pktlogger_p->src_addr);
IEEE80211_ADDR_COPY(hdr->ether_dhost, g_pktlogger_p->dst_addr);
hdr->ether_type = htons(ETH_P_IP);
}
static struct sk_buff *qdrv_pktlogger_alloc_skb(int len)
{
struct sk_buff *skb;
int cache_alignment = dma_get_cache_alignment();
int alignment;
skb = dev_alloc_skb(qtn_rx_buf_size());
if (skb == NULL) {
DBGPRINTF_E("Stopping pktlogger debug - no buffers available\n");
return NULL;
}
alignment = (unsigned int)(skb->data) & (cache_alignment - 1);
if (alignment) {
skb_reserve(skb, cache_alignment - alignment);
}
skb_put(skb, len);
memset(skb->data, 0, len);
return skb;
}
static int
qdrv_pktlogger_ip_send(void *data, int len, u_int16_t flag_off)
{
int length = 0;
struct ether_header *etherhdr_p;
struct iphdr *iphdr_p = NULL;
void *ippayload_p = NULL;
struct sk_buff *skb;
struct qdrv_wlan *qw = g_pktlogger_p->qw;
length = len + sizeof(struct iphdr) + sizeof(struct ether_header);
skb = qdrv_pktlogger_alloc_skb(length);
if (skb == NULL) {
DBGPRINTF_LIMIT_E("Failed to allocate SKB\n");
return -1;
}
etherhdr_p = (struct ether_header *)skb->data;
iphdr_p = (struct iphdr *)((char *)etherhdr_p + sizeof(struct ether_header));
ippayload_p = (void *)((char *)iphdr_p + sizeof(struct iphdr));
memcpy(ippayload_p, data, len);
qdrv_pktlogger_set_iphdr(iphdr_p, g_pktlogger_p->ip_id, flag_off, len);
qdrv_pktlogger_set_etherhdr(etherhdr_p);
if ((strncmp(g_pktlogger_p->dev->name, "wifi", 4) == 0) &&
(!IEEE80211_IS_MULTICAST(g_pktlogger_p->dst_addr)) &&
(!IEEE80211_IS_MULTICAST(g_pktlogger_p->recv_addr))) {
skb->dest_port = IEEE80211_AID(ieee80211_find_aid_by_mac_addr(&qw->ic.ic_sta,
g_pktlogger_p->recv_addr));
if (skb->dest_port == 0) {
DBGPRINTF_LIMIT_E("Could not send netdebug packet - wifi peer not found\n");
dev_kfree_skb(skb);
return -1;
}
}
if ((g_pktlogger_p->dev == NULL) ||
(g_pktlogger_p->dev->netdev_ops->ndo_start_xmit == NULL)) {
DBGPRINTF_LIMIT_E("Ethernet interface not found\n");
dev_kfree_skb(skb);
return -1;
}
local_bh_disable();
if (netif_queue_stopped(g_pktlogger_p->dev))
goto qdrv_pktlogger_ip_send_error;
if (unlikely(strncmp(g_pktlogger_p->dev->name, "wifi", 4) == 0)) {
skb->dev = g_pktlogger_p->dev;
if (dev_queue_xmit(skb) < 0)
goto qdrv_pktlogger_ip_send_error;
} else if (g_pktlogger_p->dev->netdev_ops->ndo_start_xmit(skb, g_pktlogger_p->dev) != 0) {
goto qdrv_pktlogger_ip_send_error;
}
local_bh_enable();
return 0;
qdrv_pktlogger_ip_send_error:
local_bh_enable();
dev_kfree_skb(skb);
return -1;
}
static int
qdrv_pktlogger_udp_send(void *data, uint32_t len)
{
int ret = 0;
uint16_t flag_off = 0;
char *data_s = (char *)data;
int len_s = 0;
if (g_pktlogger_p == NULL) {
DBGPRINTF_LIMIT_E("Pktlogger is not initialised\n");
return -EINVAL;
}
if (len == 0) {
return -EINVAL;
}
while ((len - len_s) > g_pktlogger_p->maxfraglen) {
flag_off |= BIT(PKTLOGGER_IP_MORE_FRAG_BIT);
ret = qdrv_pktlogger_ip_send(data_s, g_pktlogger_p->maxfraglen, flag_off);
if (ret < 0) {
/* Do not send rest of fragments. */
return -ret;
}
len_s += g_pktlogger_p->maxfraglen;
flag_off = (len_s >> 3);
data_s += g_pktlogger_p->maxfraglen;
}
/* Send one IP packet or the last one */
flag_off &= (~BIT(PKTLOGGER_IP_MORE_FRAG_BIT));
ret = qdrv_pktlogger_ip_send(data_s, len - len_s, flag_off);
if (ret >= 0) {
g_pktlogger_p->ip_id++;
return ret;
}
return ret;
}
void
qdrv_pktlogger_sendq_work(struct work_struct *work)
{
struct qdrv_pktlogger_data *first;
int rc;
g_pktlogger_p->stats.queue_send++;
spin_lock_bh(&g_pktlogger_p->sendq_lock);
while ((first = STAILQ_FIRST(&g_pktlogger_p->sendq_head))) {
STAILQ_REMOVE_HEAD(&g_pktlogger_p->sendq_head, entries);
spin_unlock_bh(&g_pktlogger_p->sendq_lock);
/* Send data on the network */
rc = qdrv_pktlogger_udp_send(first->data, first->len);
/* Send the data to pktlogger_d too */
qdrv_pktlogger_create_pktlogger_data(g_pktlogger_p->qw, first->data, first->len);
spin_lock_bh(&g_pktlogger_p->sendq_lock);
if (rc < 0) {
STAILQ_INSERT_HEAD(&g_pktlogger_p->sendq_head, first, entries);
g_pktlogger_p->stats.pkt_requeued++;
break;
}
qdrv_pktlogger_free_buffer(first->data);
kfree(first);
g_pktlogger_p->queue_len--;
}
g_pktlogger_p->sendq_scheduled = 0;
spin_unlock_bh(&g_pktlogger_p->sendq_lock);
}
void
qdrv_pktlogger_send(void *data, uint32_t len)
{
struct qdrv_pktlogger_data *tmp;
struct qdrv_pktlogger_data *first;
tmp = kmalloc(sizeof(*tmp), GFP_ATOMIC);
if (!tmp) {
g_pktlogger_p->stats.pkt_failed++;
qdrv_pktlogger_free_buffer(data);
return;
}
tmp->data = data;
tmp->len = len;
spin_lock_bh(&g_pktlogger_p->sendq_lock);
if (g_pktlogger_p->queue_len >= QDRV_PKTLOGGER_QUEUE_LEN_MAX) {
first = STAILQ_FIRST(&g_pktlogger_p->sendq_head);
STAILQ_REMOVE_HEAD(&g_pktlogger_p->sendq_head, entries);
qdrv_pktlogger_free_buffer(first->data);
kfree(first);
g_pktlogger_p->queue_len--;
g_pktlogger_p->stats.pkt_dropped++;
}
STAILQ_INSERT_TAIL(&g_pktlogger_p->sendq_head, tmp, entries);
g_pktlogger_p->queue_len++;
g_pktlogger_p->stats.pkt_queued++;
if (!g_pktlogger_p->sendq_scheduled) {
g_pktlogger_p->sendq_scheduled = 1;
schedule_work(&g_pktlogger_p->sendq_work);
}
spin_unlock_bh(&g_pktlogger_p->sendq_lock);
}
static int qdrv_pktlogger_ip_event(struct notifier_block *this,
unsigned long event, void *ptr)
{
struct in_ifaddr *ifa = (struct in_ifaddr *)ptr;
struct net_device *dev = (struct net_device *)ifa->ifa_dev->dev;
struct qdrv_wlan *qw = g_pktlogger_p->qw;
struct net_device *br_dev = qw->br_dev;
switch (event) {
case NETDEV_UP:
if (dev == br_dev) {
uint8_t *addr_p;
g_pktlogger_p->src_ip = ifa->ifa_address;
addr_p = (uint8_t *)&g_pktlogger_p->src_ip;
printk("QDRV: src ip addr %pI4\n", addr_p);
return NOTIFY_OK;
}
break;
default:
break;
}
return NOTIFY_DONE;
}
static struct notifier_block qdrv_pktlogger_ip_notifier = {
.notifier_call = qdrv_pktlogger_ip_event,
};
int
qdrv_pktlogger_init(struct qdrv_wlan *qw)
{
int i = 0;
unsigned int maxfraglen;
unsigned int fragheaderlen;
uint8_t default_dst_addr[IEEE80211_ADDR_LEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
__be32 srcip = 0;
g_pktlogger_p = &qw->pktlogger;
memset(g_pktlogger_p, 0, sizeof(struct qdrv_pktlogger));
g_pktlogger_p->rx_rate_cur = &g_pktlogger_p->rx_rates[0];
g_pktlogger_p->rx_rate_pre = &g_pktlogger_p->rx_rates[1];
g_pktlogger_p->rx_ratelog_cur = &g_pktlogger_p->rx_ratelog[0];
g_pktlogger_p->rx_ratelog_pre = &g_pktlogger_p->rx_ratelog[1];
for (i = 1; ; i++) {
g_pktlogger_p->dev = dev_get_by_index(&init_net, i);
if (g_pktlogger_p->dev == NULL) {
DBGPRINTF_E("Ethernet interface not found\n");
qdrv_pktlogger_exit(qw);
return -1;
}
if ((strncmp(g_pktlogger_p->dev->name, "eth", 3) == 0) ||
(strncmp(g_pktlogger_p->dev->name, "pcie", 4) == 0)) {
break;
}
dev_put(g_pktlogger_p->dev);
}
if (strncmp(g_pktlogger_p->dev->name, "eth", 3) == 0) {
g_pktlogger_p->dev_emac0 = dev_get_by_name(&init_net, "eth1_0");
g_pktlogger_p->dev_emac1 = dev_get_by_name(&init_net, "eth1_1");
}
if (g_pktlogger_p->dev->netdev_ops->ndo_start_xmit == NULL) {
DBGPRINTF_E("Ethernet transmit function not found\n");
dev_put(g_pktlogger_p->dev);
g_pktlogger_p->dev = NULL;
return -1;
}
if (qw->br_dev) {
srcip = qdrv_dev_ipaddr_get(qw->br_dev);
}
if (!srcip) {
srcip = PKTLOGGER_DEFAULT_SRC_IP;
}
g_pktlogger_p->dst_ip = PKTLOGGER_DEFAULT_DST_IP;
g_pktlogger_p->src_ip = srcip;
g_pktlogger_p->dst_port = htons(PKTLOGGER_UDP_DST_PORT);
g_pktlogger_p->src_port = htons(PKTLOGGER_UDP_SRC_PORT);
IEEE80211_ADDR_COPY(g_pktlogger_p->dst_addr, default_dst_addr);
IEEE80211_ADDR_COPY(g_pktlogger_p->recv_addr, default_dst_addr);
IEEE80211_ADDR_COPY(g_pktlogger_p->src_addr, qw->mac->mac_addr);
fragheaderlen = sizeof(struct iphdr);
maxfraglen = ETHERMTU - fragheaderlen;
maxfraglen -= (maxfraglen % 8);
g_pktlogger_p->maxfraglen = maxfraglen;
g_pktlogger_p->qw = qw;
g_pktlogger_p->queue_len = 0;
spin_lock_init(&g_pktlogger_p->sendq_lock);
INIT_WORK(&g_pktlogger_p->sendq_work, qdrv_pktlogger_sendq_work);
STAILQ_INIT(&g_pktlogger_p->sendq_head);
qdrv_pktlogger_start_netlink(qw);
return register_inetaddr_notifier(&qdrv_pktlogger_ip_notifier);
}
void qdrv_pktlogger_exit(struct qdrv_wlan *qw)
{
struct qdrv_pktlogger_data *first;
unregister_inetaddr_notifier(&qdrv_pktlogger_ip_notifier);
/* Stop all debug packets */
if (qw->pktlogger.flag) {
qdrv_pktlogger_stop_all(qw);
}
spin_lock_bh(&g_pktlogger_p->sendq_lock);
cancel_work_sync(&g_pktlogger_p->sendq_work);
g_pktlogger_p->sendq_scheduled = 0;
while (!STAILQ_EMPTY(&g_pktlogger_p->sendq_head)) {
first = STAILQ_FIRST(&g_pktlogger_p->sendq_head);
STAILQ_REMOVE_HEAD(&g_pktlogger_p->sendq_head, entries);
qdrv_pktlogger_free_buffer(first->data);
kfree(first);
}
spin_unlock_bh(&g_pktlogger_p->sendq_lock);
g_pktlogger_p->queue_len = 0;
g_pktlogger_p = NULL;
if (qw->pktlogger.dev == NULL) {
return;
}
dev_put(qw->pktlogger.dev);
if (g_pktlogger_p->dev_emac0)
dev_put(g_pktlogger_p->dev_emac0);
if (g_pktlogger_p->dev_emac1)
dev_put(g_pktlogger_p->dev_emac1);
qw->pktlogger.dev = NULL;
}