blob: 49551309c42c83e9903c516b7dd448c296b72f96 [file] [log] [blame]
/* Copyright (c) 2017 The Linux Foundation. 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 version 2 and
* only version 2 as published by the Free Software Foundation.
*
* 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.
*/
#define pr_fmt(fmt) "cnss_utils: " fmt
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/etherdevice.h>
#include <net/cnss_utils.h>
#define CNSS_MAX_CH_NUM 45
struct cnss_unsafe_channel_list {
u16 unsafe_ch_count;
u16 unsafe_ch_list[CNSS_MAX_CH_NUM];
};
struct cnss_dfs_nol_info {
void *dfs_nol_info;
u16 dfs_nol_info_len;
};
#define MAX_NO_OF_MAC_ADDR 4
struct cnss_wlan_mac_addr {
u8 mac_addr[MAX_NO_OF_MAC_ADDR][ETH_ALEN];
u32 no_of_mac_addr_set;
};
enum mac_type {
CNSS_MAC_PROVISIONED,
CNSS_MAC_DERIVED,
};
static struct cnss_utils_priv {
struct cnss_unsafe_channel_list unsafe_channel_list;
struct cnss_dfs_nol_info dfs_nol_info;
/* generic mutex for unsafe channel */
struct mutex unsafe_channel_list_lock;
/* generic spin-lock for dfs_nol info */
spinlock_t dfs_nol_info_lock;
int driver_load_cnt;
struct cnss_wlan_mac_addr wlan_mac_addr;
struct cnss_wlan_mac_addr wlan_der_mac_addr;
enum cnss_utils_cc_src cc_source;
} *cnss_utils_priv;
int cnss_utils_set_wlan_unsafe_channel(struct device *dev,
u16 *unsafe_ch_list, u16 ch_count)
{
struct cnss_utils_priv *priv = cnss_utils_priv;
if (!priv)
return -EINVAL;
mutex_lock(&priv->unsafe_channel_list_lock);
if ((!unsafe_ch_list) || (ch_count > CNSS_MAX_CH_NUM)) {
mutex_unlock(&priv->unsafe_channel_list_lock);
return -EINVAL;
}
priv->unsafe_channel_list.unsafe_ch_count = ch_count;
if (ch_count == 0)
goto end;
memcpy(priv->unsafe_channel_list.unsafe_ch_list,
unsafe_ch_list, ch_count * sizeof(u16));
end:
mutex_unlock(&priv->unsafe_channel_list_lock);
return 0;
}
EXPORT_SYMBOL(cnss_utils_set_wlan_unsafe_channel);
int cnss_utils_get_wlan_unsafe_channel(struct device *dev,
u16 *unsafe_ch_list,
u16 *ch_count, u16 buf_len)
{
struct cnss_utils_priv *priv = cnss_utils_priv;
if (!priv)
return -EINVAL;
mutex_lock(&priv->unsafe_channel_list_lock);
if (!unsafe_ch_list || !ch_count) {
mutex_unlock(&priv->unsafe_channel_list_lock);
return -EINVAL;
}
if (buf_len <
(priv->unsafe_channel_list.unsafe_ch_count * sizeof(u16))) {
mutex_unlock(&priv->unsafe_channel_list_lock);
return -ENOMEM;
}
*ch_count = priv->unsafe_channel_list.unsafe_ch_count;
memcpy(unsafe_ch_list, priv->unsafe_channel_list.unsafe_ch_list,
priv->unsafe_channel_list.unsafe_ch_count * sizeof(u16));
mutex_unlock(&priv->unsafe_channel_list_lock);
return 0;
}
EXPORT_SYMBOL(cnss_utils_get_wlan_unsafe_channel);
int cnss_utils_wlan_set_dfs_nol(struct device *dev,
const void *info, u16 info_len)
{
void *temp;
void *old_nol_info;
struct cnss_dfs_nol_info *dfs_info;
struct cnss_utils_priv *priv = cnss_utils_priv;
if (!priv)
return -EINVAL;
if (!info || !info_len)
return -EINVAL;
temp = kmalloc(info_len, GFP_ATOMIC);
if (!temp)
return -ENOMEM;
memcpy(temp, info, info_len);
spin_lock_bh(&priv->dfs_nol_info_lock);
dfs_info = &priv->dfs_nol_info;
old_nol_info = dfs_info->dfs_nol_info;
dfs_info->dfs_nol_info = temp;
dfs_info->dfs_nol_info_len = info_len;
spin_unlock_bh(&priv->dfs_nol_info_lock);
kfree(old_nol_info);
return 0;
}
EXPORT_SYMBOL(cnss_utils_wlan_set_dfs_nol);
int cnss_utils_wlan_get_dfs_nol(struct device *dev,
void *info, u16 info_len)
{
int len;
struct cnss_dfs_nol_info *dfs_info;
struct cnss_utils_priv *priv = cnss_utils_priv;
if (!priv)
return -EINVAL;
if (!info || !info_len)
return -EINVAL;
spin_lock_bh(&priv->dfs_nol_info_lock);
dfs_info = &priv->dfs_nol_info;
if (!dfs_info->dfs_nol_info ||
dfs_info->dfs_nol_info_len == 0) {
spin_unlock_bh(&priv->dfs_nol_info_lock);
return -ENOENT;
}
len = min(info_len, dfs_info->dfs_nol_info_len);
memcpy(info, dfs_info->dfs_nol_info, len);
spin_unlock_bh(&priv->dfs_nol_info_lock);
return len;
}
EXPORT_SYMBOL(cnss_utils_wlan_get_dfs_nol);
void cnss_utils_increment_driver_load_cnt(struct device *dev)
{
struct cnss_utils_priv *priv = cnss_utils_priv;
if (!priv)
return;
++(priv->driver_load_cnt);
}
EXPORT_SYMBOL(cnss_utils_increment_driver_load_cnt);
int cnss_utils_get_driver_load_cnt(struct device *dev)
{
struct cnss_utils_priv *priv = cnss_utils_priv;
if (!priv)
return -EINVAL;
return priv->driver_load_cnt;
}
EXPORT_SYMBOL(cnss_utils_get_driver_load_cnt);
static int set_wlan_mac_address(const u8 *mac_list, const uint32_t len,
enum mac_type type)
{
struct cnss_utils_priv *priv = cnss_utils_priv;
u32 no_of_mac_addr;
struct cnss_wlan_mac_addr *addr = NULL;
int iter;
u8 *temp = NULL;
if (!priv)
return -EINVAL;
if (len == 0 || (len % ETH_ALEN) != 0) {
pr_err("Invalid length %d\n", len);
return -EINVAL;
}
no_of_mac_addr = len / ETH_ALEN;
if (no_of_mac_addr > MAX_NO_OF_MAC_ADDR) {
pr_err("Exceed maximum supported MAC address %u %u\n",
MAX_NO_OF_MAC_ADDR, no_of_mac_addr);
return -EINVAL;
}
if (type == CNSS_MAC_PROVISIONED)
addr = &priv->wlan_mac_addr;
else
addr = &priv->wlan_der_mac_addr;
if (addr->no_of_mac_addr_set) {
pr_err("WLAN MAC address is already set, num %d type %d\n",
addr->no_of_mac_addr_set, type);
return 0;
}
addr->no_of_mac_addr_set = no_of_mac_addr;
temp = &addr->mac_addr[0][0];
for (iter = 0; iter < no_of_mac_addr;
++iter, temp += ETH_ALEN, mac_list += ETH_ALEN) {
ether_addr_copy(temp, mac_list);
pr_debug("MAC_ADDR:%02x:%02x:%02x:%02x:%02x:%02x\n",
temp[0], temp[1], temp[2],
temp[3], temp[4], temp[5]);
}
return 0;
}
int cnss_utils_set_wlan_mac_address(const u8 *mac_list, const uint32_t len)
{
return set_wlan_mac_address(mac_list, len, CNSS_MAC_PROVISIONED);
}
EXPORT_SYMBOL(cnss_utils_set_wlan_mac_address);
int cnss_utils_set_wlan_derived_mac_address(
const u8 *mac_list, const uint32_t len)
{
return set_wlan_mac_address(mac_list, len, CNSS_MAC_DERIVED);
}
EXPORT_SYMBOL(cnss_utils_set_wlan_derived_mac_address);
static u8 *get_wlan_mac_address(struct device *dev,
u32 *num, enum mac_type type)
{
struct cnss_utils_priv *priv = cnss_utils_priv;
struct cnss_wlan_mac_addr *addr = NULL;
if (!priv)
goto out;
if (type == CNSS_MAC_PROVISIONED)
addr = &priv->wlan_mac_addr;
else
addr = &priv->wlan_der_mac_addr;
if (!addr->no_of_mac_addr_set) {
pr_err("WLAN MAC address is not set, type %d\n", type);
goto out;
}
*num = addr->no_of_mac_addr_set;
return &addr->mac_addr[0][0];
out:
*num = 0;
return NULL;
}
u8 *cnss_utils_get_wlan_mac_address(struct device *dev, uint32_t *num)
{
return get_wlan_mac_address(dev, num, CNSS_MAC_PROVISIONED);
}
EXPORT_SYMBOL(cnss_utils_get_wlan_mac_address);
u8 *cnss_utils_get_wlan_derived_mac_address(
struct device *dev, uint32_t *num)
{
return get_wlan_mac_address(dev, num, CNSS_MAC_DERIVED);
}
EXPORT_SYMBOL(cnss_utils_get_wlan_derived_mac_address);
void cnss_utils_set_cc_source(struct device *dev,
enum cnss_utils_cc_src cc_source)
{
struct cnss_utils_priv *priv = cnss_utils_priv;
if (!priv)
return;
priv->cc_source = cc_source;
}
EXPORT_SYMBOL(cnss_utils_set_cc_source);
enum cnss_utils_cc_src cnss_utils_get_cc_source(struct device *dev)
{
struct cnss_utils_priv *priv = cnss_utils_priv;
if (!priv)
return -EINVAL;
return priv->cc_source;
}
EXPORT_SYMBOL(cnss_utils_get_cc_source);
static int __init cnss_utils_init(void)
{
struct cnss_utils_priv *priv = NULL;
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->cc_source = CNSS_UTILS_SOURCE_CORE;
mutex_init(&priv->unsafe_channel_list_lock);
spin_lock_init(&priv->dfs_nol_info_lock);
cnss_utils_priv = priv;
return 0;
}
static void __exit cnss_utils_exit(void)
{
kfree(cnss_utils_priv);
cnss_utils_priv = NULL;
}
module_init(cnss_utils_init);
module_exit(cnss_utils_exit);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION(DEVICE "CNSS Utilities Driver");