blob: d9695e4fded05956787045b674cc27bf9d2b754a [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2021, The Linux Foundation. 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.
**************************************************************************
*/
#include "nss_core.h"
#include "nss_tx_rx_common.h"
#include "nss_wifi_mesh.h"
#include "nss_wifi_mesh_stats.h"
#include "nss_wifi_mesh_strings.h"
#define NSS_WIFI_MESH_OUTER_STATS 0
#define NSS_WIFI_MESH_INNER_STATS 1
#define NSS_WIFI_MESH_PATH_STATS 2
#define NSS_WIFI_MESH_PROXY_PATH_STATS 3
#define NSS_WIFI_MESH_EXCEPTION_STATS 4
/*
* Wi-Fi mesh stats dentry file size.
*/
#define NSS_WIFI_MESH_DENTRY_FILE_SIZE 19
/*
* Spinlock for protecting tunnel operations colliding with a tunnel destroy
*/
static DEFINE_SPINLOCK(nss_wifi_mesh_stats_lock);
/*
* Declare atomic notifier data structure for statistics.
*/
static ATOMIC_NOTIFIER_HEAD(nss_wifi_mesh_stats_notifier);
/*
* Declare an array of Wi-Fi mesh stats handle.
*/
struct nss_wifi_mesh_stats_handle *nss_wifi_mesh_stats_hdl[NSS_WIFI_MESH_MAX_DYNAMIC_INTERFACE];
/*
* nss_wifi_mesh_max_statistics()
* Wi-Fi mesh maximum statistics.
*/
static uint32_t nss_wifi_mesh_max_statistics(void)
{
uint32_t max1;
uint32_t exception_stats_max = NSS_WIFI_MESH_EXCEPTION_STATS_TYPE_MAX;
uint32_t encap_stats_max = NSS_WIFI_MESH_ENCAP_STATS_TYPE_MAX;
uint32_t decap_stats_max = NSS_WIFI_MESH_DECAP_STATS_TYPE_MAX;
uint32_t path_stats_max = NSS_WIFI_MESH_PATH_STATS_TYPE_MAX;
uint32_t proxy_path_stats_max = NSS_WIFI_MESH_PROXY_PATH_STATS_TYPE_MAX;
max1 = max(max(encap_stats_max, decap_stats_max), max(path_stats_max, proxy_path_stats_max));
return (max(max1, exception_stats_max));
}
/*
* nss_wifi_mesh_stats_handle_alloc()
* Allocate Wi-Fi mesh tunnel instance
*/
bool nss_wifi_mesh_stats_handle_alloc(nss_if_num_t if_num, int32_t ifindex)
{
struct nss_wifi_mesh_stats_handle *h;
uint32_t idx;
/*
* Allocate a handle
*/
h = kzalloc(sizeof(struct nss_wifi_mesh_stats_handle), GFP_ATOMIC);
if (!h) {
nss_warning("Failed to allocate memory for Wi-Fi mesh instance for interface : 0x%x\n", if_num);
return false;
}
spin_lock(&nss_wifi_mesh_stats_lock);
for (idx = 0; idx < NSS_WIFI_MESH_MAX_DYNAMIC_INTERFACE; idx++) {
if (nss_wifi_mesh_stats_hdl[idx] && nss_wifi_mesh_stats_hdl[idx]->if_num == if_num) {
spin_unlock(&nss_wifi_mesh_stats_lock);
nss_warning("Already a handle present for this interface number: 0x%x\n", if_num);
kfree(h);
return false;
}
}
for (idx = 0; idx < NSS_WIFI_MESH_MAX_DYNAMIC_INTERFACE; idx++) {
if (nss_wifi_mesh_stats_hdl[idx]) {
continue;
}
h->if_num = if_num;
h->mesh_idx = idx;
h->ifindex = ifindex;
nss_wifi_mesh_stats_hdl[idx] = h;
spin_unlock(&nss_wifi_mesh_stats_lock);
return true;
}
spin_unlock(&nss_wifi_mesh_stats_lock);
nss_warning("No free index available for handle with ifnum: 0x%x\n", if_num);
kfree(h);
return false;
}
/*
* nss_wifi_mesh_stats_handle_free()
* Free Wi-Fi mesh tunnel handle instance.
*/
bool nss_wifi_mesh_stats_handle_free(nss_if_num_t if_num)
{
struct nss_wifi_mesh_stats_handle *h;
spin_lock(&nss_wifi_mesh_stats_lock);
h = nss_wifi_mesh_get_stats_handle(if_num);
if (!h) {
spin_unlock(&nss_wifi_mesh_stats_lock);
nss_warning("Unable to free Wi-Fi mesh stats handle instance for interface number: 0x%x\n", if_num);
return false;
}
nss_wifi_mesh_stats_hdl[h->mesh_idx] = NULL;
spin_unlock(&nss_wifi_mesh_stats_lock);
kfree(h);
return true;
}
/**
* nss_wifi_mesh_get_stats_handle()
* Get Wi-Fi mesh stats handle from interface number.
*/
struct nss_wifi_mesh_stats_handle *nss_wifi_mesh_get_stats_handle(nss_if_num_t if_num)
{
uint32_t idx;
assert_spin_locked(&nss_wifi_mesh_stats_lock);
for (idx = 0; idx < NSS_WIFI_MESH_MAX_DYNAMIC_INTERFACE; idx++) {
if (nss_wifi_mesh_stats_hdl[idx]) {
if (nss_wifi_mesh_stats_hdl[idx]->if_num == if_num) {
struct nss_wifi_mesh_stats_handle *h = nss_wifi_mesh_stats_hdl[idx];
return h;
}
}
}
return NULL;
}
/*
* nss_wifi_mesh_get_stats()
* API for getting stats from a Wi-Fi mesh interface stats
*/
static bool nss_wifi_mesh_get_stats(nss_if_num_t if_num, struct nss_wifi_mesh_hdl_stats_sync_msg *stats)
{
struct nss_wifi_mesh_stats_handle *h;
if (!nss_wifi_mesh_verify_if_num(if_num)) {
return false;
}
spin_lock(&nss_wifi_mesh_stats_lock);
h = nss_wifi_mesh_get_stats_handle(if_num);
if (!h) {
spin_unlock(&nss_wifi_mesh_stats_lock);
nss_warning("Invalid Wi-Fi mesh stats handle for interface number: %d\n", if_num);
return false;
}
memcpy(stats, &h->stats, sizeof(*stats));
spin_unlock(&nss_wifi_mesh_stats_lock);
return true;
}
/*
* nss_wifi_mesh_get_valid_interface_count()
* Get count of valid Wi-Fi mesh interfaces up.
*/
static uint32_t nss_wifi_mesh_get_valid_interface_count(uint16_t type, uint32_t if_num, uint32_t max_if_num)
{
uint32_t interface_count = 0;
enum nss_dynamic_interface_type dtype;
for (; if_num <= max_if_num; if_num++) {
if (!nss_is_dynamic_interface(if_num)) {
continue;
}
dtype = nss_dynamic_interface_get_type(nss_wifi_mesh_get_context(), if_num);
if ((type == NSS_WIFI_MESH_OUTER_STATS) && (dtype != NSS_DYNAMIC_INTERFACE_TYPE_WIFI_MESH_OUTER)) {
continue;
}
if ((type == NSS_WIFI_MESH_INNER_STATS) && (dtype != NSS_DYNAMIC_INTERFACE_TYPE_WIFI_MESH_INNER)) {
continue;
}
if ((type == NSS_WIFI_MESH_PATH_STATS) && (dtype != NSS_DYNAMIC_INTERFACE_TYPE_WIFI_MESH_INNER)) {
continue;
}
if ((type == NSS_WIFI_MESH_PROXY_PATH_STATS) && (dtype != NSS_DYNAMIC_INTERFACE_TYPE_WIFI_MESH_INNER)) {
continue;
}
interface_count++;
}
return interface_count;
}
/**
* nss_wifi_mesh_stats_read()
* Read Wi-Fi Mesh stats.
*/
static ssize_t nss_wifi_mesh_stats_read(struct file *fp, char __user *ubuf, size_t sz, loff_t *ppos, uint16_t type)
{
uint32_t max_output_lines, max_stats;
size_t size_al, size_wr = 0;
ssize_t bytes_read = 0;
struct nss_stats_data *data = fp->private_data;
int ifindex;
uint32_t if_num = NSS_DYNAMIC_IF_START;
uint32_t interface_count = 0;
uint32_t max_if_num = NSS_DYNAMIC_IF_START + NSS_MAX_DYNAMIC_INTERFACES;
struct nss_wifi_mesh_hdl_stats_sync_msg *stats;
struct net_device *ndev;
struct nss_wifi_mesh_stats_handle *handle;
char *lbuf;
enum nss_dynamic_interface_type dtype;
if (data) {
if_num = data->if_num;
}
/*
* If we are done accomodating all the Wi-Fi mesh interfaces.
*/
if (if_num > max_if_num) {
return 0;
}
/*
* Get number of Wi-Fi mesh interfaces up.
*/
interface_count = nss_wifi_mesh_get_valid_interface_count(type, if_num, max_if_num);
if (!interface_count) {
nss_warning("%px: Invalid number of valid interface for if_num: 0x%x\n", data, if_num);
return 0;
}
/*
* max output lines = #stats + Number of Extra outputlines for future reference to add new stats +
* Maximum node stats + Maximum of all the stats + three blank lines.
*/
max_stats = nss_wifi_mesh_max_statistics();
max_output_lines = max_stats + NSS_STATS_NODE_MAX + NSS_STATS_EXTRA_OUTPUT_LINES;
size_al = NSS_STATS_MAX_STR_LENGTH * max_output_lines * interface_count;
lbuf = kzalloc(size_al, GFP_KERNEL);
if (unlikely(lbuf == NULL)) {
nss_warning("Could not allocate memory for local statistics buffer\n");
return 0;
}
size_wr += nss_stats_banner(lbuf, size_wr, size_al, "wifi_mesh", NSS_STATS_SINGLE_CORE);
stats = kzalloc(sizeof(struct nss_wifi_mesh_hdl_stats_sync_msg), GFP_KERNEL);
if (!stats) {
nss_warning("%px: Failed to allocate stats memory for if_num: 0x%x\n", data, if_num);
kfree(lbuf);
return 0;
}
for (; if_num <= max_if_num; if_num++) {
bool ret;
if (!nss_is_dynamic_interface(if_num)) {
continue;
}
dtype = nss_dynamic_interface_get_type(nss_wifi_mesh_get_context(), if_num);
if ((type == NSS_WIFI_MESH_OUTER_STATS) && (dtype != NSS_DYNAMIC_INTERFACE_TYPE_WIFI_MESH_OUTER)) {
continue;
}
if ((type == NSS_WIFI_MESH_INNER_STATS) && (dtype != NSS_DYNAMIC_INTERFACE_TYPE_WIFI_MESH_INNER)) {
continue;
}
if ((type == NSS_WIFI_MESH_PATH_STATS) && (dtype != NSS_DYNAMIC_INTERFACE_TYPE_WIFI_MESH_INNER)) {
continue;
}
if ((type == NSS_WIFI_MESH_PROXY_PATH_STATS) && (dtype != NSS_DYNAMIC_INTERFACE_TYPE_WIFI_MESH_INNER)) {
continue;
}
/*
* If Wi-Fi mesh stats handle does not exists, then ret will be false.
*/
ret = nss_wifi_mesh_get_stats(if_num, stats);
if (!ret) {
continue;
}
spin_lock(&nss_wifi_mesh_stats_lock);
handle = nss_wifi_mesh_get_stats_handle(if_num);
if (!handle) {
spin_unlock(&nss_wifi_mesh_stats_lock);
nss_warning("Invalid Wi-Fi mesh stats handle, if_num: %d\n", if_num);
continue;
}
ifindex = handle->ifindex;
spin_unlock(&nss_wifi_mesh_stats_lock);
ndev = dev_get_by_index(&init_net, ifindex);
if (!ndev) {
continue;
}
size_wr += scnprintf(lbuf + size_wr, size_al - size_wr, "\n%s if_num:%03u\n",
ndev->name, if_num);
dev_put(ndev);
/*
* Read encap stats, path stats, proxy path stats from inner node and decap stats from outer node.
*/
switch (type) {
case NSS_WIFI_MESH_INNER_STATS:
size_wr += nss_stats_print("wifi_mesh", "encap stats", NSS_STATS_SINGLE_INSTANCE
, nss_wifi_mesh_strings_encap_stats
, stats->encap_stats
, NSS_WIFI_MESH_ENCAP_STATS_TYPE_MAX
, lbuf, size_wr, size_al);
break;
case NSS_WIFI_MESH_PATH_STATS:
size_wr += nss_stats_print("wifi_mesh", "path stats", NSS_STATS_SINGLE_INSTANCE
, nss_wifi_mesh_strings_path_stats
, stats->path_stats
, NSS_WIFI_MESH_PATH_STATS_TYPE_MAX
, lbuf, size_wr, size_al);
break;
case NSS_WIFI_MESH_PROXY_PATH_STATS:
size_wr += nss_stats_print("wifi_mesh", "proxy path stats", NSS_STATS_SINGLE_INSTANCE
, nss_wifi_mesh_strings_proxy_path_stats
, stats->proxy_path_stats
, NSS_WIFI_MESH_PROXY_PATH_STATS_TYPE_MAX
, lbuf, size_wr, size_al);
break;
case NSS_WIFI_MESH_OUTER_STATS:
size_wr += nss_stats_print("wifi_mesh", "decap stats", NSS_STATS_SINGLE_INSTANCE
, nss_wifi_mesh_strings_decap_stats
, stats->decap_stats
, NSS_WIFI_MESH_DECAP_STATS_TYPE_MAX
, lbuf, size_wr, size_al);
break;
case NSS_WIFI_MESH_EXCEPTION_STATS:
size_wr += nss_stats_print("wifi_mesh", "exception stats", NSS_STATS_SINGLE_INSTANCE
, nss_wifi_mesh_strings_exception_stats
, stats->except_stats
, NSS_WIFI_MESH_EXCEPTION_STATS_TYPE_MAX
, lbuf, size_wr, size_al);
break;
default:
nss_warning("%px: Invalid stats type: %d\n", stats, type);
nss_assert(0);
kfree(stats);
kfree(lbuf);
return 0;
}
}
bytes_read = simple_read_from_buffer(ubuf, sz, ppos, lbuf, size_wr);
kfree(stats);
kfree(lbuf);
return bytes_read;
}
/**
* nss_wifi_mesh_decap_stats_read()
* Read Wi-Fi Mesh decap stats.
*/
static ssize_t nss_wifi_mesh_decap_stats_read(struct file *fp, char __user *ubuf, size_t sz, loff_t *ppos)
{
return nss_wifi_mesh_stats_read(fp, ubuf, sz, ppos, NSS_WIFI_MESH_OUTER_STATS);
}
/**
* nss_wifi_mesh_encap_stats_read()
* Read Wi-Fi Mesh encap stats
*/
static ssize_t nss_wifi_mesh_encap_stats_read(struct file *fp, char __user *ubuf, size_t sz, loff_t *ppos)
{
return nss_wifi_mesh_stats_read(fp, ubuf, sz, ppos, NSS_WIFI_MESH_INNER_STATS);
}
/**
* nss_wifi_mesh_path_stats_read()
* Read Wi-Fi Mesh path stats
*/
static ssize_t nss_wifi_mesh_path_stats_read(struct file *fp, char __user *ubuf, size_t sz, loff_t *ppos)
{
return nss_wifi_mesh_stats_read(fp, ubuf, sz, ppos, NSS_WIFI_MESH_PATH_STATS);
}
/**
* nss_wifi_mesh_proxy_path_stats_read()
* Read Wi-Fi Mesh proxy path stats
*/
static ssize_t nss_wifi_mesh_proxy_path_stats_read(struct file *fp, char __user *ubuf, size_t sz, loff_t *ppos)
{
return nss_wifi_mesh_stats_read(fp, ubuf, sz, ppos, NSS_WIFI_MESH_PROXY_PATH_STATS);
}
/**
* nss_wifi_mesh_exception_stats_read()
* Read Wi-Fi Mesh exception stats
*/
static ssize_t nss_wifi_mesh_exception_stats_read(struct file *fp, char __user *ubuf, size_t sz, loff_t *ppos)
{
return nss_wifi_mesh_stats_read(fp, ubuf, sz, ppos, NSS_WIFI_MESH_EXCEPTION_STATS);
}
/*
* nss_wifi_mesh_stats_ops
*/
NSS_STATS_DECLARE_FILE_OPERATIONS(wifi_mesh_encap);
NSS_STATS_DECLARE_FILE_OPERATIONS(wifi_mesh_decap);
NSS_STATS_DECLARE_FILE_OPERATIONS(wifi_mesh_path);
NSS_STATS_DECLARE_FILE_OPERATIONS(wifi_mesh_proxy_path);
NSS_STATS_DECLARE_FILE_OPERATIONS(wifi_mesh_exception);
/*
* nss_wifi_mesh_get_interface_type()
* Function to get the type of dynamic interface.
*/
static enum nss_dynamic_interface_type nss_wifi_mesh_get_interface_type(nss_if_num_t if_num)
{
struct nss_ctx_instance *nss_ctx = &nss_top_main.nss[nss_top_main.wifi_handler_id];
NSS_VERIFY_CTX_MAGIC(nss_ctx);
return nss_dynamic_interface_get_type(nss_ctx, if_num);
}
/*
* nss_wifi_mesh_update_stats()
* Update stats for Wi-Fi mesh interface.
*/
void nss_wifi_mesh_update_stats(nss_if_num_t if_num, struct nss_wifi_mesh_stats_sync_msg *mstats)
{
struct nss_wifi_mesh_stats_handle *handle;
struct nss_wifi_mesh_hdl_stats_sync_msg *stats;
enum nss_dynamic_interface_type type;
uint64_t *dst;
uint32_t *src;
int i;
spin_lock(&nss_wifi_mesh_stats_lock);
handle = nss_wifi_mesh_get_stats_handle(if_num);
if (!handle) {
spin_unlock(&nss_wifi_mesh_stats_lock);
nss_warning("Invalid Wi-Fi mesh stats handle, if_num: %d\n", if_num);
return;
}
type = nss_wifi_mesh_get_interface_type(handle->if_num);;
stats = &handle->stats;
switch (type) {
case NSS_DYNAMIC_INTERFACE_TYPE_WIFI_MESH_INNER:
/*
* Update pnode Rx stats.
*/
stats->encap_stats[NSS_WIFI_MESH_ENCAP_STATS_TYPE_PNODE_RX_PACKETS] += mstats->pnode_stats.rx_packets;
stats->encap_stats[NSS_WIFI_MESH_ENCAP_STATS_TYPE_PNODE_RX_BYTES] += mstats->pnode_stats.rx_bytes;
stats->encap_stats[NSS_WIFI_MESH_ENCAP_STATS_TYPE_PNODE_RX_DROPPED] += nss_cmn_rx_dropped_sum(&mstats->pnode_stats);
/*
* Update pnode Tx stats.
*/
stats->encap_stats[NSS_WIFI_MESH_ENCAP_STATS_TYPE_PNODE_TX_PACKETS] += mstats->pnode_stats.tx_packets;
stats->encap_stats[NSS_WIFI_MESH_ENCAP_STATS_TYPE_PNODE_TX_BYTES] += mstats->pnode_stats.tx_bytes;
/*
* Update encap stats.
*/
dst = &stats->encap_stats[NSS_WIFI_MESH_ENCAP_STATS_TYPE_EXPIRY_NOTIFY_SENT];
src = &mstats->mesh_encap_stats.expiry_notify_sent;
for (i = NSS_WIFI_MESH_ENCAP_STATS_TYPE_EXPIRY_NOTIFY_SENT; i < NSS_WIFI_MESH_ENCAP_STATS_TYPE_MAX; i++) {
*dst++ += *src++;
}
/*
* Update mesh path stats.
*/
dst = &stats->path_stats[NSS_WIFI_MESH_PATH_STATS_TYPE_ALLOC_FAILURES];
src = &mstats->mesh_path_stats.alloc_failures;
for (i = NSS_WIFI_MESH_PATH_STATS_TYPE_ALLOC_FAILURES; i < NSS_WIFI_MESH_PATH_STATS_TYPE_MAX; i++) {
*dst++ += *src++;
}
/*
* Update mesh proxy path stats.
*/
dst = &stats->proxy_path_stats[NSS_WIFI_MESH_PROXY_PATH_STATS_TYPE_ALLOC_FAILURES];
src = &mstats->mesh_proxy_path_stats.alloc_failures;
for (i = NSS_WIFI_MESH_PROXY_PATH_STATS_TYPE_ALLOC_FAILURES; i < NSS_WIFI_MESH_PROXY_PATH_STATS_TYPE_MAX; i++) {
*dst++ += *src++;
}
/*
* Update exception stats.
*/
dst = &stats->except_stats[NSS_WIFI_MESH_EXCEPTION_STATS_TYPE_PACKETS_SUCCESS];
src = &mstats->mesh_except_stats.packets_success;
for (i = NSS_WIFI_MESH_EXCEPTION_STATS_TYPE_PACKETS_SUCCESS; i < NSS_WIFI_MESH_EXCEPTION_STATS_TYPE_MAX; i++) {
*dst++ += *src++;
}
spin_unlock(&nss_wifi_mesh_stats_lock);
break;
case NSS_DYNAMIC_INTERFACE_TYPE_WIFI_MESH_OUTER:
/*
* Update pnode Rx stats.
*/
stats->decap_stats[NSS_WIFI_MESH_DECAP_STATS_TYPE_PNODE_RX_PACKETS] += mstats->pnode_stats.rx_packets;
stats->decap_stats[NSS_WIFI_MESH_DECAP_STATS_TYPE_PNODE_RX_BYTES] += mstats->pnode_stats.rx_bytes;
stats->decap_stats[NSS_WIFI_MESH_DECAP_STATS_TYPE_PNODE_RX_DROPPED] += nss_cmn_rx_dropped_sum(&mstats->pnode_stats);
/*
* Update pnode Tx stats.
*/
stats->decap_stats[NSS_WIFI_MESH_DECAP_STATS_TYPE_PNODE_TX_PACKETS] += mstats->pnode_stats.tx_packets;
stats->decap_stats[NSS_WIFI_MESH_DECAP_STATS_TYPE_PNODE_TX_BYTES] += mstats->pnode_stats.tx_bytes;
/*
* Update decap stats.
*/
dst = &stats->decap_stats[NSS_WIFI_MESH_DECAP_STATS_TYPE_PATH_REFRESH_SENT];
src = &mstats->mesh_decap_stats.path_refresh_sent;
for (i = NSS_WIFI_MESH_DECAP_STATS_TYPE_PATH_REFRESH_SENT; i < NSS_WIFI_MESH_DECAP_STATS_TYPE_MAX; i++) {
*dst++ += *src++;
}
spin_unlock(&nss_wifi_mesh_stats_lock);
break;
default:
spin_unlock(&nss_wifi_mesh_stats_lock);
nss_warning("%px: Received invalid dynamic interface type: %d\n", handle, type);
nss_assert(0);
}
}
/*
* nss_wifi_mesh_stats_notify()
* Sends notifications to the registered modules.
*
* Leverage NSS-FW statistics timing to update Netlink.
*/
void nss_wifi_mesh_stats_notify(nss_if_num_t if_num, uint32_t core_id)
{
struct nss_wifi_mesh_stats_notification wifi_mesh_stats;
if (!nss_wifi_mesh_get_stats(if_num, &wifi_mesh_stats.stats)) {
nss_warning("No handle is present with ifnum: 0x%x\n", if_num);
return;
}
wifi_mesh_stats.core_id = core_id;
wifi_mesh_stats.if_num = if_num;
atomic_notifier_call_chain(&nss_wifi_mesh_stats_notifier, NSS_STATS_EVENT_NOTIFY, (void *)&wifi_mesh_stats);
}
/*
* nss_wifi_mesh_stats_dentry_create()
* Create Wi-Fi Mesh statistics debug entry
*/
struct dentry *nss_wifi_mesh_stats_dentry_create(void)
{
struct dentry *stats_dentry_dir;
struct dentry *stats_file;
char dir_name[NSS_WIFI_MESH_DENTRY_FILE_SIZE] = {0};
if (!nss_top_main.stats_dentry) {
nss_warning("qca-nss-drv/stats is not present\n");
return NULL;
}
snprintf(dir_name, sizeof(dir_name), "wifi_mesh");
stats_dentry_dir = debugfs_create_dir(dir_name, nss_top_main.stats_dentry);
if (!stats_dentry_dir) {
nss_warning("Failed to create qca-nss-drv/stats/wifi_mesh directory\n");
return NULL;
}
stats_file = debugfs_create_file("encap_stats", 0400, stats_dentry_dir, &nss_top_main, &nss_wifi_mesh_encap_stats_ops);
if (!stats_file) {
nss_warning("Failed to create qca-nss-drv/stats/wifi_mesh/encap_stats file\n");
goto fail;
}
stats_file = debugfs_create_file("decap_stats", 0400, stats_dentry_dir, &nss_top_main, &nss_wifi_mesh_decap_stats_ops);
if (!stats_file) {
nss_warning("Failed to create qca-nss-drv/stats/wifi_mesh/decap_stats file\n");
goto fail;
}
stats_file = debugfs_create_file("path_stats", 0400, stats_dentry_dir, &nss_top_main, &nss_wifi_mesh_path_stats_ops);
if (!stats_file) {
nss_warning("Failed to create qca-nss-drv/stats/wifi_mesh/path_stats file\n");
goto fail;
}
stats_file = debugfs_create_file("proxy_path_stats", 0400, stats_dentry_dir, &nss_top_main, &nss_wifi_mesh_proxy_path_stats_ops);
if (!stats_file) {
nss_warning("Failed to create qca-nss-drv/stats/wifi_mesh/proxy_path_stats file\n");
goto fail;
}
stats_file = debugfs_create_file("exception_stats", 0400, stats_dentry_dir, &nss_top_main, &nss_wifi_mesh_exception_stats_ops);
if (!stats_file) {
nss_warning("Failed to create qca-nss-drv/stats/wifi_mesh/exception_stats file\n");
goto fail;
}
return stats_dentry_dir;
fail:
debugfs_remove_recursive(stats_dentry_dir);
return NULL;
}
/**
* nss_wifi_mesh_stats_register_notifier()
* Registers statistics notifier.
*/
int nss_wifi_mesh_stats_register_notifier(struct notifier_block *nb)
{
return atomic_notifier_chain_register(&nss_wifi_mesh_stats_notifier, nb);
}
EXPORT_SYMBOL(nss_wifi_mesh_stats_register_notifier);
/**
* nss_wifi_mesh_stats_unregister_notifier()
* Deregisters statistics notifier.
*/
int nss_wifi_mesh_stats_unregister_notifier(struct notifier_block *nb)
{
return atomic_notifier_chain_unregister(&nss_wifi_mesh_stats_notifier, nb);
}
EXPORT_SYMBOL(nss_wifi_mesh_stats_unregister_notifier);