/** @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 roamoffload_in_hs; /******************************************************** 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_set_key_mgmt_offload,}, /*event_id 0x10001 */ {.vendor_id = MRVL_VENDOR_ID,.subcmd = event_fw_roam_success,}, /*event_id 0x10002 */ {.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 | WIFI_FEATURE_CONFIG_NDO | WIFI_FEATURE_SCAN_RAND; 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; } } 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; } /** * @brief vendor command to get correlated HW and System time * * @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_correlated_time(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 sk_buff *skb = NULL; mlan_ioctl_req *req = NULL; mlan_ds_misc_cfg *misc = NULL; mlan_ds_get_correlated_time *info = NULL; mlan_status status = MLAN_STATUS_SUCCESS; int err = -1; int length = 0; /* Allocate an IOCTL request buffer */ req = woal_alloc_mlan_ioctl_req(sizeof(mlan_ds_misc_cfg)); if (req == NULL) { PRINTM(MERROR, "Could not allocate mlan ioctl request!\n"); return -ENOMEM; } /* Fill request buffer */ misc = (mlan_ds_misc_cfg *)req->pbuf; req->req_id = MLAN_IOCTL_MISC_CFG; req->action = MLAN_ACT_GET; misc->sub_command = MLAN_OID_MISC_GET_CORRELATED_TIME; /* Send IOCTL request to MLAN */ status = woal_request_ioctl(priv, req, MOAL_IOCTL_WAIT); if (status != MLAN_STATUS_SUCCESS) { PRINTM(MERROR, "get correleted time fail\n"); goto done; } length = sizeof(mlan_ds_get_correlated_time); info = (mlan_ds_get_correlated_time *) (&misc->param.host_clock); DBG_HEXDUMP(MCMD_D, "get_correlated_time", (t_u8 *)info, length); /* Alloc the SKB for vendor_event */ skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, length); if (unlikely(!skb)) { PRINTM(MERROR, "skb alloc failed\n"); goto done; } /* Push the data to the skb */ nla_put_nohdr(skb, length, info); err = cfg80211_vendor_cmd_reply(skb); if (unlikely(err)) { PRINTM(MERROR, "Vendor Command reply failed ret:%d \n", err); } done: if (status != MLAN_STATUS_PENDING) kfree(req); return err; } #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 key_mgmt_set_key * * @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 fail otherwise */ static int woal_cfg80211_subcmd_set_roaming_offload_key(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int data_len) { moal_private *priv; struct net_device *dev; struct sk_buff *skb = NULL; t_u8 *pos = (t_u8 *)data; int ret = MLAN_STATUS_SUCCESS; ENTER(); if (data) DBG_HEXDUMP(MCMD_D, "Vendor pmk", (t_u8 *)data, data_len); if (!wdev || !wdev->netdev) { LEAVE(); return -EFAULT; } dev = wdev->netdev; priv = (moal_private *)woal_get_netdev_priv(dev); if (!priv || !pos) { LEAVE(); return -EFAULT; } if (data_len > MLAN_MAX_KEY_LENGTH) { memcpy(&priv->pmk.pmk_r0, pos, MLAN_MAX_KEY_LENGTH); pos += MLAN_MAX_KEY_LENGTH; memcpy(&priv->pmk.pmk_r0_name, pos, MIN(MLAN_MAX_PMKR0_NAME_LENGTH, data_len - MLAN_MAX_KEY_LENGTH)); } else { memcpy(&priv->pmk.pmk, data, MIN(MLAN_MAX_KEY_LENGTH, data_len)); } priv->pmk_saved = MTRUE; /** Allocate skb for cmd reply*/ skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, data_len); if (!skb) { PRINTM(MERROR, "allocate memory fail for vendor cmd\n"); LEAVE(); return -EFAULT; } pos = skb_put(skb, data_len); memcpy(pos, data, data_len); ret = cfg80211_vendor_cmd_reply(skb); LEAVE(); return ret; } /** * @brief vendor command to supplicant to update AP info * * @param priv A pointer to moal_private * @param data a pointer to data * @param len data length * * @return 0: success 1: fail */ int woal_roam_ap_info(IN moal_private *priv, IN t_u8 *data, IN int len) { struct wiphy *wiphy = priv->wdev->wiphy; struct sk_buff *skb = NULL; int ret = MLAN_STATUS_SUCCESS; key_info *pkey = NULL; apinfo *pinfo = NULL; apinfo *req_tlv = NULL; MrvlIEtypesHeader_t *tlv = NULL; t_u16 tlv_type = 0, tlv_len = 0, tlv_buf_left = 0; int event_id = 0; t_u8 authorized = 1; ENTER(); event_id = woal_get_event_id(event_fw_roam_success); 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 + 50, #else skb = cfg80211_vendor_event_alloc(wiphy, len + 50, #endif event_id, GFP_ATOMIC); if (!skb) { PRINTM(MERROR, "allocate memory fail for vendor event\n"); ret = 1; LEAVE(); return ret; } nla_put(skb, MRVL_WLAN_VENDOR_ATTR_ROAM_AUTH_BSSID, MLAN_MAC_ADDR_LENGTH, (t_u8 *)data); nla_put(skb, MRVL_WLAN_VENDOR_ATTR_ROAM_AUTH_AUTHORIZED, sizeof(authorized), &authorized); tlv = (MrvlIEtypesHeader_t *)(data + MLAN_MAC_ADDR_LENGTH); tlv_buf_left = len - MLAN_MAC_ADDR_LENGTH; while (tlv_buf_left >= sizeof(MrvlIEtypesHeader_t)) { tlv_type = woal_le16_to_cpu(tlv->type); tlv_len = woal_le16_to_cpu(tlv->len); if (tlv_buf_left < (tlv_len + sizeof(MrvlIEtypesHeader_t))) { PRINTM(MERROR, "Error processing firmware roam success TLVs, bytes left < TLV length\n"); break; } switch (tlv_type) { case TLV_TYPE_APINFO: pinfo = (apinfo *) tlv; nla_put(skb, MRVL_WLAN_VENDOR_ATTR_ROAM_AUTH_RESP_IE, pinfo->header.len, pinfo->rsp_ie); break; case TLV_TYPE_ASSOC_REQ_IE: req_tlv = (apinfo *) tlv; nla_put(skb, MRVL_WLAN_VENDOR_ATTR_ROAM_AUTH_REQ_IE, req_tlv->header.len, req_tlv->rsp_ie); break; case TLV_TYPE_KEYINFO: pkey = (key_info *) tlv; nla_put(skb, MRVL_WLAN_VENDOR_ATTR_ROAM_AUTH_KEY_REPLAY_CTR, MLAN_REPLAY_CTR_LEN, pkey->key.replay_ctr); nla_put(skb, MRVL_WLAN_VENDOR_ATTR_ROAM_AUTH_PTK_KCK, MLAN_KCK_LEN, pkey->key.kck); nla_put(skb, MRVL_WLAN_VENDOR_ATTR_ROAM_AUTH_PTK_KEK, MLAN_KEK_LEN, pkey->key.kek); break; default: break; } tlv_buf_left -= tlv_len + sizeof(MrvlIEtypesHeader_t); tlv = (MrvlIEtypesHeader_t *)((t_u8 *)tlv + tlv_len + sizeof(MrvlIEtypesHeader_t)); } /**send event*/ cfg80211_vendor_event(skb, GFP_ATOMIC); LEAVE(); return ret; } /** * @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; mlan_ds_11k_cfg *pcfg_11k = 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; } /* Allocate an IOCTL request buffer */ req = woal_alloc_mlan_ioctl_req(sizeof(mlan_ds_11k_cfg)); if (req == NULL) { PRINTM(MERROR, "Could not allocate mlan ioctl request!\n"); ret = -EFAULT; goto done; } /* Fill request buffer */ pcfg_11k = (mlan_ds_11k_cfg *) req->pbuf; pcfg_11k->sub_command = MLAN_OID_11K_CFG_ENABLE; req->req_id = MLAN_IOCTL_11K_CFG; req->action = MLAN_ACT_SET; if (nla_get_u32(tb_vendor[ATTR_ND_OFFLOAD_CONTROL])) pcfg_11k->param.enable_11k = MTRUE; else pcfg_11k->param.enable_11k = MFALSE; PRINTM(MCMND, "11k enable = %d\n", pcfg_11k->param.enable_11k); status = woal_request_ioctl(priv, req, MOAL_IOCTL_WAIT); if (status != MLAN_STATUS_SUCCESS) { ret = -EFAULT; goto done; } done: if (status != MLAN_STATUS_PENDING) kfree(req); LEAVE(); return ret; } /** * @brief vendor command to set scan mac oui * * @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_set_scan_mac_oui(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int data_len) { struct net_device *dev = NULL; moal_private *priv = NULL; struct nlattr *tb_vendor[ATTR_WIFI_MAX + 1]; t_u8 mac_oui[3]; int ret = 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_WIFI_MAX, (struct nlattr *)data, data_len, NULL #if CFG80211_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) , NULL #endif ); if (!tb_vendor[ATTR_SCAN_MAC_OUI_SET]) { PRINTM(MINFO, "%s: ATTR_SCAN_MAC_OUI_SET not found\n", __FUNCTION__); ret = -EFAULT; goto done; } memcpy(mac_oui, nla_data(tb_vendor[ATTR_SCAN_MAC_OUI_SET]), 3); memcpy(priv->random_mac, priv->current_addr, 6); memcpy(priv->random_mac, mac_oui, 3); PRINTM(MCMND, "random_mac is " FULL_MACSTR "\n", FULL_MAC2STR(priv->random_mac)); done: 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, }, { .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = sub_cmd_set_scan_mac_oui,}, .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, .doit = woal_cfg80211_subcmd_set_scan_mac_oui, }, #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_set_roaming_offload_key,}, .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, .doit = woal_cfg80211_subcmd_set_roaming_offload_key, }, { .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_get_correlated_time,}, .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, .doit = woal_cfg80211_subcmd_get_correlated_time, }, { .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