| /** @file moal_cfgvendor.c |
| * |
| * @brief This file contains the functions for CFG80211 vendor. |
| * |
| * Copyright (C) 2011-2018, 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 */ |
| {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| event_wifi_logger_ring_buffer_data,}, |
| {.vendor_id = MRVL_VENDOR_ID,.subcmd = event_wifi_logger_alert,}, |
| {.vendor_id = MRVL_VENDOR_ID,.subcmd = event_packet_fate_monitor,}, |
| {.vendor_id = MRVL_VENDOR_ID,.subcmd = event_wake_reason_report,}, |
| {.vendor_id = MRVL_VENDOR_ID,.subcmd = event_nan_cb,}, |
| /**add vendor event here*/ |
| {.vendor_id = MRVL_VENDOR_ID,.subcmd = event_rtt_result,}, /*event_id ??? */ |
| }; |
| |
| /** |
| * @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, NULL, 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, NULL, 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 firmware memory dump |
| * |
| * @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_dump(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int data_len) |
| { |
| struct net_device *dev = NULL; |
| moal_private *priv = NULL; |
| moal_handle *handle = NULL; |
| int ret = MLAN_STATUS_SUCCESS; |
| int length = 0; |
| struct sk_buff *skb = NULL; |
| |
| ENTER(); |
| |
| if (!wdev || !wdev->netdev) { |
| LEAVE(); |
| return -EFAULT; |
| } |
| dev = wdev->netdev; |
| priv = (moal_private *)woal_get_netdev_priv(dev); |
| handle = priv->phandle; |
| if (handle->firmware_dump_file) { |
| memset(handle->firmware_dump_file, 0, |
| sizeof(handle->firmware_dump_file)); |
| } |
| |
| length = sizeof(handle->firmware_dump_file); |
| skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, length); |
| if (!skb) { |
| PRINTM(MERROR, "Failed to allocate memory for skb\n"); |
| ret = -ENOMEM; |
| goto done; |
| } |
| nla_put(skb, ATTR_FW_DUMP_PATH, sizeof(handle->firmware_dump_file), |
| handle->firmware_dump_file); |
| ret = cfg80211_vendor_cmd_reply(skb); |
| if (ret) |
| PRINTM(MERROR, "Command reply failed ret = %d\n", ret); |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to get driver memory dump |
| * |
| * @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_dump(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int data_len) |
| { |
| struct net_device *dev = NULL; |
| moal_private *priv = NULL; |
| moal_handle *handle = NULL; |
| int ret = MLAN_STATUS_SUCCESS; |
| int length = 0; |
| char driver_dump_file[128]; |
| char path_name[64]; |
| struct sk_buff *skb = NULL; |
| |
| ENTER(); |
| |
| if (!wdev || !wdev->netdev) { |
| LEAVE(); |
| return -EFAULT; |
| } |
| dev = wdev->netdev; |
| priv = (moal_private *)woal_get_netdev_priv(dev); |
| handle = priv->phandle; |
| memset(path_name, 0, sizeof(path_name)); |
| woal_create_dump_dir(handle, path_name, sizeof(path_name)); |
| PRINTM(MMSG, "driver dump path name is %s\n", path_name); |
| woal_dump_drv_info(handle, path_name); |
| memset(driver_dump_file, 0, sizeof(driver_dump_file)); |
| sprintf(driver_dump_file, "%s/%s", path_name, "file_drv_info"); |
| PRINTM(MMSG, "driver dump file is %s\n", driver_dump_file); |
| length = sizeof(driver_dump_file); |
| skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, length); |
| if (!skb) { |
| PRINTM(MERROR, "Failed to allocate memory for skb\n"); |
| ret = -ENOMEM; |
| goto done; |
| } |
| nla_put_string(skb, ATTR_DRV_DUMP_PATH, driver_dump_file); |
| ret = cfg80211_vendor_cmd_reply(skb); |
| if (ret) |
| PRINTM(MERROR, "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; |
| ENTER(); |
| |
| supp_feature_set = WIFI_FEATURE_INFRA |
| #if defined(UAP_SUPPORT) && defined(STA_SUPPORT) |
| | WIFI_FEATURE_AP_STA |
| #endif |
| | WIFI_FEATURE_LINK_LAYER_STATS |
| | WIFI_FEATURE_LOGGER |
| | WIFI_FEATURE_RSSI_MONITOR |
| | WIFI_FEATURE_CONFIG_NDO | WIFI_FEATURE_SCAN_RAND; |
| |
| 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 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_wifi_logger_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; |
| |
| ENTER(); |
| |
| supp_feature_set = WIFI_LOGGER_CONNECT_EVENT_SUPPORTED; |
| 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_WIFI_LOGGER_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 get ring status |
| * |
| * @param priv A pointer to moal_private struct |
| * @param ring_id ring buffer id |
| * @param status a pointer to wifi_ring_buffer_status |
| * |
| * @return void |
| */ |
| static void |
| woal_get_ring_status(moal_private *priv, int ring_id, |
| wifi_ring_buffer_status * status) |
| { |
| int id = 0; |
| wifi_ring_buffer *ring; |
| |
| ENTER(); |
| for (id = 0; id < RING_ID_MAX; id++) { |
| ring = (wifi_ring_buffer *) priv->rings[id]; |
| if (VALID_RING(ring->ring_id) && ring_id == ring->ring_id) { |
| strncpy(status->name, ring->name, |
| sizeof(status->name) - 1); |
| status->ring_id = ring->ring_id; |
| status->ring_buffer_byte_size = ring->ring_size; |
| status->written_bytes = ring->ctrl.written_bytes; |
| status->written_records = ring->ctrl.written_records; |
| status->read_bytes = ring->ctrl.read_bytes; |
| status->verbose_level = ring->log_level; |
| PRINTM(MINFO, |
| "%s, name: %s, ring_id: %d, ring_size : %d, written_bytes: %d, written_records: " |
| "%d, read_bytes: %d \n", |
| __FUNCTION__, status->name, status->ring_id, |
| status->ring_buffer_byte_size, |
| status->written_bytes, status->written_records, |
| status->read_bytes); |
| break; |
| } |
| } |
| LEAVE(); |
| return; |
| } |
| |
| /** |
| * @brief vendor command to get ring buffer status |
| * |
| * @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_ring_buff_status(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; |
| t_u8 ringIdx, ring_cnt = 0; |
| int ret = 0; |
| wifi_ring_buffer_status status[RING_ID_MAX]; |
| wifi_ring_buffer_status ring_status; |
| |
| ENTER(); |
| reply_len = |
| RING_ID_MAX * sizeof(wifi_ring_buffer_status) + sizeof(t_u32); |
| /** 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; |
| } |
| |
| /* shoud sync with HAL side to decide payload layout. |
| just ring buffer status or ring buffer num + ring buffer status |
| currently only have ring buffer status |
| */ |
| for (ringIdx = 0; ringIdx < RING_ID_MAX; ringIdx++) { |
| memset(&ring_status, 0, sizeof(wifi_ring_buffer_status)); |
| woal_get_ring_status(priv, ringIdx, &ring_status); |
| memcpy(&status[ring_cnt++], &ring_status, |
| sizeof(wifi_ring_buffer_status)); |
| } |
| |
| nla_put_u32(skb, ATTR_NUM_RINGS, ring_cnt); |
| nla_put(skb, ATTR_RING_BUFFER_STATUS, |
| sizeof(wifi_ring_buffer_status) * ring_cnt, status); |
| |
| ret = cfg80211_vendor_cmd_reply(skb); |
| |
| if (ret) |
| PRINTM(MERROR, "Vendor command reply failed ret = %d\n", ret); |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief return ring_id based on ring_name |
| * |
| * @param priv A pointer to moal_private struct |
| * @param ring_name A pointer to ring_name |
| * |
| * @return An invalid ring id for failure or valid ring id on success. |
| */ |
| int |
| woal_get_ring_id_by_name(moal_private *priv, char *ring_name) |
| { |
| int id; |
| wifi_ring_buffer *ring; |
| |
| ENTER(); |
| |
| for (id = 0; id < RING_ID_MAX; id++) { |
| ring = (wifi_ring_buffer *) priv->rings[id]; |
| if (!strncmp(ring->name, ring_name, sizeof(ring->name) - 1)) |
| break; |
| } |
| |
| LEAVE(); |
| return id; |
| } |
| |
| /** |
| * @brief start logger |
| * |
| * @param priv A pointer to moal_private struct |
| * @param ring_name string ring_name |
| * @param log_level log level to record |
| * @param flags reserved |
| * @param time_intval interval to report log to HAL |
| * @param threshold buffer threshold to report log to HAL |
| * |
| * @return 0: success 1: fail |
| */ |
| int |
| woal_start_logging(moal_private *priv, char *ring_name, int log_level, |
| int flags, int time_intval, int threshold) |
| { |
| int ret = 0; |
| int ring_id; |
| wifi_ring_buffer *ring_buffer; |
| unsigned long lock_flags; |
| |
| ENTER(); |
| |
| ring_id = woal_get_ring_id_by_name(priv, ring_name); |
| if (!VALID_RING(ring_id)) { |
| ret = -EINVAL; |
| goto done; |
| } |
| |
| PRINTM(MCMND, |
| "%s , log_level : %d, time_intval : %d, threshod %d Bytes\n", |
| __FUNCTION__, log_level, time_intval, threshold); |
| |
| ring_buffer = (wifi_ring_buffer *) priv->rings[ring_id]; |
| if (ring_buffer->state == RING_STOP) { |
| PRINTM(MERROR, "Ring is stopped!\n"); |
| ret = -EAGAIN; |
| goto done; |
| } |
| |
| spin_lock_irqsave(&ring_buffer->lock, lock_flags); |
| ring_buffer->log_level = log_level; |
| ring_buffer->threshold = threshold; |
| if (log_level == 0) |
| ring_buffer->state = RING_SUSPEND; |
| else |
| ring_buffer->state = RING_ACTIVE; |
| ring_buffer->interval = msecs_to_jiffies(time_intval * MSEC_PER_SEC); |
| spin_unlock_irqrestore(&ring_buffer->lock, lock_flags); |
| |
| if (log_level == 0) { |
| cancel_delayed_work_sync(&ring_buffer->work); |
| } else { |
| schedule_delayed_work(&ring_buffer->work, |
| ring_buffer->interval); |
| } |
| |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to start logging |
| * |
| * @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_start_logging(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, |
| int len) |
| { |
| int ret = 0, rem, type; |
| char ring_name[RING_NAME_MAX] = { 0 }; |
| int log_level = 0, flags = 0, time_intval = 0, threshold = 0; |
| const struct nlattr *iter; |
| struct net_device *dev = wdev->netdev; |
| moal_private *priv = (moal_private *)woal_get_netdev_priv(dev); |
| |
| ENTER(); |
| |
| nla_for_each_attr(iter, data, len, rem) { |
| type = nla_type(iter); |
| switch (type) { |
| case ATTR_WIFI_LOGGER_RING_ID: |
| strncpy(ring_name, nla_data(iter), |
| MIN(sizeof(ring_name) - 1, nla_len(iter))); |
| break; |
| case ATTR_WIFI_LOGGER_VERBOSE_LEVEL: |
| log_level = nla_get_u32(iter); |
| break; |
| case ATTR_WIFI_LOGGER_FLAGS: |
| flags = nla_get_u32(iter); |
| break; |
| case ATTR_WIFI_LOGGER_MAX_INTERVAL_SEC: |
| time_intval = nla_get_u32(iter); |
| break; |
| case ATTR_WIFI_LOGGER_MIN_DATA_SIZE: |
| threshold = nla_get_u32(iter); |
| break; |
| default: |
| PRINTM(MERROR, "Unknown type: %d\n", type); |
| ret = -EINVAL; |
| goto done; |
| } |
| } |
| |
| ret = woal_start_logging(priv, ring_name, log_level, flags, time_intval, |
| threshold); |
| if (ret < 0) { |
| PRINTM(MERROR, "Start_logging is failed ret: %d\n", ret); |
| } |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief get log data in ring buffer |
| * |
| * @param priv A pointer to moal_private struct |
| * @param ring_name ring name string |
| * |
| * @return 0: success 1: fail |
| */ |
| static int |
| woal_trigger_get_ring_data(moal_private *priv, char *ring_name) |
| { |
| int ret = 0; |
| int ring_id; |
| wifi_ring_buffer *ring_buffer; |
| |
| ENTER(); |
| |
| ring_id = woal_get_ring_id_by_name(priv, ring_name); |
| if (!VALID_RING(ring_id)) { |
| PRINTM(MERROR, "invalid ring_id \n"); |
| ret = -EINVAL; |
| goto done; |
| } |
| |
| ring_buffer = (wifi_ring_buffer *) priv->rings[ring_id]; |
| if (ring_buffer->interval) |
| cancel_delayed_work_sync(&ring_buffer->work); |
| schedule_delayed_work(&ring_buffer->work, 0); |
| |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to get ring buffer data |
| * |
| * @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_ring_data(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, |
| int len) |
| { |
| int ret = 0, rem, type; |
| char ring_name[RING_NAME_MAX] = { 0 }; |
| const struct nlattr *iter; |
| struct net_device *dev = wdev->netdev; |
| moal_private *priv = (moal_private *)woal_get_netdev_priv(dev); |
| |
| ENTER(); |
| |
| nla_for_each_attr(iter, data, len, rem) { |
| type = nla_type(iter); |
| switch (type) { |
| case ATTR_WIFI_LOGGER_RING_ID: |
| strncpy(ring_name, nla_data(iter), |
| MIN(sizeof(ring_name) - 1, nla_len(iter))); |
| break; |
| default: |
| PRINTM(MERROR, "Unknown type: %d\n", type); |
| ret = -EINVAL; |
| goto done; |
| } |
| } |
| |
| ret = woal_trigger_get_ring_data(priv, ring_name); |
| if (ret < 0) { |
| PRINTM(MERROR, "trigger_get_data failed ret:%d\n", ret); |
| } |
| |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief get ring buffer entry |
| * |
| * @param ring A pointer to wifi_ring_buffer struct |
| * @param offset entry offset |
| * |
| * @return A pointer to wifi_ring_buffer_entry struct |
| */ |
| static wifi_ring_buffer_entry * |
| woal_get_ring_entry(wifi_ring_buffer * ring, t_u32 offset) |
| { |
| wifi_ring_buffer_entry *entry = |
| (wifi_ring_buffer_entry *) (ring->ring_buf + offset); |
| |
| ENTER(); |
| |
| if (!entry->entry_size) |
| return (wifi_ring_buffer_entry *) ring->ring_buf; |
| |
| LEAVE(); |
| return entry; |
| } |
| |
| /** |
| * @brief get next ring buffer entry |
| * |
| * @param ring A pointer to wifi_ring_buffer struct |
| * @param offset next entry offset |
| * |
| * @return offset of next entry |
| */ |
| static t_u32 |
| woal_get_ring_next_entry(wifi_ring_buffer * ring, t_u32 offset) |
| { |
| wifi_ring_buffer_entry *entry = |
| (wifi_ring_buffer_entry *) (ring->ring_buf + offset); |
| |
| ENTER(); |
| |
| if (!entry->entry_size) { |
| entry = (wifi_ring_buffer_entry *) ring->ring_buf; |
| LEAVE(); |
| return ENTRY_LENGTH(entry); |
| } |
| |
| LEAVE(); |
| return offset + ENTRY_LENGTH(entry); |
| } |
| |
| /** |
| * @brief prepare log data to send to HAL |
| * |
| * @param priv A pointer to moal_private struct |
| * @param ring_id ring ID |
| * @param data A pointer to data buffer |
| * @param buf_len log data length |
| * |
| * @return data length |
| */ |
| int |
| woal_ring_pull_data(moal_private *priv, int ring_id, void *data, t_s32 buf_len) |
| { |
| t_s32 avail_len, r_len = 0; |
| unsigned long flags; |
| wifi_ring_buffer *ring; |
| wifi_ring_buffer_entry *hdr; |
| |
| ENTER(); |
| |
| ring = (wifi_ring_buffer *) priv->rings[ring_id]; |
| if (ring->state != RING_ACTIVE) { |
| PRINTM(MERROR, "Ring is not active!\n"); |
| goto done; |
| } |
| |
| spin_lock_irqsave(&ring->lock, flags); |
| |
| /* get a fresh pending length */ |
| avail_len = READ_AVAIL_SPACE(ring->wp, ring->rp, ring->ring_size); |
| while (avail_len > 0 && buf_len > 0) { |
| hdr = woal_get_ring_entry(ring, ring->rp); |
| memcpy(data, hdr, ENTRY_LENGTH(hdr)); |
| r_len += ENTRY_LENGTH(hdr); |
| /* update read pointer */ |
| ring->rp = woal_get_ring_next_entry(ring, ring->rp); |
| data += ENTRY_LENGTH(hdr); |
| avail_len -= ENTRY_LENGTH(hdr); |
| buf_len -= ENTRY_LENGTH(hdr); |
| ring->ctrl.read_bytes += ENTRY_LENGTH(hdr); |
| PRINTM(MINFO, "%s read_bytes %d\n", __FUNCTION__, |
| ring->ctrl.read_bytes); |
| } |
| spin_unlock_irqrestore(&ring->lock, flags); |
| |
| done: |
| LEAVE(); |
| return r_len; |
| } |
| |
| /** |
| * @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_ring_buffer_data_vendor_event(IN moal_private *priv, IN int ring_id, |
| IN t_u8 *data, IN int len) |
| { |
| struct wiphy *wiphy = NULL; |
| struct sk_buff *skb = NULL; |
| int event_id = 0; |
| int ret = 0; |
| wifi_ring_buffer_status ring_status; |
| |
| ENTER(); |
| |
| if (!priv || !priv->wdev || !priv->wdev->wiphy) { |
| PRINTM(MERROR, "priv is null \n"); |
| ret = -EINVAL; |
| goto done; |
| } |
| wiphy = priv->wdev->wiphy; |
| PRINTM(MEVENT, "woal_ring_buffer_data_vendor_event ring_id:%d\n", |
| ring_id); |
| event_id = woal_get_event_id(event_wifi_logger_ring_buffer_data); |
| if (event_max == event_id) { |
| PRINTM(MERROR, "Not find this event %d \n", event_id); |
| ret = -EINVAL; |
| goto done; |
| } |
| |
| /**allocate skb*/ |
| #if CFG80211_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) |
| skb = cfg80211_vendor_event_alloc(wiphy, NULL, |
| len + sizeof(wifi_ring_buffer_status), |
| event_id, GFP_ATOMIC); |
| #else |
| skb = cfg80211_vendor_event_alloc(wiphy, |
| len + sizeof(wifi_ring_buffer_status), |
| event_id, GFP_ATOMIC); |
| #endif |
| |
| if (!skb) { |
| PRINTM(MERROR, "allocate memory fail for vendor event\n"); |
| ret = -ENOMEM; |
| goto done; |
| } |
| |
| woal_get_ring_status(priv, ring_id, &ring_status); |
| |
| nla_put(skb, ATTR_RING_BUFFER_STATUS, sizeof(wifi_ring_buffer_status), |
| &ring_status); |
| DBG_HEXDUMP(MEVT_D, "ring_buffer_data", data, len); |
| nla_put(skb, ATTR_RING_BUFFER, len, data); |
| |
| /**send event*/ |
| cfg80211_vendor_event(skb, GFP_ATOMIC); |
| |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief send log data to HAL |
| * |
| * @param priv A pointer to moal_private struct |
| * @param ring_id ring ID |
| * @param data A pointer to data buffer |
| * @param len log data length |
| * @param ring_status A pointer to wifi_ring_buffer status struct |
| * |
| * @return void |
| */ |
| static void |
| woal_ring_data_send(moal_private *priv, int ring_id, const void *data, |
| const t_u32 len, const wifi_ring_buffer_status ring_status) |
| { |
| struct net_device *ndev = priv->netdev; |
| ENTER(); |
| |
| if (!ndev) |
| goto done; |
| woal_ring_buffer_data_vendor_event(priv, ring_id, (t_u8 *)data, len); |
| |
| done: |
| LEAVE(); |
| return; |
| } |
| |
| /** |
| * @brief main thread to send log data |
| * |
| * @param work A pointer to work_struct struct |
| * |
| * @return void |
| */ |
| void |
| woal_ring_poll_worker(struct work_struct *work) |
| { |
| struct delayed_work *d_work = to_delayed_work(work); |
| wifi_ring_buffer *ring_info = |
| container_of(d_work, wifi_ring_buffer, work); |
| moal_private *priv = ring_info->priv; |
| int ringid = ring_info->ring_id; |
| wifi_ring_buffer_status ring_status; |
| void *buf; |
| wifi_ring_buffer_entry *hdr; |
| t_s32 buflen, rlen; |
| |
| ENTER(); |
| |
| woal_get_ring_status(priv, ringid, &ring_status); |
| PRINTM(MINFO, "woal_ring_poll_worker write %d, read %d, size %d\n", |
| ring_status.written_bytes, ring_status.read_bytes, |
| ring_status.ring_buffer_byte_size); |
| if (ring_status.written_bytes > ring_status.read_bytes) |
| buflen = ring_status.written_bytes - ring_status.read_bytes; |
| else if (ring_status.written_bytes < ring_status.read_bytes) |
| buflen = ring_status.ring_buffer_byte_size + |
| ring_status.written_bytes - ring_status.read_bytes; |
| else { |
| PRINTM(MERROR, "No new records\n"); |
| goto exit; |
| } |
| |
| buf = kmalloc(buflen, GFP_KERNEL); |
| if (!buf) { |
| PRINTM(MERROR, "%s failed to allocate read buf\n", |
| __FUNCTION__); |
| LEAVE(); |
| return; |
| } |
| rlen = woal_ring_pull_data(priv, ringid, buf, buflen); |
| hdr = (wifi_ring_buffer_entry *) buf; |
| while (rlen > 0) { |
| ring_status.read_bytes += ENTRY_LENGTH(hdr); |
| woal_ring_data_send(priv, ringid, hdr, ENTRY_LENGTH(hdr), |
| ring_status); |
| rlen -= ENTRY_LENGTH(hdr); |
| hdr = (wifi_ring_buffer_entry *) ((void *)hdr + |
| ENTRY_LENGTH(hdr)); |
| } |
| kfree(buf); |
| |
| if (!ring_info->interval) { |
| LEAVE(); |
| return; |
| } |
| woal_get_ring_status(priv, ring_info->ring_id, &ring_status); |
| |
| exit: |
| if (ring_info->interval) { |
| /* retrigger the work at same interval */ |
| if (READ_AVAIL_SPACE |
| (ring_info->wp, ring_info->rp, ring_info->ring_size) <= 0) |
| schedule_delayed_work(d_work, ring_info->interval); |
| else |
| schedule_delayed_work(d_work, 0); |
| } |
| |
| LEAVE(); |
| return; |
| } |
| |
| /** |
| * @brief add log data to ring buffer |
| * |
| * @param priv A pointer to moal_private struct |
| * @param ring_id ring ID |
| * @param hdr A pointer to wifi_ring_buffer_entry struct |
| * @param data A pointer to data buffer |
| * |
| * @return 0: success -1: fail |
| */ |
| int |
| woal_ring_push_data(moal_private *priv, int ring_id, |
| wifi_ring_buffer_entry * hdr, void *data) |
| { |
| unsigned long flags; |
| t_u32 w_len; |
| wifi_ring_buffer *ring; |
| wifi_ring_buffer_entry *w_entry; |
| int ret = 0; |
| |
| ENTER(); |
| |
| ring = (wifi_ring_buffer *) priv->rings[ring_id]; |
| |
| if (ring->state != RING_ACTIVE) { |
| PRINTM(MERROR, "Ring is not active\n"); |
| ret = -EINVAL; |
| goto done; |
| } |
| |
| spin_lock_irqsave(&ring->lock, flags); |
| |
| w_len = ENTRY_LENGTH(hdr); |
| /* prep the space */ |
| do { |
| if (ring->rp == ring->wp) |
| break; |
| if (ring->rp < ring->wp) { |
| if (ring->ring_size - ring->wp == w_len) { |
| if (ring->rp == 0) |
| ring->rp = |
| woal_get_ring_next_entry(ring, |
| ring-> |
| rp); |
| break; |
| } else if (ring->ring_size - ring->wp < w_len) { |
| if (ring->rp == 0) |
| ring->rp = |
| woal_get_ring_next_entry(ring, |
| ring-> |
| rp); |
| ring->wp = 0; |
| continue; |
| } else { |
| break; |
| } |
| } |
| if (ring->rp > ring->wp) { |
| if (ring->rp - ring->wp <= w_len) { |
| ring->rp = |
| woal_get_ring_next_entry(ring, |
| ring->rp); |
| if (ring->rp >= ring->ring_size) { |
| PRINTM(MINFO, |
| "log size exceed ring size\n"); |
| ring->rp = 0; |
| break; |
| } else |
| continue; |
| } else { |
| break; |
| } |
| } |
| } while (1); |
| |
| w_entry = (wifi_ring_buffer_entry *) (ring->ring_buf + ring->wp); |
| /* header */ |
| memcpy(w_entry, hdr, RING_ENTRY_SIZE); |
| /* payload */ |
| memcpy((char *)w_entry + RING_ENTRY_SIZE, data, w_entry->entry_size); |
| /* update write pointer */ |
| ring->wp += w_len; |
| /* update statistics */ |
| ring->ctrl.written_records++; |
| ring->ctrl.written_bytes += w_len; |
| spin_unlock_irqrestore(&ring->lock, flags); |
| PRINTM(MINFO, "%s : written_records %d, written_bytes %d\n", |
| __FUNCTION__, ring->ctrl.written_records, |
| ring->ctrl.written_bytes); |
| |
| /* if the current pending size is bigger than threshold */ |
| if (ring->threshold > 0 && |
| (READ_AVAIL_SPACE(ring->wp, ring->rp, ring->ring_size) >= |
| ring->threshold) && ring->interval != 0) |
| schedule_delayed_work(&ring->work, 0); |
| |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function will init wifi logger ring buffer |
| * |
| * @param priv A pointer to moal_private structure |
| * @param ring_buff A pointer to wifi_ring_buffer structure |
| * @param ringIdx Ring buffer ID |
| * @param name Ring buffer name |
| * @param ring_sz Ring buffer size |
| * |
| * @return 0 - success |
| */ |
| static int |
| woal_init_ring_buffer_internal(moal_private *priv, void **ring, t_u16 ringIdx, |
| t_u8 *name, t_u32 ring_sz) |
| { |
| unsigned long flags; |
| wifi_ring_buffer *ring_buff; |
| ENTER(); |
| |
| *ring = vmalloc(sizeof(wifi_ring_buffer)); |
| memset(*ring, 0, sizeof(wifi_ring_buffer)); |
| ring_buff = (wifi_ring_buffer *) * ring; |
| |
| ring_buff->ring_buf = vmalloc(ring_sz); |
| if (!unlikely(ring_buff->ring_buf)) { |
| PRINTM(MERROR, "WiFi Logger: ring buffer data alloc failed\n"); |
| } |
| memset(ring_buff->ring_buf, 0, ring_sz); |
| spin_lock_init(&ring_buff->lock); |
| spin_lock_irqsave(&ring_buff->lock, flags); |
| ring_buff->rp = ring_buff->wp = 0; |
| INIT_DELAYED_WORK(&ring_buff->work, woal_ring_poll_worker); |
| memcpy(ring_buff->name, name, MIN(strlen(name), RING_NAME_MAX)); |
| ring_buff->name[RING_NAME_MAX - 1] = 0; |
| ring_buff->ring_id = ringIdx; |
| ring_buff->ring_size = ring_sz; |
| ring_buff->state = RING_SUSPEND; |
| ring_buff->threshold = ring_buff->ring_size / 2; |
| ring_buff->priv = priv; |
| spin_unlock_irqrestore(&ring_buff->lock, flags); |
| |
| LEAVE(); |
| return 0; |
| } |
| |
| /** |
| * @brief init each ring in moal_private |
| * |
| * @param priv A pointer to moal_private struct |
| * |
| * @return data length |
| */ |
| static int |
| woal_init_ring_buffer(moal_private *priv) |
| { |
| ENTER(); |
| woal_init_ring_buffer_internal(priv, &priv->rings[VERBOSE_RING_ID], |
| VERBOSE_RING_ID, VERBOSE_RING_NAME, |
| DEFAULT_RING_BUFFER_SIZE); |
| woal_init_ring_buffer_internal(priv, &priv->rings[EVENT_RING_ID], |
| EVENT_RING_ID, EVENT_RING_NAME, |
| DEFAULT_RING_BUFFER_SIZE); |
| LEAVE(); |
| return 0; |
| } |
| |
| /** |
| * @brief deinit each ring in moal_private |
| * |
| * @param priv A pointer to moal_private struct |
| * |
| * @return data length |
| */ |
| static int |
| woal_deinit_ring_buffer(moal_private *priv) |
| { |
| int i; |
| enum ring_state ring_state = RING_STOP; |
| unsigned long lock_flags = 0; |
| wifi_ring_buffer *ring_buff; |
| ENTER(); |
| |
| for (i = 0; i < RING_ID_MAX - 1; i++) { |
| ring_buff = (wifi_ring_buffer *) priv->rings[i]; |
| spin_lock_irqsave(&ring_buff->lock, lock_flags); |
| ring_state = ring_buff->state; |
| if (ring_state == RING_ACTIVE) { |
| ring_buff->state = RING_STOP; |
| } |
| spin_unlock_irqrestore(&ring_buff->lock, lock_flags); |
| if (ring_state == RING_ACTIVE) |
| cancel_delayed_work_sync(&ring_buff->work); |
| vfree(ring_buff->ring_buf); |
| ring_buff->ring_buf = NULL; |
| vfree(ring_buff); |
| priv->rings[i] = NULL; |
| } |
| |
| LEAVE(); |
| return 0; |
| } |
| |
| /** |
| * @brief add log data to ring buffer |
| * |
| * @param priv A pointer to moal_private struct |
| * @param ring_id ring ID |
| * @param hdr A pointer to wifi_ring_buffer_entry struct |
| * @param data A pointer to data buffer |
| * |
| * @return 0: success -1: fail |
| */ |
| int |
| woal_ring_event_logger(moal_private *priv, int ring_id, pmlan_event pmevent) |
| { |
| t_u8 event_buf[100] = { 0 }; |
| wifi_ring_buffer_driver_connectivity_event *connectivity_event; |
| tlv_log *tlv; |
| t_u8 *pos; |
| wifi_ring_buffer_entry msg_hdr; |
| wifi_ring_buffer *ring; |
| |
| ENTER(); |
| ring = (wifi_ring_buffer *) priv->rings[ring_id]; |
| |
| if (ring->state != RING_ACTIVE) { |
| PRINTM(MINFO, "Ring is not active\n"); |
| goto done; |
| } |
| |
| switch (pmevent->event_id) { |
| case MLAN_EVENT_ID_DRV_ASSOC_SUCC_LOGGER: |
| if (GET_BSS_ROLE(priv) == MLAN_BSS_ROLE_STA) { |
| assoc_logger_data *pbss_desc = |
| (assoc_logger_data *) pmevent->event_buf; |
| memset(&msg_hdr, 0, sizeof(msg_hdr)); |
| msg_hdr.flags |= RING_BUFFER_ENTRY_FLAGS_HAS_TIMESTAMP; |
| msg_hdr.type = ENTRY_TYPE_CONNECT_EVENT; |
| connectivity_event = |
| (wifi_ring_buffer_driver_connectivity_event *) |
| event_buf; |
| connectivity_event->event = WIFI_EVENT_ASSOC_COMPLETE; |
| pos = (t_u8 *)connectivity_event->tlvs; |
| if (pbss_desc->oui) { |
| tlv = (tlv_log *) pos; |
| tlv->tag = WIFI_TAG_VENDOR_SPECIFIC; |
| tlv->length = MLAN_MAC_ADDR_LENGTH / 2; |
| memcpy(tlv->value, pbss_desc->oui, tlv->length); |
| msg_hdr.entry_size += |
| tlv->length + TLV_LOG_HEADER_LEN; |
| pos = pos + tlv->length + TLV_LOG_HEADER_LEN; |
| } |
| if (pbss_desc->bssid) { |
| tlv = (tlv_log *) pos; |
| tlv->tag = WIFI_TAG_BSSID; |
| tlv->length = sizeof(pbss_desc->bssid); |
| memcpy(tlv->value, pbss_desc->bssid, |
| sizeof(pbss_desc->bssid)); |
| msg_hdr.entry_size += |
| tlv->length + TLV_LOG_HEADER_LEN; |
| pos = pos + tlv->length + TLV_LOG_HEADER_LEN; |
| } |
| if (pbss_desc->ssid) { |
| tlv = (tlv_log *) pos; |
| tlv->tag = WIFI_TAG_SSID; |
| tlv->length = strlen(pbss_desc->ssid); |
| memcpy(tlv->value, pbss_desc->ssid, |
| tlv->length); |
| msg_hdr.entry_size += |
| tlv->length + TLV_LOG_HEADER_LEN; |
| pos = pos + tlv->length + TLV_LOG_HEADER_LEN; |
| } |
| if (pbss_desc->rssi) { |
| tlv = (tlv_log *) pos; |
| tlv->tag = WIFI_TAG_RSSI; |
| tlv->length = sizeof(pbss_desc->rssi); |
| memcpy(tlv->value, &pbss_desc->rssi, |
| tlv->length); |
| msg_hdr.entry_size += |
| tlv->length + TLV_LOG_HEADER_LEN; |
| pos = pos + tlv->length + TLV_LOG_HEADER_LEN; |
| } |
| if (pbss_desc->channel) { |
| tlv = (tlv_log *) pos; |
| tlv->tag = WIFI_TAG_CHANNEL; |
| tlv->length = sizeof(pbss_desc->channel); |
| memcpy(tlv->value, &pbss_desc->channel, |
| sizeof(pbss_desc->channel)); |
| msg_hdr.entry_size += |
| tlv->length + TLV_LOG_HEADER_LEN; |
| } |
| msg_hdr.entry_size += sizeof(connectivity_event->event); |
| DBG_HEXDUMP(MCMD_D, "connectivity_event", |
| (t_u8 *)connectivity_event, |
| msg_hdr.entry_size); |
| woal_ring_push_data(priv, ring_id, &msg_hdr, |
| connectivity_event); |
| } |
| break; |
| case MLAN_EVENT_ID_DRV_ASSOC_FAILURE_LOGGER: |
| if (GET_BSS_ROLE(priv) == MLAN_BSS_ROLE_STA) { |
| int status_code = *(int *)pmevent->event_buf; |
| memset(&msg_hdr, 0, sizeof(msg_hdr)); |
| msg_hdr.flags |= RING_BUFFER_ENTRY_FLAGS_HAS_TIMESTAMP; |
| msg_hdr.type = ENTRY_TYPE_CONNECT_EVENT; |
| connectivity_event = |
| (wifi_ring_buffer_driver_connectivity_event *) |
| event_buf; |
| connectivity_event->event = WIFI_EVENT_ASSOC_COMPLETE; |
| pos = (t_u8 *)connectivity_event->tlvs; |
| tlv = (tlv_log *) pos; |
| tlv->tag = WIFI_TAG_STATUS; |
| tlv->length = sizeof(status_code); |
| memcpy(tlv->value, &status_code, sizeof(status_code)); |
| msg_hdr.entry_size += tlv->length + 4; |
| msg_hdr.entry_size += sizeof(connectivity_event->event); |
| woal_ring_push_data(priv, ring_id, &msg_hdr, |
| connectivity_event); |
| } |
| break; |
| case MLAN_EVENT_ID_DRV_DISCONNECT_LOGGER: |
| if (GET_BSS_ROLE(priv) == MLAN_BSS_ROLE_STA) { |
| t_u16 reason_code = *(t_u16 *)pmevent->event_buf; |
| memset(&msg_hdr, 0, sizeof(msg_hdr)); |
| msg_hdr.flags |= RING_BUFFER_ENTRY_FLAGS_HAS_TIMESTAMP; |
| msg_hdr.type = ENTRY_TYPE_CONNECT_EVENT; |
| connectivity_event = |
| (wifi_ring_buffer_driver_connectivity_event *) |
| event_buf; |
| connectivity_event->event = WIFI_EVENT_ASSOC_COMPLETE; |
| pos = (t_u8 *)connectivity_event->tlvs; |
| tlv = (tlv_log *) pos; |
| tlv->tag = WIFI_TAG_REASON_CODE; |
| tlv->length = sizeof(reason_code); |
| memcpy(tlv->value, &reason_code, sizeof(reason_code)); |
| msg_hdr.entry_size += tlv->length + 4; |
| msg_hdr.entry_size += sizeof(connectivity_event->event); |
| woal_ring_push_data(priv, ring_id, &msg_hdr, |
| connectivity_event); |
| } |
| break; |
| default: |
| break; |
| } |
| done: |
| LEAVE(); |
| return 0; |
| } |
| |
| /** |
| * @brief send vendor event to kernel |
| * |
| * @param priv A pointer to moal_private struct |
| * @param wake_reason wake_reason |
| * |
| * @return 0: success 1: fail |
| */ |
| int |
| woal_wake_reason_vendor_event(IN moal_private *priv, |
| IN mlan_ds_hs_wakeup_reason wake_reason) |
| { |
| struct wiphy *wiphy = NULL; |
| struct sk_buff *skb = NULL; |
| int event_id = 0; |
| int ret = 0; |
| |
| ENTER(); |
| |
| if (!priv || !priv->wdev || !priv->wdev->wiphy) { |
| PRINTM(MERROR, "priv is null \n"); |
| ret = -EINVAL; |
| goto done; |
| } |
| wiphy = priv->wdev->wiphy; |
| |
| event_id = woal_get_event_id(event_wake_reason_report); |
| if (event_max == event_id) { |
| PRINTM(MERROR, "Not find this event %d \n", event_id); |
| ret = MLAN_STATUS_FAILURE; |
| goto done; |
| } |
| |
| /**allocate skb*/ |
| #if CFG80211_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) |
| skb = cfg80211_vendor_event_alloc(wiphy, NULL, |
| sizeof(wake_reason.hs_wakeup_reason), |
| event_id, GFP_ATOMIC); |
| #else |
| skb = cfg80211_vendor_event_alloc(wiphy, |
| sizeof(wake_reason.hs_wakeup_reason), |
| event_id, GFP_ATOMIC); |
| #endif |
| |
| if (!skb) { |
| PRINTM(MERROR, "allocate memory fail for vendor event\n"); |
| ret = -ENOMEM; |
| goto done; |
| } |
| |
| PRINTM(MINFO, "wake_reason.hs_wakeup_reason = %d\n", |
| wake_reason.hs_wakeup_reason); |
| nla_put_u16(skb, ATTR_WAKE_REASON_STAT, wake_reason.hs_wakeup_reason); |
| /**send event*/ |
| cfg80211_vendor_event(skb, GFP_ATOMIC); |
| |
| done: |
| PRINTM(MINFO, "wake reason vendor event ret %d\n", ret); |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief log wake reason |
| * |
| * @param priv A pointer to moal_private struct |
| * @param wake_reason wake_reason |
| * |
| * @return 0: success -1: fail |
| */ |
| int |
| woal_wake_reason_logger(moal_private *priv, |
| mlan_ds_hs_wakeup_reason wake_reason) |
| { |
| int ret = 0; |
| ENTER(); |
| |
| ret = woal_wake_reason_vendor_event(priv, wake_reason); |
| |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to start packet fate 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_start_packet_fate_monitor(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; |
| |
| ENTER(); |
| |
| /** 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; |
| } |
| |
| priv->pkt_fate_monitor_enable = MTRUE; |
| |
| ret = cfg80211_vendor_cmd_reply(skb); |
| |
| if (ret) |
| PRINTM(MERROR, "Vendor command reply failed ret = %d\n", ret); |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief send vendor event to kernel |
| * |
| * @param priv A pointer to moal_private struct |
| * @param pkt_type tx or rx |
| * @param fate packet fate |
| * @param payload_type type of payload |
| * @param drv_ts_usec driver time in usec |
| * @param fw_ts_usec firmware time in usec |
| * @param data frame data |
| * @param len frame data len |
| * |
| * @return 0: success 1: fail |
| */ |
| int |
| woal_packet_fate_vendor_event(IN moal_private *priv, |
| IN packet_fate_packet_type pkt_type, IN t_u8 fate, |
| IN frame_type payload_type, IN t_u32 drv_ts_usec, |
| IN t_u32 fw_ts_usec, IN t_u8 *data, IN t_u32 len) |
| { |
| struct wiphy *wiphy = NULL; |
| struct sk_buff *skb = NULL; |
| int event_id = 0; |
| int ret = 0; |
| PACKET_FATE_REPORT fate_report; |
| |
| ENTER(); |
| |
| if (!priv || !priv->wdev || !priv->wdev->wiphy) { |
| PRINTM(MERROR, "priv is null \n"); |
| ret = -EINVAL; |
| goto done; |
| } |
| wiphy = priv->wdev->wiphy; |
| |
| event_id = woal_get_event_id(event_packet_fate_monitor); |
| if (event_max == event_id) { |
| PRINTM(MERROR, "Not find this event %d \n", event_id); |
| ret = MLAN_STATUS_FAILURE; |
| goto done; |
| } |
| |
| /**allocate skb*/ |
| #if CFG80211_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) |
| skb = cfg80211_vendor_event_alloc(wiphy, NULL, |
| len + sizeof(PACKET_FATE_REPORT), |
| event_id, GFP_ATOMIC); |
| #else |
| skb = cfg80211_vendor_event_alloc(wiphy, |
| len + sizeof(PACKET_FATE_REPORT), |
| event_id, GFP_ATOMIC); |
| #endif |
| |
| if (!skb) { |
| PRINTM(MERROR, "allocate memory fail for vendor event\n"); |
| ret = -ENOMEM; |
| goto done; |
| } |
| |
| memset(&fate_report, 0, sizeof(PACKET_FATE_REPORT)); |
| if (pkt_type == PACKET_TYPE_TX) { |
| fate_report.u.tx_report_i.fate = fate; |
| fate_report.u.tx_report_i.frame_inf.payload_type = payload_type; |
| fate_report.u.tx_report_i.frame_inf.driver_timestamp_usec = |
| drv_ts_usec; |
| fate_report.u.tx_report_i.frame_inf.firmware_timestamp_usec = |
| fw_ts_usec; |
| fate_report.u.tx_report_i.frame_inf.frame_len = len; |
| nla_put(skb, ATTR_PACKET_FATE_TX, sizeof(PACKET_FATE_REPORT), |
| &fate_report); |
| } else { |
| fate_report.u.rx_report_i.fate = fate; |
| fate_report.u.rx_report_i.frame_inf.payload_type = payload_type; |
| fate_report.u.rx_report_i.frame_inf.driver_timestamp_usec = |
| drv_ts_usec; |
| fate_report.u.rx_report_i.frame_inf.firmware_timestamp_usec = |
| fw_ts_usec; |
| fate_report.u.rx_report_i.frame_inf.frame_len = len; |
| nla_put(skb, ATTR_PACKET_FATE_RX, sizeof(PACKET_FATE_REPORT), |
| &fate_report); |
| } |
| DBG_HEXDUMP(MCMD_D, "fate_report", (t_u8 *)&fate_report, |
| sizeof(PACKET_FATE_REPORT)); |
| |
| nla_put(skb, ATTR_PACKET_FATE_DATA, len, data); |
| DBG_HEXDUMP(MCMD_D, "packet_fate_data", data, len); |
| |
| /**send event*/ |
| cfg80211_vendor_event(skb, GFP_ATOMIC); |
| |
| done: |
| PRINTM(MINFO, "packet fate vendor event ret %d\n", ret); |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief log packet fate |
| * |
| * @param priv A pointer to moal_private struct |
| * @param pkt_type tx or rx |
| * @param fate packet fate |
| * @param payload_type type of payload |
| * @param drv_ts_usec driver time in usec |
| * @param fw_ts_usec firmware time in usec |
| * @param data frame data |
| * @param len frame data len |
| * |
| * @return 0: success -1: fail |
| */ |
| int |
| woal_packet_fate_monitor(moal_private *priv, packet_fate_packet_type pkt_type, |
| t_u8 fate, frame_type payload_type, t_u32 drv_ts_usec, |
| t_u32 fw_ts_usec, t_u8 *data, t_u32 len) |
| { |
| int ret = 0; |
| |
| ENTER(); |
| |
| if (priv->pkt_fate_monitor_enable) |
| ret = woal_packet_fate_vendor_event(priv, pkt_type, fate, |
| payload_type, drv_ts_usec, |
| fw_ts_usec, data, len); |
| |
| PRINTM(MINFO, "packet fate monitor ret %d\n", ret); |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief init packet_filter in moal_private |
| * |
| * @param priv A pointer to moal_private struct |
| * |
| * @return 0: success -1: fail |
| */ |
| static int |
| woal_init_packet_filter(moal_private *priv) |
| { |
| int ret = 0; |
| packet_filter *pkt_filter = NULL; |
| |
| ENTER(); |
| |
| pkt_filter = vmalloc(sizeof(pkt_filter)); |
| if (!unlikely(pkt_filter)) { |
| PRINTM(MERROR, "WiFi Logger: packet_filter alloc failed\n"); |
| ret = -ENOMEM; |
| goto done; |
| } |
| |
| memset(pkt_filter, 0, sizeof(packet_filter)); |
| |
| spin_lock_init(&pkt_filter->lock); |
| pkt_filter->packet_filter_max_len = PACKET_FILTER_MAX_LEN; |
| pkt_filter->packet_filter_len = 0; |
| pkt_filter->packet_filter_version = APF_VERSION; |
| pkt_filter->state = PACKET_FILTER_STATE_STOP; |
| |
| priv->packet_filter = pkt_filter; |
| |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief deinit packet_filter in moal_private |
| * |
| * @param priv A pointer to moal_private struct |
| * |
| * @return 0: success -1: fail |
| */ |
| static int |
| woal_deinit_packet_filter(moal_private *priv) |
| { |
| int ret = 0; |
| packet_filter *pkt_filter = NULL; |
| unsigned long flags; |
| |
| ENTER(); |
| |
| pkt_filter = priv->packet_filter; |
| |
| if (!unlikely(pkt_filter)) { |
| goto done; |
| } |
| |
| spin_lock_irqsave(&pkt_filter->lock, flags); |
| pkt_filter->state = PACKET_FILTER_STATE_INIT; |
| spin_unlock_irqrestore(&pkt_filter->lock, flags); |
| |
| vfree(pkt_filter); |
| priv->packet_filter = NULL; |
| |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to set packet filter |
| * |
| * @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_packet_filter(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); |
| int ret = 0, rem, type; |
| const struct nlattr *iter; |
| packet_filter *pkt_filter = NULL; |
| t_u32 packet_filter_len = 0; |
| unsigned long flags; |
| |
| ENTER(); |
| pkt_filter = priv->packet_filter; |
| if (!unlikely(pkt_filter) || |
| pkt_filter->state == PACKET_FILTER_STATE_INIT) { |
| PRINTM(MERROR, "packet_filter not init\n"); |
| ret = -EINVAL; |
| goto done; |
| } |
| |
| nla_for_each_attr(iter, data, len, rem) { |
| type = nla_type(iter); |
| switch (type) { |
| case ATTR_PACKET_FILTER_TOTAL_LENGTH: |
| packet_filter_len = nla_get_u32(iter); |
| if (packet_filter_len > |
| pkt_filter->packet_filter_max_len) { |
| PRINTM(MERROR, |
| "packet_filter_len exceed max\n"); |
| ret = -EINVAL; |
| goto done; |
| } |
| break; |
| case ATTR_PACKET_FILTER_PROGRAM: |
| spin_lock_irqsave(&pkt_filter->lock, flags); |
| strncpy(pkt_filter->packet_filter_program, |
| nla_data(iter), MIN(packet_filter_len, |
| nla_len(iter))); |
| pkt_filter->packet_filter_len = |
| MIN(packet_filter_len, nla_len(iter)); |
| pkt_filter->state = PACKET_FILTER_STATE_START; |
| spin_unlock_irqrestore(&pkt_filter->lock, flags); |
| DBG_HEXDUMP(MCMD_D, "packet_filter_program", |
| pkt_filter->packet_filter_program, |
| pkt_filter->packet_filter_len); |
| break; |
| default: |
| PRINTM(MERROR, "Unknown type: %d\n", type); |
| ret = -EINVAL; |
| goto done; |
| } |
| } |
| |
| if (ret) |
| PRINTM(MERROR, "Vendor command reply failed ret = %d\n", ret); |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to get packet filter capability |
| * |
| * @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_packet_filter_capability(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; |
| packet_filter *pkt_filter; |
| |
| ENTER(); |
| pkt_filter = priv->packet_filter; |
| if (!unlikely(pkt_filter)) { |
| PRINTM(MERROR, "packet_filter not init\n"); |
| ret = -EINVAL; |
| goto done; |
| } |
| |
| reply_len = |
| sizeof(pkt_filter->packet_filter_version) + |
| sizeof(pkt_filter->packet_filter_max_len); |
| /** 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_PACKET_FILTER_VERSION, |
| pkt_filter->packet_filter_version); |
| nla_put_u32(skb, ATTR_PACKET_FILTER_MAX_LEN, |
| pkt_filter->packet_filter_max_len); |
| ret = cfg80211_vendor_cmd_reply(skb); |
| |
| if (ret) |
| PRINTM(MERROR, "Vendor command reply failed ret = %d\n", ret); |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * Runs a packet filtering program over a packet. |
| * |
| * @param program the program bytecode. |
| * @param program_len the length of {@code apf_program} in bytes. |
| * @param packet the packet bytes, starting from the 802.3 header and not |
| * including any CRC bytes at the end. |
| * @param packet_len the length of {@code packet} in bytes. |
| * @param filter_age the number of seconds since the filter was programmed. |
| * |
| * @return non-zero if packet should be passed to AP, zero if |
| * packet should be dropped. |
| */ |
| int |
| accept_packet(const t_u8 *program, t_u32 program_len, const t_u8 *packet, |
| t_u32 packet_len, t_u32 filter_age) |
| { |
| /* Program counter. */ |
| t_u32 pc = 0; |
| /* Memory slot values. */ |
| t_u32 memory[MEMORY_ITEMS] = { }; |
| /* Register values. */ |
| t_u32 registers[2] = { }; |
| /* Count of instructions remaining to execute. This is done to ensure an |
| * upper bound on execution time. It should never be hit and is only for |
| * safety. Initialize to the number of bytes in the program which is an |
| * upper bound on the number of instructions in the program. */ |
| t_u32 instructions_remaining = program_len; |
| |
| t_u8 bytecode; |
| t_u32 opcode; |
| t_u32 reg_num; |
| t_u32 len_field; |
| t_u32 imm_len; |
| t_u32 end_offs; |
| t_u32 val = 0; |
| t_u32 last_packet_offs; |
| t_u32 imm = 0; |
| int32_t signed_imm = 0; |
| t_u32 offs = 0; |
| t_u32 i; |
| t_u32 load_size; |
| |
| /* Is offset within program bounds? */ |
| #define IN_PROGRAM_BOUNDS(p) (ENFORCE_UNSIGNED(p) && (p) < program_len) |
| /* Is offset within packet bounds? */ |
| #define IN_PACKET_BOUNDS(p) (ENFORCE_UNSIGNED(p) && (p) < packet_len) |
| /* Verify an internal condition and accept packet if it fails. */ |
| #define ASSERT_RETURN(c) if (!(c)) return PASS_PACKET |
| /* Accept packet if not within program bounds */ |
| #define ASSERT_IN_PROGRAM_BOUNDS(p) ASSERT_RETURN(IN_PROGRAM_BOUNDS(p)) |
| /* Accept packet if not within packet bounds */ |
| #define ASSERT_IN_PACKET_BOUNDS(p) ASSERT_RETURN(IN_PACKET_BOUNDS(p)) |
| /* Accept packet if not within program or not ahead of program counter */ |
| #define ASSERT_FORWARD_IN_PROGRAM(p) ASSERT_RETURN(IN_PROGRAM_BOUNDS(p) && (p) >= pc) |
| |
| /* Fill in pre-filled memory slot values. */ |
| memory[MEMORY_OFFSET_PACKET_SIZE] = packet_len; |
| memory[MEMORY_OFFSET_FILTER_AGE] = filter_age; |
| ASSERT_IN_PACKET_BOUNDS(APF_FRAME_HEADER_SIZE); |
| |
| /* Only populate if IP version is IPv4. */ |
| if ((packet[APF_FRAME_HEADER_SIZE] & 0xf0) == 0x40) { |
| memory[MEMORY_OFFSET_IPV4_HEADER_SIZE] = |
| (packet[APF_FRAME_HEADER_SIZE] & 15) * 4; |
| } |
| |
| do { |
| if (pc == program_len) { |
| return PASS_PACKET; |
| } else if (pc == (program_len + 1)) { |
| return DROP_PACKET; |
| } |
| ASSERT_IN_PROGRAM_BOUNDS(pc); |
| bytecode = program[pc++]; |
| opcode = EXTRACT_OPCODE(bytecode); |
| reg_num = EXTRACT_REGISTER(bytecode); |
| |
| /* All instructions have immediate fields, so load them now. */ |
| len_field = EXTRACT_IMM_LENGTH(bytecode); |
| imm = 0; |
| signed_imm = 0; |
| |
| #define REG (registers[reg_num]) |
| #define OTHER_REG (registers[reg_num ^ 1]) |
| |
| if (len_field != 0) { |
| imm_len = 1 << (len_field - 1); |
| ASSERT_FORWARD_IN_PROGRAM(pc + imm_len - 1); |
| for (i = 0; i < imm_len; i++) |
| imm = (imm << 8) | program[pc++]; |
| /* Sign extend imm into signed_imm. */ |
| signed_imm = imm << ((4 - imm_len) * 8); |
| signed_imm >>= (4 - imm_len) * 8; |
| } |
| switch (opcode) { |
| case LDB_OPCODE: |
| case LDH_OPCODE: |
| case LDW_OPCODE: |
| case LDBX_OPCODE: |
| case LDHX_OPCODE: |
| case LDWX_OPCODE:{ |
| offs = imm; |
| if (opcode >= LDBX_OPCODE) { |
| /* Note: this can overflow and actually decrease offs. */ |
| offs += registers[1]; |
| } |
| ASSERT_IN_PACKET_BOUNDS(offs); |
| switch (opcode) { |
| case LDB_OPCODE: |
| case LDBX_OPCODE: |
| load_size = 1; |
| break; |
| case LDH_OPCODE: |
| case LDHX_OPCODE: |
| load_size = 2; |
| break; |
| case LDW_OPCODE: |
| case LDWX_OPCODE: |
| load_size = 4; |
| break; |
| /* Immediately enclosing switch statement guarantees |
| * opcode cannot be any other value. */ |
| } |
| end_offs = offs + (load_size - 1); |
| /* Catch overflow/wrap-around. */ |
| ASSERT_RETURN(end_offs >= offs); |
| ASSERT_IN_PACKET_BOUNDS(end_offs); |
| val = 0; |
| while (load_size--) |
| val = (val << 8) | packet[offs++]; |
| REG = val; |
| break; |
| } |
| case JMP_OPCODE: |
| /* This can jump backwards. Infinite looping prevented by instructions_remaining. */ |
| pc += imm; |
| break; |
| case JEQ_OPCODE: |
| case JNE_OPCODE: |
| case JGT_OPCODE: |
| case JLT_OPCODE: |
| case JSET_OPCODE: |
| case JNEBS_OPCODE:{ |
| /* Load second immediate field. */ |
| t_u32 cmp_imm = 0; |
| if (reg_num == 1) { |
| cmp_imm = registers[1]; |
| } else if (len_field != 0) { |
| t_u32 cmp_imm_len = |
| 1 << (len_field - 1); |
| ASSERT_FORWARD_IN_PROGRAM(pc + |
| cmp_imm_len - |
| 1); |
| for (i = 0; i < cmp_imm_len; i++) |
| cmp_imm = |
| (cmp_imm << 8) | |
| program[pc++]; |
| } |
| switch (opcode) { |
| case JEQ_OPCODE: |
| if (registers[0] == cmp_imm) |
| pc += imm; |
| break; |
| case JNE_OPCODE: |
| if (registers[0] != cmp_imm) |
| pc += imm; |
| break; |
| case JGT_OPCODE: |
| if (registers[0] > cmp_imm) |
| pc += imm; |
| break; |
| case JLT_OPCODE: |
| if (registers[0] < cmp_imm) |
| pc += imm; |
| break; |
| case JSET_OPCODE: |
| if (registers[0] & cmp_imm) |
| pc += imm; |
| break; |
| case JNEBS_OPCODE:{ |
| /* cmp_imm is size in bytes of data to compare. |
| * pc is offset of program bytes to compare. |
| * imm is jump target offset. |
| * REG is offset of packet bytes to compare. */ |
| ASSERT_FORWARD_IN_PROGRAM(pc + |
| cmp_imm |
| - 1); |
| ASSERT_IN_PACKET_BOUNDS(REG); |
| last_packet_offs = |
| REG + cmp_imm - 1; |
| ASSERT_RETURN(last_packet_offs |
| >= REG); |
| ASSERT_IN_PACKET_BOUNDS |
| (last_packet_offs); |
| if (memcmp |
| (program + pc, packet + REG, |
| cmp_imm)) |
| pc += imm; |
| /* skip past comparison bytes */ |
| pc += cmp_imm; |
| break; |
| } |
| } |
| break; |
| } |
| case ADD_OPCODE: |
| registers[0] += reg_num ? registers[1] : imm; |
| break; |
| case MUL_OPCODE: |
| registers[0] *= reg_num ? registers[1] : imm; |
| break; |
| case DIV_OPCODE:{ |
| const t_u32 div_operand = |
| reg_num ? registers[1] : imm; |
| ASSERT_RETURN(div_operand); |
| registers[0] /= div_operand; |
| break; |
| } |
| case AND_OPCODE: |
| registers[0] &= reg_num ? registers[1] : imm; |
| break; |
| case OR_OPCODE: |
| registers[0] |= reg_num ? registers[1] : imm; |
| break; |
| case SH_OPCODE:{ |
| const int32_t shift_val = |
| reg_num ? (int32_t) registers[1] : |
| signed_imm; |
| if (shift_val > 0) |
| registers[0] <<= shift_val; |
| else |
| registers[0] >>= -shift_val; |
| break; |
| } |
| case LI_OPCODE: |
| REG = signed_imm; |
| break; |
| case EXT_OPCODE: |
| if ( |
| /* If LDM_EXT_OPCODE is 0 and imm is compared with it, a compiler error will result, |
| * instead just enforce that imm is unsigned (so it's always greater or equal to 0). */ |
| #if LDM_EXT_OPCODE == 0 |
| ENFORCE_UNSIGNED(imm) && |
| #else |
| imm >= LDM_EXT_OPCODE && |
| #endif |
| imm < (LDM_EXT_OPCODE + MEMORY_ITEMS)) { |
| REG = memory[imm - LDM_EXT_OPCODE]; |
| } else if (imm >= STM_EXT_OPCODE && |
| imm < (STM_EXT_OPCODE + MEMORY_ITEMS)) { |
| memory[imm - STM_EXT_OPCODE] = REG; |
| } else |
| switch (imm) { |
| case NOT_EXT_OPCODE: |
| REG = ~REG; |
| break; |
| case NEG_EXT_OPCODE: |
| REG = -REG; |
| break; |
| case SWAP_EXT_OPCODE:{ |
| t_u32 tmp = REG; |
| REG = OTHER_REG; |
| OTHER_REG = tmp; |
| break; |
| } |
| case MOV_EXT_OPCODE: |
| REG = OTHER_REG; |
| break; |
| /* Unknown extended opcode */ |
| default: |
| /* Bail out */ |
| return PASS_PACKET; |
| } |
| break; |
| /* Unknown opcode */ |
| default: |
| /* Bail out */ |
| return PASS_PACKET; |
| } |
| } while (instructions_remaining--); |
| return PASS_PACKET; |
| } |
| |
| /** |
| * @brief filter packet |
| * |
| * @param priv A pointer to moal_private struct |
| * @param data packet data |
| * @param len packet len |
| * @param filter_age filter age |
| * @return non-zero if packet should be passed to AP, zero if |
| * packet should be dropped. |
| */ |
| int |
| woal_filter_packet(moal_private *priv, t_u8 *data, t_u32 len, t_u32 filter_age) |
| { |
| packet_filter *pkt_filter = NULL; |
| int ret = PASS_PACKET; |
| unsigned long flags; |
| |
| ENTER(); |
| pkt_filter = priv->packet_filter; |
| if (!unlikely(pkt_filter)) { |
| PRINTM(MINFO, "packet_filter not init\n"); |
| goto done; |
| } |
| |
| if (pkt_filter->state != PACKET_FILTER_STATE_START) |
| goto done; |
| |
| DBG_HEXDUMP(MCMD_D, "packet_filter_program", |
| pkt_filter->packet_filter_program, |
| pkt_filter->packet_filter_len); |
| DBG_HEXDUMP(MCMD_D, "packet_filter_data", data, len); |
| spin_lock_irqsave(&pkt_filter->lock, flags); |
| ret = accept_packet(pkt_filter->packet_filter_program, |
| pkt_filter->packet_filter_len, data, len, |
| filter_age); |
| spin_unlock_irqrestore(&pkt_filter->lock, flags); |
| |
| done: |
| PRINTM(MINFO, "packet filter ret %d\n", ret); |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief send vendor event to kernel |
| * |
| * @param priv A pointer to moal_private |
| * @param cmd nan_cmd |
| * |
| * @return 0: success 1: fail |
| */ |
| int |
| woal_nan_vendor_event(IN moal_private *priv, nan_cmd cmd) |
| { |
| struct wiphy *wiphy = NULL; |
| struct sk_buff *skb = NULL; |
| int ret = 0; |
| int event_id = 0; |
| |
| ENTER(); |
| |
| if (!priv || !priv->wdev || !priv->wdev->wiphy) { |
| PRINTM(MERROR, "priv is null \n"); |
| ret = -EINVAL; |
| goto done; |
| } |
| wiphy = priv->wdev->wiphy; |
| PRINTM(MEVENT, "woal_nan_worker\n"); |
| event_id = woal_get_event_id(event_nan_cb); |
| if (event_max == event_id) { |
| PRINTM(MERROR, "Not find this event %d \n", event_id); |
| ret = -EINVAL; |
| goto done; |
| } |
| |
| /**allocate skb*/ |
| #if CFG80211_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) |
| skb = cfg80211_vendor_event_alloc(wiphy, NULL, sizeof(nan_cmd), |
| event_id, GFP_ATOMIC); |
| #else |
| skb = cfg80211_vendor_event_alloc(wiphy, sizeof(nan_cmd), event_id, |
| GFP_ATOMIC); |
| #endif |
| |
| if (!skb) { |
| PRINTM(MERROR, "allocate memory fail for vendor event\n"); |
| ret = -ENOMEM; |
| goto done; |
| } |
| |
| nla_put(skb, ATTR_NAN_FAKE, sizeof(NanHeader_Ext), &cmd.nan_header_ext); |
| |
| if (cmd.indicate_enable) { |
| nla_put_u32(skb, ATTR_NAN_IND, cmd.indicate_type); |
| } |
| |
| PRINTM(MCMND, "woal_nan_vendor_event %d, %d, %d,%d\n", |
| cmd.nan_header_ext.nan_header.MsgId, |
| cmd.nan_header_ext.nan_header.transactionId, cmd.indicate_enable, |
| cmd.indicate_type); |
| |
| /**send event*/ |
| cfg80211_vendor_event(skb, GFP_ATOMIC); |
| |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief worker to send vendor event |
| * |
| * @param work A pointer to work_struct struct |
| * |
| * @return void |
| */ |
| void |
| woal_nan_worker(struct work_struct *work) |
| { |
| struct delayed_work *d_work = to_delayed_work(work); |
| nan_cb *nan = container_of(d_work, nan_cb, response_work); |
| moal_private *priv = nan->priv; |
| nan_cmd cmd; |
| int ret; |
| ENTER(); |
| |
| PRINTM(MCMND, "fifo len: %u\n", kfifo_len(&nan->cmd_fifo)); |
| |
| ret = kfifo_out_peek(&nan->cmd_fifo, &cmd, sizeof(nan_cmd)); |
| PRINTM(MCMND, "fifo ret: %u\n", ret); |
| |
| if (ret) { |
| kfifo_out(&nan->cmd_fifo, &cmd, sizeof(nan_cmd)); |
| } else { |
| PRINTM(MERROR, "not enough element:\n"); |
| goto done; |
| } |
| |
| woal_nan_vendor_event(priv, cmd); |
| |
| ret = kfifo_out_peek(&nan->cmd_fifo, &cmd, sizeof(nan_cmd)); |
| |
| if (ret) |
| schedule_delayed_work(&nan->response_work, 0); |
| |
| done: |
| LEAVE(); |
| return; |
| } |
| |
| /** |
| * @brief init nan in moal_private |
| * |
| * @param priv A pointer to moal_private struct |
| * |
| * @return 0: success -1: fail |
| */ |
| static int |
| woal_init_nan(moal_private *priv) |
| { |
| int ret = 0; |
| nan_cb *nan; |
| moal_handle *phandle = priv->phandle; |
| ENTER(); |
| |
| nan = vmalloc(sizeof(nan_cb)); |
| if (!unlikely(nan)) { |
| PRINTM(MERROR, "WiFi Logger: packet_filter alloc failed\n"); |
| ret = -ENOMEM; |
| goto done; |
| } |
| |
| if (!unlikely(phandle)) { |
| PRINTM(MERROR, "WiFi Logger: phandle is null\n"); |
| ret = -ENOMEM; |
| goto done; |
| } |
| |
| memset(nan, 0, sizeof(nan_cb)); |
| INIT_DELAYED_WORK(&nan->response_work, woal_nan_worker); |
| nan->priv = priv; |
| |
| ret = kfifo_alloc(&nan->cmd_fifo, CMD_FIFO_SIZE * sizeof(nan_cmd), |
| GFP_KERNEL); |
| if (ret) { |
| PRINTM(MERROR, "error kfifo_alloc\n"); |
| goto done; |
| } |
| |
| priv->nan_cb = nan; |
| |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief deinit nan in moal_private |
| * |
| * @param priv A pointer to moal_private struct |
| * |
| * @return 0: success -1: fail |
| */ |
| static int |
| woal_deinit_nan(moal_private *priv) |
| { |
| int ret = 0; |
| nan_cb *nan = NULL; |
| |
| ENTER(); |
| |
| nan = priv->nan_cb; |
| |
| if (!unlikely(nan)) { |
| goto done; |
| } |
| |
| cancel_delayed_work_sync(&nan->response_work); |
| |
| kfifo_free(&nan->cmd_fifo); |
| |
| vfree(nan); |
| priv->nan_cb = NULL; |
| |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to enable nan |
| * |
| * @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 |
| nan_handler(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; |
| t_u32 reply_len = 0; |
| int ret = 0; |
| int rem, type; |
| |
| const struct nlattr *iter; |
| nan_cb *nan = priv->nan_cb; |
| nan_cmd cmd; |
| t_u32 indicate_number_total = 0; |
| t_u32 indicate_array[MAX_INDICATE_ARRAY_SIZE] = { 0 }; |
| int i = 0; |
| |
| ENTER(); |
| |
| memset(&cmd, 0, sizeof(nan_cmd)); |
| /** 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; |
| } |
| |
| if (!unlikely(nan)) { |
| PRINTM(MERROR, "WiFi hal: nan not init\n"); |
| ret = -EINVAL; |
| goto done; |
| } |
| |
| nla_for_each_attr(iter, data, len, rem) { |
| type = nla_type(iter); |
| switch (type) { |
| case ATTR_NAN_FAKE: |
| memcpy(&cmd, nla_data(iter), sizeof(NanHeader)); |
| PRINTM(MCMND, "ATTR_NAN_FAKE %d, %d\n", |
| cmd.nan_header_ext.nan_header.MsgId, |
| cmd.nan_header_ext.nan_header.transactionId); |
| break; |
| case ATTR_NAN_IND: |
| indicate_array[indicate_number_total] = |
| nla_get_u32(iter); |
| indicate_number_total++; |
| PRINTM(MCMND, "ATTR_NAN_IND %d, %d\n", |
| indicate_array[indicate_number_total - 1], |
| indicate_number_total); |
| break; |
| default: |
| PRINTM(MERROR, "Unknown type: %d\n", type); |
| ret = -EINVAL; |
| goto done; |
| } |
| } |
| |
| cmd.indicate_enable = 0; |
| kfifo_in(&nan->cmd_fifo, &cmd, sizeof(nan_cmd)); |
| PRINTM(MCMND, "fifo len: %u\n", kfifo_len(&nan->cmd_fifo)); |
| |
| for (i = 0; i < MAX_INDICATE_ARRAY_SIZE && i < indicate_number_total; |
| i++) { |
| cmd.indicate_enable = 1; |
| cmd.indicate_type = indicate_array[i]; |
| kfifo_in(&nan->cmd_fifo, &cmd, sizeof(nan_cmd)); |
| PRINTM(MCMND, "fifo len: %u\n", kfifo_len(&nan->cmd_fifo)); |
| DBG_HEXDUMP(MCMD_D, "nan_cmd", (t_u8 *)&cmd, sizeof(nan_cmd)); |
| } |
| |
| ret = cfg80211_vendor_cmd_reply(skb); |
| |
| schedule_delayed_work(&nan->response_work, 0); |
| |
| if (ret) |
| PRINTM(MERROR, "Vendor command reply failed ret = %d\n", ret); |
| |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to enable nan |
| * |
| * @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_nan_enable_req(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, |
| int len) |
| { |
| int ret = 0; |
| ENTER(); |
| |
| ret = nan_handler(wiphy, wdev, data, len); |
| |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to disable nan |
| * |
| * @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_nan_disable_req(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| int ret = 0; |
| ENTER(); |
| |
| ret = nan_handler(wiphy, wdev, data, len); |
| |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to nan publish req |
| * |
| * @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_nan_publish_req(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| int ret = 0; |
| ENTER(); |
| |
| ret = nan_handler(wiphy, wdev, data, len); |
| |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to cancel nan publish |
| * |
| * @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_nan_publish_cancel(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| int ret = 0; |
| ENTER(); |
| |
| ret = nan_handler(wiphy, wdev, data, len); |
| |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to nan subscribe req |
| * |
| * @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_nan_subscribe_req(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| int ret = 0; |
| ENTER(); |
| |
| ret = nan_handler(wiphy, wdev, data, len); |
| |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to cancel nan subscribe |
| * |
| * @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_nan_subscribe_cancel(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| int ret = 0; |
| ENTER(); |
| |
| ret = nan_handler(wiphy, wdev, data, len); |
| |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to trasmit followup |
| * |
| * @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_nan_trasmit_followup(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| int ret = 0; |
| ENTER(); |
| |
| ret = nan_handler(wiphy, wdev, data, len); |
| |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to req nan stats |
| * |
| * @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_nan_stats_req(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, |
| int len) |
| { |
| int ret = 0; |
| ENTER(); |
| |
| ret = nan_handler(wiphy, wdev, data, len); |
| |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to config nan |
| * |
| * @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_nan_config_req(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, |
| int len) |
| { |
| int ret = 0; |
| ENTER(); |
| |
| ret = nan_handler(wiphy, wdev, data, len); |
| |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to nan tca req |
| * |
| * @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_nan_tca_req(struct wiphy *wiphy, struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| int ret = 0; |
| ENTER(); |
| |
| ret = nan_handler(wiphy, wdev, data, len); |
| |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to beacon sdf payload |
| * |
| * @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_nan_beacon_sdf_payload(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| int ret = 0; |
| ENTER(); |
| |
| ret = nan_handler(wiphy, wdev, data, len); |
| |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to get nan 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_nan_get_version(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| int ret = 0; |
| ENTER(); |
| |
| ret = nan_handler(wiphy, wdev, data, len); |
| |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to get nan capability |
| * |
| * @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_nan_get_capability(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| int ret = 0; |
| ENTER(); |
| |
| ret = nan_handler(wiphy, wdev, data, len); |
| |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to data if create |
| * |
| * @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_nan_data_if_create(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| int ret = 0; |
| ENTER(); |
| |
| ret = nan_handler(wiphy, wdev, data, len); |
| |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to data if delete |
| * |
| * @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_nan_data_if_delete(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| int ret = 0; |
| ENTER(); |
| |
| ret = nan_handler(wiphy, wdev, data, len); |
| |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to data req initor |
| * |
| * @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_nan_data_req_initor(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| int ret = 0; |
| ENTER(); |
| |
| ret = nan_handler(wiphy, wdev, data, len); |
| |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to data indication resp |
| * |
| * @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_nan_data_indi_resp(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| int ret = 0; |
| ENTER(); |
| |
| ret = nan_handler(wiphy, wdev, data, len); |
| |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to date end |
| * |
| * @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_nan_data_end(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, |
| int len) |
| { |
| int ret = 0; |
| ENTER(); |
| |
| ret = nan_handler(wiphy, wdev, data, len); |
| |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief init wifi hal |
| * |
| * @param priv A pointer to moal_private struct |
| * |
| * @return 0: success 1: fail |
| */ |
| int |
| woal_init_wifi_hal(moal_private *priv) |
| { |
| ENTER(); |
| woal_init_ring_buffer(priv); |
| priv->pkt_fate_monitor_enable = MFALSE; |
| woal_init_packet_filter(priv); |
| woal_init_nan(priv); |
| LEAVE(); |
| return 0; |
| } |
| |
| /** |
| * @brief deinit wifi hal |
| * |
| * @param priv A pointer to moal_private struct |
| * |
| * @return 0: success 1: fail |
| */ |
| int |
| woal_deinit_wifi_hal(moal_private *priv) |
| { |
| ENTER(); |
| woal_deinit_ring_buffer(priv); |
| priv->pkt_fate_monitor_enable = MFALSE; |
| woal_deinit_packet_filter(priv); |
| woal_deinit_nan(priv); |
| LEAVE(); |
| return 0; |
| } |
| |
| /** |
| * @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; |
| } |
| |
| /** |
| * @brief vendor command to get link layer statistic |
| * |
| * @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_link_statistic_get(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_get_info *info = NULL; |
| mlan_status status = MLAN_STATUS_SUCCESS; |
| wifi_radio_stat *radio_stat = NULL; |
| wifi_iface_stat *iface_stat = NULL; |
| t_u32 num_radio = 0, iface_stat_len = 0, radio_stat_len = 0; |
| int err = -1, length = 0, i; |
| char *ioctl_link_stats_buf = NULL; |
| mlan_ds_get_stats stats; |
| |
| /* Allocate an IOCTL request buffer */ |
| req = woal_alloc_mlan_ioctl_req(sizeof(t_u32) + BUF_MAXLEN); |
| if (req == NULL) { |
| PRINTM(MERROR, "Could not allocate mlan ioctl request!\n"); |
| return -ENOMEM; |
| } |
| |
| /* Fill request buffer */ |
| info = (mlan_ds_get_info *)req->pbuf; |
| info->sub_command = MLAN_OID_LINK_STATS; |
| req->req_id = MLAN_IOCTL_GET_INFO; |
| req->action = MLAN_ACT_GET; |
| |
| /* Send IOCTL request to MLAN */ |
| status = woal_request_ioctl(priv, req, MOAL_IOCTL_WAIT); |
| if (status != MLAN_STATUS_SUCCESS) { |
| PRINTM(MERROR, "get link layer statistic fail\n"); |
| goto done; |
| } |
| |
| /* Get Log from the firmware */ |
| memset(&stats, 0, sizeof(mlan_ds_get_stats)); |
| if (MLAN_STATUS_SUCCESS != |
| woal_get_stats_info(priv, MOAL_IOCTL_WAIT, &stats)) { |
| PRINTM(MERROR, "Error getting stats information\n"); |
| goto done; |
| } |
| |
| ioctl_link_stats_buf = info->param.link_statistic; |
| num_radio = *((t_u32 *)info->param.link_statistic); |
| |
| radio_stat = |
| (wifi_radio_stat *) (info->param.link_statistic + |
| sizeof(num_radio)); |
| radio_stat_len = num_radio * sizeof(wifi_radio_stat); |
| |
| iface_stat = |
| (wifi_iface_stat *) (info->param.link_statistic + |
| sizeof(num_radio) + radio_stat_len); |
| iface_stat_len = sizeof(wifi_iface_stat); |
| /* Fill some fileds */ |
| iface_stat->beacon_rx = stats.bcn_rcv_cnt; |
| |
| /* could get peer info with seperate cmd */ |
| for (i = 0; i < iface_stat->num_peers; i++) { |
| /* no need copy, just increase iface_stat length */ |
| iface_stat_len += |
| sizeof(wifi_peer_info) + |
| sizeof(wifi_rate_stat) * |
| iface_stat->peer_info[i].num_rate; |
| } |
| |
| /* Here the length doesn't contain addition 2 attribute header length */ |
| length = NLA_HDRLEN * 2 + sizeof(num_radio) + radio_stat_len + |
| iface_stat_len; |
| |
| /* 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; |
| } |
| |
| if (nla_put_u32(skb, ATTR_LL_STATS_NUM_RADIO, num_radio) || |
| nla_put(skb, ATTR_LL_STATS_RADIO, radio_stat_len, radio_stat) || |
| nla_put(skb, ATTR_LL_STATS_IFACE, iface_stat_len, iface_stat)) { |
| PRINTM(MERROR, "nla_put failed!\n"); |
| kfree(skb); |
| goto done; |
| } |
| PRINTM(MCMD_D, "num_radio=%d\n", num_radio); |
| DBG_HEXDUMP(MCMD_D, "radio_stat", (t_u8 *)radio_stat, radio_stat_len); |
| DBG_HEXDUMP(MCMD_D, "iface_stat", (t_u8 *)iface_stat, iface_stat_len); |
| |
| 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; |
| } |
| |
| /** |
| * @brief API to trigger the link layer statistics collection. |
| * Unless his API is invoked - link layer statistics will not be collected. |
| * Radio statistics (once started) do not stop or get reset unless |
| * wifi_clear_link_stats is invoked, Interface statistics (once started) |
| * reset and start afresh after each connection. |
| * |
| * @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_link_statistic_set(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| moal_private *priv = (moal_private *)woal_get_netdev_priv(wdev->netdev); |
| struct nlattr *tb[ATTR_LL_STATS_MAX + 1]; |
| wifi_link_layer_params ll_params; |
| mlan_ioctl_req *req = NULL; |
| mlan_ds_get_info *info = NULL; |
| mlan_status status = MLAN_STATUS_SUCCESS; |
| int err = 0; |
| |
| err = nla_parse(tb, ATTR_LL_STATS_MAX, data, len, NULL |
| #if CFG80211_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) |
| , NULL |
| #endif |
| ); |
| if (err) |
| return err; |
| |
| if (!tb[ATTR_LL_STATS_MPDU_SIZE_THRESHOLD] || |
| !tb[ATTR_LL_STATS_AGGRSSIVE_STATS_GATHERING]) |
| return -EINVAL; |
| |
| ll_params.mpdu_size_threshold = |
| nla_get_u32(tb[ATTR_LL_STATS_MPDU_SIZE_THRESHOLD]); |
| ll_params.aggressive_statistics_gathering = |
| nla_get_u32(tb[ATTR_LL_STATS_AGGRSSIVE_STATS_GATHERING]); |
| |
| PRINTM(MEVENT, |
| "link layer params mpdu_size_threshold = 0x%x, aggressive_statistics_gathering = 0x%x\n", |
| ll_params.mpdu_size_threshold, |
| ll_params.aggressive_statistics_gathering); |
| |
| /* Allocate an IOCTL request buffer */ |
| req = woal_alloc_mlan_ioctl_req(sizeof(t_u32) + |
| sizeof(mlan_ds_get_info)); |
| if (req == NULL) { |
| PRINTM(MERROR, "Could not allocate mlan ioctl request!\n"); |
| return -ENOMEM; |
| } |
| |
| /* Fill request buffer */ |
| info = (mlan_ds_get_info *)req->pbuf; |
| info->sub_command = MLAN_OID_LINK_STATS; |
| req->req_id = MLAN_IOCTL_GET_INFO; |
| req->action = MLAN_ACT_SET; |
| |
| /* Configure parameter to firmware */ |
| memcpy(info->param.link_statistic, &ll_params, sizeof(ll_params)); |
| |
| /* Send IOCTL request to MLAN */ |
| status = woal_request_ioctl(priv, req, MOAL_IOCTL_WAIT); |
| if (status == MLAN_STATUS_SUCCESS) { |
| PRINTM(MMSG, "enable link layer statistic successfully\n"); |
| } |
| |
| if (status != MLAN_STATUS_PENDING) |
| kfree(req); |
| return 0; |
| } |
| |
| /** |
| * @brief clear function should download command to fimrware, |
| * so that firmware could cleanup per peer statistic number |
| * |
| * @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_link_statistic_clr(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| moal_private *priv = (moal_private *)woal_get_netdev_priv(wdev->netdev); |
| struct nlattr *tb[ATTR_LL_STATS_MAX + 1]; |
| mlan_ioctl_req *req = NULL; |
| mlan_ds_get_info *info = NULL; |
| mlan_status status = MLAN_STATUS_SUCCESS; |
| u32 stats_clear_req_mask = 0x0, stats_clear_rsp_mask = 0x0; |
| u8 stop_req = 0x0, stop_rsp = 0x0; |
| struct sk_buff *skb = NULL; |
| int err = 0, length = 0; |
| |
| err = nla_parse(tb, ATTR_LL_STATS_MAX, data, len, NULL |
| #if CFG80211_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) |
| , NULL |
| #endif |
| ); |
| if (err) |
| return err; |
| |
| if (!tb[ATTR_LL_STATS_CLEAR_REQ_MASK]) |
| return -EINVAL; |
| else |
| stats_clear_req_mask = |
| nla_get_u32(tb[ATTR_LL_STATS_CLEAR_REQ_MASK]); |
| |
| if (!tb[ATTR_LL_STATS_STOP_REQ]) |
| return -EINVAL; |
| else |
| stop_req = nla_get_u8(tb[ATTR_LL_STATS_STOP_REQ]); |
| |
| PRINTM(MEVENT, |
| "link layer clear stats_clear_req_mask = 0x%x, stop_req = 0x%x\n", |
| stats_clear_req_mask, stop_req); |
| |
| /* Allocate an IOCTL request buffer */ |
| req = woal_alloc_mlan_ioctl_req(sizeof(t_u32) + |
| sizeof(mlan_ds_get_info)); |
| if (req == NULL) { |
| PRINTM(MERROR, "Could not allocate mlan ioctl request!\n"); |
| return -ENOMEM; |
| } |
| |
| /* Fill request buffer */ |
| info = (mlan_ds_get_info *)req->pbuf; |
| info->sub_command = MLAN_OID_LINK_STATS; |
| req->req_id = MLAN_IOCTL_GET_INFO; |
| req->action = MLAN_ACT_CLEAR; |
| |
| /* Send IOCTL request to MLAN */ |
| status = woal_request_ioctl(priv, req, MOAL_IOCTL_WAIT); |
| if (status == MLAN_STATUS_SUCCESS) { |
| PRINTM(MMSG, "enable link layer statistic successfully\n"); |
| } |
| |
| length = NLA_HDRLEN + sizeof(stats_clear_rsp_mask) + sizeof(stop_rsp); |
| /* Alloc the SKB for vendor_event */ |
| skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, length); |
| if (unlikely(!skb)) { |
| PRINTM(MERROR, "skb alloc failed\n"); |
| err = -EINVAL; |
| goto exit; |
| } |
| |
| /* clear api to reset statistics, stats_clear_rsp_mask identifies what stats have been cleared |
| * stop_req = 1 will imply whether to stop the statistics collection. |
| * stop_rsp = 1 will imply that stop_req was honored and statistics collection was stopped. |
| */ |
| stats_clear_rsp_mask = WIFI_STATS_RADIO | WIFI_STATS_IFACE; |
| stop_rsp = 1; |
| if (nla_put_u32(skb, ATTR_LL_STATS_CLEAR_RSP_MASK, stats_clear_rsp_mask) |
| || nla_put_u8(skb, ATTR_LL_STATS_STOP_RSP, stop_rsp)) { |
| PRINTM(MERROR, "nla_put failed!\n"); |
| kfree(skb); |
| err = -EINVAL; |
| goto exit; |
| } |
| |
| err = cfg80211_vendor_cmd_reply(skb); |
| if (unlikely(err)) { |
| PRINTM(MERROR, "Vendor Command reply failed ret:%d \n", err); |
| goto exit;; |
| } |
| |
| exit: |
| 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) { |
| 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, NULL, 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 get fw roaming capability |
| * |
| * @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_get_roaming_capability(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| int ret = MLAN_STATUS_SUCCESS; |
| wifi_roaming_capabilities capa; |
| struct sk_buff *skb = NULL; |
| int err = 0; |
| |
| ENTER(); |
| |
| if (!wdev || !wdev->netdev) { |
| LEAVE(); |
| return -EFAULT; |
| } |
| |
| capa.max_blacklist_size = MAX_AP_LIST; |
| capa.max_whitelist_size = MAX_SSID_NUM; |
| |
| /* Alloc the SKB for vendor_event */ |
| skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, |
| sizeof |
| (wifi_roaming_capabilities) + |
| 50); |
| if (unlikely(!skb)) { |
| PRINTM(MERROR, "skb alloc failed\n"); |
| goto done; |
| } |
| |
| /* Push the data to the skb */ |
| nla_put(skb, MRVL_WLAN_VENDOR_ATTR_FW_ROAMING_CAPA, |
| sizeof(wifi_roaming_capabilities), (t_u8 *)&capa); |
| |
| err = cfg80211_vendor_cmd_reply(skb); |
| if (unlikely(err)) { |
| PRINTM(MERROR, "Vendor Command reply failed ret:%d \n", err); |
| } |
| |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to enable/disable fw roaming |
| * |
| * @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_fw_roaming_enable(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| moal_private *priv; |
| struct net_device *dev; |
| int ret = MLAN_STATUS_SUCCESS; |
| struct sk_buff *skb = NULL; |
| const struct nlattr *iter; |
| int type, rem, err; |
| t_u32 fw_roaming_enable = 0; |
| #ifdef STA_CFG80211 |
| #if CFG80211_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) |
| t_u8 enable = 0; |
| #endif |
| #endif |
| |
| ENTER(); |
| |
| if (!wdev || !wdev->netdev) { |
| LEAVE(); |
| return -EFAULT; |
| } |
| |
| dev = wdev->netdev; |
| priv = (moal_private *)woal_get_netdev_priv(dev); |
| if (!priv || !priv->phandle) { |
| LEAVE(); |
| return -EFAULT; |
| } |
| |
| nla_for_each_attr(iter, data, len, rem) { |
| type = nla_type(iter); |
| switch (type) { |
| case MRVL_WLAN_VENDOR_ATTR_FW_ROAMING_CONTROL: |
| fw_roaming_enable = nla_get_u32(iter); |
| break; |
| default: |
| PRINTM(MERROR, "Unknown type: %d\n", type); |
| ret = -EINVAL; |
| goto done; |
| } |
| } |
| |
| PRINTM(MMSG, "FW roaming set enable=%d from wifi hal.\n", |
| fw_roaming_enable); |
| ret = woal_enable_fw_roaming(priv, fw_roaming_enable); |
| /* Alloc the SKB for vendor_event */ |
| skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(t_u32) + 50); |
| if (unlikely(!skb)) { |
| PRINTM(MERROR, "skb alloc failed\n"); |
| goto done; |
| } |
| |
| nla_put(skb, MRVL_WLAN_VENDOR_ATTR_FW_ROAMING_CONTROL, sizeof(t_u32), |
| &fw_roaming_enable); |
| err = cfg80211_vendor_cmd_reply(skb); |
| if (unlikely(err)) { |
| PRINTM(MERROR, "Vendor Command reply failed ret:%d \n", err); |
| } |
| #ifdef STA_CFG80211 |
| #if CFG80211_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) |
| if (!fw_roaming_enable) { |
| woal_cfg80211_vendor_event(priv, event_set_key_mgmt_offload, |
| &enable, sizeof(enable)); |
| } |
| #endif |
| #endif |
| |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to config blacklist and whitelist for fw roaming |
| * |
| * @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_fw_roaming_config(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| moal_private *priv; |
| struct net_device *dev; |
| int ret = MLAN_STATUS_SUCCESS; |
| const struct nlattr *iter; |
| int type, rem; |
| woal_roam_offload_cfg *roam_offload_cfg = NULL; |
| wifi_bssid_params blacklist; |
| wifi_ssid_params whitelist; |
| |
| ENTER(); |
| |
| if (!wdev || !wdev->netdev) { |
| LEAVE(); |
| return -EFAULT; |
| } |
| |
| dev = wdev->netdev; |
| priv = (moal_private *)woal_get_netdev_priv(dev); |
| if (!priv || !priv->phandle) { |
| LEAVE(); |
| return -EFAULT; |
| } |
| |
| memset((char *)&blacklist, 0, sizeof(wifi_bssid_params)); |
| memset((char *)&whitelist, 0, sizeof(wifi_ssid_params)); |
| nla_for_each_attr(iter, data, len, rem) { |
| type = nla_type(iter); |
| switch (type) { |
| case MRVL_WLAN_VENDOR_ATTR_FW_ROAMING_CONFIG_BSSID: |
| memcpy((t_u8 *)&blacklist, nla_data(iter), |
| sizeof(wifi_bssid_params)); |
| break; |
| case MRVL_WLAN_VENDOR_ATTR_FW_ROAMING_CONFIG_SSID: |
| memcpy((t_u8 *)&whitelist, nla_data(iter), |
| sizeof(wifi_ssid_params)); |
| break; |
| default: |
| PRINTM(MERROR, "Unknown type: %d\n", type); |
| ret = -EINVAL; |
| goto done; |
| } |
| } |
| |
| if (roamoffload_in_hs) { |
| /*save blacklist and whitelist in driver */ |
| priv->phandle->fw_roam_params.black_list.ap_num = |
| blacklist.num_bssid; |
| memcpy((t_u8 *)priv->phandle->fw_roam_params.black_list.ap_mac, |
| (t_u8 *)blacklist.mac_addr, |
| sizeof(wifi_bssid_params) - sizeof(blacklist.num_bssid)); |
| priv->phandle->fw_roam_params.ssid_list.ssid_num = |
| whitelist.num_ssid; |
| memcpy((t_u8 *)priv->phandle->fw_roam_params.ssid_list.ssids, |
| (t_u8 *)whitelist.whitelist_ssid, |
| sizeof(wifi_ssid_params) - sizeof(whitelist.num_ssid)); |
| } else { |
| roam_offload_cfg = |
| (woal_roam_offload_cfg *) |
| kmalloc(sizeof(woal_roam_offload_cfg), GFP_KERNEL); |
| if (!roam_offload_cfg) { |
| PRINTM(MERROR, "kmalloc failed!\n"); |
| ret = -ENOMEM; |
| goto done; |
| } |
| /*download parameters directly to fw */ |
| memset((char *)roam_offload_cfg, 0, |
| sizeof(woal_roam_offload_cfg)); |
| roam_offload_cfg->black_list.ap_num = blacklist.num_bssid; |
| memcpy((t_u8 *)&roam_offload_cfg->black_list.ap_mac, |
| (t_u8 *)blacklist.mac_addr, |
| sizeof(wifi_bssid_params) - sizeof(blacklist.num_bssid)); |
| roam_offload_cfg->ssid_list.ssid_num = whitelist.num_ssid; |
| memcpy((t_u8 *)&roam_offload_cfg->ssid_list.ssids, |
| (t_u8 *)whitelist.whitelist_ssid, |
| sizeof(wifi_ssid_params) - sizeof(whitelist.num_ssid)); |
| woal_config_fw_roaming(priv, ROAM_OFFLOAD_PARAM_CFG, |
| roam_offload_cfg); |
| } |
| done: |
| 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; |
| } |
| |
| /** |
| * @brief vendor command to get rtt capability |
| * |
| * @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_rtt_get_capa(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); |
| moal_handle *handle = priv->phandle; |
| struct sk_buff *skb = NULL; |
| int err = 0; |
| |
| ENTER(); |
| PRINTM(MCMND, "CfgVendor: cfg80211_subcmd_rtt_get_capa\n"); |
| |
| DBG_HEXDUMP(MCMD_D, "input data", (t_u8 *)data, len); |
| |
| /* Alloc the SKB for vendor_event */ |
| skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, |
| nla_total_size(sizeof |
| (handle-> |
| rtt_capa)) + |
| VENDOR_REPLY_OVERHEAD); |
| if (unlikely(!skb)) { |
| PRINTM(MERROR, "skb alloc failed in %s\n", __FUNCTION__); |
| goto done; |
| } |
| |
| /* Put the attribute to the skb */ |
| nla_put(skb, ATTR_RTT_CAPA, sizeof(handle->rtt_capa), |
| &(handle->rtt_capa)); |
| |
| PRINTM(MCMND, "NL80211_CMD_VENDOR=0x%x\n", NL80211_CMD_VENDOR); |
| PRINTM(MCMND, "NL80211_ATTR_WIPHY=0x%x\n", NL80211_ATTR_WIPHY); |
| PRINTM(MCMND, "NL80211_ATTR_VENDOR_ID=0x%x\n", NL80211_ATTR_VENDOR_ID); |
| PRINTM(MCMND, "NL80211_ATTR_VENDOR_SUBCMD=0x%x\n", |
| NL80211_ATTR_VENDOR_SUBCMD); |
| PRINTM(MCMND, "NL80211_ATTR_VENDOR_DATA=0x%x\n", |
| NL80211_ATTR_VENDOR_DATA); |
| PRINTM(MCMND, "NL80211_ATTR_VENDOR_EVENTS=0x%x\n", |
| NL80211_ATTR_VENDOR_EVENTS); |
| |
| DBG_HEXDUMP(MCMD_D, "output data skb->head", (t_u8 *)skb->head, 50); |
| DBG_HEXDUMP(MCMD_D, "output data skb->data", (t_u8 *)skb->data, 50); |
| err = cfg80211_vendor_cmd_reply(skb); |
| if (unlikely(err)) |
| PRINTM(MERROR, "Vendor Command reply failed err:%d \n", err); |
| |
| done: |
| LEAVE(); |
| return err; |
| } |
| |
| static void |
| woal_dump_rtt_params(wifi_rtt_config_params_t * rtt_params) |
| { |
| int i = 0; |
| |
| PRINTM(MMSG, "===== Start DUMP RTT Params =====\n"); |
| PRINTM(MMSG, "rtt_config_num=%d\n\n", rtt_params->rtt_config_num); |
| |
| for (i = 0; i < rtt_params->rtt_config_num; i++) { |
| PRINTM(MMSG, "----------[%d]----------\n", i); |
| PRINTM(MMSG, "rtt_config[%d].addr=" MACSTR "\n", i, |
| MAC2STR(rtt_params->rtt_config[i].addr)); |
| PRINTM(MMSG, "rtt_config[%d].type=%d\n", i, |
| rtt_params->rtt_config[i].type); |
| PRINTM(MMSG, "rtt_config[%d].peer=%d\n", i, |
| rtt_params->rtt_config[i].peer); |
| PRINTM(MMSG, "rtt_config[%d].channel=[%d %d %d %d]\n", i, |
| rtt_params->rtt_config[i].channel.width, |
| rtt_params->rtt_config[i].channel.center_freq, |
| rtt_params->rtt_config[i].channel.center_freq0, |
| rtt_params->rtt_config[i].channel.center_freq1); |
| PRINTM(MMSG, "rtt_config[%d].burst_period=%d\n", i, |
| rtt_params->rtt_config[i].burst_period); |
| PRINTM(MMSG, "rtt_config[%d].num_burst=%d\n", i, |
| rtt_params->rtt_config[i].num_burst); |
| PRINTM(MMSG, "rtt_config[%d].num_frames_per_burst=%d\n", i, |
| rtt_params->rtt_config[i].num_frames_per_burst); |
| PRINTM(MMSG, "rtt_config[%d].num_retries_per_rtt_frame=%d\n", i, |
| rtt_params->rtt_config[i].num_retries_per_rtt_frame); |
| PRINTM(MMSG, "rtt_config[%d].num_retries_per_ftmr=%d\n", i, |
| rtt_params->rtt_config[i].num_retries_per_ftmr); |
| PRINTM(MMSG, "rtt_config[%d].LCI_request=%d\n", i, |
| rtt_params->rtt_config[i].LCI_request); |
| PRINTM(MMSG, "rtt_config[%d].LCR_request=%d\n", i, |
| rtt_params->rtt_config[i].LCR_request); |
| PRINTM(MMSG, "rtt_config[%d].burst_duration=%d\n", i, |
| rtt_params->rtt_config[i].burst_duration); |
| PRINTM(MMSG, "rtt_config[%d].preamble=%d\n", i, |
| rtt_params->rtt_config[i].preamble); |
| PRINTM(MMSG, "rtt_config[%d].bw=%d\n", i, |
| rtt_params->rtt_config[i].bw); |
| PRINTM(MMSG, "\n"); |
| } |
| } |
| |
| /** |
| * @brief vendor command to request rtt range |
| * |
| * @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_rtt_range_request(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); |
| moal_handle *handle = priv->phandle; |
| struct nlattr *tb[ATTR_RTT_MAX]; |
| t_u8 zero_mac[MLAN_MAC_ADDR_LENGTH] = { 0 }; |
| t_u8 rtt_config_num = 0; |
| wifi_rtt_config *rtt_config = NULL; |
| t_u8 i = 0, j = 0; |
| wifi_rtt_config_params_t rtt_params; |
| mlan_status ret = MLAN_STATUS_SUCCESS; |
| int err = 0; |
| |
| ENTER(); |
| PRINTM(MCMND, "Enter woal_cfg80211_subcmd_rtt_range_request()\n"); |
| |
| err = nla_parse(tb, ATTR_RTT_MAX, data, len, NULL |
| #if CFG80211_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) |
| , NULL |
| #endif |
| ); |
| if (err) { |
| err = -EFAULT; |
| PRINTM(MERROR, "%s: nla_parse fail\n", __FUNCTION__); |
| goto done; |
| } |
| |
| if (!tb[ATTR_RTT_TARGET_NUM] || !tb[ATTR_RTT_TARGET_CONFIG]) { |
| PRINTM(MERROR, |
| "%s: null attr: tb[ATTR_RTT_TARGET_NUM]=%p tb[ATTR_RTT_TARGET_CONFIG]=%p\n", |
| __FUNCTION__, tb[ATTR_RTT_TARGET_NUM], |
| tb[ATTR_RTT_TARGET_CONFIG]); |
| err = -EINVAL; |
| goto done; |
| } |
| |
| rtt_config_num = nla_get_u8(tb[ATTR_RTT_TARGET_NUM]); |
| |
| if ((rtt_config_num == 0) || |
| ((handle->rtt_params.rtt_config_num + rtt_config_num) > |
| MAX_RTT_CONFIG_NUM)) { |
| PRINTM(MERROR, "%s: invalid num=%d num in handle=%d MAX=%d\n", |
| __FUNCTION__, rtt_config_num, |
| handle->rtt_params.rtt_config_num, MAX_RTT_CONFIG_NUM); |
| err = -EINVAL; |
| goto done; |
| } |
| if (nla_len(tb[ATTR_RTT_TARGET_CONFIG]) != |
| sizeof(rtt_params.rtt_config[0]) * rtt_config_num) { |
| PRINTM(MERROR, "%s: invalid %d(total) != %d(num) * %lu(each)\n", |
| __FUNCTION__, nla_len(tb[ATTR_RTT_TARGET_CONFIG]), |
| rtt_config_num, sizeof(rtt_params.rtt_config[0])); |
| err = -EINVAL; |
| goto done; |
| } |
| |
| rtt_config = (wifi_rtt_config *) nla_data(tb[ATTR_RTT_TARGET_CONFIG]); |
| memset(&rtt_params, 0, sizeof(rtt_params)); |
| /** Strip the zero mac config */ |
| for (i = 0; i < rtt_config_num; i++) { |
| if (!memcmp |
| (rtt_config[i].addr, zero_mac, sizeof(rtt_config[i].addr))) |
| continue; |
| else { |
| memcpy(&rtt_params. |
| rtt_config[rtt_params.rtt_config_num], |
| &rtt_config[i], |
| sizeof(rtt_params. |
| rtt_config[rtt_params.rtt_config_num])); |
| rtt_params.rtt_config_num++; |
| } |
| } |
| if (!rtt_params.rtt_config_num) { |
| PRINTM(MERROR, "%s: no valid mac addr\n", __FUNCTION__); |
| goto done; |
| } |
| woal_dump_rtt_params(&rtt_params); |
| |
| ret = woal_config_rtt(priv, MOAL_IOCTL_WAIT, &rtt_params); |
| if (ret != MLAN_STATUS_SUCCESS) { |
| PRINTM(MERROR, "%s: woal_config_rtt() failed\n", __FUNCTION__); |
| err = -EFAULT; |
| goto done; |
| } |
| |
| for (i = 0; i < rtt_params.rtt_config_num; i++) { |
| for (j = 0; j < handle->rtt_params.rtt_config_num; j++) { |
| if (!memcmp |
| (handle->rtt_params.rtt_config[j].addr, |
| rtt_params.rtt_config[i].addr, |
| sizeof(handle->rtt_params.rtt_config[j].addr))) |
| break; |
| } |
| memcpy(&(handle->rtt_params.rtt_config[j]), |
| &(rtt_params.rtt_config[i]), |
| sizeof(handle->rtt_params.rtt_config[j])); |
| if (j == handle->rtt_params.rtt_config_num) |
| handle->rtt_params.rtt_config_num++; |
| } |
| |
| woal_dump_rtt_params(&(handle->rtt_params)); |
| |
| done: |
| LEAVE(); |
| return err; |
| } |
| |
| /** |
| * @brief vendor command to cancel rtt range |
| * |
| * @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_rtt_range_cancel(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); |
| moal_handle *handle = priv->phandle; |
| t_u8 rtt_config_num = handle->rtt_params.rtt_config_num; |
| struct nlattr *tb[ATTR_RTT_MAX]; |
| t_u32 target_num = 0; |
| t_u8 addr[MAX_RTT_CONFIG_NUM][MLAN_MAC_ADDR_LENGTH]; |
| int i = 0, j = 0; |
| mlan_status ret = MLAN_STATUS_SUCCESS; |
| int err = 0; |
| |
| ENTER(); |
| PRINTM(MCMND, "Enter woal_cfg80211_subcmd_rtt_range_cancel()\n"); |
| |
| err = nla_parse(tb, ATTR_RTT_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__); |
| goto done; |
| } |
| |
| if (!tb[ATTR_RTT_TARGET_NUM] || !tb[ATTR_RTT_TARGET_ADDR]) { |
| PRINTM(MERROR, |
| "%s: null attr: tb[ATTR_RTT_TARGET_NUM]=%p tb[ATTR_RTT_TARGET_ADDR]=%p\n", |
| __FUNCTION__, tb[ATTR_RTT_TARGET_NUM], |
| tb[ATTR_RTT_TARGET_ADDR]); |
| err = -EINVAL; |
| goto done; |
| } |
| |
| target_num = nla_get_u8(tb[ATTR_RTT_TARGET_NUM]); |
| |
| if ((target_num <= 0 || target_num > MAX_RTT_CONFIG_NUM) || |
| (nla_len(tb[ATTR_RTT_TARGET_ADDR]) != |
| sizeof(t_u8) * MLAN_MAC_ADDR_LENGTH * target_num)) { |
| PRINTM(MERROR, "%s: Check if %din[1-%d] or %d*%lu=%d\n", |
| __FUNCTION__, target_num, MAX_RTT_CONFIG_NUM, target_num, |
| sizeof(t_u8) * MLAN_MAC_ADDR_LENGTH, |
| nla_len(tb[ATTR_RTT_TARGET_ADDR])); |
| err = -EINVAL; |
| goto done; |
| } |
| woal_dump_rtt_params(&(handle->rtt_params)); |
| |
| memcpy(addr, nla_data(tb[ATTR_RTT_TARGET_ADDR]), |
| nla_len(tb[ATTR_RTT_TARGET_ADDR])); |
| |
| for (i = 0; i < target_num; i++) |
| PRINTM(MMSG, "cancel[%d].addr=" MACSTR "\n", i, |
| MAC2STR(addr[i])); |
| |
| for (i = 0; i < target_num; i++) { |
| for (j = 0; j < handle->rtt_params.rtt_config_num; j++) { |
| if (!memcmp |
| (addr[i], handle->rtt_params.rtt_config[j].addr, |
| sizeof(addr[0]))) { |
| memset(&(handle->rtt_params.rtt_config[j]), |
| 0x00, |
| sizeof(handle->rtt_params. |
| rtt_config[0])); |
| if ((j + 1) < handle->rtt_params.rtt_config_num) { |
| memmove(& |
| (handle->rtt_params. |
| rtt_config[j]), |
| &(handle->rtt_params. |
| rtt_config[j + 1]), |
| sizeof(handle->rtt_params. |
| rtt_config[0]) * |
| (handle->rtt_params. |
| rtt_config_num - (j + 1))); |
| memset(& |
| (handle->rtt_params. |
| rtt_config[handle->rtt_params. |
| rtt_config_num - 1]), |
| 0x00, |
| sizeof(handle->rtt_params. |
| rtt_config[0])); |
| } |
| handle->rtt_params.rtt_config_num--; |
| continue; |
| } |
| } |
| } |
| |
| if (handle->rtt_params.rtt_config_num >= rtt_config_num) { |
| PRINTM(MERROR, "%s: No matched mac addr in rtt_config\n", |
| __FUNCTION__); |
| goto done; |
| } |
| |
| ret = woal_cancel_rtt(priv, MOAL_IOCTL_WAIT, target_num, addr); |
| if (ret != MLAN_STATUS_SUCCESS) { |
| PRINTM(MERROR, "%s: woal_cancel_rtt() failed\n", __FUNCTION__); |
| err = -EFAULT; |
| goto done; |
| } |
| woal_dump_rtt_params(&(handle->rtt_params)); |
| |
| done: |
| LEAVE(); |
| return err; |
| } |
| |
| /** |
| * @brief vendor event to report RTT Results |
| * |
| * @param priv A pointer to moal_private |
| * @param data a pointer to data |
| * @param len data length |
| * |
| * @return mlan_status |
| */ |
| mlan_status |
| woal_cfg80211_event_rtt_result(IN moal_private *priv, IN t_u8 *data, IN int len) |
| { |
| //moal_handle *handle = priv->phandle; |
| mlan_status ret = MLAN_STATUS_SUCCESS; |
| t_u8 *pos = data; |
| t_u32 event_left_len = len; |
| struct sk_buff *skb = NULL; |
| t_u32 vdr_event_len = 0; |
| t_u32 complete = 0; |
| wifi_rtt_result_element *rtt_result_elem = NULL; |
| t_u32 num_results = 0; |
| |
| ENTER(); |
| |
| PRINTM(MEVENT, "Enter woal_cfg80211_event_rtt_result()\n"); |
| |
| vdr_event_len = nla_total_size(sizeof(complete)) + |
| nla_total_size(sizeof(num_results)) + |
| nla_total_size(len) + NLA_ALIGNTO * num_results |
| + VENDOR_REPLY_OVERHEAD; |
| PRINTM(MEVENT, "vdr_event_len = %d\n", vdr_event_len); |
| skb = woal_cfg80211_alloc_vendor_event(priv, event_rtt_result, |
| vdr_event_len); |
| if (!skb) |
| goto done; |
| |
| complete = *pos; |
| nla_put(skb, ATTR_RTT_RESULT_COMPLETE, sizeof(complete), &complete); |
| pos++; |
| event_left_len--; |
| |
| while (event_left_len > sizeof(wifi_rtt_result_element)) { |
| rtt_result_elem = (wifi_rtt_result_element *) pos; |
| |
| nla_put(skb, ATTR_RTT_RESULT_FULL, rtt_result_elem->len, |
| rtt_result_elem->data); |
| num_results++; |
| |
| pos += sizeof(*rtt_result_elem) + rtt_result_elem->len; |
| event_left_len -= |
| sizeof(*rtt_result_elem) + rtt_result_elem->len; |
| } |
| |
| nla_put(skb, ATTR_RTT_RESULT_NUM, sizeof(num_results), &num_results); |
| |
| DBG_HEXDUMP(MEVT_D, "output data skb->data", (t_u8 *)skb->data, |
| skb->len); |
| /**send event*/ |
| cfg80211_vendor_event(skb, GFP_KERNEL); |
| |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief vendor command to get rtt responder info |
| * |
| * @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_rtt_get_responder_info(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); |
| mlan_rtt_responder rtt_rsp_cfg; |
| struct sk_buff *skb = NULL; |
| wifi_rtt_responder rtt_rsp; |
| mlan_status ret = MLAN_STATUS_SUCCESS; |
| int err = 0; |
| |
| ENTER(); |
| PRINTM(MCMND, "Enter woal_cfg80211_subcmd_rtt_get_responder_info()\n"); |
| |
| memset(&rtt_rsp_cfg, 0x00, sizeof(rtt_rsp_cfg)); |
| rtt_rsp_cfg.action = RTT_GET_RESPONDER_INFO; |
| ret = woal_rtt_responder_cfg(priv, MOAL_IOCTL_WAIT, &rtt_rsp_cfg); |
| if (ret != MLAN_STATUS_SUCCESS) { |
| PRINTM(MERROR, "%s: woal_rtt_responder_cfg() failed\n", |
| __FUNCTION__); |
| err = -EFAULT; |
| goto done; |
| } |
| PRINTM(MCMD_D, |
| "mlan_rtt_responder from FW: channel=%d bandcfg=%d %d %d %d preamble=%d\n", |
| rtt_rsp_cfg.u.info.channel, rtt_rsp_cfg.u.info.bandcfg.chanBand, |
| rtt_rsp_cfg.u.info.bandcfg.chanWidth, |
| rtt_rsp_cfg.u.info.bandcfg.chan2Offset, |
| rtt_rsp_cfg.u.info.bandcfg.scanMode, |
| rtt_rsp_cfg.u.info.preamble); |
| |
| memset(&rtt_rsp, 0x00, sizeof(rtt_rsp)); |
| woal_bandcfg_to_channel_info(priv, &(rtt_rsp_cfg.u.info.bandcfg), |
| rtt_rsp_cfg.u.info.channel, |
| &(rtt_rsp.channel)); |
| rtt_rsp.preamble = rtt_rsp_cfg.u.info.preamble; |
| PRINTM(MCMD_D, "wifi_rtt_responder report to HAL:\n"); |
| PRINTM(MCMD_D, |
| "channel: width=%d center_freq=%d center_freq0=%d center_freq1=%d\n", |
| rtt_rsp.channel.width, rtt_rsp.channel.center_freq, |
| rtt_rsp.channel.center_freq0, rtt_rsp.channel.center_freq1); |
| PRINTM(MCMD_D, "preamble=%d\n", rtt_rsp.preamble); |
| |
| /* Alloc the SKB for vendor_event */ |
| skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, |
| nla_total_size(sizeof |
| (rtt_rsp)) + |
| VENDOR_REPLY_OVERHEAD); |
| if (unlikely(!skb)) { |
| PRINTM(MERROR, "skb alloc failed in %s\n", __FUNCTION__); |
| goto done; |
| } |
| |
| /* Put the attribute to the skb */ |
| nla_put(skb, ATTR_RTT_CHANNEL_INFO, sizeof(rtt_rsp.channel), |
| &(rtt_rsp.channel)); |
| nla_put(skb, ATTR_RTT_PREAMBLE, sizeof(rtt_rsp.preamble), |
| &(rtt_rsp.preamble)); |
| DBG_HEXDUMP(MCMD_D, "output data skb->data", (t_u8 *)skb->data, |
| skb->len); |
| |
| err = cfg80211_vendor_cmd_reply(skb); |
| if (unlikely(err)) |
| PRINTM(MERROR, "Vendor Command reply failed err:%d \n", err); |
| |
| done: |
| LEAVE(); |
| return err; |
| } |
| |
| /** |
| * @brief vendor command to enable rtt responder |
| * |
| * @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_rtt_enable_responder(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_RTT_MAX]; |
| wifi_channel_info *ch_info = NULL; |
| t_u32 max_dur_sec = 0; |
| mlan_rtt_responder rtt_rsp_cfg; |
| wifi_rtt_responder rtt_rsp; |
| struct sk_buff *skb = NULL; |
| mlan_status ret = MLAN_STATUS_SUCCESS; |
| int err = 0; |
| |
| ENTER(); |
| PRINTM(MCMND, "Enter woal_cfg80211_subcmd_rtt_enable_responder()\n"); |
| |
| err = nla_parse(tb, ATTR_RTT_MAX, data, len, NULL |
| #if CFG80211_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) |
| , NULL |
| #endif |
| ); |
| if (err) { |
| err = -EFAULT; |
| PRINTM(MERROR, "%s: nla_parse fail\n", __FUNCTION__); |
| goto done; |
| } |
| |
| if (!tb[ATTR_RTT_CHANNEL_INFO] || !tb[ATTR_RTT_MAX_DUR_SEC]) { |
| PRINTM(MERROR, |
| "%s: null attr: tb[ATTR_RTT_TARGET_NUM]=%p tb[ATTR_RTT_TARGET_CONFIG]=%p\n", |
| __FUNCTION__, tb[ATTR_RTT_CHANNEL_INFO], |
| tb[ATTR_RTT_MAX_DUR_SEC]); |
| err = -EINVAL; |
| goto done; |
| } |
| ch_info = (wifi_channel_info *) nla_data(tb[ATTR_RTT_CHANNEL_INFO]); |
| max_dur_sec = nla_get_u32(tb[ATTR_RTT_MAX_DUR_SEC]); |
| PRINTM(MCMD_D, "HAL input: \n"); |
| PRINTM(MCMD_D, |
| "wifi_channel_info: width=%d center_freq=%d center_freq0=%d center_freq1=%d\n", |
| ch_info->width, ch_info->center_freq, ch_info->center_freq0, |
| ch_info->center_freq1); |
| PRINTM(MCMD_D, "max_dur_sec=%d\n", max_dur_sec); |
| |
| memset(&rtt_rsp_cfg, 0x00, sizeof(rtt_rsp_cfg)); |
| rtt_rsp_cfg.action = RTT_SET_RESPONDER_ENABLE; |
| rtt_rsp_cfg.u.encfg.channel = |
| ieee80211_frequency_to_channel(ch_info->center_freq); |
| woal_channel_info_to_bandcfg(priv, ch_info, |
| &(rtt_rsp_cfg.u.encfg.bandcfg)); |
| rtt_rsp_cfg.u.encfg.max_dur_sec = max_dur_sec; |
| PRINTM(MCMD_D, "HAL input to rtt_responder_encfg: \n"); |
| PRINTM(MCMD_D, |
| "channel=%d bandcfg=[chanBand=%d chanWidth=%d chan2Offset=%d scanMode=%d]\n", |
| rtt_rsp_cfg.u.encfg.channel, |
| rtt_rsp_cfg.u.encfg.bandcfg.chanBand, |
| rtt_rsp_cfg.u.encfg.bandcfg.chanWidth, |
| rtt_rsp_cfg.u.encfg.bandcfg.chan2Offset, |
| rtt_rsp_cfg.u.encfg.bandcfg.scanMode); |
| PRINTM(MCMD_D, "max_dur_sec=%d\n", rtt_rsp_cfg.u.encfg.max_dur_sec); |
| ret = woal_rtt_responder_cfg(priv, MOAL_IOCTL_WAIT, &rtt_rsp_cfg); |
| if (ret != MLAN_STATUS_SUCCESS) { |
| PRINTM(MERROR, "%s: woal_rtt_responder_cfg() failed\n", |
| __FUNCTION__); |
| err = -EFAULT; |
| goto done; |
| } |
| |
| memset(&rtt_rsp, 0x00, sizeof(rtt_rsp)); |
| woal_bandcfg_to_channel_info(priv, &(rtt_rsp_cfg.u.info.bandcfg), |
| rtt_rsp_cfg.u.info.channel, |
| &(rtt_rsp.channel)); |
| rtt_rsp.preamble = rtt_rsp_cfg.u.info.preamble; |
| PRINTM(MCMD_D, "wifi_rtt_responder report to HAL:\n"); |
| PRINTM(MCMD_D, |
| "channel: width=%d center_freq=%d center_freq0=%d center_freq1=%d\n", |
| rtt_rsp.channel.width, rtt_rsp.channel.center_freq, |
| rtt_rsp.channel.center_freq0, rtt_rsp.channel.center_freq1); |
| PRINTM(MCMD_D, "preamble=%d\n", rtt_rsp.preamble); |
| |
| /* Alloc the SKB for vendor_event */ |
| skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, |
| nla_total_size(sizeof |
| (rtt_rsp)) + |
| VENDOR_REPLY_OVERHEAD); |
| if (unlikely(!skb)) { |
| PRINTM(MERROR, "skb alloc failed in %s\n", __FUNCTION__); |
| goto done; |
| } |
| |
| /* Put the attribute to the skb */ |
| nla_put(skb, ATTR_RTT_CHANNEL_INFO, sizeof(rtt_rsp.channel), |
| &(rtt_rsp.channel)); |
| nla_put(skb, ATTR_RTT_PREAMBLE, sizeof(rtt_rsp.preamble), |
| &(rtt_rsp.preamble)); |
| DBG_HEXDUMP(MCMD_D, "output data skb->data", (t_u8 *)skb->data, |
| skb->len); |
| |
| err = cfg80211_vendor_cmd_reply(skb); |
| if (unlikely(err)) |
| PRINTM(MERROR, "Vendor Command reply failed err:%d \n", err); |
| |
| done: |
| LEAVE(); |
| return err; |
| } |
| |
| /** |
| * @brief vendor command to disable rtt responder |
| * |
| * @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_rtt_disable_responder(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); |
| mlan_rtt_responder rtt_rsp_cfg; |
| mlan_status ret = MLAN_STATUS_SUCCESS; |
| int err = 0; |
| |
| ENTER(); |
| PRINTM(MCMND, "Enter woal_cfg80211_subcmd_rtt_disable_responder()\n"); |
| |
| memset(&rtt_rsp_cfg, 0x00, sizeof(rtt_rsp_cfg)); |
| rtt_rsp_cfg.action = RTT_SET_RESPONDER_DISABLE; |
| ret = woal_rtt_responder_cfg(priv, MOAL_IOCTL_WAIT, &rtt_rsp_cfg); |
| if (ret != MLAN_STATUS_SUCCESS) { |
| PRINTM(MERROR, "%s: woal_rtt_responder_cfg() failed\n", |
| __FUNCTION__); |
| err = -EFAULT; |
| goto done; |
| } |
| |
| done: |
| LEAVE(); |
| return err; |
| } |
| |
| /** |
| * @brief vendor command to set rtt lci |
| * @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_rtt_set_lci(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_RTT_MAX]; |
| mlan_rtt_responder rtt_rsp_cfg; |
| wifi_lci_information *lci_info; |
| mlan_status ret = MLAN_STATUS_SUCCESS; |
| int err = 0; |
| |
| ENTER(); |
| PRINTM(MCMND, "Enter woal_cfg80211_subcmd_rtt_set_lci()\n"); |
| |
| err = nla_parse(tb, ATTR_RTT_MAX, data, len, NULL |
| #if CFG80211_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) |
| , NULL |
| #endif |
| ); |
| if (err) { |
| err = -EFAULT; |
| PRINTM(MERROR, "%s: nla_parse fail\n", __FUNCTION__); |
| goto done; |
| } |
| |
| if (!tb[ATTR_RTT_LCI_INFO]) { |
| PRINTM(MERROR, "%s: null attr: tb[ATTR_RTT_LCI_INFO]=%p\n", |
| __FUNCTION__, tb[ATTR_RTT_LCI_INFO]); |
| err = -EINVAL; |
| goto done; |
| } |
| lci_info = (wifi_lci_information *) nla_data(tb[ATTR_RTT_LCI_INFO]); |
| PRINTM(MCMD_D, "HAL input: \n"); |
| PRINTM(MCMD_D, |
| "wifi_lci_information: latitude=%lu longitude=%lu altitude=%d latitude_unc=%d longitude_unc=%d altitude_unc=%d \n", |
| lci_info->latitude, lci_info->longitude, lci_info->altitude, |
| lci_info->latitude_unc, lci_info->longitude_unc, |
| lci_info->altitude_unc); |
| PRINTM(MCMD_D, |
| "wifi_lci_information: motion_pattern=%d floor=%d height_above_floor=%d height_unc=%d\n", |
| lci_info->motion_pattern, lci_info->floor, |
| lci_info->height_above_floor, lci_info->height_unc); |
| |
| memset(&rtt_rsp_cfg, 0x00, sizeof(rtt_rsp_cfg)); |
| rtt_rsp_cfg.action = RTT_SET_RESPONDER_LCI; |
| memcpy(&(rtt_rsp_cfg.u.lci), lci_info, sizeof(rtt_rsp_cfg.u.lci)); |
| ret = woal_rtt_responder_cfg(priv, MOAL_IOCTL_WAIT, &rtt_rsp_cfg); |
| if (ret != MLAN_STATUS_SUCCESS) { |
| PRINTM(MERROR, "%s: woal_rtt_responder_cfg() failed\n", |
| __FUNCTION__); |
| err = -EFAULT; |
| goto done; |
| } |
| |
| done: |
| LEAVE(); |
| return err; |
| } |
| |
| /** |
| * @brief vendor command to set rtt lcr |
| * |
| * @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_rtt_set_lcr(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_RTT_MAX]; |
| mlan_rtt_responder rtt_rsp_cfg; |
| wifi_lcr_information *lcr_info; |
| mlan_status ret = MLAN_STATUS_SUCCESS; |
| int err = 0; |
| |
| ENTER(); |
| PRINTM(MCMND, "Enter woal_cfg80211_subcmd_rtt_set_lcr()\n"); |
| |
| err = nla_parse(tb, ATTR_RTT_MAX, data, len, NULL |
| #if CFG80211_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) |
| , NULL |
| #endif |
| ); |
| if (err) { |
| err = -EFAULT; |
| PRINTM(MERROR, "%s: nla_parse fail\n", __FUNCTION__); |
| goto done; |
| } |
| |
| if (!tb[ATTR_RTT_LCR_INFO]) { |
| PRINTM(MERROR, "%s: null attr: tb[ATTR_RTT_LCR_INFO]=%p\n", |
| __FUNCTION__, tb[ATTR_RTT_LCR_INFO]); |
| err = -EINVAL; |
| goto done; |
| } |
| lcr_info = (wifi_lcr_information *) nla_data(tb[ATTR_RTT_LCR_INFO]); |
| PRINTM(MCMD_D, "HAL input: \n"); |
| PRINTM(MCMD_D, "wifi_lcr_information: country_code='%c' '%c'\n", |
| lcr_info->country_code[0], lcr_info->country_code[1]); |
| PRINTM(MCMD_D, "wifi_lci_information: length=%d civic_info=%s\n", |
| lcr_info->length, lcr_info->civic_info); |
| |
| memset(&rtt_rsp_cfg, 0x00, sizeof(rtt_rsp_cfg)); |
| rtt_rsp_cfg.action = RTT_SET_RESPONDER_LCR; |
| memcpy(&(rtt_rsp_cfg.u.lcr), lcr_info, sizeof(rtt_rsp_cfg.u.lcr)); |
| ret = woal_rtt_responder_cfg(priv, MOAL_IOCTL_WAIT, &rtt_rsp_cfg); |
| if (ret != MLAN_STATUS_SUCCESS) { |
| PRINTM(MERROR, "%s: woal_rtt_responder_cfg() failed\n", |
| __FUNCTION__); |
| err = -EFAULT; |
| goto done; |
| } |
| |
| done: |
| LEAVE(); |
| return err; |
| } |
| |
| 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, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| sub_cmd_link_statistic_set,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = woal_cfg80211_subcmd_link_statistic_set, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| sub_cmd_link_statistic_get,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = woal_cfg80211_subcmd_link_statistic_get, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| sub_cmd_link_statistic_clr,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = woal_cfg80211_subcmd_link_statistic_clr, |
| }, |
| #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_get_roaming_capability,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = woal_cfg80211_subcmd_get_roaming_capability, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| sub_cmd_fw_roaming_enable,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = woal_cfg80211_subcmd_fw_roaming_enable, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| sub_cmd_fw_roaming_config,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = woal_cfg80211_subcmd_fw_roaming_config, |
| }, |
| |
| { |
| .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 = SUBCMD_RTT_GET_CAPA,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = woal_cfg80211_subcmd_rtt_get_capa, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| SUBCMD_RTT_RANGE_REQUEST,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = woal_cfg80211_subcmd_rtt_range_request, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| SUBCMD_RTT_RANGE_CANCEL,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = woal_cfg80211_subcmd_rtt_range_cancel, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| SUBCMD_RTT_GET_RESPONDER_INFO,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = woal_cfg80211_subcmd_rtt_get_responder_info, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| SUBCMD_RTT_ENABLE_RESPONDER,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = woal_cfg80211_subcmd_rtt_enable_responder, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| SUBCMD_RTT_DISABLE_RESPONDER,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = woal_cfg80211_subcmd_rtt_disable_responder, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = SUBCMD_RTT_SET_LCI,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = woal_cfg80211_subcmd_rtt_set_lci, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = SUBCMD_RTT_SET_LCR,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = woal_cfg80211_subcmd_rtt_set_lcr, |
| }, |
| |
| { |
| .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, |
| }, |
| |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| sub_cmd_get_wifi_logger_supp_feature_set,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = woal_cfg80211_subcmd_get_wifi_logger_supp_feature_set, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| sub_cmd_get_ring_buff_status,}, |
| .flags = |
| WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV | |
| WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_get_ring_buff_status, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = sub_cmd_start_logging,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | |
| WIPHY_VENDOR_CMD_NEED_NETDEV | WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_start_logging, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| sub_cmd_get_ring_buff_data,}, |
| .flags = |
| WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV | |
| WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_get_ring_data, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| sub_cmd_start_packet_fate_monitor,}, |
| .flags = |
| WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV | |
| WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_start_packet_fate_monitor, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| sub_cmd_get_fw_mem_dump,}, |
| .flags = |
| WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV | |
| WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_get_fw_dump, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| sub_cmd_get_drv_mem_dump,}, |
| .flags = |
| WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV | |
| WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_get_drv_dump, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| sub_cmd_set_packet_filter,}, |
| .flags = |
| WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV | |
| WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_set_packet_filter, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| sub_cmd_get_packet_filter_capability,}, |
| .flags = |
| WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV | |
| WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_get_packet_filter_capability, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = subcmd_nan_enable_req,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | |
| WIPHY_VENDOR_CMD_NEED_NETDEV | WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_nan_enable_req, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| subcmd_nan_disable_req,}, |
| .flags = |
| WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV | |
| WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_nan_disable_req, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| subcmd_nan_publish_req,}, |
| .flags = |
| WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV | |
| WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_nan_publish_req, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| subcmd_nan_publish_cancel,}, |
| .flags = |
| WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV | |
| WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_nan_publish_cancel, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| subcmd_nan_subscribe_req,}, |
| .flags = |
| WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV | |
| WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_nan_subscribe_req, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| subcmd_nan_subscribe_cancel,}, |
| .flags = |
| WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV | |
| WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_nan_subscribe_cancel, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| subcmd_nan_trasmit_followup,}, |
| .flags = |
| WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV | |
| WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_nan_trasmit_followup, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = subcmd_nan_stats_req,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | |
| WIPHY_VENDOR_CMD_NEED_NETDEV | WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_nan_stats_req, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = subcmd_nan_config_req,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | |
| WIPHY_VENDOR_CMD_NEED_NETDEV | WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_nan_config_req, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = subcmd_nan_tca_req,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | |
| WIPHY_VENDOR_CMD_NEED_NETDEV | WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_nan_tca_req, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| subcmd_nan_beacon_sdf_payload,}, |
| .flags = |
| WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV | |
| WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_nan_beacon_sdf_payload, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| subcmd_nan_get_version,}, |
| .flags = |
| WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV | |
| WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_nan_get_version, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = subcmd_nan_get_capa,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | |
| WIPHY_VENDOR_CMD_NEED_NETDEV | WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_nan_get_capability, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| subcmd_nan_data_if_create,}, |
| .flags = |
| WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV | |
| WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_nan_data_if_create, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| subcmd_nan_data_if_delete,}, |
| .flags = |
| WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV | |
| WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_nan_data_if_delete, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| subcmd_nan_data_req_initor,}, |
| .flags = |
| WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV | |
| WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_nan_data_req_initor, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = |
| subcmd_nan_data_indi_resp,}, |
| .flags = |
| WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV | |
| WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_nan_data_indi_resp, |
| }, |
| { |
| .info = {.vendor_id = MRVL_VENDOR_ID,.subcmd = subcmd_nan_data_end,}, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | |
| WIPHY_VENDOR_CMD_NEED_NETDEV | WIPHY_VENDOR_CMD_NEED_RUNNING, |
| .doit = woal_cfg80211_subcmd_nan_data_end, |
| }, |
| }; |
| |
| /** |
| * @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 |