| /** @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 |