blob: 8cd710b5581d78d45d42ef6188df6963cbfadddf [file] [log] [blame]
/** @file moal_cfgvendor.c
*
* @brief This file contains the functions for CFG80211 vendor.
*
* Copyright (C) 2011-2019, Marvell International Ltd.
*
* This software file (the "File") is distributed by Marvell International
* Ltd. under the terms of the GNU General Public License Version 2, June 1991
* (the "License"). You may use, redistribute and/or modify this File in
* accordance with the terms and conditions of the License, a copy of which
* is available by writing to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the
* worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
* IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
* ARE EXPRESSLY DISCLAIMED. The License provides additional details about
* this warranty disclaimer.
*
*/
#include "moal_cfgvendor.h"
#include "moal_cfg80211.h"
/********************************************************
Local Variables
********************************************************/
/********************************************************
Global Variables
********************************************************/
#if CFG80211_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)
extern int dfs_offload;
#endif
extern int disable_regd_by_driver;
/********************************************************
Local Functions
********************************************************/
/********************************************************
Global Functions
********************************************************/
#if CFG80211_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)
/**marvell vendor command and event*/
#define MRVL_VENDOR_ID 0x005043
/** vendor events */
const struct nl80211_vendor_cmd_info vendor_events[] = {
{.vendor_id = MRVL_VENDOR_ID,.subcmd = event_hang,}, /*event_id 0 */
{.vendor_id = MRVL_VENDOR_ID,.subcmd = event_rssi_monitor,}, /*event_id 0x1501 */
{.vendor_id = MRVL_VENDOR_ID,.subcmd = event_dfs_radar_detected,}, /*event_id 0x10004 */
{.vendor_id = MRVL_VENDOR_ID,.subcmd = event_dfs_cac_started,}, /*event_id 0x10005 */
{.vendor_id = MRVL_VENDOR_ID,.subcmd = event_dfs_cac_finished,}, /*event_id 0x10006 */
{.vendor_id = MRVL_VENDOR_ID,.subcmd = event_dfs_cac_aborted,}, /*event_id 0x10007 */
{.vendor_id = MRVL_VENDOR_ID,.subcmd = event_dfs_nop_finished,}, /*event_id 0x10008 */
/**add vendor event here*/
};
/**
* @brief get the event id of the events array
*
* @param event vendor event
*
* @return index of events array
*/
int
woal_get_event_id(int event)
{
int i = 0;
for (i = 0; i < ARRAY_SIZE(vendor_events); i++) {
if (vendor_events[i].subcmd == event)
return i;
}
return event_max;
}
/**
* @brief send vendor event to kernel
*
* @param priv A pointer to moal_private
* @param event vendor event
* @param data a pointer to data
* @param len data length
*
* @return 0: success 1: fail
*/
int
woal_cfg80211_vendor_event(IN moal_private *priv,
IN int event, IN t_u8 *data, IN int len)
{
struct wiphy *wiphy = NULL;
struct sk_buff *skb = NULL;
int event_id = 0;
t_u8 *pos = NULL;
int ret = 0;
ENTER();
if (!priv || !priv->wdev || !priv->wdev->wiphy) {
LEAVE();
return ret;
}
wiphy = priv->wdev->wiphy;
PRINTM(MEVENT, "vendor event :0x%x\n", event);
event_id = woal_get_event_id(event);
if (event_max == event_id) {
PRINTM(MERROR, "Not find this event %d \n", event_id);
ret = 1;
LEAVE();
return ret;
}
/**allocate skb*/
#if CFG80211_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)
skb = cfg80211_vendor_event_alloc(wiphy, priv->wdev, len, event_id,
GFP_ATOMIC);
#else
skb = cfg80211_vendor_event_alloc(wiphy, len, event_id, GFP_ATOMIC);
#endif
if (!skb) {
PRINTM(MERROR, "allocate memory fail for vendor event\n");
ret = 1;
LEAVE();
return ret;
}
pos = skb_put(skb, len);
memcpy(pos, data, len);
/**send event*/
cfg80211_vendor_event(skb, GFP_ATOMIC);
LEAVE();
return ret;
}
/**
* @brief send vendor event to kernel
*
* @param priv A pointer to moal_private
* @param event vendor event
* @param len data length
*
* @return 0: success 1: fail
*/
struct sk_buff *
woal_cfg80211_alloc_vendor_event(IN moal_private *priv,
IN int event, IN int len)
{
struct wiphy *wiphy = NULL;
struct sk_buff *skb = NULL;
int event_id = 0;
ENTER();
if (!priv || !priv->wdev || !priv->wdev->wiphy) {
PRINTM(MERROR, "Not find this event %d \n", event_id);
goto done;
}
wiphy = priv->wdev->wiphy;
PRINTM(MEVENT, "vendor event :0x%x\n", event);
event_id = woal_get_event_id(event);
if (event_max == event_id) {
PRINTM(MERROR, "Not find this event %d \n", event_id);
goto done;
}
/**allocate skb*/
#if CFG80211_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)
skb = cfg80211_vendor_event_alloc(wiphy, priv->wdev, len, event_id,
GFP_ATOMIC);
#else
skb = cfg80211_vendor_event_alloc(wiphy, len, event_id, GFP_ATOMIC);
#endif
if (!skb) {
PRINTM(MERROR, "allocate memory fail for vendor event\n");
goto done;
}
done:
LEAVE();
return skb;
}
/**
* @brief send dfs vendor event to kernel
*
* @param priv A pointer to moal_private
* @param event dfs vendor event
* @param chandef a pointer to struct cfg80211_chan_def
*
* @return N/A
*/
void
woal_cfg80211_dfs_vendor_event(moal_private *priv, int event,
struct cfg80211_chan_def *chandef)
{
dfs_event evt;
ENTER();
if (!chandef) {
LEAVE();
return;
}
memset(&evt, 0, sizeof(dfs_event));
evt.freq = chandef->chan->center_freq;
evt.chan_width = chandef->width;
evt.cf1 = chandef->center_freq1;
evt.cf2 = chandef->center_freq2;
switch (chandef->width) {
case NL80211_CHAN_WIDTH_20_NOHT:
evt.ht_enabled = 0;
break;
case NL80211_CHAN_WIDTH_20:
evt.ht_enabled = 1;
break;
case NL80211_CHAN_WIDTH_40:
evt.ht_enabled = 1;
if (chandef->center_freq1 < chandef->chan->center_freq)
evt.chan_offset = -1;
else
evt.chan_offset = 1;
break;
case NL80211_CHAN_WIDTH_80:
case NL80211_CHAN_WIDTH_80P80:
case NL80211_CHAN_WIDTH_160:
evt.ht_enabled = 1;
break;
default:
break;
}
woal_cfg80211_vendor_event(priv, event, (t_u8 *)&evt,
sizeof(dfs_event));
LEAVE();
return;
}
/**
* @brief vendor command to set drvdbg
*
* @param wiphy A pointer to wiphy struct
* @param wdev A pointer to wireless_dev struct
* @param data a pointer to data
* @param len data length
*
* @return 0: success 1: fail
*/
static int
woal_cfg80211_subcmd_set_drvdbg(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
#ifdef DEBUG_LEVEL1
struct net_device *dev = wdev->netdev;
moal_private *priv = (moal_private *)woal_get_netdev_priv(dev);
struct sk_buff *skb = NULL;
t_u8 *pos = NULL;
#endif
int ret = 1;
ENTER();
#ifdef DEBUG_LEVEL1
/**handle this sub command*/
DBG_HEXDUMP(MCMD_D, "Vendor drvdbg", (t_u8 *)data, data_len);
if (data_len) {
/* Get the driver debug bit masks from user */
drvdbg = *((t_u32 *)data);
PRINTM(MIOCTL, "new drvdbg %x\n", drvdbg);
/* Set the driver debug bit masks into mlan */
if (woal_set_drvdbg(priv, drvdbg)) {
PRINTM(MERROR, "Set drvdbg failed!\n");
ret = 1;
}
}
/** Allocate skb for cmd reply*/
skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(drvdbg));
if (!skb) {
PRINTM(MERROR, "allocate memory fail for vendor cmd\n");
ret = 1;
LEAVE();
return ret;
}
pos = skb_put(skb, sizeof(drvdbg));
memcpy(pos, &drvdbg, sizeof(drvdbg));
ret = cfg80211_vendor_cmd_reply(skb);
#endif
LEAVE();
return ret;
}
/**
* @brief process one channel in bucket
*
* @param priv A pointer to moal_private struct
*
* @param channel a pointer to channel
*
* @return 0: success other: fail
*/
static mlan_status
woal_band_to_valid_channels(moal_private *priv, wifi_band w_band, int channel[],
t_u32 *nchannel)
{
int band = 0;
struct ieee80211_supported_band *sband;
struct ieee80211_channel *ch;
int i = 0;
t_u8 cnt = 0;
int *ch_ptr = channel;
for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
if (!priv->wdev->wiphy->bands[band])
continue;
if ((band == IEEE80211_BAND_2GHZ) && !(w_band & WIFI_BAND_BG))
continue;
if ((band == IEEE80211_BAND_5GHZ) &&
!((w_band & WIFI_BAND_A) || (w_band & WIFI_BAND_A_DFS)))
continue;
sband = priv->wdev->wiphy->bands[band];
for (i = 0; (i < sband->n_channels); i++) {
ch = &sband->channels[i];
if (ch->flags & IEEE80211_CHAN_DISABLED) {
PRINTM(MERROR, "Skip DISABLED channel %d\n",
ch->center_freq);
continue;
}
if ((band == IEEE80211_BAND_5GHZ)) {
if (((ch->flags & IEEE80211_CHAN_RADAR) &&
!(w_band & WIFI_BAND_A_DFS)) ||
(!(ch->flags & IEEE80211_CHAN_RADAR) &&
!(w_band & WIFI_BAND_A)))
continue;
}
if (cnt >= *nchannel) {
PRINTM(MERROR,
"cnt=%d is exceed %d, cur ch=%d %dMHz\n",
cnt, *nchannel, ch->hw_value,
ch->center_freq);
break;
}
*ch_ptr = ch->center_freq;
ch_ptr++;
cnt++;
}
}
PRINTM(MCMND, "w_band=%d cnt=%d\n", w_band, cnt);
*nchannel = cnt;
return MLAN_STATUS_SUCCESS;
}
/**
* @brief GSCAN subcmd - enable full scan results
*
* @param wiphy A pointer to wiphy struct
* @param wdev A pointer to wireless_dev struct
*
* @param data a pointer to data
* @param data_len data length
*
* @return 0: success other: fail
*/
static int
woal_cfg80211_subcmd_get_valid_channels(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int len)
{
struct net_device *dev = wdev->netdev;
moal_private *priv = (moal_private *)woal_get_netdev_priv(dev);
struct nlattr *tb[ATTR_WIFI_MAX];
t_u32 band = 0;
int ch_out[MAX_CHANNEL_NUM];
t_u32 nchannel = 0;
t_u32 mem_needed = 0;
struct sk_buff *skb = NULL;
int err = 0;
ENTER();
PRINTM(MCMND, "Enter woal_cfg80211_subcmd_get_valid_channels\n");
err = nla_parse(tb, ATTR_WIFI_MAX, data, len, NULL
#if CFG80211_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
, NULL
#endif
);
if (err) {
PRINTM(MERROR, "%s: nla_parse fail\n", __FUNCTION__);
err = -EFAULT;
goto done;
}
if (!tb[ATTR_CHANNELS_BAND]) {
PRINTM(MERROR, "%s: null attr: tb[ATTR_GET_CH]=%p\n",
__FUNCTION__, tb[ATTR_CHANNELS_BAND]);
err = -EINVAL;
goto done;
}
band = nla_get_u32(tb[ATTR_CHANNELS_BAND]);
if (band > WIFI_BAND_MAX) {
PRINTM(MERROR, "%s: invalid band=%d\n", __FUNCTION__, band);
err = -EINVAL;
goto done;
}
memset(ch_out, 0x00, sizeof(ch_out));
nchannel = MAX_CHANNEL_NUM;
if (woal_band_to_valid_channels(priv, band, ch_out, &nchannel) !=
MLAN_STATUS_SUCCESS) {
PRINTM(MERROR,
"get_channel_list: woal_band_to_valid_channels fail\n");
return -EFAULT;
}
mem_needed =
nla_total_size(nchannel * sizeof(ch_out[0])) +
nla_total_size(sizeof(nchannel))
+ VENDOR_REPLY_OVERHEAD;
/* Alloc the SKB for vendor_event */
skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, mem_needed);
if (unlikely(!skb)) {
PRINTM(MERROR, "skb alloc failed");
err = -ENOMEM;
goto done;
}
nla_put_u32(skb, ATTR_NUM_CHANNELS, nchannel);
nla_put(skb, ATTR_CHANNEL_LIST, nchannel * sizeof(ch_out[0]), ch_out);
err = cfg80211_vendor_cmd_reply(skb);
if (err) {
PRINTM(MERROR, "Vendor Command reply failed ret:%d \n", err);
goto done;
}
done:
LEAVE();
return err;
}
/**
* @brief vendor command to get driver version
*
* @param wiphy A pointer to wiphy struct
* @param wdev A pointer to wireless_dev struct
* @param data a pointer to data
* @param len data length
*
* @return 0: success 1: fail
*/
static int
woal_cfg80211_subcmd_get_drv_version(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct net_device *dev = wdev->netdev;
moal_private *priv = (moal_private *)woal_get_netdev_priv(dev);
struct sk_buff *skb = NULL;
t_u32 reply_len = 0;
int ret = 0;
t_u32 drv_len = 0;
char drv_version[MLAN_MAX_VER_STR_LEN] = { 0 };
char *pos;
ENTER();
memcpy(drv_version, &priv->phandle->driver_version,
MLAN_MAX_VER_STR_LEN);
drv_len = strlen(drv_version);
pos = strstr(drv_version, "%s");
/* remove 3 char "-%s" in driver_version string */
if (pos != NULL)
memcpy(pos, pos + 3, strlen(pos) - 3);
reply_len = strlen(drv_version) + 1;
drv_len -= 3;
drv_version[drv_len] = '\0';
/** Allocate skb for cmd reply*/
skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, reply_len);
if (!skb) {
PRINTM(MERROR, "allocate memory fail for vendor cmd\n");
ret = -ENOMEM;
goto done;
}
nla_put(skb, MRVL_WLAN_VENDOR_ATTR_NAME, reply_len,
(t_u8 *)drv_version);
ret = cfg80211_vendor_cmd_reply(skb);
if (ret)
PRINTM(MERROR, "Vendor command reply failed ret = %d\n", ret);
done:
LEAVE();
return ret;
}
/**
* @brief vendor command to get firmware version
*
* @param wiphy A pointer to wiphy struct
* @param wdev A pointer to wireless_dev struct
* @param data a pointer to data
* @param len data length
*
* @return 0: success 1: fail
*/
static int
woal_cfg80211_subcmd_get_fw_version(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct net_device *dev = wdev->netdev;
moal_private *priv = (moal_private *)woal_get_netdev_priv(dev);
struct sk_buff *skb = NULL;
t_u32 reply_len = 0;
char end_c = '\0';
int ret = 0;
char fw_ver[32] = { 0 };
union {
t_u32 l;
t_u8 c[4];
} ver;
ENTER();
ver.l = priv->phandle->fw_release_number;
snprintf(fw_ver, sizeof(fw_ver), "%u.%u.%u.p%u%c",
ver.c[2], ver.c[1], ver.c[0], ver.c[3], end_c);
reply_len = strlen(fw_ver) + 1;
/** Allocate skb for cmd reply*/
skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, reply_len);
if (!skb) {
PRINTM(MERROR, "allocate memory fail for vendor cmd\n");
ret = -ENOMEM;
goto done;
}
nla_put(skb, MRVL_WLAN_VENDOR_ATTR_NAME, reply_len, (t_u8 *)fw_ver);
ret = cfg80211_vendor_cmd_reply(skb);
if (ret)
PRINTM(MERROR, "Vendor command reply failed ret = %d\n", ret);
done:
LEAVE();
return ret;
}
/**
* @brief vendor command to get supported feature set
*
* @param wiphy A pointer to wiphy struct
* @param wdev A pointer to wireless_dev struct
* @param data a pointer to data
* @param len data length
*
* @return 0: success 1: fail
*/
static int
woal_cfg80211_subcmd_get_supp_feature_set(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct sk_buff *skb = NULL;
t_u32 reply_len = 0;
int ret = 0;
t_u32 supp_feature_set = 0;
struct net_device *dev = wdev->netdev;
moal_private *priv = (moal_private *)woal_get_netdev_priv(dev);
mlan_fw_info fw_info;
ENTER();
supp_feature_set = WIFI_FEATURE_INFRA
#if defined(UAP_SUPPORT) && defined(STA_SUPPORT)
| WIFI_FEATURE_AP_STA
#endif
| WIFI_FEATURE_RSSI_MONITOR;
woal_request_get_fw_info(priv, MOAL_IOCTL_WAIT, &fw_info);
if (fw_info.fw_bands & BAND_A)
supp_feature_set |= WIFI_FEATURE_INFRA_5G;
reply_len = sizeof(supp_feature_set);
/** Allocate skb for cmd reply*/
skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, reply_len);
if (!skb) {
PRINTM(MERROR, "allocate memory fail for vendor cmd\n");
ret = -ENOMEM;
goto done;
}
nla_put_u32(skb, ATTR_FEATURE_SET, supp_feature_set);
ret = cfg80211_vendor_cmd_reply(skb);
if (ret)
PRINTM(MERROR, "Vendor command reply failed ret = %d\n", ret);
done:
LEAVE();
return ret;
}
/**
* @brief vendor command to set country code
*
* @param wiphy A pointer to wiphy struct
* @param wdev A pointer to wireless_dev struct
* @param data a pointer to data
* @param len data length
*
* @return 0: success 1: fail
*/
static int
woal_cfg80211_subcmd_set_country_code(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct sk_buff *skb = NULL;
t_u32 reply_len = 0;
int ret = 0, rem, type;
const struct nlattr *iter;
char country[COUNTRY_CODE_LEN] = { 0 };
ENTER();
nla_for_each_attr(iter, data, data_len, rem) {
type = nla_type(iter);
switch (type) {
case ATTR_COUNTRY_CODE:
strncpy(country, nla_data(iter),
MIN(sizeof(country) - 1, nla_len(iter)));
break;
default:
PRINTM(MERROR, "Unknown type: %d\n", type);
return ret;
}
}
if (!disable_regd_by_driver)
regulatory_hint(wiphy, country);
/** Allocate skb for cmd reply*/
skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, reply_len);
if (!skb) {
PRINTM(MERROR, "allocate memory fail for vendor cmd\n");
ret = -ENOMEM;
goto done;
}
ret = cfg80211_vendor_cmd_reply(skb);
if (ret)
PRINTM(MERROR, "Vendor command reply failed ret = %d\n", ret);
done:
LEAVE();
return ret;
}
#ifdef STA_CFG80211
#define RSSI_MONOTOR_START 1
#define RSSI_MONOTOR_STOP 0
/**
* @brief vendor command to control rssi monitor
*
* @param wiphy A pointer to wiphy struct
* @param wdev A pointer to wireless_dev struct
* @param data a pointer to data
* @param len data length
*
* @return 0: success -1: fail
*/
static int
woal_cfg80211_subcmd_rssi_monitor(struct wiphy *wiphy,
struct wireless_dev *wdev, const void *data,
int len)
{
struct nlattr *tb[ATTR_RSSI_MONITOR_MAX + 1];
moal_private *priv = (moal_private *)woal_get_netdev_priv(wdev->netdev);
u32 rssi_monitor_control = 0x0;
s8 rssi_min = 0, rssi_max = 0;
int err = 0;
t_u8 *pos = NULL;
struct sk_buff *skb = NULL;
int ret = 0;
ENTER();
if (!priv->media_connected) {
ret = -EINVAL;
goto done;
}
ret = nla_parse(tb, ATTR_RSSI_MONITOR_MAX, data, len, NULL
#if CFG80211_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
, NULL
#endif
);
if (ret)
goto done;
if (!tb[ATTR_RSSI_MONITOR_CONTROL]) {
ret = -EINVAL;
goto done;
}
rssi_monitor_control = nla_get_u32(tb[ATTR_RSSI_MONITOR_CONTROL]);
if (rssi_monitor_control == RSSI_MONOTOR_START) {
if ((!tb[ATTR_RSSI_MONITOR_MIN_RSSI]) ||
(!tb[ATTR_RSSI_MONITOR_MAX_RSSI])) {
ret = -EINVAL;
goto done;
}
rssi_min = nla_get_s8(tb[ATTR_RSSI_MONITOR_MIN_RSSI]);
rssi_max = nla_get_s8(tb[ATTR_RSSI_MONITOR_MAX_RSSI]);
PRINTM(MEVENT,
"start rssi monitor rssi_min = %d, rssi_max= %d\n",
rssi_min, rssi_max);
/* set rssi low/high threshold */
priv->cqm_rssi_high_thold = rssi_max;
priv->cqm_rssi_thold = rssi_min;
priv->cqm_rssi_hyst = 4;
woal_set_rssi_threshold(priv, 0, MOAL_IOCTL_WAIT);
} else if (rssi_monitor_control == RSSI_MONOTOR_STOP) {
/* stop rssi monitor */
PRINTM(MEVENT, "stop rssi monitor\n");
/* set both rssi_thold/hyst to 0, will trigger subscribe event clear */
priv->cqm_rssi_high_thold = 0;
priv->cqm_rssi_thold = 0;
priv->cqm_rssi_hyst = 0;
woal_set_rssi_threshold(priv, 0, MOAL_IOCTL_WAIT);
} else {
PRINTM(MERROR, "invalid rssi_monitor control request\n");
ret = -EINVAL;
goto done;
}
/* Alloc the SKB for cmd reply */
skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, len);
if (unlikely(!skb)) {
PRINTM(MERROR, "skb alloc failed\n");
ret = -EINVAL;
goto done;
}
pos = skb_put(skb, len);
memcpy(pos, data, len);
err = cfg80211_vendor_cmd_reply(skb);
if (unlikely(err)) {
PRINTM(MERROR, "Vendor Command reply failed ret:%d \n", err);
ret = err;
}
done:
LEAVE();
return ret;
}
/**
* @brief send rssi event to kernel
*
* @param priv A pointer to moal_private
* @param rssi current rssi value
*
* @return N/A
*/
void
woal_cfg80211_rssi_monitor_event(moal_private *priv, t_s16 rssi)
{
struct sk_buff *skb = NULL;
t_s8 rssi_value = 0;
ENTER();
skb = dev_alloc_skb(NLA_HDRLEN * 2 + ETH_ALEN + sizeof(t_s8));
if (!skb)
goto done;
/* convert t_s16 to t_s8 */
rssi_value = -abs(rssi);
if (nla_put
(skb, ATTR_RSSI_MONITOR_CUR_BSSID, ETH_ALEN, priv->conn_bssid) ||
nla_put_s8(skb, ATTR_RSSI_MONITOR_CUR_RSSI, rssi_value)) {
PRINTM(MERROR, "nla_put failed!\n");
kfree(skb);
goto done;
}
woal_cfg80211_vendor_event(priv, event_rssi_monitor, (t_u8 *)skb->data,
skb->len);
kfree(skb);
done:
LEAVE();
}
#endif
/**
* @brief vendor command to enable/disable 11k
*
* @param wiphy A pointer to wiphy struct
* @param wdev A pointer to wireless_dev struct
* @param data a pointer to data
* @param data_len data length
*
* @return 0: success <0: fail
*/
static int
woal_cfg80211_subcmd_11k_cfg(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct net_device *dev = NULL;
moal_private *priv = NULL;
mlan_ioctl_req *req = NULL;
struct nlattr *tb_vendor[ATTR_ND_OFFLOAD_MAX + 1];
int ret = 0;
int status = MLAN_STATUS_SUCCESS;
ENTER();
if (!wdev || !wdev->netdev) {
LEAVE();
return -EFAULT;
}
dev = wdev->netdev;
priv = (moal_private *)woal_get_netdev_priv(dev);
nla_parse(tb_vendor, ATTR_ND_OFFLOAD_MAX,
(struct nlattr *)data, data_len, NULL
#if CFG80211_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
, NULL
#endif
);
if (!tb_vendor[ATTR_ND_OFFLOAD_CONTROL]) {
PRINTM(MINFO, "%s: ATTR_ND_OFFLOAD not found\n", __FUNCTION__);
ret = -EFAULT;
goto done;
}
done:
if (status != MLAN_STATUS_PENDING)
kfree(req);
LEAVE();
return ret;
}
/**
* @brief vendor command to set enable/disable dfs offload
*
* @param wiphy A pointer to wiphy struct
* @param wdev A pointer to wireless_dev struct
* @param data a pointer to data
* @param len data length
*
* @return 0: success 1: fail
*/
static int
woal_cfg80211_subcmd_set_dfs_offload(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct sk_buff *skb = NULL;
int ret = 1;
ENTER();
/** Allocate skb for cmd reply*/
skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(dfs_offload));
if (!skb) {
PRINTM(MERROR, "allocate memory fail for vendor cmd\n");
ret = 1;
LEAVE();
return ret;
}
nla_put(skb, MRVL_WLAN_VENDOR_ATTR_DFS, sizeof(t_u32), &dfs_offload);
ret = cfg80211_vendor_cmd_reply(skb);
LEAVE();
return ret;
}
const struct wiphy_vendor_command vendor_commands[] = {
{
.info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = sub_cmd_set_drvdbg,},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
.doit = woal_cfg80211_subcmd_set_drvdbg,
},
{
.info = {.vendor_id = MRVL_VENDOR_ID,.subcmd =
sub_cmd_get_valid_channels,},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
.doit = woal_cfg80211_subcmd_get_valid_channels,
},
#ifdef STA_CFG80211
{
.info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = sub_cmd_rssi_monitor,},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
.doit = woal_cfg80211_subcmd_rssi_monitor,
},
#endif
{
.info = {.vendor_id = MRVL_VENDOR_ID,.subcmd =
sub_cmd_dfs_capability,},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
.doit = woal_cfg80211_subcmd_set_dfs_offload,
},
{
.info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = sub_cmd_nd_offload},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
.doit = woal_cfg80211_subcmd_11k_cfg,
},
{
.info = {.vendor_id = MRVL_VENDOR_ID,.subcmd =
sub_cmd_get_drv_version,},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
.doit = woal_cfg80211_subcmd_get_drv_version,
},
{
.info = {.vendor_id = MRVL_VENDOR_ID,.subcmd =
sub_cmd_get_fw_version,},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
.doit = woal_cfg80211_subcmd_get_fw_version,
},
{
.info = {.vendor_id = MRVL_VENDOR_ID,.subcmd =
sub_cmd_get_wifi_supp_feature_set,},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
.doit = woal_cfg80211_subcmd_get_supp_feature_set,
},
{
.info = {.vendor_id = MRVL_VENDOR_ID,.subcmd =
sub_cmd_set_country_code,},
.flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV,
.doit = woal_cfg80211_subcmd_set_country_code,
},
};
/**
* @brief register vendor commands and events
*
* @param wiphy A pointer to wiphy struct
*
* @return
*/
void
woal_register_cfg80211_vendor_command(struct wiphy *wiphy)
{
ENTER();
wiphy->vendor_commands = vendor_commands;
wiphy->n_vendor_commands = ARRAY_SIZE(vendor_commands);
wiphy->vendor_events = vendor_events;
wiphy->n_vendor_events = ARRAY_SIZE(vendor_events);
LEAVE();
}
#endif