blob: b9218d1c8912d0040eb5218a8c01e9f6fda756c9 [file] [log] [blame]
/*
* Copyright (c) 2012-2018 The Linux Foundation. All rights reserved.
*
* Previously licensed under the ISC license by Qualcomm Atheros, Inc.
*
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all
* copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/**
* DOC: wlan_hdd_debugfs_llstat.c
*
* WLAN Host Device Driver implementation to update
* debugfs with Link Layer statistics
*/
#include <wlan_hdd_debugfs_llstat.h>
#include <cds_sched.h>
#include <wlan_hdd_stats.h>
#include <wma_api.h>
struct ll_stats_buf {
ssize_t len;
uint8_t *result;
};
static struct ll_stats_buf ll_stats;
static DEFINE_MUTEX(llstats_mutex);
void hdd_debugfs_process_iface_stats(hdd_adapter_t *adapter,
void *data, uint32_t num_peers)
{
tpSirWifiIfaceStat iface_stat;
tpSirWifiInterfaceInfo iface_info;
tpSirWifiWmmAcStat ac_stat;
struct wifi_iface_offload_stat *offload_stat;
uint64_t average_tsf_offset;
int i;
ssize_t len = 0;
uint8_t *buffer;
ENTER();
mutex_lock(&llstats_mutex);
if (!ll_stats.result) {
mutex_unlock(&llstats_mutex);
hdd_err("LL statistics buffer is NULL");
return;
}
iface_stat = data;
buffer = ll_stats.result;
buffer += ll_stats.len;
len = scnprintf(buffer, DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
"\n\n===LL_STATS_IFACE: num_peers: %d===", num_peers);
if (false == hdd_get_interface_info(adapter, &iface_stat->info)) {
mutex_unlock(&llstats_mutex);
hdd_err("hdd_get_interface_info get fail");
return;
}
iface_info = &iface_stat->info;
buffer += len;
ll_stats.len += len;
len = scnprintf(buffer, DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
"\nmode: %u, MAC_ADDR: %pM, state: %u, roaming: %u, capabilities: %u, SSID: %s, BSSID_MAC: %pM, ap_country_str: %s, country_str: %s",
iface_info->mode, &iface_info->macAddr.bytes[0],
iface_info->state, iface_info->roaming,
iface_info->capabilities, iface_info->ssid,
&iface_info->bssid.bytes[0], iface_info->apCountryStr,
iface_info->countryStr);
average_tsf_offset = iface_stat->avg_bcn_spread_offset_high;
average_tsf_offset = (average_tsf_offset << 32) |
iface_stat->avg_bcn_spread_offset_low;
buffer += len;
ll_stats.len += len;
len = scnprintf(buffer, DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
"\nbeacon_rx: %u, mgmt_rx: %u, mgmt_action_rx: %u, mgmt_action_tx: %u, rssi_mgmt: %u, rssi_data: %u, rssi_ack: %u, is_leaky_ap: %u, avg_rx_frms_leaked: %u, rx_leak_window: %u, average_tsf_offset: %llu, Tx RTS success count: %u, Tx RTS fail count: %u, Tx ppdu success count: %u, Tx ppdu fail count: %u, Connected duration: %u, Disconnected duration: %u, RTT ranging duration: %u, RTT responder duration: %u, Num tx probes: %u, Num beacon miss: %u,\n\nNumber of AC: %d",
iface_stat->beaconRx, iface_stat->mgmtRx,
iface_stat->mgmtActionRx, iface_stat->mgmtActionTx,
iface_stat->rssiMgmt, iface_stat->rssiData,
iface_stat->rssiAck, iface_stat->is_leaky_ap,
iface_stat->avg_rx_frms_leaked,
iface_stat->rx_leak_window, average_tsf_offset,
iface_stat->tx_rts_succ_cnt,
iface_stat->tx_rts_fail_cnt,
iface_stat->tx_ppdu_succ_cnt,
iface_stat->tx_ppdu_fail_cnt,
iface_stat->connected_duration,
iface_stat->disconnected_duration,
iface_stat->rtt_ranging_duration,
iface_stat->rtt_responder_duration,
iface_stat->num_probes_tx, iface_stat->num_beacon_miss,
iface_stat->num_ac);
for (i = 0; i < iface_stat->num_ac; i++) {
ac_stat = &iface_stat->AccessclassStats[i];
buffer += len;
ll_stats.len += len;
len = scnprintf(buffer,
DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
"\nAC: %d, tx_mpdu: %u, rx_mpdu: %u, tx_mcast: %u, rx_mcast: %u, rx_ampdu: %u tx_ampdu: %u, mpdu_lost: %u, retries: %u, retries_short: %u, retries_long: %u, contention_time: min-%u max-%u avg-%u, contention num samples: %u",
ac_stat->ac, ac_stat->txMpdu, ac_stat->rxMpdu,
ac_stat->txMcast, ac_stat->rxMcast,
ac_stat->rxAmpdu, ac_stat->txAmpdu,
ac_stat->mpduLost, ac_stat->retries,
ac_stat->retriesShort, ac_stat->retriesLong,
ac_stat->contentionTimeMin,
ac_stat->contentionTimeMax,
ac_stat->contentionTimeAvg,
ac_stat->contentionNumSamples);
}
buffer += len;
ll_stats.len += len;
len = scnprintf(buffer, DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
"\n\nNumber of offload stats: %d",
iface_stat->num_offload_stats);
for (i = 0; i < iface_stat->num_offload_stats; i++) {
offload_stat = &iface_stat->offload_stat[i];
buffer += len;
ll_stats.len += len;
len = scnprintf(buffer,
DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
"\ntype: %d, rx_count: %u, drp_count: %u, fwd_count: %u",
offload_stat->type, offload_stat->rx_count,
offload_stat->drp_count,
offload_stat->fwd_count);
}
ll_stats.len += len;
mutex_unlock(&llstats_mutex);
EXIT();
}
void hdd_debugfs_process_peer_stats(hdd_adapter_t *adapter, void *data)
{
tpSirWifiPeerStat peer_stat;
tpSirWifiPeerInfo peer_info;
tpSirWifiRateStat rate_stat;
int i, j, num_rate;
ssize_t len = 0;
uint8_t *buffer;
ENTER();
mutex_lock(&llstats_mutex);
if (!ll_stats.result) {
mutex_unlock(&llstats_mutex);
hdd_err("LL statistics buffer is NULL");
return;
}
peer_stat = (tpSirWifiPeerStat) data;
buffer = ll_stats.result;
buffer += ll_stats.len;
len = scnprintf(buffer, DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
"\n\n===LL_STATS_PEER_ALL : num_peers %u===",
peer_stat->numPeers);
peer_info = (tpSirWifiPeerInfo) ((uint8_t *) peer_stat->peerInfo);
for (i = 1; i <= peer_stat->numPeers; i++) {
buffer += len;
ll_stats.len += len;
len = scnprintf(buffer,
DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
"\nType: %d, peer_mac: %pM, capabilities: %u\nnum_rates: %d",
wmi_to_sir_peer_type(peer_info->type),
&peer_info->peerMacAddress.bytes[0],
peer_info->capabilities, peer_info->numRate);
num_rate = peer_info->numRate;
for (j = 0; j < num_rate; j++) {
rate_stat = (tpSirWifiRateStat) ((uint8_t *)
peer_info->rateStats + (j *
sizeof(tSirWifiRateStat)));
buffer += len;
ll_stats.len += len;
len = scnprintf(buffer,
DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
"\npreamble: %0x, nss: %0x, bw: %0x, mcs: %0x, bitrate: %0x, txmpdu: %u, rxmpdu: %u, mpdu_lost: %u, retries: %u, retries_short: %u, retries_long: %u",
rate_stat->rate.preamble, rate_stat->rate.nss,
rate_stat->rate.bw, rate_stat->rate.rateMcsIdx,
rate_stat->rate.bitrate, rate_stat->txMpdu,
rate_stat->rxMpdu, rate_stat->mpduLost,
rate_stat->retries, rate_stat->retriesShort,
rate_stat->retriesLong);
}
peer_info = (tpSirWifiPeerInfo) ((uint8_t *)
peer_stat->peerInfo + (i *
sizeof(tSirWifiPeerInfo)) +
(num_rate * sizeof(tSirWifiRateStat)));
}
ll_stats.len += len;
mutex_unlock(&llstats_mutex);
EXIT();
}
void hdd_debugfs_process_radio_stats(hdd_adapter_t *adapter,
uint32_t more_data, void *data, uint32_t num_radio)
{
int i, j;
ssize_t len = 0;
uint8_t *buffer;
tSirWifiRadioStat *radio_stat = (tpSirWifiRadioStat) data;
tSirWifiChannelStats *chan_stat;
ENTER();
mutex_lock(&llstats_mutex);
if (!ll_stats.result) {
mutex_unlock(&llstats_mutex);
hdd_err("LL statistics buffer is NULL");
return;
}
buffer = ll_stats.result;
buffer += ll_stats.len;
len = scnprintf(buffer, DEBUGFS_LLSTATS_BUF_SIZE,
"\n\n===LL_STATS_RADIO: number of radios: %u===",
num_radio);
for (i = 0; i < num_radio; i++) {
buffer += len;
ll_stats.len += len;
len = scnprintf(buffer,
DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
"\nRadio: %u on_time: %u, tx_time: %u, rx_time: %u, on_time_scan: %u, on_time_nbd: %u, on_time_gscan: %u, on_time_roam_scan: %u, on_time_pno_scan: %u on_time_hs20: %u, on_time_host_scan: %u, on_time_lpi_scan: %u\ntotal_num_tx_pwr_levels: %u\n",
radio_stat->radio, radio_stat->onTime,
radio_stat->txTime, radio_stat->rxTime,
radio_stat->onTimeScan, radio_stat->onTimeNbd,
radio_stat->onTimeGscan,
radio_stat->onTimeRoamScan,
radio_stat->onTimePnoScan,
radio_stat->onTimeHs20,
radio_stat->on_time_host_scan,
radio_stat->on_time_lpi_scan,
radio_stat->total_num_tx_power_levels);
for (j = 0; j < radio_stat->total_num_tx_power_levels; j++) {
buffer += len;
ll_stats.len += len;
len = scnprintf(buffer,
DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
"%d ", radio_stat->tx_time_per_power_level[j]);
}
buffer += len;
ll_stats.len += len;
len = scnprintf(buffer,
DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
"\nNum channels: %d", radio_stat->numChannels);
for (j = 0; j < radio_stat->numChannels; j++) {
chan_stat = (tSirWifiChannelStats *)
((uint8_t *)radio_stat->channels +
(j * sizeof(tSirWifiChannelStats)));
buffer += len;
ll_stats.len += len;
len = scnprintf(buffer,
DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
"\nChan width: %d, center_freq: %d, center_freq0: %d, center_freq1: %d, on_time: %d, cca_busy_time: %d",
chan_stat->channel.width,
chan_stat->channel.centerFreq,
chan_stat->channel.centerFreq0,
chan_stat->channel.centerFreq1,
chan_stat->onTime, chan_stat->ccaBusyTime);
}
radio_stat++;
}
ll_stats.len += len;
mutex_unlock(&llstats_mutex);
EXIT();
}
static inline void wlan_hdd_llstats_free_buf(void)
{
mutex_lock(&llstats_mutex);
qdf_mem_free(ll_stats.result);
ll_stats.result = NULL;
ll_stats.len = 0;
mutex_unlock(&llstats_mutex);
}
static int wlan_hdd_llstats_alloc_buf(void)
{
mutex_lock(&llstats_mutex);
if (ll_stats.result) {
mutex_unlock(&llstats_mutex);
hdd_err("Buffer is already allocated");
return 0;
}
ll_stats.len = 0;
ll_stats.result = qdf_mem_malloc(DEBUGFS_LLSTATS_BUF_SIZE);
if (!ll_stats.result) {
mutex_unlock(&llstats_mutex);
hdd_err("LL Stats buffer allocation failed");
return -EINVAL;
}
mutex_unlock(&llstats_mutex);
return 0;
}
/**
* hdd_debugfs_stats_update() - Update userspace with local statistics buffer
* @buf: userspace buffer (to which data is being copied into)
* @count: max data that can be copied into buf
* @pos: offset (where data should be copied into)
*
* This function should copies link layer statistics buffer into debugfs
* entry.
*
* Return: number of characters copied; 0 on no-copy
*/
static ssize_t hdd_debugfs_stats_update(char __user *buf, size_t count,
loff_t *pos)
{
ssize_t ret_cnt;
ENTER();
mutex_lock(&llstats_mutex);
if (!ll_stats.result) {
mutex_unlock(&llstats_mutex);
hdd_err("Trying to read from NULL buffer");
return 0;
}
ret_cnt = simple_read_from_buffer(buf, count, pos,
ll_stats.result, ll_stats.len);
mutex_unlock(&llstats_mutex);
hdd_debug("LL stats read req: count: %zu, pos: %lld", count, *pos);
EXIT();
return ret_cnt;
}
/**
* __wlan_hdd_read_ll_stats_debugfs() - API to collect LL stats from FW
* @file: file pointer
* @buf: buffer
* @count: count
* @pos: position pointer
*
* Return: Number of bytes read on success, error number otherwise
*/
static ssize_t __wlan_hdd_read_ll_stats_debugfs(struct file *file,
char __user *buf, size_t count, loff_t *pos)
{
hdd_adapter_t *adapter;
hdd_context_t *hdd_ctx;
ssize_t ret = 0;
ENTER();
adapter = (hdd_adapter_t *)file->private_data;
if ((!adapter) || (WLAN_HDD_ADAPTER_MAGIC != adapter->magic)) {
hdd_err("Invalid adapter or adapter has invalid magic");
return -EINVAL;
}
hdd_ctx = WLAN_HDD_GET_CTX(adapter);
ret = wlan_hdd_validate_context(hdd_ctx);
if (0 != ret)
return ret;
/* All the events are received and buffer is populated */
ret = hdd_debugfs_stats_update(buf, count, pos);
hdd_info("%zu characters written into debugfs", ret);
EXIT();
return ret;
}
/**
* wlan_hdd_read_ll_stats_debugfs() - SSR wrapper function to read LL debugfs
* @file: file pointer
* @buf: buffer
* @count: count
* @pos: position pointer
*
* Return: Number of bytes read on success, error number otherwise
*/
static ssize_t wlan_hdd_read_ll_stats_debugfs(struct file *file,
char __user *buf,
size_t count, loff_t *pos)
{
int ret;
cds_ssr_protect(__func__);
ret = __wlan_hdd_read_ll_stats_debugfs(file, buf, count, pos);
cds_ssr_unprotect(__func__);
return ret;
}
/**
* __wlan_hdd_open_ll_stats_debugfs() - Function to save private on open
* @inode: Pointer to inode structure
* @file: file pointer
*
* Return: zero
*/
static int __wlan_hdd_open_ll_stats_debugfs(struct inode *inode,
struct file *file)
{
hdd_adapter_t *adapter;
hdd_context_t *hdd_ctx;
int ret;
ENTER();
if (inode->i_private)
file->private_data = inode->i_private;
adapter = (hdd_adapter_t *)file->private_data;
if ((NULL == adapter) || (WLAN_HDD_ADAPTER_MAGIC != adapter->magic)) {
hdd_err("Invalid adapter or adapter has invalid magic");
return -EINVAL;
}
hdd_ctx = WLAN_HDD_GET_CTX(adapter);
ret = wlan_hdd_validate_context(hdd_ctx);
if (0 != ret)
return ret;
ret = wlan_hdd_llstats_alloc_buf();
if (0 != ret)
return ret;
ret = wlan_hdd_ll_stats_get(adapter, DEBUGFS_LLSTATS_REQID,
DEBUGFS_LLSTATS_REQMASK);
if (0 != ret)
return ret;
EXIT();
return 0;
}
/**
* wlan_hdd_open_ll_stats_debugfs() - SSR wrapper function to save private
* on open
* @inode: Pointer to inode structure
* @file: file pointer
*
* Return: zero
*/
static int wlan_hdd_open_ll_stats_debugfs(struct inode *inode,
struct file *file)
{
int ret;
cds_ssr_protect(__func__);
ret = __wlan_hdd_open_ll_stats_debugfs(inode, file);
cds_ssr_unprotect(__func__);
return ret;
}
/**
* __wlan_hdd_release_ll_stats_debugfs() - Function to save private on release
* @inode: Pointer to inode structure
* @file: file pointer
*
* Return: zero
*/
static int __wlan_hdd_release_ll_stats_debugfs(struct inode *inode,
struct file *file)
{
hdd_adapter_t *adapter;
hdd_context_t *hdd_ctx;
int ret;
ENTER();
if (inode->i_private)
file->private_data = inode->i_private;
adapter = (hdd_adapter_t *)file->private_data;
if ((NULL == adapter) || (WLAN_HDD_ADAPTER_MAGIC != adapter->magic)) {
hdd_err("Invalid adapter or adapter has invalid magic");
return -EINVAL;
}
hdd_ctx = WLAN_HDD_GET_CTX(adapter);
ret = wlan_hdd_validate_context(hdd_ctx);
if (0 != ret)
return ret;
wlan_hdd_llstats_free_buf();
EXIT();
return 0;
}
/**
* wlan_hdd_release_ll_stats_debugfs() - SSR wrapper function to save private
* on release
* @inode: Pointer to inode structure
* @file: file pointer
*
* Return: zero
*/
static int wlan_hdd_release_ll_stats_debugfs(struct inode *inode,
struct file *file)
{
int ret;
cds_ssr_protect(__func__);
ret = __wlan_hdd_release_ll_stats_debugfs(inode, file);
cds_ssr_unprotect(__func__);
return ret;
}
static const struct file_operations fops_ll_stats_debugfs = {
.read = wlan_hdd_read_ll_stats_debugfs,
.open = wlan_hdd_open_ll_stats_debugfs,
.release = wlan_hdd_release_ll_stats_debugfs,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
int wlan_hdd_create_ll_stats_file(hdd_adapter_t *adapter)
{
if (!debugfs_create_file("ll_stats", 0444, adapter->debugfs_phy,
adapter, &fops_ll_stats_debugfs))
return -EINVAL;
return 0;
}