| /* |
| * Linux cfg80211 Vendor Extension Code |
| * |
| * Copyright 1999-2016, Broadcom Corporation |
| * All rights reserved, |
| * |
| * Redistribution and use in source and binary forms, with or without modification, |
| * are permitted provided that the following conditions are met: |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * This software is provided by the copyright holder "as is" and any express or |
| * implied warranties, including, but not limited to, the implied warranties of |
| * merchantability and fitness for a particular purpose are disclaimed. In no event |
| * shall copyright holder be liable for any direct, indirect, incidental, special, |
| * exemplary, or consequential damages (including, but not limited to, procurement |
| * of substitute goods or services; loss of use, data, or profits; or business |
| * interruption) however caused and on any theory of liability, whether in |
| * contract, strict liability, or tort (including negligence or otherwise) arising |
| * in any way out of the use of this software, even if advised of the possibility |
| * of such damage |
| * |
| * |
| * <<Broadcom-WL-IPTag/Open:>> |
| * |
| * $Id: wl_cfgvendor.c 605796 2015-12-11 13:45:36Z $ |
| */ |
| |
| /* |
| * New vendor interface additon to nl80211/cfg80211 to allow vendors |
| * to implement proprietary features over the cfg80211 stack. |
| */ |
| |
| #include <typedefs.h> |
| #include <linuxver.h> |
| #include <osl.h> |
| #include <linux/kernel.h> |
| #include <linux/vmalloc.h> |
| |
| #include <bcmutils.h> |
| #include <bcmwifi_channels.h> |
| #include <bcmendian.h> |
| #include <proto/ethernet.h> |
| #include <proto/802.11.h> |
| #include <linux/if_arp.h> |
| #include <asm/uaccess.h> |
| #include <dngl_stats.h> |
| #include <dhd.h> |
| #include <dhdioctl.h> |
| #include <wlioctl.h> |
| #include <wlioctl_utils.h> |
| #include <dhd_cfg80211.h> |
| #ifdef PNO_SUPPORT |
| #include <dhd_pno.h> |
| #endif /* PNO_SUPPORT */ |
| #ifdef RTT_SUPPORT |
| #include <dhd_rtt.h> |
| #endif /* RTT_SUPPORT */ |
| #include <proto/ethernet.h> |
| #include <linux/kernel.h> |
| #include <linux/kthread.h> |
| #include <linux/netdevice.h> |
| #include <linux/sched.h> |
| #include <linux/etherdevice.h> |
| #include <linux/wireless.h> |
| #include <linux/ieee80211.h> |
| #include <linux/wait.h> |
| #include <net/cfg80211.h> |
| #include <net/rtnetlink.h> |
| |
| #include <wlioctl.h> |
| #include <wldev_common.h> |
| #ifdef WL_CFG80211_V1 |
| #include <wl_cfg80211_v1.h> |
| #else |
| #include <wl_cfg80211.h> |
| #endif /* WL_CFG80211_V1 */ |
| #include <wl_cfgp2p.h> |
| #include <wl_android.h> |
| #include <wl_cfgvendor.h> |
| |
| #ifdef PROP_TXSTATUS |
| #include <dhd_wlfc.h> |
| #endif |
| #include <brcm_nl80211.h> |
| |
| #if defined(WL_VENDOR_EXT_SUPPORT) |
| /* |
| * This API is to be used for asynchronous vendor events. This |
| * shouldn't be used in response to a vendor command from its |
| * do_it handler context (instead wl_cfgvendor_send_cmd_reply should |
| * be used). |
| */ |
| int wl_cfgvendor_send_async_event(struct wiphy *wiphy, |
| struct net_device *dev, int event_id, const void *data, int len) |
| { |
| u16 kflags; |
| struct sk_buff *skb; |
| |
| kflags = in_atomic() ? GFP_ATOMIC : GFP_KERNEL; |
| |
| /* Alloc the SKB for vendor_event */ |
| #if defined(SUPPORT_WDEV_CFG80211_VENDOR_EVENT_ALLOC) |
| skb = cfg80211_vendor_event_alloc(wiphy, NULL, len, event_id, kflags); |
| #else |
| skb = cfg80211_vendor_event_alloc(wiphy, len, event_id, kflags); |
| #endif /* SUPPORT_WDEV_CFG80211_VENDOR_EVENT_ALLOC */ |
| if (!skb) { |
| WL_ERR(("skb alloc failed")); |
| return -ENOMEM; |
| } |
| |
| /* Push the data to the skb */ |
| nla_put_nohdr(skb, len, data); |
| |
| cfg80211_vendor_event(skb, kflags); |
| |
| return 0; |
| } |
| |
| static int |
| wl_cfgvendor_send_cmd_reply(struct wiphy *wiphy, |
| struct net_device *dev, const void *data, int len) |
| { |
| struct sk_buff *skb; |
| |
| /* Alloc the SKB for vendor_event */ |
| skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, len); |
| if (unlikely(!skb)) { |
| WL_ERR(("skb alloc failed")); |
| return -ENOMEM; |
| } |
| |
| /* Push the data to the skb */ |
| nla_put_nohdr(skb, len, data); |
| |
| return cfg80211_vendor_cmd_reply(skb); |
| } |
| |
| static int |
| wl_cfgvendor_get_feature_set(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| int reply; |
| |
| reply = dhd_dev_get_feature_set(bcmcfg_to_prmry_ndev(cfg)); |
| |
| err = wl_cfgvendor_send_cmd_reply(wiphy, bcmcfg_to_prmry_ndev(cfg), |
| &reply, sizeof(int)); |
| if (unlikely(err)) |
| WL_ERR(("Vendor Command reply failed ret:%d \n", err)); |
| |
| return err; |
| } |
| |
| static int |
| wl_cfgvendor_get_feature_set_matrix(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| struct sk_buff *skb; |
| int *reply; |
| int num, mem_needed, i; |
| |
| reply = dhd_dev_get_feature_set_matrix(bcmcfg_to_prmry_ndev(cfg), &num); |
| |
| if (!reply) { |
| WL_ERR(("Could not get feature list matrix\n")); |
| err = -EINVAL; |
| return err; |
| } |
| mem_needed = VENDOR_REPLY_OVERHEAD + (ATTRIBUTE_U32_LEN * num) + |
| ATTRIBUTE_U32_LEN; |
| |
| /* Alloc the SKB for vendor_event */ |
| skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, mem_needed); |
| if (unlikely(!skb)) { |
| WL_ERR(("skb alloc failed")); |
| err = -ENOMEM; |
| goto exit; |
| } |
| |
| nla_put_u32(skb, ANDR_WIFI_ATTRIBUTE_NUM_FEATURE_SET, num); |
| for (i = 0; i < num; i++) { |
| nla_put_u32(skb, ANDR_WIFI_ATTRIBUTE_FEATURE_SET, reply[i]); |
| } |
| |
| err = cfg80211_vendor_cmd_reply(skb); |
| |
| if (unlikely(err)) |
| WL_ERR(("Vendor Command reply failed ret:%d \n", err)); |
| |
| exit: |
| kfree(reply); |
| return err; |
| } |
| |
| static int |
| wl_cfgvendor_set_pno_mac_oui(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| int type; |
| uint8 pno_random_mac_oui[DOT11_OUI_LEN]; |
| |
| type = nla_type(data); |
| |
| if (type == ANDR_WIFI_ATTRIBUTE_PNO_RANDOM_MAC_OUI) { |
| memcpy(pno_random_mac_oui, nla_data(data), DOT11_OUI_LEN); |
| |
| err = dhd_dev_pno_set_mac_oui(bcmcfg_to_prmry_ndev(cfg), pno_random_mac_oui); |
| |
| if (unlikely(err)) |
| WL_ERR(("Bad OUI, could not set:%d \n", err)); |
| |
| |
| } else { |
| err = -1; |
| } |
| |
| return err; |
| } |
| |
| #ifdef CUSTOM_FORCE_NODFS_FLAG |
| static int |
| wl_cfgvendor_set_nodfs_flag(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| int type; |
| u32 nodfs; |
| |
| type = nla_type(data); |
| if (type == ANDR_WIFI_ATTRIBUTE_NODFS_SET) { |
| nodfs = nla_get_u32(data); |
| err = dhd_dev_set_nodfs(bcmcfg_to_prmry_ndev(cfg), nodfs); |
| } else { |
| err = -1; |
| } |
| return err; |
| } |
| #endif /* CUSTOM_FORCE_NODFS_FLAG */ |
| |
| #ifdef WIFI_STATS_SUPPORT |
| static int |
| wl_cfgvendor_wifi_stats_get_tdls_peers(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| |
| err = wl_cfgvendor_send_cmd_reply(wiphy, |
| bcmcfg_to_prmry_ndev(cfg), |
| &cfg->tdls_active_peers, |
| sizeof(struct wl_tdls_active_peers)); |
| |
| if (unlikely(err)) |
| WL_ERR(("Vendor Command reply failed ret:%d\n", err)); |
| |
| return err; |
| } |
| |
| static int |
| wl_cfgvendor_wifi_stats_get_num_frame_check_seq_errors(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| int64_t reply = 0; |
| |
| err = dhd_dev_get_num_frame_check_seq_errors( |
| bcmcfg_to_prmry_ndev(cfg), &reply); |
| |
| if (unlikely(err)) |
| WL_ERR(("Get number of frame check seq failed ret:%d\n", err)); |
| |
| err = wl_cfgvendor_send_cmd_reply(wiphy, bcmcfg_to_prmry_ndev(cfg), |
| &reply, sizeof(reply)); |
| |
| if (unlikely(err)) |
| WL_ERR(("Vendor Command reply failed ret:%d\n", err)); |
| |
| return err; |
| } |
| |
| static int |
| wl_cfgvendor_wifi_stats_clear_histograms(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = BCME_ERROR, rem, type; |
| const struct nlattr *iter; |
| struct ether_addr *mac_addr = NULL; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| uint32 reply; |
| |
| nla_for_each_attr(iter, data, len, rem) { |
| type = nla_type(iter); |
| switch (type) { |
| case ANDR_WIFI_ATTRIBUTE_STATION_MAC: |
| mac_addr = (struct ether_addr *)nla_data(iter); |
| WL_ERR(("%s: mac address = %02x:%02x:%02x:%02x:%02x:%02x\n", |
| __FUNCTION__, |
| mac_addr->octet[0], mac_addr->octet[1], |
| mac_addr->octet[2], mac_addr->octet[3], |
| mac_addr->octet[4], mac_addr->octet[5])); |
| break; |
| default: |
| WL_ERR(("Unknown type: %d\n", type)); |
| break; |
| } |
| } |
| |
| reply = dhd_dev_clear_cnt(bcmcfg_to_prmry_ndev(cfg), mac_addr); |
| if (reply) { |
| WL_ERR(("Could not clear histograms\n")); |
| err = -EINVAL; |
| return err; |
| } |
| |
| err = wl_cfgvendor_send_cmd_reply(wiphy, bcmcfg_to_prmry_ndev(cfg), |
| &reply, sizeof(uint32)); |
| |
| if (unlikely(err)) |
| WL_ERR(("Vendor Command reply failed ret:%d\n", err)); |
| |
| return err; |
| } |
| |
| static int |
| wl_cfgvendor_wifi_stats_get_histograms(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = BCME_ERROR, rem, type; |
| const struct nlattr *iter; |
| struct ether_addr *mac_addr = NULL; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| struct histograms_report *reply = NULL; |
| uint32 reply_len = 0; |
| |
| nla_for_each_attr(iter, data, len, rem) { |
| type = nla_type(iter); |
| switch (type) { |
| case ANDR_WIFI_ATTRIBUTE_STATION_MAC: |
| mac_addr = (struct ether_addr *)nla_data(iter); |
| WL_ERR(("%s: mac address = %02x:%02x:%02x:%02x:%02x:%02x\n", |
| __FUNCTION__, |
| mac_addr->octet[0], mac_addr->octet[1], |
| mac_addr->octet[2], mac_addr->octet[3], |
| mac_addr->octet[4], mac_addr->octet[5])); |
| break; |
| default: |
| WL_ERR(("Unknown type: %d\n", type)); |
| break; |
| } |
| } |
| |
| reply = dhd_dev_get_histograms(bcmcfg_to_prmry_ndev(cfg), mac_addr, &reply_len); |
| if (!reply_len) { |
| WL_ERR(("Could not get histograms\n")); |
| err = -EINVAL; |
| return err; |
| } |
| |
| err = wl_cfgvendor_send_cmd_reply(wiphy, bcmcfg_to_prmry_ndev(cfg), |
| reply, reply_len); |
| |
| if (unlikely(err)) |
| WL_ERR(("Vendor Command reply failed ret:%d\n", err)); |
| |
| kfree(reply); |
| |
| return err; |
| } |
| #endif /* WIFI_STATS_SUPPORT */ |
| |
| #ifdef GSCAN_SUPPORT |
| int |
| wl_cfgvendor_send_hotlist_event(struct wiphy *wiphy, |
| struct net_device *dev, void *data, int len, wl_vendor_event_t event) |
| { |
| u16 kflags; |
| const void *ptr; |
| struct sk_buff *skb; |
| int malloc_len, total, iter_cnt_to_send, cnt; |
| gscan_results_cache_t *cache = (gscan_results_cache_t *)data; |
| total = len/sizeof(wifi_gscan_result_t); |
| while (total > 0) { |
| malloc_len = (total * sizeof(wifi_gscan_result_t)) + VENDOR_DATA_OVERHEAD; |
| if (malloc_len > NLMSG_DEFAULT_SIZE) { |
| malloc_len = NLMSG_DEFAULT_SIZE; |
| } |
| iter_cnt_to_send = |
| (malloc_len - VENDOR_DATA_OVERHEAD)/sizeof(wifi_gscan_result_t); |
| total = total - iter_cnt_to_send; |
| |
| kflags = in_atomic() ? GFP_ATOMIC : GFP_KERNEL; |
| |
| /* Alloc the SKB for vendor_event */ |
| #if defined(SUPPORT_WDEV_CFG80211_VENDOR_EVENT_ALLOC) |
| skb = cfg80211_vendor_event_alloc(wiphy, NULL, malloc_len, event, kflags); |
| #else |
| skb = cfg80211_vendor_event_alloc(wiphy, malloc_len, event, kflags); |
| #endif /* SUPPORT_WDEV_CFG80211_VENDOR_EVENT_ALLOC */ |
| if (!skb) { |
| WL_ERR(("skb alloc failed")); |
| return -ENOMEM; |
| } |
| |
| while (cache && iter_cnt_to_send) { |
| ptr = (const void *) &cache->results[cache->tot_consumed]; |
| |
| if (iter_cnt_to_send < (cache->tot_count - cache->tot_consumed)) { |
| cnt = iter_cnt_to_send; |
| } else { |
| cnt = (cache->tot_count - cache->tot_consumed); |
| } |
| |
| iter_cnt_to_send -= cnt; |
| cache->tot_consumed += cnt; |
| /* Push the data to the skb */ |
| nla_append(skb, cnt * sizeof(wifi_gscan_result_t), ptr); |
| if (cache->tot_consumed == cache->tot_count) { |
| cache = cache->next; |
| } |
| |
| } |
| |
| cfg80211_vendor_event(skb, kflags); |
| } |
| |
| return 0; |
| } |
| |
| |
| static int |
| wl_cfgvendor_gscan_get_capabilities(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| dhd_pno_gscan_capabilities_t *reply = NULL; |
| uint32 reply_len = 0; |
| |
| |
| reply = dhd_dev_pno_get_gscan(bcmcfg_to_prmry_ndev(cfg), |
| DHD_PNO_GET_CAPABILITIES, NULL, &reply_len); |
| if (!reply) { |
| WL_ERR(("Could not get capabilities\n")); |
| err = -EINVAL; |
| return err; |
| } |
| |
| err = wl_cfgvendor_send_cmd_reply(wiphy, bcmcfg_to_prmry_ndev(cfg), |
| reply, reply_len); |
| |
| if (unlikely(err)) { |
| WL_ERR(("Vendor Command reply failed ret:%d \n", err)); |
| } |
| |
| kfree(reply); |
| return err; |
| } |
| |
| static int |
| wl_cfgvendor_gscan_get_channel_list(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0, type, band; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| uint16 *reply = NULL; |
| uint32 reply_len = 0, num_channels, mem_needed; |
| struct sk_buff *skb; |
| |
| type = nla_type(data); |
| |
| if (type == GSCAN_ATTRIBUTE_BAND) { |
| band = nla_get_u32(data); |
| } else { |
| return -EINVAL; |
| } |
| |
| reply = dhd_dev_pno_get_gscan(bcmcfg_to_prmry_ndev(cfg), |
| DHD_PNO_GET_CHANNEL_LIST, &band, &reply_len); |
| |
| if (!reply) { |
| WL_ERR(("Could not get channel list\n")); |
| err = -EINVAL; |
| return err; |
| } |
| num_channels = reply_len/ sizeof(uint32); |
| mem_needed = reply_len + VENDOR_REPLY_OVERHEAD + (ATTRIBUTE_U32_LEN * 2); |
| |
| /* Alloc the SKB for vendor_event */ |
| skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, mem_needed); |
| if (unlikely(!skb)) { |
| WL_ERR(("skb alloc failed")); |
| err = -ENOMEM; |
| goto exit; |
| } |
| |
| nla_put_u32(skb, GSCAN_ATTRIBUTE_NUM_CHANNELS, num_channels); |
| nla_put(skb, GSCAN_ATTRIBUTE_CHANNEL_LIST, reply_len, reply); |
| |
| err = cfg80211_vendor_cmd_reply(skb); |
| |
| if (unlikely(err)) { |
| WL_ERR(("Vendor Command reply failed ret:%d \n", err)); |
| } |
| exit: |
| kfree(reply); |
| return err; |
| } |
| |
| static int |
| wl_cfgvendor_gscan_get_batch_results(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| gscan_results_cache_t *results, *iter; |
| uint32 reply_len, complete = 0, num_results_iter; |
| int32 mem_needed; |
| wifi_gscan_result_t *ptr; |
| uint16 num_scan_ids, num_results; |
| struct sk_buff *skb; |
| struct nlattr *scan_hdr; |
| |
| dhd_dev_wait_batch_results_complete(bcmcfg_to_prmry_ndev(cfg)); |
| dhd_dev_pno_lock_access_batch_results(bcmcfg_to_prmry_ndev(cfg)); |
| results = dhd_dev_pno_get_gscan(bcmcfg_to_prmry_ndev(cfg), |
| DHD_PNO_GET_BATCH_RESULTS, NULL, &reply_len); |
| |
| if (!results) { |
| WL_ERR(("No results to send %d\n", err)); |
| err = wl_cfgvendor_send_cmd_reply(wiphy, bcmcfg_to_prmry_ndev(cfg), |
| results, 0); |
| |
| if (unlikely(err)) |
| WL_ERR(("Vendor Command reply failed ret:%d \n", err)); |
| dhd_dev_pno_unlock_access_batch_results(bcmcfg_to_prmry_ndev(cfg)); |
| return err; |
| } |
| num_scan_ids = reply_len & 0xFFFF; |
| num_results = (reply_len & 0xFFFF0000) >> 16; |
| mem_needed = (num_results * sizeof(wifi_gscan_result_t)) + |
| (num_scan_ids * GSCAN_BATCH_RESULT_HDR_LEN) + |
| VENDOR_REPLY_OVERHEAD + SCAN_RESULTS_COMPLETE_FLAG_LEN; |
| |
| if (mem_needed > (int32)NLMSG_DEFAULT_SIZE) { |
| mem_needed = (int32)NLMSG_DEFAULT_SIZE; |
| complete = 0; |
| } else { |
| complete = 1; |
| } |
| |
| WL_TRACE(("complete %d mem_needed %d max_mem %d\n", complete, mem_needed, |
| (int)NLMSG_DEFAULT_SIZE)); |
| /* Alloc the SKB for vendor_event */ |
| skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, mem_needed); |
| if (unlikely(!skb)) { |
| WL_ERR(("skb alloc failed")); |
| dhd_dev_pno_unlock_access_batch_results(bcmcfg_to_prmry_ndev(cfg)); |
| return -ENOMEM; |
| } |
| iter = results; |
| |
| nla_put_u32(skb, GSCAN_ATTRIBUTE_SCAN_RESULTS_COMPLETE, complete); |
| mem_needed = mem_needed - (SCAN_RESULTS_COMPLETE_FLAG_LEN + VENDOR_REPLY_OVERHEAD); |
| while (iter && ((mem_needed - GSCAN_BATCH_RESULT_HDR_LEN) > 0)) { |
| |
| scan_hdr = nla_nest_start(skb, GSCAN_ATTRIBUTE_SCAN_RESULTS); |
| nla_put_u32(skb, GSCAN_ATTRIBUTE_SCAN_ID, iter->scan_id); |
| nla_put_u8(skb, GSCAN_ATTRIBUTE_SCAN_FLAGS, iter->flag); |
| |
| num_results_iter = |
| (mem_needed - GSCAN_BATCH_RESULT_HDR_LEN)/sizeof(wifi_gscan_result_t); |
| |
| if ((iter->tot_count - iter->tot_consumed) < num_results_iter) |
| num_results_iter = iter->tot_count - iter->tot_consumed; |
| nla_put_u32(skb, GSCAN_ATTRIBUTE_NUM_OF_RESULTS, num_results_iter); |
| if (num_results_iter) { |
| ptr = &iter->results[iter->tot_consumed]; |
| iter->tot_consumed += num_results_iter; |
| nla_put(skb, GSCAN_ATTRIBUTE_SCAN_RESULTS, |
| num_results_iter * sizeof(wifi_gscan_result_t), ptr); |
| } |
| nla_nest_end(skb, scan_hdr); |
| mem_needed -= GSCAN_BATCH_RESULT_HDR_LEN + |
| (num_results_iter * sizeof(wifi_gscan_result_t)); |
| iter = iter->next; |
| } |
| |
| dhd_dev_gscan_batch_cache_cleanup(bcmcfg_to_prmry_ndev(cfg)); |
| dhd_dev_pno_unlock_access_batch_results(bcmcfg_to_prmry_ndev(cfg)); |
| |
| return cfg80211_vendor_cmd_reply(skb); |
| } |
| |
| static int |
| wl_cfgvendor_initiate_gscan(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| int type, tmp = len; |
| int run = 0xFF; |
| int flush = 0; |
| const struct nlattr *iter; |
| |
| nla_for_each_attr(iter, data, len, tmp) { |
| type = nla_type(iter); |
| if (type == GSCAN_ATTRIBUTE_ENABLE_FEATURE) |
| run = nla_get_u32(iter); |
| else if (type == GSCAN_ATTRIBUTE_FLUSH_FEATURE) |
| flush = nla_get_u32(iter); |
| } |
| |
| if (run != 0xFF) { |
| err = dhd_dev_pno_run_gscan(bcmcfg_to_prmry_ndev(cfg), run, flush); |
| |
| if (unlikely(err)) { |
| WL_ERR(("Could not run gscan:%d \n", err)); |
| } |
| return err; |
| } else { |
| return -EINVAL; |
| } |
| |
| |
| } |
| |
| static int |
| wl_cfgvendor_enable_full_scan_result(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| int type; |
| bool real_time = FALSE; |
| |
| type = nla_type(data); |
| |
| if (type == GSCAN_ATTRIBUTE_ENABLE_FULL_SCAN_RESULTS) { |
| real_time = nla_get_u32(data); |
| |
| err = dhd_dev_pno_enable_full_scan_result(bcmcfg_to_prmry_ndev(cfg), real_time); |
| |
| if (unlikely(err)) { |
| WL_ERR(("Could not run gscan:%d \n", err)); |
| } |
| |
| } else { |
| err = -EINVAL; |
| } |
| |
| return err; |
| } |
| |
| static int |
| wl_cfgvendor_set_scan_cfg(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| gscan_scan_params_t *scan_param; |
| int j = 0; |
| int type, tmp, tmp1, tmp2, k = 0; |
| const struct nlattr *iter, *iter1, *iter2; |
| struct dhd_pno_gscan_channel_bucket *ch_bucket; |
| |
| scan_param = kzalloc(sizeof(gscan_scan_params_t), GFP_KERNEL); |
| if (!scan_param) { |
| WL_ERR(("Could not set GSCAN scan cfg, mem alloc failure\n")); |
| err = -EINVAL; |
| return err; |
| |
| } |
| |
| scan_param->scan_fr = PNO_SCAN_MIN_FW_SEC; |
| nla_for_each_attr(iter, data, len, tmp) { |
| type = nla_type(iter); |
| |
| if (j >= GSCAN_MAX_CH_BUCKETS) { |
| break; |
| } |
| |
| switch (type) { |
| case GSCAN_ATTRIBUTE_BASE_PERIOD: |
| scan_param->scan_fr = nla_get_u32(iter)/1000; |
| break; |
| case GSCAN_ATTRIBUTE_NUM_BUCKETS: |
| scan_param->nchannel_buckets = nla_get_u32(iter); |
| break; |
| case GSCAN_ATTRIBUTE_CH_BUCKET_1: |
| case GSCAN_ATTRIBUTE_CH_BUCKET_2: |
| case GSCAN_ATTRIBUTE_CH_BUCKET_3: |
| case GSCAN_ATTRIBUTE_CH_BUCKET_4: |
| case GSCAN_ATTRIBUTE_CH_BUCKET_5: |
| case GSCAN_ATTRIBUTE_CH_BUCKET_6: |
| case GSCAN_ATTRIBUTE_CH_BUCKET_7: |
| nla_for_each_nested(iter1, iter, tmp1) { |
| type = nla_type(iter1); |
| ch_bucket = |
| scan_param->channel_bucket; |
| |
| switch (type) { |
| case GSCAN_ATTRIBUTE_BUCKET_ID: |
| break; |
| case GSCAN_ATTRIBUTE_BUCKET_PERIOD: |
| ch_bucket[j].bucket_freq_multiple = |
| nla_get_u32(iter1)/1000; |
| break; |
| case GSCAN_ATTRIBUTE_BUCKET_NUM_CHANNELS: |
| ch_bucket[j].num_channels = |
| nla_get_u32(iter1); |
| break; |
| case GSCAN_ATTRIBUTE_BUCKET_CHANNELS: |
| nla_for_each_nested(iter2, iter1, tmp2) { |
| if (k >= PFN_SWC_RSSI_WINDOW_MAX) |
| break; |
| ch_bucket[j].chan_list[k] = |
| nla_get_u32(iter2); |
| k++; |
| } |
| k = 0; |
| break; |
| case GSCAN_ATTRIBUTE_BUCKETS_BAND: |
| ch_bucket[j].band = (uint16) |
| nla_get_u32(iter1); |
| break; |
| case GSCAN_ATTRIBUTE_REPORT_EVENTS: |
| ch_bucket[j].report_flag = (uint8) |
| nla_get_u32(iter1); |
| break; |
| default: |
| WL_ERR(("bucket attribute type error %d\n", |
| type)); |
| break; |
| } |
| } |
| j++; |
| break; |
| default: |
| WL_ERR(("Unknown type %d\n", type)); |
| break; |
| } |
| } |
| |
| if (dhd_dev_pno_set_cfg_gscan(bcmcfg_to_prmry_ndev(cfg), |
| DHD_PNO_SCAN_CFG_ID, scan_param, 0) < 0) { |
| WL_ERR(("Could not set GSCAN scan cfg\n")); |
| err = -EINVAL; |
| } |
| |
| kfree(scan_param); |
| return err; |
| |
| } |
| |
| static int |
| wl_cfgvendor_hotlist_cfg(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| gscan_hotlist_scan_params_t *hotlist_params; |
| int tmp, tmp1, tmp2, type, j = 0, dummy; |
| const struct nlattr *outer, *inner, *iter; |
| uint8 flush = 0; |
| struct bssid_t *pbssid; |
| |
| hotlist_params = (gscan_hotlist_scan_params_t *)kzalloc(len, GFP_KERNEL); |
| if (!hotlist_params) { |
| WL_ERR(("Cannot Malloc mem to parse config commands size - %d bytes \n", len)); |
| return -ENOMEM; |
| } |
| |
| hotlist_params->lost_ap_window = GSCAN_LOST_AP_WINDOW_DEFAULT; |
| |
| nla_for_each_attr(iter, data, len, tmp2) { |
| type = nla_type(iter); |
| switch (type) { |
| case GSCAN_ATTRIBUTE_HOTLIST_BSSIDS: |
| pbssid = hotlist_params->bssid; |
| nla_for_each_nested(outer, iter, tmp) { |
| nla_for_each_nested(inner, outer, tmp1) { |
| type = nla_type(inner); |
| |
| switch (type) { |
| case GSCAN_ATTRIBUTE_BSSID: |
| memcpy(&(pbssid[j].macaddr), |
| nla_data(inner), ETHER_ADDR_LEN); |
| break; |
| case GSCAN_ATTRIBUTE_RSSI_LOW: |
| pbssid[j].rssi_reporting_threshold = |
| (int8) nla_get_u8(inner); |
| break; |
| case GSCAN_ATTRIBUTE_RSSI_HIGH: |
| dummy = (int8) nla_get_u8(inner); |
| break; |
| default: |
| WL_ERR(("ATTR unknown %d\n", |
| type)); |
| break; |
| } |
| } |
| j++; |
| } |
| hotlist_params->nbssid = j; |
| break; |
| case GSCAN_ATTRIBUTE_HOTLIST_FLUSH: |
| flush = nla_get_u8(iter); |
| break; |
| case GSCAN_ATTRIBUTE_LOST_AP_SAMPLE_SIZE: |
| hotlist_params->lost_ap_window = nla_get_u32(iter); |
| break; |
| default: |
| WL_ERR(("Unknown type %d\n", type)); |
| break; |
| } |
| |
| } |
| |
| if (dhd_dev_pno_set_cfg_gscan(bcmcfg_to_prmry_ndev(cfg), |
| DHD_PNO_GEOFENCE_SCAN_CFG_ID, |
| hotlist_params, flush) < 0) { |
| WL_ERR(("Could not set GSCAN HOTLIST cfg\n")); |
| err = -EINVAL; |
| goto exit; |
| } |
| exit: |
| kfree(hotlist_params); |
| return err; |
| } |
| static int |
| wl_cfgvendor_set_batch_scan_cfg(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0, tmp, type; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| gscan_batch_params_t batch_param; |
| const struct nlattr *iter; |
| |
| batch_param.mscan = batch_param.bestn = 0; |
| batch_param.buffer_threshold = GSCAN_BATCH_NO_THR_SET; |
| |
| nla_for_each_attr(iter, data, len, tmp) { |
| type = nla_type(iter); |
| |
| switch (type) { |
| case GSCAN_ATTRIBUTE_NUM_AP_PER_SCAN: |
| batch_param.bestn = nla_get_u32(iter); |
| break; |
| case GSCAN_ATTRIBUTE_NUM_SCANS_TO_CACHE: |
| batch_param.mscan = nla_get_u32(iter); |
| break; |
| case GSCAN_ATTRIBUTE_REPORT_THRESHOLD: |
| batch_param.buffer_threshold = nla_get_u32(iter); |
| break; |
| default: |
| WL_ERR(("Unknown type %d\n", type)); |
| break; |
| } |
| } |
| |
| if (dhd_dev_pno_set_cfg_gscan(bcmcfg_to_prmry_ndev(cfg), |
| DHD_PNO_BATCH_SCAN_CFG_ID, |
| &batch_param, 0) < 0) { |
| WL_ERR(("Could not set batch cfg\n")); |
| err = -EINVAL; |
| return err; |
| } |
| |
| return err; |
| } |
| |
| static int |
| wl_cfgvendor_significant_change_cfg(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| gscan_swc_params_t *significant_params; |
| int tmp, tmp1, tmp2, type, j = 0; |
| const struct nlattr *outer, *inner, *iter; |
| bool flush = FALSE; |
| wl_pfn_significant_bssid_t *pbssid; |
| uint16 num_bssid = 0; |
| uint16 max_buf_size = sizeof(gscan_swc_params_t) + |
| sizeof(wl_pfn_significant_bssid_t) * (PFN_SWC_MAX_NUM_APS - 1); |
| |
| significant_params = kzalloc(max_buf_size, GFP_KERNEL); |
| if (!significant_params) { |
| WL_ERR(("Cannot Malloc mem size:%d\n", len)); |
| return BCME_NOMEM; |
| } |
| |
| nla_for_each_attr(iter, data, len, tmp2) { |
| type = nla_type(iter); |
| |
| switch (type) { |
| case GSCAN_ATTRIBUTE_SIGNIFICANT_CHANGE_FLUSH: |
| flush = (bool) nla_get_u8(iter); |
| break; |
| case GSCAN_ATTRIBUTE_RSSI_SAMPLE_SIZE: |
| significant_params->rssi_window = nla_get_u16(iter); |
| break; |
| case GSCAN_ATTRIBUTE_LOST_AP_SAMPLE_SIZE: |
| significant_params->lost_ap_window = nla_get_u16(iter); |
| break; |
| case GSCAN_ATTRIBUTE_MIN_BREACHING: |
| significant_params->swc_threshold = nla_get_u16(iter); |
| break; |
| case GSCAN_ATTRIBUTE_NUM_BSSID: |
| num_bssid = nla_get_u16(iter); |
| if (num_bssid > PFN_SWC_MAX_NUM_APS) { |
| WL_ERR(("ovar max SWC bssids:%d\n", |
| num_bssid)); |
| err = BCME_BADARG; |
| goto exit; |
| } |
| break; |
| case GSCAN_ATTRIBUTE_SIGNIFICANT_CHANGE_BSSIDS: |
| if (num_bssid == 0) { |
| WL_ERR(("num_bssid : 0\n")); |
| err = BCME_BADARG; |
| goto exit; |
| } |
| pbssid = significant_params->bssid_elem_list; |
| nla_for_each_nested(outer, iter, tmp) { |
| if (j >= num_bssid) { |
| j++; |
| break; |
| } |
| nla_for_each_nested(inner, outer, tmp1) { |
| switch (nla_type(inner)) { |
| case GSCAN_ATTRIBUTE_BSSID: |
| memcpy(&(pbssid[j].macaddr), |
| nla_data(inner), |
| ETHER_ADDR_LEN); |
| break; |
| case GSCAN_ATTRIBUTE_RSSI_HIGH: |
| pbssid[j].rssi_high_threshold = |
| (int8) nla_get_u8(inner); |
| break; |
| case GSCAN_ATTRIBUTE_RSSI_LOW: |
| pbssid[j].rssi_low_threshold = |
| (int8) nla_get_u8(inner); |
| break; |
| default: |
| WL_ERR(("ATTR unknown %d\n", |
| type)); |
| break; |
| } |
| } |
| j++; |
| } |
| break; |
| default: |
| WL_ERR(("Unknown type %d\n", type)); |
| break; |
| } |
| } |
| if (j != num_bssid) { |
| WL_ERR(("swc bssids count:%d not matched to num_bssid:%d\n", |
| j, num_bssid)); |
| err = BCME_BADARG; |
| goto exit; |
| } |
| significant_params->nbssid = j; |
| |
| if (dhd_dev_pno_set_cfg_gscan(bcmcfg_to_prmry_ndev(cfg), |
| DHD_PNO_SIGNIFICANT_SCAN_CFG_ID, |
| significant_params, flush) < 0) { |
| WL_ERR(("Could not set GSCAN significant cfg\n")); |
| err = BCME_ERROR; |
| goto exit; |
| } |
| exit: |
| kfree(significant_params); |
| return err; |
| } |
| #endif /* GSCAN_SUPPORT */ |
| |
| #ifdef RTT_SUPPORT |
| void |
| wl_cfgvendor_rtt_evt(void *ctx, void *rtt_data) |
| { |
| struct wireless_dev *wdev = (struct wireless_dev *)ctx; |
| struct wiphy *wiphy; |
| struct sk_buff *skb; |
| uint32 evt_complete = 0; |
| gfp_t kflags; |
| rtt_result_t *rtt_result; |
| rtt_results_header_t *rtt_header; |
| struct list_head *rtt_cache_list; |
| struct nlattr *rtt_nl_hdr; |
| wiphy = wdev->wiphy; |
| |
| WL_DBG(("In\n")); |
| /* Push the data to the skb */ |
| if (!rtt_data) { |
| WL_ERR(("rtt_data is NULL\n")); |
| return; |
| } |
| rtt_cache_list = (struct list_head *)rtt_data; |
| kflags = in_atomic() ? GFP_ATOMIC : GFP_KERNEL; |
| if (list_empty(rtt_cache_list)) { |
| #if (defined(CONFIG_ARCH_MSM) && defined(SUPPORT_WDEV_CFG80211_VENDOR_EVENT_ALLOC)) || \ |
| LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) |
| skb = cfg80211_vendor_event_alloc(wiphy, NULL, 100, |
| GOOGLE_RTT_COMPLETE_EVENT, kflags); |
| #else |
| skb = cfg80211_vendor_event_alloc(wiphy, 100, GOOGLE_RTT_COMPLETE_EVENT, kflags); |
| #endif /* (defined(CONFIG_ARCH_MSM) && defined(SUPPORT_WDEV_CFG80211_VENDOR_EVENT_ALLOC)) || */ |
| /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) */ |
| if (!skb) { |
| WL_ERR(("skb alloc failed")); |
| return; |
| } |
| evt_complete = 1; |
| nla_put_u32(skb, RTT_ATTRIBUTE_RESULTS_COMPLETE, evt_complete); |
| cfg80211_vendor_event(skb, kflags); |
| return; |
| } |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wcast-qual" |
| #endif // endif |
| list_for_each_entry(rtt_header, rtt_cache_list, list) { |
| /* Alloc the SKB for vendor_event */ |
| #if (defined(CONFIG_ARCH_MSM) && defined(SUPPORT_WDEV_CFG80211_VENDOR_EVENT_ALLOC)) || \ |
| LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) |
| skb = cfg80211_vendor_event_alloc(wiphy, NULL, rtt_header->result_tot_len + 100, |
| GOOGLE_RTT_COMPLETE_EVENT, kflags); |
| #else |
| skb = cfg80211_vendor_event_alloc(wiphy, rtt_header->result_tot_len + 100, |
| GOOGLE_RTT_COMPLETE_EVENT, kflags); |
| #endif /* (defined(CONFIG_ARCH_MSM) && defined(SUPPORT_WDEV_CFG80211_VENDOR_EVENT_ALLOC)) || */ |
| /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) */ |
| if (!skb) { |
| WL_ERR(("skb alloc failed")); |
| return; |
| } |
| if (list_is_last(&rtt_header->list, rtt_cache_list)) { |
| evt_complete = 1; |
| } |
| nla_put_u32(skb, RTT_ATTRIBUTE_RESULTS_COMPLETE, evt_complete); |
| rtt_nl_hdr = nla_nest_start(skb, RTT_ATTRIBUTE_RESULTS_PER_TARGET); |
| if (!rtt_nl_hdr) { |
| WL_ERR(("rtt_nl_hdr is NULL\n")); |
| break; |
| } |
| nla_put(skb, RTT_ATTRIBUTE_TARGET_MAC, ETHER_ADDR_LEN, &rtt_header->peer_mac); |
| nla_put_u32(skb, RTT_ATTRIBUTE_RESULT_CNT, rtt_header->result_cnt); |
| list_for_each_entry(rtt_result, &rtt_header->result_list, list) { |
| nla_put(skb, RTT_ATTRIBUTE_RESULT, |
| rtt_result->report_len, &rtt_result->report); |
| } |
| nla_nest_end(skb, rtt_nl_hdr); |
| cfg80211_vendor_event(skb, kflags); |
| } |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) |
| #pragma GCC diagnostic pop |
| #endif // endif |
| } |
| |
| static int |
| wl_cfgvendor_rtt_set_config(struct wiphy *wiphy, struct wireless_dev *wdev, |
| const void *data, int len) { |
| int err = 0, rem, rem1, rem2, type; |
| int target_cnt; |
| rtt_config_params_t rtt_param; |
| rtt_target_info_t* rtt_target = NULL; |
| const struct nlattr *iter, *iter1, *iter2; |
| int8 eabuf[ETHER_ADDR_STR_LEN]; |
| int8 chanbuf[CHANSPEC_STR_LEN]; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| rtt_capabilities_t capability; |
| |
| WL_DBG(("In\n")); |
| err = dhd_dev_rtt_register_noti_callback(wdev->netdev, wdev, wl_cfgvendor_rtt_evt); |
| if (err < 0) { |
| WL_ERR(("failed to register rtt_noti_callback\n")); |
| goto exit; |
| } |
| err = dhd_dev_rtt_capability(bcmcfg_to_prmry_ndev(cfg), &capability); |
| if (err < 0) { |
| WL_ERR(("failed to get the capability\n")); |
| goto exit; |
| } |
| |
| memset(&rtt_param, 0, sizeof(rtt_param)); |
| if (len <= 0) { |
| WL_ERR(("Length of the nlattr is not valid len : %d\n", len)); |
| err = BCME_ERROR; |
| goto exit; |
| } |
| nla_for_each_attr(iter, data, len, rem) { |
| type = nla_type(iter); |
| switch (type) { |
| case RTT_ATTRIBUTE_TARGET_CNT: |
| target_cnt = nla_get_u8(iter); |
| if ((target_cnt <= 0) || |
| (target_cnt > RTT_MAX_TARGET_CNT)) { |
| WL_ERR(("target_cnt is not valid: %d", |
| target_cnt)); |
| err = BCME_RANGE; |
| goto exit; |
| } |
| rtt_param.rtt_target_cnt = target_cnt; |
| rtt_param.target_info = kzalloc(TARGET_INFO_SIZE(target_cnt), GFP_KERNEL); |
| if (rtt_param.target_info == NULL) { |
| WL_ERR(("failed to allocate target info for (%d)\n", target_cnt)); |
| err = BCME_NOMEM; |
| goto exit; |
| } |
| break; |
| case RTT_ATTRIBUTE_TARGET_INFO: |
| /* Added this variable for safe check to avoid crash |
| * incase the caller did not respect the order |
| */ |
| if (!rtt_param.target_info) { |
| err = BCME_NOMEM; |
| goto exit; |
| } |
| rtt_target = rtt_param.target_info; |
| nla_for_each_nested(iter1, iter, rem1) { |
| nla_for_each_nested(iter2, iter1, rem2) { |
| type = nla_type(iter2); |
| switch (type) { |
| case RTT_ATTRIBUTE_TARGET_MAC: |
| memcpy(&rtt_target->addr, nla_data(iter2), |
| ETHER_ADDR_LEN); |
| break; |
| case RTT_ATTRIBUTE_TARGET_TYPE: |
| rtt_target->type = nla_get_u8(iter2); |
| if (rtt_target->type == RTT_INVALID || |
| (rtt_target->type == RTT_ONE_WAY && |
| !capability.rtt_one_sided_supported)) { |
| WL_ERR(("doesn't support RTT type" |
| " : %d\n", |
| rtt_target->type)); |
| err = -EINVAL; |
| goto exit; |
| } |
| break; |
| case RTT_ATTRIBUTE_TARGET_PEER: |
| rtt_target->peer = nla_get_u8(iter2); |
| break; |
| case RTT_ATTRIBUTE_TARGET_CHAN: |
| memcpy(&rtt_target->channel, nla_data(iter2), |
| sizeof(rtt_target->channel)); |
| break; |
| case RTT_ATTRIBUTE_TARGET_PERIOD: |
| rtt_target->burst_period = nla_get_u32(iter2); |
| if (rtt_target->burst_period < 32) { |
| rtt_target->burst_period *= 100; /* 100 ms unit */ |
| } else { |
| WL_ERR(("%d value must in (0-31)\n", rtt_target->burst_period)); |
| err = EINVAL; |
| goto exit; |
| } |
| break; |
| case RTT_ATTRIBUTE_TARGET_NUM_BURST: |
| rtt_target->num_burst = nla_get_u32(iter2); |
| if (rtt_target->num_burst > 16) { |
| WL_ERR(("%d value must in (0-15)\n", |
| rtt_target->num_burst)); |
| err = -EINVAL; |
| goto exit; |
| } |
| rtt_target->num_burst = BIT(rtt_target->num_burst); |
| break; |
| case RTT_ATTRIBUTE_TARGET_NUM_FTM_BURST: |
| rtt_target->num_frames_per_burst = |
| nla_get_u32(iter2); |
| break; |
| case RTT_ATTRIBUTE_TARGET_NUM_RETRY_FTM: |
| rtt_target->num_retries_per_ftm = |
| nla_get_u32(iter2); |
| break; |
| case RTT_ATTRIBUTE_TARGET_NUM_RETRY_FTMR: |
| rtt_target->num_retries_per_ftmr = |
| nla_get_u32(iter2); |
| if (rtt_target->num_retries_per_ftmr > 3) { |
| WL_ERR(("%d value must in (0-3)\n", |
| rtt_target->num_retries_per_ftmr)); |
| err = -EINVAL; |
| goto exit; |
| } |
| break; |
| case RTT_ATTRIBUTE_TARGET_LCI: |
| rtt_target->LCI_request = nla_get_u8(iter2); |
| break; |
| case RTT_ATTRIBUTE_TARGET_LCR: |
| rtt_target->LCI_request = nla_get_u8(iter2); |
| break; |
| case RTT_ATTRIBUTE_TARGET_BURST_DURATION: |
| if ((nla_get_u32(iter2) > 1 && |
| nla_get_u32(iter2) < 12)) { |
| rtt_target->burst_duration = |
| dhd_rtt_idx_to_burst_duration(nla_get_u32(iter2)); |
| } else if (nla_get_u32(iter2) == 15) { |
| /* use default value */ |
| rtt_target->burst_duration = 128; //ms |
| } else { |
| WL_ERR(("%d value must in (2-11) or 15\n", |
| nla_get_u32(iter2))); |
| err = -EINVAL; |
| goto exit; |
| } |
| break; |
| case RTT_ATTRIBUTE_TARGET_BW: |
| rtt_target->bw = nla_get_u8(iter2); |
| break; |
| case RTT_ATTRIBUTE_TARGET_PREAMBLE: |
| rtt_target->preamble = nla_get_u8(iter2); |
| break; |
| } |
| } |
| /* convert to chanspec value */ |
| rtt_target->chanspec = |
| dhd_rtt_convert_to_chspec(rtt_target->channel); |
| if (rtt_target->chanspec == 0) { |
| WL_ERR(("Channel is not valid \n")); |
| err = -EINVAL; |
| goto exit; |
| } |
| WL_INFORM(("Target addr %s, Channel : %s for RTT \n", |
| bcm_ether_ntoa((const struct ether_addr *)&rtt_target->addr, |
| eabuf), |
| wf_chspec_ntoa(rtt_target->chanspec, chanbuf))); |
| rtt_target++; |
| } |
| break; |
| } |
| } |
| WL_DBG(("leave :target_cnt : %d\n", rtt_param.rtt_target_cnt)); |
| if (dhd_dev_rtt_set_cfg(bcmcfg_to_prmry_ndev(cfg), &rtt_param) < 0) { |
| WL_ERR(("Could not set RTT configuration\n")); |
| err = -EINVAL; |
| } |
| exit: |
| /* free the target info list */ |
| kfree(rtt_param.target_info); |
| rtt_param.target_info = NULL; |
| return err; |
| } |
| |
| static int |
| wl_cfgvendor_rtt_cancel_config(struct wiphy *wiphy, struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| int err = 0, rem, type, target_cnt = 0; |
| int target_idx = 0; |
| const struct nlattr *iter; |
| struct ether_addr *mac_list = NULL; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| |
| if (len <= 0) { |
| WL_ERR(("Length of nlattr is not valid len : %d\n", len)); |
| err = -EINVAL; |
| goto exit; |
| } |
| nla_for_each_attr(iter, data, len, rem) { |
| type = nla_type(iter); |
| switch (type) { |
| case RTT_ATTRIBUTE_TARGET_CNT: |
| if (mac_list != NULL) { |
| WL_ERR(("mac_list is not NULL\n")); |
| err = -EINVAL; |
| goto exit; |
| } |
| target_cnt = nla_get_u8(iter); |
| if ((target_cnt > 0) && |
| (target_cnt < RTT_MAX_TARGET_CNT)) { |
| mac_list = (struct ether_addr *)kzalloc(target_cnt * ETHER_ADDR_LEN, |
| GFP_KERNEL); |
| if (mac_list == NULL) { |
| WL_ERR(("failed to allocate mem for mac list\n")); |
| err = -EINVAL; |
| goto exit; |
| } |
| } else { |
| /* cancel the current whole RTT process */ |
| goto cancel; |
| } |
| break; |
| case RTT_ATTRIBUTE_TARGET_MAC: |
| if (!mac_list) { |
| WL_ERR(("ATTRIBUTE_TARGET_CNT not found before " |
| " ATTRIBUTE_TARGET_MAC\n")); |
| err = -EINVAL; |
| goto exit; |
| } |
| |
| if (target_idx >= target_cnt) { |
| WL_ERR(("More TARGET_MAC entries found, " |
| "expected TARGET_CNT:%d\n", target_cnt)); |
| err = -EINVAL; |
| goto exit; |
| } |
| |
| if (nla_len(iter) != ETHER_ADDR_LEN) { |
| WL_ERR(("Invalid TARGET_MAC ATTR len :%d\n", nla_len(iter))); |
| err = -EINVAL; |
| goto exit; |
| } |
| |
| memcpy(&mac_list[target_idx], nla_data(iter), ETHER_ADDR_LEN); |
| target_idx++; |
| break; |
| default: |
| WL_ERR(("Uknown type : %d\n", type)); |
| err = -EINVAL; |
| goto exit; |
| } |
| } |
| cancel: |
| if (dhd_dev_rtt_cancel_cfg(bcmcfg_to_prmry_ndev(cfg), mac_list, target_cnt) < 0) { |
| WL_ERR(("Could not cancel RTT configuration\n")); |
| err = -EINVAL; |
| } |
| |
| exit: |
| if (mac_list) { |
| kfree(mac_list); |
| } |
| return err; |
| } |
| |
| static int |
| wl_cfgvendor_rtt_get_capability(struct wiphy *wiphy, struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| rtt_capabilities_t capability; |
| |
| err = dhd_dev_rtt_capability(bcmcfg_to_prmry_ndev(cfg), &capability); |
| if (unlikely(err)) { |
| WL_ERR(("Vendor Command reply failed ret:%d \n", err)); |
| goto exit; |
| } |
| err = wl_cfgvendor_send_cmd_reply(wiphy, bcmcfg_to_prmry_ndev(cfg), |
| &capability, sizeof(capability)); |
| |
| if (unlikely(err)) { |
| WL_ERR(("Vendor Command reply failed ret:%d \n", err)); |
| } |
| exit: |
| return err; |
| } |
| |
| static int |
| get_responder_info(struct bcm_cfg80211 *cfg, |
| struct wifi_rtt_responder *responder_info) |
| { |
| int err = 0; |
| rtt_capabilities_t capability; |
| |
| err = dhd_dev_rtt_capability(bcmcfg_to_prmry_ndev(cfg), &capability); |
| if (unlikely(err)) { |
| WL_ERR(("Could not get responder capability:%d \n", err)); |
| return err; |
| } |
| if (capability.preamble_support & RTT_PREAMBLE_VHT) { |
| responder_info->preamble |= RTT_PREAMBLE_VHT; |
| } |
| if (capability.preamble_support & RTT_PREAMBLE_HT) { |
| responder_info->preamble |= RTT_PREAMBLE_HT; |
| } |
| err = dhd_dev_rtt_avail_channel(bcmcfg_to_prmry_ndev(cfg), &(responder_info->channel)); |
| if (unlikely(err)) { |
| WL_ERR(("Could not get available channel:%d \n", err)); |
| return err; |
| } |
| return err; |
| } |
| |
| static int |
| wl_cfgvendor_rtt_get_responder_info(struct wiphy *wiphy, struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| wifi_rtt_responder_t responder_info; |
| |
| WL_DBG(("Recv -get_avail_ch command \n")); |
| |
| memset(&responder_info, 0, sizeof(responder_info)); |
| err = get_responder_info(cfg, &responder_info); |
| if (unlikely(err)) { |
| WL_ERR(("Failed to get responder info:%d \n", err)); |
| return err; |
| } |
| |
| err = wl_cfgvendor_send_cmd_reply(wiphy, bcmcfg_to_prmry_ndev(cfg), |
| &responder_info, sizeof(responder_info)); |
| |
| if (unlikely(err)) { |
| WL_ERR(("Vendor cmd reply for -get_avail_ch failed ret:%d \n", err)); |
| } |
| return err; |
| } |
| |
| static int |
| wl_cfgvendor_rtt_set_responder(struct wiphy *wiphy, struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| wifi_rtt_responder_t responder_info; |
| |
| WL_DBG(("Recv rtt -enable_resp cmd.\n")); |
| |
| memset(&responder_info, 0, sizeof(responder_info)); |
| err = dhd_dev_rtt_enable_responder(bcmcfg_to_prmry_ndev(cfg), &responder_info.channel); |
| if (unlikely(err)) { |
| WL_ERR(("Could not enable responder ret:%d \n", err)); |
| /* Continue further to get the current responder info. |
| * Responder info comprises of current channel and preamble supported. |
| * Setting responder failed but we can still send current reponder info. |
| * Userspace need this information. |
| */ |
| } |
| |
| err = get_responder_info(cfg, &responder_info); |
| if (unlikely(err)) { |
| WL_ERR(("Failed to get responder info:%d \n", err)); |
| return err; |
| } |
| |
| err = wl_cfgvendor_send_cmd_reply(wiphy, bcmcfg_to_prmry_ndev(cfg), |
| &responder_info, sizeof(responder_info)); |
| |
| if (unlikely(err)) { |
| WL_ERR(("Vendor cmd reply for -enable_resp failed ret:%d \n", err)); |
| } |
| return err; |
| } |
| |
| static int |
| wl_cfgvendor_rtt_cancel_responder(struct wiphy *wiphy, struct wireless_dev *wdev, |
| const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| |
| WL_DBG(("Recv rtt -cancel_resp cmd \n")); |
| |
| err = dhd_dev_rtt_cancel_responder(bcmcfg_to_prmry_ndev(cfg)); |
| if (unlikely(err)) { |
| WL_ERR(("Vendor cmd -cancel_resp failed ret:%d \n", err)); |
| } |
| return err; |
| } |
| #endif /* RTT_SUPPORT */ |
| |
| static int |
| wl_cfgvendor_priv_string_handler(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| int ret = 0; |
| int ret_len = 0, payload = 0, msglen; |
| const struct bcm_nlmsg_hdr *nlioc = data; |
| void *buf = NULL, *cur; |
| int maxmsglen = PAGE_SIZE - 0x100; |
| struct sk_buff *reply; |
| uint cmd = nlioc->cmd; |
| |
| WL_DBG(("entry: cmd = %d\n", cmd)); |
| |
| len -= sizeof(struct bcm_nlmsg_hdr); |
| ret_len = nlioc->len; |
| if (ret_len > 0 || len > 0) { |
| if (len > DHD_IOCTL_MAXLEN) { |
| WL_ERR(("oversize input buffer %d, cmd %d\n", len, cmd)); |
| len = DHD_IOCTL_MAXLEN; |
| } |
| if (ret_len > DHD_IOCTL_MAXLEN) { |
| WL_ERR(("oversize return buffer %d, cmd %d\n", ret_len, cmd)); |
| ret_len = DHD_IOCTL_MAXLEN; |
| } |
| payload = max(ret_len, len) + 1; |
| buf = vzalloc(payload); |
| if (!buf) { |
| return -ENOMEM; |
| } |
| memcpy(buf, (void *)nlioc + nlioc->offset, len); |
| *(char *)(buf + len) = '\0'; |
| } |
| |
| ret = dhd_cfgvendor_priv_string_handler(cfg, wdev, nlioc, buf, |
| nlioc->ifindex); |
| if (ret) { |
| goto done; |
| } |
| cur = buf; |
| while (ret_len > 0) { |
| msglen = nlioc->len > maxmsglen ? maxmsglen : ret_len; |
| ret_len -= msglen; |
| payload = msglen + sizeof(msglen); |
| reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, payload); |
| if (!reply) { |
| WL_ERR(("Failed to allocate reply msg, cmd %d\n", cmd)); |
| ret = -ENOMEM; |
| break; |
| } |
| |
| if (nla_put(reply, BCM_NLATTR_DATA, msglen, cur) || |
| nla_put_u16(reply, BCM_NLATTR_LEN, msglen)) { |
| kfree_skb(reply); |
| ret = -ENOBUFS; |
| break; |
| } |
| |
| ret = cfg80211_vendor_cmd_reply(reply); |
| if (ret) { |
| WL_ERR(("testmode reply failed:%d, cmd %d\n", ret, cmd)); |
| break; |
| } |
| cur += msglen; |
| } |
| |
| done: |
| vfree(buf); |
| return ret; |
| } |
| |
| #ifdef WL_SAE |
| static int |
| wl_cfgvendor_set_sae_password(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = BCME_OK; |
| struct net_device *net = wdev->netdev; |
| struct bcm_cfg80211 *cfg = wl_get_cfg(net); |
| wsec_pmk_t pmk; |
| s32 bssidx; |
| |
| WL_DBG(("%s enter\n", __func__)); |
| if ((bssidx = wl_get_bssidx_by_wdev(cfg, net->ieee80211_ptr)) < 0) { |
| WL_ERR(("Find p2p index from wdev(%p) failed\n", net->ieee80211_ptr)); |
| return BCME_ERROR; |
| } |
| |
| if (len < WSEC_MIN_PSK_LEN || len >= WSEC_MAX_PSK_LEN) { |
| WL_ERR(("Invalid passphrase length %d..should be >=8 and <=63\n", |
| len)); |
| err = BCME_BADLEN; |
| goto done; |
| } |
| /* Set AUTH to SAE */ |
| err = wldev_iovar_setint_bsscfg(net, "wpa_auth", WPA3_AUTH_SAE_PSK, bssidx); |
| if (unlikely(err)) { |
| WL_ERR(("could not set wpa_auth (0x%x)\n", err)); |
| goto done; |
| } |
| pmk.key_len = htod16(len); |
| bcopy((u8*)data, pmk.key, len); |
| pmk.flags = htod16(WSEC_PASSPHRASE); |
| |
| err = wldev_ioctl(net, WLC_SET_WSEC_PMK, &pmk, sizeof(pmk), true); |
| if (err) { |
| WL_ERR(("failed to set pmk %d\n", err)); |
| goto done; |
| } |
| done: |
| return err; |
| } |
| #endif /* WL_SAE */ |
| |
| static int |
| wl_cfgvendor_priv_bcm_handler(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| int err = 0; |
| int data_len = 0; |
| |
| WL_INFORM(("%s: Enter \n", __func__)); |
| |
| bzero(cfg->ioctl_buf, WLC_IOCTL_MAXLEN); |
| |
| if (strncmp((char *)data, BRCM_VENDOR_SCMD_CAPA, strlen(BRCM_VENDOR_SCMD_CAPA)) == 0) { |
| err = wldev_iovar_getbuf(bcmcfg_to_prmry_ndev(cfg), "cap", NULL, 0, |
| cfg->ioctl_buf, WLC_IOCTL_MAXLEN, &cfg->ioctl_buf_sync); |
| if (unlikely(err)) { |
| WL_ERR(("error (%d)\n", err)); |
| return err; |
| } |
| data_len = strlen(cfg->ioctl_buf); |
| cfg->ioctl_buf[data_len] = '\0'; |
| } |
| |
| err = wl_cfgvendor_send_cmd_reply(wiphy, bcmcfg_to_prmry_ndev(cfg), |
| cfg->ioctl_buf, data_len+1); |
| if (unlikely(err)) |
| WL_ERR(("Vendor Command reply failed ret:%d \n", err)); |
| else |
| WL_INFORM(("Vendor Command reply sent successfully!\n")); |
| |
| return err; |
| } |
| |
| #ifdef LINKSTAT_SUPPORT |
| #define NUM_RATE 32 |
| #define NUM_PEER 1 |
| #define NUM_CHAN 11 |
| #define HEADER_SIZE sizeof(ver_len) |
| static int wl_cfgvendor_lstats_get_info(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| static char iovar_buf[WLC_IOCTL_MAXLEN]; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| int err = 0, i; |
| wifi_iface_stat *iface; |
| wifi_radio_stat *radio; |
| wl_wme_cnt_t *wl_wme_cnt; |
| wl_cnt_v_le10_mcst_t *macstat_cnt; |
| wl_cnt_wlc_t *wlc_cnt; |
| scb_val_t scbval; |
| char *output; |
| |
| WL_INFORM(("%s: Enter \n", __func__)); |
| RETURN_EIO_IF_NOT_UP(cfg); |
| |
| bzero(cfg->ioctl_buf, WLC_IOCTL_MAXLEN); |
| bzero(iovar_buf, WLC_IOCTL_MAXLEN); |
| |
| output = cfg->ioctl_buf; |
| |
| err = wldev_iovar_getbuf(bcmcfg_to_prmry_ndev(cfg), "radiostat", NULL, 0, |
| iovar_buf, WLC_IOCTL_MAXLEN, NULL); |
| if (err != BCME_OK && err != BCME_UNSUPPORTED) { |
| WL_ERR(("error (%d) - size = %zu\n", err, sizeof(wifi_radio_stat))); |
| return err; |
| } |
| radio = (wifi_radio_stat *)iovar_buf; |
| radio->num_channels = NUM_CHAN; |
| memcpy(output, iovar_buf+HEADER_SIZE, sizeof(wifi_radio_stat)-HEADER_SIZE); |
| |
| output += (sizeof(wifi_radio_stat) - HEADER_SIZE); |
| output += (NUM_CHAN*sizeof(wifi_channel_stat)); |
| |
| err = wldev_iovar_getbuf(bcmcfg_to_prmry_ndev(cfg), "wme_counters", NULL, 0, |
| iovar_buf, WLC_IOCTL_MAXLEN, NULL); |
| if (unlikely(err)) { |
| WL_ERR(("error (%d)\n", err)); |
| return err; |
| } |
| wl_wme_cnt = (wl_wme_cnt_t *)iovar_buf; |
| iface = (wifi_iface_stat *)output; |
| |
| iface->ac[WIFI_AC_VO].ac = WIFI_AC_VO; |
| iface->ac[WIFI_AC_VO].tx_mpdu = wl_wme_cnt->tx[AC_VO].packets; |
| iface->ac[WIFI_AC_VO].rx_mpdu = wl_wme_cnt->rx[AC_VO].packets; |
| iface->ac[WIFI_AC_VO].mpdu_lost = wl_wme_cnt->tx_failed[WIFI_AC_VO].packets; |
| |
| iface->ac[WIFI_AC_VI].ac = WIFI_AC_VI; |
| iface->ac[WIFI_AC_VI].tx_mpdu = wl_wme_cnt->tx[AC_VI].packets; |
| iface->ac[WIFI_AC_VI].rx_mpdu = wl_wme_cnt->rx[AC_VI].packets; |
| iface->ac[WIFI_AC_VI].mpdu_lost = wl_wme_cnt->tx_failed[WIFI_AC_VI].packets; |
| |
| iface->ac[WIFI_AC_BE].ac = WIFI_AC_BE; |
| iface->ac[WIFI_AC_BE].tx_mpdu = wl_wme_cnt->tx[AC_BE].packets; |
| iface->ac[WIFI_AC_BE].rx_mpdu = wl_wme_cnt->rx[AC_BE].packets; |
| iface->ac[WIFI_AC_BE].mpdu_lost = wl_wme_cnt->tx_failed[WIFI_AC_BE].packets; |
| |
| iface->ac[WIFI_AC_BK].ac = WIFI_AC_BK; |
| iface->ac[WIFI_AC_BK].tx_mpdu = wl_wme_cnt->tx[AC_BK].packets; |
| iface->ac[WIFI_AC_BK].rx_mpdu = wl_wme_cnt->rx[AC_BK].packets; |
| iface->ac[WIFI_AC_BK].mpdu_lost = wl_wme_cnt->tx_failed[WIFI_AC_BK].packets; |
| bzero(iovar_buf, WLC_IOCTL_MAXLEN); |
| |
| err = wldev_iovar_getbuf(bcmcfg_to_prmry_ndev(cfg), "counters", NULL, 0, |
| iovar_buf, WLC_IOCTL_MAXLEN, NULL); |
| if (unlikely(err)) { |
| WL_ERR(("error (%d) - size = %zu\n", err, sizeof(wl_cnt_wlc_t))); |
| return err; |
| } |
| |
| /* Translate traditional (ver <= 10) counters struct to new xtlv type struct */ |
| err = wl_cntbuf_to_xtlv_format(NULL, iovar_buf, WL_CNTBUF_MAX_SIZE, 0); |
| if (err != BCME_OK) { |
| WL_ERR(("%s wl_cntbuf_to_xtlv_format ERR %d\n", __FUNCTION__, err)); |
| return err; |
| } |
| |
| if (!(wlc_cnt = GET_WLCCNT_FROM_CNTBUF(iovar_buf))) { |
| WL_ERR(("%s wlc_cnt NULL!\n", __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| iface->ac[WIFI_AC_BE].retries = wlc_cnt->txretry; |
| |
| if ((macstat_cnt = bcm_get_data_from_xtlv_buf(((wl_cnt_info_t *)iovar_buf)->data, |
| ((wl_cnt_info_t *)iovar_buf)->datalen, |
| WL_CNT_XTLV_CNTV_LE10_UCODE, NULL, |
| BCM_XTLV_OPTION_ALIGN32)) == NULL) { |
| macstat_cnt = bcm_get_data_from_xtlv_buf(((wl_cnt_info_t *)iovar_buf)->data, |
| ((wl_cnt_info_t *)iovar_buf)->datalen, |
| WL_CNT_XTLV_LT40_UCODE_V1, NULL, |
| BCM_XTLV_OPTION_ALIGN32); |
| } |
| |
| if (macstat_cnt == NULL) { |
| printf("wlmTxGetAckedPackets: macstat_cnt NULL!\n"); |
| return FALSE; |
| } |
| |
| iface->beacon_rx = macstat_cnt->rxbeaconmbss; |
| |
| err = wldev_get_rssi(bcmcfg_to_prmry_ndev(cfg), &scbval); |
| if (unlikely(err)) { |
| WL_ERR(("get_rssi error (%d)\n", err)); |
| return err; |
| } |
| iface->rssi_mgmt = scbval.val; |
| |
| iface->num_peers = NUM_PEER; |
| iface->peer_info->num_rate = NUM_RATE; |
| |
| bzero(iovar_buf, WLC_IOCTL_MAXLEN); |
| output = (char *)iface + sizeof(wifi_iface_stat) + NUM_PEER*sizeof(wifi_peer_info); |
| |
| err = wldev_iovar_getbuf(bcmcfg_to_prmry_ndev(cfg), "ratestat", NULL, 0, |
| iovar_buf, WLC_IOCTL_MAXLEN, NULL); |
| if (err != BCME_OK && err != BCME_UNSUPPORTED) { |
| WL_ERR(("error (%d) - size = %zu\n", err, NUM_RATE*sizeof(wifi_rate_stat))); |
| return err; |
| } |
| for (i = 0; i < NUM_RATE; i++) |
| memcpy(output, iovar_buf+HEADER_SIZE+i*sizeof(wifi_rate_stat), |
| sizeof(wifi_rate_stat)-HEADER_SIZE); |
| |
| err = wl_cfgvendor_send_cmd_reply(wiphy, bcmcfg_to_prmry_ndev(cfg), |
| cfg->ioctl_buf, |
| sizeof(wifi_radio_stat)-HEADER_SIZE + |
| NUM_CHAN*sizeof(wifi_channel_stat) + |
| sizeof(wifi_iface_stat)+NUM_PEER*sizeof(wifi_peer_info) + |
| NUM_RATE*(sizeof(wifi_rate_stat)-HEADER_SIZE)); |
| if (unlikely(err)) |
| WL_ERR(("Vendor Command reply failed ret:%d \n", err)); |
| |
| return err; |
| } |
| #endif /* LINKSTAT_SUPPORT */ |
| |
| #ifdef GWIFI_SUPPORT |
| static int wl_cfgvendor_interface_ops(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = BCME_ERROR, rem, type, opcode = -1; |
| const struct nlattr *iter; |
| char ifname[IFNAMSIZ] = {0}; |
| bool useFourAddr = false; |
| u32 iftype = NL80211_IFTYPE_STATION; |
| |
| nla_for_each_attr(iter, data, len, rem) { |
| type = nla_type(iter); |
| switch (type) { |
| case ANDR_WIFI_ATTRIBUTE_IFACE_OP_CODE: |
| opcode = nla_get_u8(iter); |
| WL_ERR(("%s: opcode = %d\n", __FUNCTION__, opcode)); |
| break; |
| |
| case ANDR_WIFI_ATTRIBUTE_IFACE_NAME_VALUE: |
| memcpy(ifname, nla_data(iter), |
| MIN(nla_len(iter), IFNAMSIZ)); |
| WL_ERR(("%s: ifname = %s\n", __FUNCTION__, ifname)); |
| break; |
| |
| case ANDR_WIFI_ATTRIBUTE_IFACE_TYPE_VALUE: |
| iftype = nla_get_u32(iter); |
| WL_ERR(("%s: iftype = %d\n", __FUNCTION__, iftype)); |
| break; |
| |
| case ANDR_WIFI_ATTRIBUTE_IFACE_4ADDR_VALUE: |
| useFourAddr = nla_get_u8(iter); |
| WL_ERR(("%s: use four addrress = %d\n", __FUNCTION__, useFourAddr)); |
| break; |
| |
| default: |
| WL_ERR(("Unknown type: %d\n", type)); |
| return err; |
| } |
| } |
| |
| switch(opcode) { |
| case GWIFI_INTERFACE_OP_CREATE: |
| err = wl_cfg80211_interface_create(wdev->netdev, ifname, iftype, useFourAddr); |
| if (err < 0) { |
| WL_ERR(("Create interface failed ret:%d\n", err)); |
| } |
| break; |
| |
| case GWIFI_INTERFACE_OP_REMOVE: |
| err = wl_cfg80211_interface_delete(wdev->netdev, ifname); |
| if (err < 0) { |
| WL_ERR(("Remove interface failed ret:%d\n", err)); |
| } |
| break; |
| |
| default: |
| err = BCME_ERROR; |
| WL_ERR(("Unsupported OP code %d ret:%d\n", opcode, err)); |
| } |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_station_get_mcs_data(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| int err = BCME_ERROR, rem, type; |
| const struct nlattr *iter; |
| struct ether_addr mac_addr; |
| char *output; |
| int ret; |
| |
| nla_for_each_attr(iter, data, len, rem) { |
| type = nla_type(iter); |
| switch (type) { |
| case ANDR_WIFI_ATTRIBUTE_STATION_MAC: |
| memcpy(mac_addr.octet, nla_data(iter), |
| MIN(nla_len(iter), ETHER_ADDR_LEN)); |
| WL_ERR(("%s: mac address = %02x:%02x:%02x:%02x:%02x:%02x\n", |
| __FUNCTION__, |
| mac_addr.octet[0], mac_addr.octet[1], |
| mac_addr.octet[2], mac_addr.octet[3], |
| mac_addr.octet[4], mac_addr.octet[5])); |
| break; |
| default: |
| WL_ERR(("Unknown type: %d\n", type)); |
| return err; |
| } |
| } |
| |
| bzero(cfg->ioctl_buf, WLC_IOCTL_MAXLEN); |
| |
| output = cfg->ioctl_buf; |
| |
| ret = dhd_dev_get_gwifi_station_mcs_metric(wdev->netdev, (void *)&mac_addr, |
| output, WLC_IOCTL_MAXLEN); |
| if (ret<0) { |
| if (ret == BCME_NOTFOUND) { |
| WL_ERR(("%s:Cannot find the station %02x:%02x:%02x:%02x:%02x:%02x\n", |
| __FUNCTION__, |
| mac_addr.octet[0], mac_addr.octet[1], |
| mac_addr.octet[2], mac_addr.octet[3], |
| mac_addr.octet[4], mac_addr.octet[5])); |
| } |
| return ret; |
| } |
| |
| WL_ERR(("%s: output len=%d\n", __FUNCTION__, ret)); |
| |
| err = wl_cfgvendor_send_cmd_reply(wiphy, bcmcfg_to_prmry_ndev(cfg), |
| cfg->ioctl_buf, ret); |
| if (unlikely(err)) |
| WL_ERR(("Vendor Command reply failed ret:%d \n", err)); |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_station_set_ps_monitor(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| int err = BCME_ERROR, rem, type; |
| const struct nlattr *iter; |
| |
| nla_for_each_attr(iter, data, len, rem) { |
| type = nla_type(iter); |
| switch (type) { |
| case ANDR_WIFI_ATTRIBUTE_MONITOR_STA_PS: |
| cfg->isMonitorStaPsOn = nla_get_u8(iter); |
| WL_ERR(("%s: STA Monitor PS %s\n", |
| __FUNCTION__, (cfg->isMonitorStaPsOn) ? "on" : "off")); |
| break; |
| default: |
| WL_ERR(("Unknown type: %d\n", type)); |
| return err; |
| } |
| } |
| |
| return BCME_OK; |
| } |
| |
| static int wl_cfgvendor_station_get_ps_state(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| int err = BCME_ERROR, rem, type; |
| const struct nlattr *iter; |
| struct ether_addr mac_addr; |
| char *output; |
| int ret; |
| |
| nla_for_each_attr(iter, data, len, rem) { |
| type = nla_type(iter); |
| switch (type) { |
| case ANDR_WIFI_ATTRIBUTE_STATION_MAC: |
| memcpy(mac_addr.octet, nla_data(iter), |
| MIN(nla_len(iter), ETHER_ADDR_LEN)); |
| WL_ERR(("%s: mac address = %02x:%02x:%02x:%02x:%02x:%02x\n", |
| __FUNCTION__, |
| mac_addr.octet[0], mac_addr.octet[1], |
| mac_addr.octet[2], mac_addr.octet[3], |
| mac_addr.octet[4], mac_addr.octet[5])); |
| break; |
| default: |
| WL_ERR(("Unknown type: %d\n", type)); |
| return err; |
| } |
| } |
| |
| bzero(cfg->ioctl_buf, WLC_IOCTL_MAXLEN); |
| |
| output = cfg->ioctl_buf; |
| |
| if (cfg->isMonitorStaPsOn) { |
| ret = dhd_dev_get_gwifi_station_ps_state(wdev->netdev, (void *)&mac_addr, |
| output, WLC_IOCTL_MAXLEN); |
| if (ret<0) { |
| if (ret == BCME_NOTFOUND) { |
| WL_ERR(("%s:Cannot find the station %02x:%02x:%02x:%02x:%02x:%02x\n", |
| __FUNCTION__, |
| mac_addr.octet[0], mac_addr.octet[1], |
| mac_addr.octet[2], mac_addr.octet[3], |
| mac_addr.octet[4], mac_addr.octet[5])); |
| } |
| return ret; |
| } |
| } else { |
| u32 *ps_state = (u32*)output; |
| *ps_state = PS_STATE_NOT_MONITORED; |
| ret = 4; |
| } |
| |
| WL_ERR(("%s: output len=%d\n", __FUNCTION__, ret)); |
| |
| err = wl_cfgvendor_send_cmd_reply(wiphy, bcmcfg_to_prmry_ndev(cfg), |
| cfg->ioctl_buf, ret); |
| if (unlikely(err)) |
| WL_ERR(("Vendor Command reply failed ret:%d \n", err)); |
| |
| return err; |
| } |
| |
| static int |
| wl_cfgvendor_interface_get_fouraddr_support(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| return wdev->use_4addr; |
| } |
| |
| #ifdef CSI_SUPPORT |
| static int |
| wl_cfgvendor_csi_get_dump_path(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| int err = BCME_ERROR, rem, type; |
| char *output; |
| int ret; |
| const struct nlattr *iter; |
| int ifidx = 0, dhd_ifidx = 0; |
| struct net_device *ndev = bcmcfg_to_prmry_ndev(cfg); |
| struct dhd_info *dhd_info = cfg->pub->info; |
| |
| nla_for_each_attr(iter, data, len, rem) { |
| type = nla_type(iter); |
| switch (type) { |
| case CSI_ATTRIBUTE_CONFIG_IFIDX: |
| ifidx = nla_get_u32(iter); |
| dhd_ifidx = dhd_ifindex2idx(dhd_info, ifidx); |
| if (dhd_ifidx == DHD_BAD_IF) { |
| WL_ERR(("%s: Bad ifidx %d\n", __FUNCTION__, dhd_ifidx)); |
| err = -ENODEV; |
| goto exit; |
| } |
| ndev = dhd_idx2net(cfg->pub, dhd_ifidx); |
| break; |
| } |
| } |
| |
| if ((ifidx <= 0) || !ndev) { |
| WL_ERR(("%s: No ifidx or null dev\n", __FUNCTION__)); |
| err = -ENODEV; |
| goto exit; |
| } |
| |
| bzero(cfg->ioctl_buf, WLC_IOCTL_MAXLEN); |
| |
| output = cfg->ioctl_buf; |
| |
| if (dhd_dev_csi_get_dump_path(ndev, |
| output, WLC_IOCTL_MAXLEN) < 0) { |
| WL_ERR(("Could not get CSI dump path from %s\n", ndev->name)); |
| err = -EINVAL; |
| goto exit; |
| } |
| |
| ret = strlen(output) + 1; |
| WL_DBG(("%s: output len=%d\n", __FUNCTION__, ret)); |
| |
| err = wl_cfgvendor_send_cmd_reply(wiphy, bcmcfg_to_prmry_ndev(cfg), |
| cfg->ioctl_buf, ret); |
| exit: |
| if (unlikely(err)) |
| WL_ERR(("Vendor Command reply failed ret:%d \n", err)); |
| |
| return err; |
| } |
| |
| static int |
| wl_cfgvendor_csi_set_config(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0, rem, rem1, rem2, type; |
| int target_cnt; |
| csi_config_params_t csi_param; |
| wifi_csi_config_t *configs; |
| const struct nlattr *iter, *iter1, *iter2; |
| int8 eabuf[ETHER_ADDR_STR_LEN]; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| int ifidx = 0, dhd_ifidx = 0; |
| struct dhd_info *dhd_info = cfg->pub->info; |
| |
| nla_for_each_attr(iter, data, len, rem) { |
| type = nla_type(iter); |
| switch (type) { |
| case CSI_ATTRIBUTE_CONFIG_CNT: |
| target_cnt = nla_get_u8(iter); |
| if ((target_cnt <= 0) || |
| (target_cnt > CSI_MAX_PEER_NUM)) { |
| WL_ERR(("target_cnt is not valid: %d", |
| target_cnt)); |
| err = BCME_RANGE; |
| goto exit; |
| } |
| csi_param.cnt = target_cnt; |
| csi_param.configs = kzalloc(CSI_CONFIGS_SIZE(target_cnt), GFP_KERNEL); |
| if (csi_param.configs == NULL) { |
| WL_ERR(("failed to allocate csi configs for (%d)\n", target_cnt)); |
| err = BCME_NOMEM; |
| goto exit; |
| } |
| break; |
| case CSI_ATTRIBUTE_CONFIG_INFO: |
| /* Added this variable for safe check to avoid crash |
| * incase the caller did not respect the order |
| */ |
| if (!csi_param.configs) { |
| err = BCME_NOMEM; |
| goto exit; |
| } |
| configs = csi_param.configs; |
| nla_for_each_nested(iter1, iter, rem1) { |
| nla_for_each_nested(iter2, iter1, rem2) { |
| type = nla_type(iter2); |
| switch (type) { |
| case CSI_ATTRIBUTE_CONFIG_MAC: |
| memcpy(&configs->addr, nla_data(iter2), |
| ETHER_ADDR_LEN); |
| break; |
| case CSI_ATTRIBUTE_CONFIG_METHOD: |
| configs->method = nla_get_u8(iter2); |
| break; |
| case CSI_ATTRIBUTE_CONFIG_PERIOD: |
| configs->period = nla_get_u32(iter2); |
| break; |
| case CSI_ATTRIBUTE_CONFIG_BW: |
| configs->bw = nla_get_u8(iter2); |
| break; |
| } |
| } |
| WL_INFORM(("Config addr %s, method:%d for CSI \n", |
| bcm_ether_ntoa((const struct ether_addr *)&configs->addr, |
| eabuf), configs->method)); |
| configs++; |
| } |
| break; |
| case CSI_ATTRIBUTE_CONFIG_IFIDX: |
| ifidx = nla_get_u32(iter); |
| dhd_ifidx = dhd_ifindex2idx(dhd_info, ifidx); |
| if (dhd_ifidx == DHD_BAD_IF) { |
| WL_ERR(("%s: Bad ifidx %d\n", __FUNCTION__, dhd_ifidx)); |
| err = -EINVAL; |
| goto exit; |
| } |
| break; |
| } |
| } |
| WL_DBG(("leave :configs cnt : %d\n", csi_param.cnt)); |
| if (dhd_dev_csi_set_cfg(bcmcfg_to_prmry_ndev(cfg), &csi_param, ifidx) < 0) { |
| WL_ERR(("Could not set CSI configuration\n")); |
| err = -EINVAL; |
| } |
| exit: |
| /* free configs */ |
| kfree(csi_param.configs); |
| csi_param.configs = NULL; |
| return err; |
| } |
| |
| static int |
| wl_cfgvendor_csi_cancel_config(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0, rem, type, target_cnt = 0; |
| int target_idx = 0; |
| const struct nlattr *iter; |
| struct ether_addr *mac_list = NULL; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| int ifidx = 0, dhd_ifidx = 0; |
| struct dhd_info *dhd_info = cfg->pub->info; |
| |
| if (len <= 0) { |
| WL_ERR(("Length of nlattr is not valid len : %d\n", len)); |
| err = -EINVAL; |
| goto exit; |
| } |
| nla_for_each_attr(iter, data, len, rem) { |
| type = nla_type(iter); |
| switch (type) { |
| case CSI_ATTRIBUTE_CONFIG_CNT: |
| if (mac_list != NULL) { |
| WL_ERR(("mac_list is not NULL\n")); |
| err = -EINVAL; |
| goto exit; |
| } |
| target_cnt = nla_get_u8(iter); |
| if ((target_cnt > 0) && |
| (target_cnt <= CSI_MAX_PEER_NUM)) { |
| mac_list = (struct ether_addr *)kzalloc(target_cnt * ETHER_ADDR_LEN, |
| GFP_KERNEL); |
| if (mac_list == NULL) { |
| WL_ERR(("failed to allocate mem for mac list\n")); |
| err = -EINVAL; |
| goto exit; |
| } |
| } else { |
| /* cancel the current whole CSI process */ |
| goto cancel; |
| } |
| break; |
| case CSI_ATTRIBUTE_CONFIG_MAC: |
| if (!mac_list) { |
| WL_ERR(("ATTRIBUTE_CONFIG_CNT not found before " |
| " ATTRIBUTE_CONFIG_MAC\n")); |
| err = -EINVAL; |
| goto exit; |
| } |
| |
| if (target_idx >= target_cnt) { |
| WL_ERR(("More TARGET_MAC entries found, " |
| "expected TARGET_CNT:%d\n", target_cnt)); |
| err = -EINVAL; |
| goto exit; |
| } |
| |
| if (nla_len(iter) != ETHER_ADDR_LEN) { |
| WL_ERR(("Invalid TARGET_MAC ATTR len :%d\n", nla_len(iter))); |
| err = -EINVAL; |
| goto exit; |
| } |
| |
| memcpy(&mac_list[target_idx], nla_data(iter), ETHER_ADDR_LEN); |
| target_idx++; |
| break; |
| case CSI_ATTRIBUTE_CONFIG_IFIDX: |
| ifidx = nla_get_u32(iter); |
| dhd_ifidx = dhd_ifindex2idx(dhd_info, ifidx); |
| if (dhd_ifidx == DHD_BAD_IF) { |
| WL_ERR(("%s: Bad ifidx %d\n", __FUNCTION__, dhd_ifidx)); |
| err = -EINVAL; |
| goto exit; |
| } |
| break; |
| default: |
| WL_ERR(("Uknown type : %d\n", type)); |
| err = -EINVAL; |
| goto exit; |
| } |
| } |
| cancel: |
| if (dhd_dev_csi_cancel_cfg(bcmcfg_to_prmry_ndev(cfg), mac_list, target_cnt, ifidx) < 0) { |
| WL_ERR(("Could not cancel CSI configuration\n")); |
| err = -EINVAL; |
| } |
| |
| exit: |
| if (mac_list) { |
| kfree(mac_list); |
| } |
| return err; |
| } |
| |
| static int |
| wl_cfgvendor_csi_get_config(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| csi_config_params_t csi_param; |
| int err = BCME_ERROR; |
| int outlen, ret; |
| |
| bzero(&csi_param, sizeof(csi_param)); |
| |
| ret = dhd_dev_csi_get_cfg(bcmcfg_to_prmry_ndev(cfg), &csi_param); |
| if (ret < 0) { |
| WL_ERR(("Could not get CSI configuration\n")); |
| return -EINVAL; |
| } |
| |
| /* assign the num of config */ |
| memcpy(cfg->ioctl_buf, (char*)&(csi_param.cnt), sizeof(csi_param.cnt)); |
| outlen = sizeof(csi_param.cnt); |
| |
| /* assign all configs */ |
| memcpy(cfg->ioctl_buf+outlen, (char*)csi_param.configs, (csi_param.cnt)*sizeof(wifi_csi_config_t)); |
| outlen += (csi_param.cnt)*sizeof(wifi_csi_config_t); |
| |
| /* free all configs */ |
| if (csi_param.configs) kfree(csi_param.configs); |
| |
| WL_ERR(("%s: output len=%d(%d)\n", __FUNCTION__, outlen, ret)); |
| |
| err = wl_cfgvendor_send_cmd_reply(wiphy, bcmcfg_to_prmry_ndev(cfg), |
| cfg->ioctl_buf, outlen); |
| if (unlikely(err)) |
| WL_ERR(("Vendor Command reply failed ret:%d \n", err)); |
| |
| return err; |
| } |
| |
| static int |
| wl_cfgvendor_csi_get_capability(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| int err = BCME_ERROR; |
| wifi_csi_capabilities_t caps; |
| int outlen, ret; |
| |
| bzero(&caps, sizeof(caps)); |
| |
| ret = dhd_dev_csi_get_capability(bcmcfg_to_prmry_ndev(cfg), &caps); |
| if(ret < 0) { |
| WL_ERR(("Could not get CSI capabilities\n")); |
| return -EINVAL; |
| } |
| |
| /* assign capabilities */ |
| memcpy(cfg->ioctl_buf, (char*)&caps, sizeof(caps)); |
| outlen = sizeof(caps); |
| WL_ERR(("%s: output len=%d\n", __FUNCTION__, outlen)); |
| |
| err = wl_cfgvendor_send_cmd_reply(wiphy, bcmcfg_to_prmry_ndev(cfg), |
| cfg->ioctl_buf, outlen); |
| if (unlikely(err)) |
| WL_ERR(("Vendor Command reply failed ret:%d \n", err)); |
| |
| return err; |
| } |
| #endif /* CSI_SUPPORT */ |
| |
| static int |
| wl_cfgvendor_interface_get_combination_limit(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| int err = BCME_ERROR; |
| wifi_interface_combination_set_t combinset; |
| int outlen, ret; |
| |
| bzero(&combinset, sizeof(combinset)); |
| |
| ret = dhd_dev_interface_get_combination_limit(bcmcfg_to_prmry_ndev(cfg), &combinset); |
| if (ret < 0) { |
| WL_ERR(("Could not get interface combination limits\n")); |
| return -EINVAL; |
| } |
| |
| /* assign combinations */ |
| memcpy(cfg->ioctl_buf, (char*)&combinset, sizeof(combinset)); |
| outlen = sizeof(combinset); |
| WL_ERR(("%s: output len=%d\n", __FUNCTION__, outlen)); |
| |
| err = wl_cfgvendor_send_cmd_reply(wiphy, bcmcfg_to_prmry_ndev(cfg), |
| cfg->ioctl_buf, outlen); |
| if (unlikely(err)) |
| WL_ERR(("Vendor Command reply failed ret:%d \n", err)); |
| |
| return err; |
| } |
| |
| #endif /* GWIFI_SUPPORT */ |
| |
| #ifdef WL_QOS_CTRL |
| static int wl_cfgvendor_set_pkt_lifetime(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| |
| WL_ERR(("set pkt lifetime\n")); |
| |
| if (dhd_dev_set_pkt_lifetime(bcmcfg_to_prmry_ndev(cfg), data, len) < 0) { |
| WL_ERR(("Could not set pkt lifetime\n")); |
| err = -EINVAL; |
| } |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_get_pkt_lifetime(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| typedef struct ac_lifetime { |
| int32 ac; |
| int32 lifetime; |
| } ac_lifetime_t; |
| uint32 *pbuf = (uint32*)data; |
| struct sk_buff *reply; |
| |
| ac_lifetime_t cur_ac_lifetime; |
| |
| cur_ac_lifetime.ac = pbuf[0]; |
| |
| WL_ERR(("get ac lifetime ac: %d\n", cur_ac_lifetime.ac)); |
| |
| if (dhd_dev_get_pkt_lifetime(bcmcfg_to_prmry_ndev(cfg), &cur_ac_lifetime, sizeof(ac_lifetime_t)) < 0) { |
| WL_ERR(("Could not get lifetime\n")); |
| err = -EINVAL; |
| } |
| |
| pbuf = &(cur_ac_lifetime.lifetime); |
| reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(uint32)); |
| if (!reply) { |
| WL_ERR(("Failed to allocate reply msg\n")); |
| err = -ENOMEM; |
| goto exit; |
| } |
| |
| if (nla_put(reply, BCM_NLATTR_DATA, sizeof(uint32), pbuf)){ |
| kfree_skb(reply); |
| err = -ENOBUFS; |
| goto exit; |
| } |
| |
| err = cfg80211_vendor_cmd_reply(reply); |
| if (err) { |
| WL_ERR(("reply failed:%d\n", err)); |
| goto exit; |
| } |
| exit: |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_set_ac_srl(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| |
| WL_ERR(("set ac srl\n")); |
| |
| if (dhd_dev_set_ac_srl(bcmcfg_to_prmry_ndev(cfg), data, len) < 0) { |
| WL_ERR(("Could not set srl\n")); |
| err = -EINVAL; |
| } |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_get_ac_srl(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| typedef struct ac_srl { |
| int8 ac; |
| int8 srl; |
| } ac_srl_t; |
| uint8 *pbuf = (uint8*)data; |
| struct sk_buff *reply; |
| |
| ac_srl_t cur_ac_srl; |
| |
| cur_ac_srl.ac = pbuf[0]; |
| |
| WL_ERR(("get ac srl\n")); |
| |
| if (dhd_dev_get_ac_srl(bcmcfg_to_prmry_ndev(cfg), &cur_ac_srl, sizeof(ac_srl_t)) < 0) { |
| WL_ERR(("Could not get srl\n")); |
| err = -EINVAL; |
| } |
| |
| pbuf = &(cur_ac_srl.srl); |
| reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 1); |
| if (!reply) { |
| WL_ERR(("Failed to allocate reply msg\n")); |
| err = -ENOMEM; |
| goto exit; |
| } |
| |
| if (nla_put(reply, BCM_NLATTR_DATA, 1, pbuf)){ |
| kfree_skb(reply); |
| err = -ENOBUFS; |
| goto exit; |
| } |
| |
| err = cfg80211_vendor_cmd_reply(reply); |
| if (err) { |
| WL_ERR(("reply failed:%d\n", err)); |
| goto exit; |
| } |
| exit: |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_set_ac_lrl(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| |
| WL_ERR(("set ac lrl\n")); |
| |
| if (dhd_dev_set_ac_lrl(bcmcfg_to_prmry_ndev(cfg), data, len) < 0) { |
| WL_ERR(("Could not set lrl\n")); |
| err = -EINVAL; |
| } |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_get_ac_lrl(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| typedef struct ac_lrl { |
| int8 ac; |
| int8 lrl; |
| } ac_lrl_t; |
| uint8 *pbuf = (uint8*)data; |
| struct sk_buff *reply; |
| |
| ac_lrl_t cur_ac_lrl; |
| |
| cur_ac_lrl.ac = pbuf[0]; |
| |
| WL_ERR(("get ac lrl\n")); |
| |
| if (dhd_dev_get_ac_lrl(bcmcfg_to_prmry_ndev(cfg), &cur_ac_lrl, sizeof(ac_lrl_t)) < 0) { |
| WL_ERR(("Could not get lrl\n")); |
| err = -EINVAL; |
| } |
| |
| pbuf = &(cur_ac_lrl.lrl); |
| reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 1); |
| if (!reply) { |
| WL_ERR(("Failed to allocate reply msg\n")); |
| err = -ENOMEM; |
| goto exit; |
| } |
| |
| if (nla_put(reply, BCM_NLATTR_DATA, 1, pbuf)){ |
| kfree_skb(reply); |
| err = -ENOBUFS; |
| goto exit; |
| } |
| |
| err = cfg80211_vendor_cmd_reply(reply); |
| if (err) { |
| WL_ERR(("reply failed:%d\n", err)); |
| goto exit; |
| } |
| exit: |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_set_wme_noack(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| |
| WL_ERR(("set wme no ack\n")); |
| |
| if (dhd_dev_set_wme_noack(bcmcfg_to_prmry_ndev(cfg), data, len) < 0) { |
| WL_ERR(("Could not set wme noack\n")); |
| err = -EINVAL; |
| } |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_get_wme_noack(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| uint32 wme_noack = 0; |
| struct sk_buff *reply; |
| |
| WL_ERR(("get wme no ack\n")); |
| |
| if (dhd_dev_get_wme_noack(bcmcfg_to_prmry_ndev(cfg), &wme_noack, sizeof(uint32)) < 0) { |
| WL_ERR(("Could not get wme noack\n")); |
| err = -EINVAL; |
| } |
| |
| reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(uint32)); |
| if (!reply) { |
| WL_ERR(("Failed to allocate reply msg\n")); |
| err = -ENOMEM; |
| goto exit; |
| } |
| |
| if (nla_put(reply, BCM_NLATTR_DATA, sizeof(uint32), &wme_noack)){ |
| kfree_skb(reply); |
| err = -ENOBUFS; |
| goto exit; |
| } |
| |
| err = cfg80211_vendor_cmd_reply(reply); |
| if (err) { |
| WL_ERR(("reply failed:%d\n", err)); |
| goto exit; |
| } |
| exit: |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_set_wme_apsd(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| |
| WL_ERR(("set wme apsd\n")); |
| |
| if (dhd_dev_set_wme_apsd(bcmcfg_to_prmry_ndev(cfg), data, len) < 0) { |
| WL_ERR(("Could not set wme apsd\n")); |
| err = -EINVAL; |
| } |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_get_wme_apsd(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| uint32 wme_apsd = 0; |
| struct sk_buff *reply; |
| |
| WL_ERR(("get wme apsd\n")); |
| |
| if (dhd_dev_get_wme_apsd(bcmcfg_to_prmry_ndev(cfg), &wme_apsd, sizeof(uint32)) < 0) { |
| WL_ERR(("Could not get wme apsd\n")); |
| err = -EINVAL; |
| } |
| |
| reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(uint32)); |
| if (!reply) { |
| WL_ERR(("Failed to allocate reply msg\n")); |
| err = -ENOMEM; |
| goto exit; |
| } |
| |
| if (nla_put(reply, BCM_NLATTR_DATA, sizeof(uint32), &wme_apsd)){ |
| kfree_skb(reply); |
| err = -ENOBUFS; |
| goto exit; |
| } |
| |
| err = cfg80211_vendor_cmd_reply(reply); |
| if (err) { |
| WL_ERR(("reply failed:%d\n", err)); |
| goto exit; |
| } |
| exit: |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_set_ampdu_per_tid(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| |
| WL_ERR(("set pkt amdpu per tid\n")); |
| |
| if (dhd_dev_set_ampdu_per_tid(bcmcfg_to_prmry_ndev(cfg), data, len) < 0) { |
| WL_ERR(("Could not set pkt ampdu per tid\n")); |
| err = -EINVAL; |
| } |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_get_ampdu_per_tid(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| typedef struct ac_ampdu { |
| int8 ac; |
| int8 ampdu; |
| } ac_ampdu_t; |
| uint8 *pbuf = (uint8*)data; |
| struct sk_buff *reply; |
| |
| ac_ampdu_t cur_ac_ampdu; |
| |
| cur_ac_ampdu.ac = pbuf[0]; |
| |
| WL_ERR(("get ac ampdu ac: %d\n", cur_ac_ampdu.ac)); |
| |
| if (dhd_dev_get_ampdu_per_tid(bcmcfg_to_prmry_ndev(cfg), &cur_ac_ampdu, sizeof(ac_ampdu_t)) < 0) { |
| WL_ERR(("Could not get ampdu per tid\n")); |
| err = -EINVAL; |
| } |
| |
| pbuf = &(cur_ac_ampdu.ampdu); |
| reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(uint8)); |
| if (!reply) { |
| WL_ERR(("Failed to allocate reply msg\n")); |
| err = -ENOMEM; |
| goto exit; |
| } |
| |
| if (nla_put(reply, BCM_NLATTR_DATA, sizeof(uint8), pbuf)){ |
| kfree_skb(reply); |
| err = -ENOBUFS; |
| goto exit; |
| } |
| |
| err = cfg80211_vendor_cmd_reply(reply); |
| if (err) { |
| WL_ERR(("reply failed:%d\n", err)); |
| goto exit; |
| } |
| exit: |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_set_ampdu_mpdu(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| |
| WL_ERR(("set ampdu mpdu \n")); |
| |
| if (dhd_dev_set_ampdu_mpdu(bcmcfg_to_prmry_ndev(cfg), data, len) < 0) { |
| WL_ERR(("Could not set ampdu mpdu\n")); |
| err = -EINVAL; |
| } |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_get_ampdu_mpdu(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| int32 ampdu_mpdu = 0; |
| struct sk_buff *reply; |
| |
| WL_ERR(("get ampdu_mpdu\n")); |
| |
| if (dhd_dev_get_ampdu_mpdu(bcmcfg_to_prmry_ndev(cfg), &du_mpdu, sizeof(int32)) < 0) { |
| WL_ERR(("Could not get ampdu_mpdu\n")); |
| err = -EINVAL; |
| } |
| |
| reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(int32)); |
| if (!reply) { |
| WL_ERR(("Failed to allocate reply msg\n")); |
| err = -ENOMEM; |
| goto exit; |
| } |
| |
| if (nla_put(reply, BCM_NLATTR_DATA, sizeof(int32), &du_mpdu)){ |
| kfree_skb(reply); |
| err = -ENOBUFS; |
| goto exit; |
| } |
| |
| err = cfg80211_vendor_cmd_reply(reply); |
| if (err) { |
| WL_ERR(("reply failed:%d\n", err)); |
| goto exit; |
| } |
| exit: |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_set_ampdu_rts(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| |
| WL_ERR(("set ampdu rts \n")); |
| |
| if (dhd_dev_set_ampdu_rts(bcmcfg_to_prmry_ndev(cfg), data, len) < 0) { |
| WL_ERR(("Could not set ampdu rts\n")); |
| err = -EINVAL; |
| } |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_get_ampdu_rts(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| uint32 ampdu_rts = 0; |
| struct sk_buff *reply; |
| |
| WL_ERR(("get ampdu_rts\n")); |
| |
| if (dhd_dev_get_ampdu_rts(bcmcfg_to_prmry_ndev(cfg), &du_rts, sizeof(uint32)) < 0) { |
| WL_ERR(("Could not get ampdu_rts\n")); |
| err = -EINVAL; |
| } |
| |
| reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(uint32)); |
| if (!reply) { |
| WL_ERR(("Failed to allocate reply msg\n")); |
| err = -ENOMEM; |
| goto exit; |
| } |
| |
| if (nla_put(reply, BCM_NLATTR_DATA, sizeof(uint32), &du_rts)){ |
| kfree_skb(reply); |
| err = -ENOBUFS; |
| goto exit; |
| } |
| |
| err = cfg80211_vendor_cmd_reply(reply); |
| if (err) { |
| WL_ERR(("reply failed:%d\n", err)); |
| goto exit; |
| } |
| exit: |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_set_rtsthresh(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| |
| WL_ERR(("set rtsthresh \n")); |
| |
| if (dhd_dev_set_rtsthresh(bcmcfg_to_prmry_ndev(cfg), data, len) < 0) { |
| WL_ERR(("Could not set rtsthresh\n")); |
| err = -EINVAL; |
| } |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_get_rtsthresh(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| uint32 rtsthresh = 0; |
| struct sk_buff *reply; |
| |
| WL_ERR(("get rtsthresh\n")); |
| |
| if (dhd_dev_get_rtsthresh(bcmcfg_to_prmry_ndev(cfg), &rtsthresh, sizeof(uint32)) < 0) { |
| WL_ERR(("Could not get rtsthresh\n")); |
| err = -EINVAL; |
| } |
| |
| reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(uint32)); |
| if (!reply) { |
| WL_ERR(("Failed to allocate reply msg\n")); |
| err = -ENOMEM; |
| goto exit; |
| } |
| |
| if (nla_put(reply, BCM_NLATTR_DATA, sizeof(uint32), &rtsthresh)){ |
| kfree_skb(reply); |
| err = -ENOBUFS; |
| goto exit; |
| } |
| |
| err = cfg80211_vendor_cmd_reply(reply); |
| if (err) { |
| WL_ERR(("reply failed:%d\n", err)); |
| goto exit; |
| } |
| exit: |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_set_2g_rate(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| |
| WL_ERR(("set 2g_rate \n")); |
| |
| if (dhd_dev_set_2g_rate(bcmcfg_to_prmry_ndev(cfg), data, len) < 0) { |
| WL_ERR(("Could not set 2g_rate\n")); |
| err = -EINVAL; |
| } |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_get_2g_rate(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| uint32 rate = 0; |
| struct sk_buff *reply; |
| |
| WL_ERR(("get 2g rate\n")); |
| |
| if (dhd_dev_get_2g_rate(bcmcfg_to_prmry_ndev(cfg), &rate, sizeof(uint32)) < 0) { |
| WL_ERR(("Could not get rate\n")); |
| err = -EINVAL; |
| } |
| |
| reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(uint32)); |
| if (!reply) { |
| WL_ERR(("Failed to allocate reply msg\n")); |
| err = -ENOMEM; |
| goto exit; |
| } |
| |
| if (nla_put(reply, BCM_NLATTR_DATA, sizeof(uint32), &rate)){ |
| kfree_skb(reply); |
| err = -ENOBUFS; |
| goto exit; |
| } |
| |
| err = cfg80211_vendor_cmd_reply(reply); |
| if (err) { |
| WL_ERR(("reply failed:%d\n", err)); |
| goto exit; |
| } |
| exit: |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_set_5g_rate(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| WL_ERR(("set 5g_rate \n")); |
| |
| if (dhd_dev_set_5g_rate(bcmcfg_to_prmry_ndev(cfg), data, len) < 0) { |
| WL_ERR(("Could not set 5g_rate\n")); |
| err = -EINVAL; |
| } |
| |
| return err; |
| } |
| |
| static int wl_cfgvendor_get_5g_rate(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = 0; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| uint32 rate = 0; |
| struct sk_buff *reply; |
| |
| WL_ERR(("get 5g rate\n")); |
| |
| if (dhd_dev_get_5g_rate(bcmcfg_to_prmry_ndev(cfg), &rate, sizeof(uint32)) < 0) { |
| WL_ERR(("Could not get rate\n")); |
| err = -EINVAL; |
| } |
| |
| reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(uint32)); |
| if (!reply) { |
| WL_ERR(("Failed to allocate reply msg\n")); |
| err = -ENOMEM; |
| goto exit; |
| } |
| |
| if (nla_put(reply, BCM_NLATTR_DATA, sizeof(uint32), &rate)){ |
| kfree_skb(reply); |
| err = -ENOBUFS; |
| goto exit; |
| } |
| |
| err = cfg80211_vendor_cmd_reply(reply); |
| if (err) { |
| WL_ERR(("reply failed:%d\n", err)); |
| goto exit; |
| } |
| exit: |
| |
| return err; |
| } |
| #endif /* WL_QOS_CTRL */ |
| |
| static const struct wiphy_vendor_command wl_vendor_cmds [] = { |
| { |
| { |
| .vendor_id = OUI_BRCM, |
| .subcmd = BRCM_VENDOR_SCMD_PRIV_STR |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_priv_string_handler |
| }, |
| { |
| { |
| .vendor_id = OUI_BRCM, |
| .subcmd = BRCM_VENDOR_SCMD_BCM_STR |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_priv_bcm_handler |
| }, |
| #ifdef WIFI_STATS_SUPPORT |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_STATS_GET_TDLS_PEERS |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_wifi_stats_get_tdls_peers |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_STATS_GET_NUM_FRAME_CHECK_SEQ_ERRORS |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_wifi_stats_get_num_frame_check_seq_errors |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_STATS_CLEAR_HISTOGRAMS |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_wifi_stats_clear_histograms |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_STATS_GET_HISTOGRAMS |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_wifi_stats_get_histograms |
| }, |
| #endif /* WIFI_STATS_SUPPORT */ |
| #ifdef WL_SAE |
| { |
| { |
| .vendor_id = OUI_BRCM, |
| .subcmd = BRCM_VENDOR_SCMD_BCM_PSK |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_set_sae_password |
| }, |
| #endif /* WL_SAE */ |
| #ifdef GSCAN_SUPPORT |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = GSCAN_SUBCMD_GET_CAPABILITIES |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_gscan_get_capabilities |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = GSCAN_SUBCMD_SET_CONFIG |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_set_scan_cfg |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = GSCAN_SUBCMD_SET_SCAN_CONFIG |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_set_batch_scan_cfg |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = GSCAN_SUBCMD_ENABLE_GSCAN |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_initiate_gscan |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = GSCAN_SUBCMD_ENABLE_FULL_SCAN_RESULTS |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_enable_full_scan_result |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = GSCAN_SUBCMD_SET_HOTLIST |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_hotlist_cfg |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = GSCAN_SUBCMD_SET_SIGNIFICANT_CHANGE_CONFIG |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_significant_change_cfg |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = GSCAN_SUBCMD_GET_SCAN_RESULTS |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_gscan_get_batch_results |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = GSCAN_SUBCMD_GET_CHANNEL_LIST |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_gscan_get_channel_list |
| }, |
| #endif /* GSCAN_SUPPORT */ |
| |
| #ifdef WL_QOS_CTRL |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_SET_AC_SRL |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_set_ac_srl |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_SET_AC_LRL |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_set_ac_lrl |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_SET_PKT_LIFETIME |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_set_pkt_lifetime |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_SET_PKT_LIFETIME |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_get_pkt_lifetime |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_SET_WME_NOACK |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_set_wme_noack |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_SET_WME_APSD |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_set_wme_apsd |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_SET_AMPDU_MPDU |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_set_ampdu_mpdu |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_SET_AMPDU_RTS |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_set_ampdu_rts |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_SET_AMPDU_TID |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_set_ampdu_per_tid |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_SET_RTSTHRESH |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_set_rtsthresh |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_SET_2G_RATE |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_set_2g_rate |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_SET_5G_RATE |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_set_5g_rate |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_GET_PKT_LIFETIME |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_get_pkt_lifetime |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_GET_AC_SRL |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_get_ac_srl |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_GET_AC_LRL |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_get_ac_lrl |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_GET_WME_NOACK |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_get_wme_noack |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_GET_WME_APSD |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_get_wme_apsd |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_GET_AMPDU_MPDU |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_get_ampdu_mpdu |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_GET_AMPDU_RTS |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_get_ampdu_rts |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_GET_AMPDU_TID |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_get_ampdu_per_tid |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_GET_RTSTHRESH |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_get_rtsthresh |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_GET_2G_RATE |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_get_2g_rate |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = WIFI_SUBCMD_GET_5G_RATE |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_get_5g_rate |
| }, |
| #endif /* WL_QOS_CTRL */ |
| |
| #ifdef RTT_SUPPORT |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = RTT_SUBCMD_SET_CONFIG |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_rtt_set_config |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = RTT_SUBCMD_CANCEL_CONFIG |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_rtt_cancel_config |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = RTT_SUBCMD_GETCAPABILITY |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_rtt_get_capability |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = RTT_SUBCMD_GETAVAILCHANNEL |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_rtt_get_responder_info |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = RTT_SUBCMD_SET_RESPONDER |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_rtt_set_responder |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = RTT_SUBCMD_CANCEL_RESPONDER |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_rtt_cancel_responder |
| }, |
| #endif /* RTT_SUPPORT */ |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = ANDR_WIFI_SUBCMD_GET_FEATURE_SET |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_get_feature_set |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = ANDR_WIFI_SUBCMD_GET_FEATURE_SET_MATRIX |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_get_feature_set_matrix |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = ANDR_WIFI_PNO_RANDOM_MAC_OUI |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_set_pno_mac_oui |
| }, |
| #ifdef CUSTOM_FORCE_NODFS_FLAG |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = ANDR_WIFI_NODFS_CHANNELS |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_set_nodfs_flag |
| |
| }, |
| #endif /* CUSTOM_FORCE_NODFS_FLAG */ |
| #ifdef LINKSTAT_SUPPORT |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = LSTATS_SUBCMD_GET_INFO |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_lstats_get_info |
| }, |
| #endif /* LINKSTAT_SUPPORT */ |
| #ifdef GWIFI_SUPPORT |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = GWIFI_SUBCMD_INTERFACE_OPERATION |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_interface_ops |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = GWIFI_SUBCMD_STATION_GET_MCS_DATA, |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_station_get_mcs_data |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = GWIFI_SUBCMD_INTERFACE_GET_FOURADDR_SUPPORT, |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_interface_get_fouraddr_support |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = GWIFI_SUBCMD_STATION_SET_PS_MONITOR, |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_station_set_ps_monitor |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = GWIFI_SUBCMD_STATION_GET_PS_STATE, |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_station_get_ps_state |
| }, |
| #ifdef CSI_SUPPORT |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = GWIFI_SUBCMD_CSI_GET_DUMP_PATH, |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_csi_get_dump_path |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = GWIFI_SUBCMD_CSI_SET_CONFIG, |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_csi_set_config |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = GWIFI_SUBCMD_CSI_CANCEL_CONFIG, |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_csi_cancel_config |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = GWIFI_SUBCMD_CSI_GET_CONFIG, |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_csi_get_config |
| }, |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = GWIFI_SUBCMD_CSI_GET_CAPABILITY, |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_csi_get_capability |
| }, |
| #endif /* CSI_SUPPORT */ |
| { |
| { |
| .vendor_id = OUI_GOOGLE, |
| .subcmd = GWIFI_SUBCMD_INTERFACE_GET_COMBINATION_LIMIT, |
| }, |
| .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, |
| .doit = wl_cfgvendor_interface_get_combination_limit |
| }, |
| #endif /* GWIFI_SUPPORT */ |
| }; |
| |
| static const struct nl80211_vendor_cmd_info wl_vendor_events [] = { |
| { OUI_BRCM, BRCM_VENDOR_EVENT_UNSPEC }, |
| { OUI_BRCM, BRCM_VENDOR_EVENT_PRIV_STR }, |
| { OUI_GOOGLE, GOOGLE_GSCAN_SIGNIFICANT_EVENT }, |
| { OUI_GOOGLE, GOOGLE_GSCAN_GEOFENCE_FOUND_EVENT }, |
| { OUI_GOOGLE, GOOGLE_GSCAN_BATCH_SCAN_EVENT }, |
| { OUI_GOOGLE, GOOGLE_SCAN_FULL_RESULTS_EVENT }, |
| { OUI_GOOGLE, GOOGLE_RTT_COMPLETE_EVENT }, |
| { OUI_GOOGLE, GOOGLE_SCAN_COMPLETE_EVENT }, |
| { OUI_GOOGLE, GOOGLE_GSCAN_GEOFENCE_LOST_EVENT }, |
| { OUI_BRCM, BRCM_VENDOR_EVENT_IDSUP_STATUS }, |
| { OUI_BRCM, BRCM_VENDOR_EVENT_DRIVER_STATUS }, |
| #ifdef WL_SAE |
| { OUI_BRCM, BRCM_VENDOR_EVENT_SAE_KEY}, |
| #endif /* WL_SAE */ |
| }; |
| |
| int wl_cfgvendor_attach(struct wiphy *wiphy) |
| { |
| |
| WL_INFORM(("Vendor: Register BRCM cfg80211 vendor cmd(0x%x) interface \n", |
| NL80211_CMD_VENDOR)); |
| |
| wiphy->vendor_commands = wl_vendor_cmds; |
| wiphy->n_vendor_commands = ARRAY_SIZE(wl_vendor_cmds); |
| wiphy->vendor_events = wl_vendor_events; |
| wiphy->n_vendor_events = ARRAY_SIZE(wl_vendor_events); |
| |
| return 0; |
| } |
| |
| int wl_cfgvendor_detach(struct wiphy *wiphy) |
| { |
| WL_INFORM(("Vendor: Unregister BRCM cfg80211 vendor interface \n")); |
| |
| wiphy->vendor_commands = NULL; |
| wiphy->vendor_events = NULL; |
| wiphy->n_vendor_commands = 0; |
| wiphy->n_vendor_events = 0; |
| |
| return 0; |
| } |
| #endif /* defined(WL_VENDOR_EXT_SUPPORT) */ |