/* * Linux cfg80211 Vendor Extension Code * * Copyright (C) 1999-2014, Broadcom Corporation * Copyright (C) 2015-2017, NVIDIA CORPORATION. All rights reserved. * * Unless you and Broadcom execute a separate written software license * agreement governing use of this software, this software is licensed to you * under the terms of the GNU General Public License version 2 (the "GPL"), * available at http://www.broadcom.com/licenses/GPLv2.php, with the * following added to such license: * * As a special exception, the copyright holders of this software give you * permission to link this software with independent modules, and to copy and * distribute the resulting executable under terms of your choice, provided that * you also meet, for each linked independent module, the terms and conditions of * the license of that module. An independent module is a module which is not * derived from this software. The special exception does not apply to any * modifications of the software. * * Notwithstanding the above, under no circumstances may you combine this * software in any way with any other Broadcom software provided under a license * other than the GPL, without Broadcom's express prior written consent. * * $Id: wl_cfgvendor.c 473890 2014-04-30 01:55:06Z $ */ /* * New vendor interface additon to nl80211/cfg80211 to allow vendors * to implement proprietary features over the cfg80211 stack. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef PNO_SUPPORT #include #endif /* PNO_SUPPORT */ #ifdef RTT_SUPPORT #include #endif /* RTT_SUPPORT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef PROP_TXSTATUS #include #endif #if (LINUX_VERSION_CODE > KERNEL_VERSION(3, 13, 0)) || 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 */ #ifdef VENDOR_NET_SKB_ALLOC skb = cfg80211_vendor_event_skb_alloc(dev, wiphy, len, event_id, kflags); #else #if defined(CONFIG_ARCH_MSM) && 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 /* CONFIG_ARCH_MSM && SUPPORT_WDEV_CFG80211_VENDOR_EVENT_ALLOC */ #endif /* VENDOR_NET_SKB_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_unsupported_feature(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) { // return unsupported error code return WIFI_ERROR_NOT_SUPPORTED; } static int wl_cfgvendor_set_country(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) { int err = BCME_ERROR, rem, type; char country_code[WLC_CNTRY_BUF_SZ] = {0}; const struct nlattr *iter; nla_for_each_attr(iter, data, len, rem) { type = nla_type(iter); switch (type) { case ANDR_WIFI_ATTRIBUTE_COUNTRY: memcpy(country_code, nla_data(iter), MIN(nla_len(iter), WLC_CNTRY_BUF_SZ)); break; default: WL_ERR(("Unknown type: %d\n", type)); return err; } } err = wldev_set_country(wdev->netdev, country_code, true, true); if (err < 0) { WL_ERR(("Set country failed ret:%d\n", err)); } return err; } #ifdef GSCAN_SUPPORT 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 -1; } 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; } #endif /* GSCAN_SUPPORT */ #if defined(KEEP_ALIVE) static int wl_cfgvendor_start_mkeep_alive(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) { /* max size of IP packet for keep alive */ const int MKEEP_ALIVE_IP_PKT_MAX = 256; int ret = BCME_OK, rem, type; u8 mkeep_alive_id = 0; u8 *ip_pkt = NULL; u16 ip_pkt_len = 0; u8 src_mac[ETHER_ADDR_LEN]; u8 dst_mac[ETHER_ADDR_LEN]; u32 period_msec = 0; const struct nlattr *iter; struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); dhd_pub_t *dhd_pub = cfg->pub; gfp_t kflags = in_atomic() ? GFP_ATOMIC : GFP_KERNEL; nla_for_each_attr(iter, data, len, rem) { type = nla_type(iter); switch (type) { case MKEEP_ALIVE_ATTRIBUTE_ID: mkeep_alive_id = nla_get_u8(iter); break; case MKEEP_ALIVE_ATTRIBUTE_IP_PKT_LEN: ip_pkt_len = nla_get_u16(iter); if (ip_pkt_len > MKEEP_ALIVE_IP_PKT_MAX) { ret = BCME_BADARG; goto exit; } break; case MKEEP_ALIVE_ATTRIBUTE_IP_PKT: if (!ip_pkt_len) { ret = BCME_BADARG; WL_ERR(("ip packet length is 0\n")); goto exit; } ip_pkt = (u8 *)kzalloc(ip_pkt_len, kflags); if (ip_pkt == NULL) { ret = BCME_NOMEM; WL_ERR(("Failed to allocate mem for ip packet\n")); goto exit; } memcpy(ip_pkt, (u8*)nla_data(iter), ip_pkt_len); break; case MKEEP_ALIVE_ATTRIBUTE_SRC_MAC_ADDR: memcpy(src_mac, nla_data(iter), ETHER_ADDR_LEN); break; case MKEEP_ALIVE_ATTRIBUTE_DST_MAC_ADDR: memcpy(dst_mac, nla_data(iter), ETHER_ADDR_LEN); break; case MKEEP_ALIVE_ATTRIBUTE_PERIOD_MSEC: period_msec = nla_get_u32(iter); break; default: WL_ERR(("Unknown type: %d\n", type)); ret = BCME_BADARG; goto exit; } } if (ip_pkt == NULL) { ret = BCME_BADARG; WL_ERR(("ip packet is NULL\n")); goto exit; } ret = dhd_dev_start_mkeep_alive(dhd_pub, mkeep_alive_id, ip_pkt, ip_pkt_len, src_mac, dst_mac, period_msec); if (ret < 0) { WL_ERR(("start_mkeep_alive is failed ret: %d\n", ret)); } exit: if (ip_pkt) { kfree(ip_pkt); } return ret; } static int wl_cfgvendor_stop_mkeep_alive(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) { int ret = BCME_OK, rem, type; u8 mkeep_alive_id = 0; const struct nlattr *iter; struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); dhd_pub_t *dhd_pub = cfg->pub; nla_for_each_attr(iter, data, len, rem) { type = nla_type(iter); switch (type) { case MKEEP_ALIVE_ATTRIBUTE_ID: mkeep_alive_id = nla_get_u8(iter); break; default: WL_ERR(("Unknown type: %d\n", type)); ret = BCME_BADARG; break; } } ret = dhd_dev_stop_mkeep_alive(dhd_pub, mkeep_alive_id); if (ret < 0) { WL_ERR(("stop_mkeep_alive is failed ret: %d\n", ret)); } return ret; } #endif /* defined(KEEP_ALIVE) */ 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_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_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; } static int wl_cfgvendor_dbg_get_version(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int len) { int ret = BCME_OK, rem, type; int buf_len = 1024; bool dhd_ver = FALSE; char *buf_ptr; const struct nlattr *iter; gfp_t kflags; struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); kflags = in_atomic() ? GFP_ATOMIC : GFP_KERNEL; buf_ptr = kzalloc(buf_len, kflags); if (!buf_ptr) { WL_ERR(("failed to allocate the buffer for version n")); ret = BCME_NOMEM; goto exit; } nla_for_each_attr(iter, data, len, rem) { type = nla_type(iter); switch (type) { case DEBUG_ATTRIBUTE_GET_DRIVER: dhd_ver = TRUE; break; case DEBUG_ATTRIBUTE_GET_FW: dhd_ver = FALSE; break; default: WL_ERR(("Unknown type: %d\n", type)); ret = BCME_ERROR; goto exit; } } ret = dhd_os_get_version(bcmcfg_to_prmry_ndev(cfg), dhd_ver, &buf_ptr, buf_len); if (ret < 0) { WL_ERR(("failed to get the version %d\n", ret)); goto exit; } ret = wl_cfgvendor_send_cmd_reply(wiphy, bcmcfg_to_prmry_ndev(cfg), buf_ptr, strlen(buf_ptr)); exit: kfree(buf_ptr); return ret; } static const struct wiphy_vendor_command wl_vendor_cmds [] = { { { .vendor_id = OUI_GOOGLE, .subcmd = ANDR_WIFI_SET_COUNTRY }, .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, .doit = wl_cfgvendor_set_country }, { { .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_RANDOM_MAC_OUI }, .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, .doit = wl_cfgvendor_set_pno_mac_oui }, { { .vendor_id = OUI_GOOGLE, .subcmd = DEBUG_GET_VER }, .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, .doit = wl_cfgvendor_dbg_get_version }, { { .vendor_id = OUI_GOOGLE, .subcmd = DEBUG_GET_RING_STATUS }, .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, .doit = wl_cfgvendor_unsupported_feature }, { { .vendor_id = OUI_GOOGLE, .subcmd = DEBUG_START_LOGGING }, .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, .doit = wl_cfgvendor_unsupported_feature }, { { .vendor_id = OUI_GOOGLE, .subcmd = DEBUG_TRIGGER_MEM_DUMP }, .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, .doit = wl_cfgvendor_unsupported_feature }, { { .vendor_id = OUI_GOOGLE, .subcmd = DEBUG_GET_MEM_DUMP }, .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, .doit = wl_cfgvendor_unsupported_feature }, { { .vendor_id = OUI_GOOGLE, .subcmd = DEBUG_GET_RING_STATUS }, .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, .doit = wl_cfgvendor_unsupported_feature }, { { .vendor_id = OUI_GOOGLE, .subcmd = DEBUG_GET_RING_DATA }, .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, .doit = wl_cfgvendor_unsupported_feature }, { { .vendor_id = OUI_GOOGLE, .subcmd = DEBUG_GET_FEATURE }, .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, .doit = wl_cfgvendor_unsupported_feature }, { { .vendor_id = OUI_GOOGLE, .subcmd = DEBUG_RESET_LOGGING }, .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, .doit = wl_cfgvendor_unsupported_feature }, #ifdef GSCAN_SUPPORT { { .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 KEEP_ALIVE { { .vendor_id = OUI_GOOGLE, .subcmd = WIFI_OFFLOAD_SUBCMD_START_MKEEP_ALIVE }, .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, .doit = wl_cfgvendor_start_mkeep_alive }, { { .vendor_id = OUI_GOOGLE, .subcmd = WIFI_OFFLOAD_SUBCMD_STOP_MKEEP_ALIVE }, .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, .doit = wl_cfgvendor_stop_mkeep_alive }, #endif /* KEEP_ALIVE */ }; 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_GOOGLE, GOOGLE_SCAN_EPNO_EVENT }, { OUI_GOOGLE, GOOGLE_DEBUG_RING_EVENT }, { OUI_GOOGLE, GOOGLE_FW_DUMP_EVENT }, { OUI_GOOGLE, GOOGLE_PNO_HOTSPOT_FOUND_EVENT }, { OUI_GOOGLE, GOOGLE_RSSI_MONITOR_EVENT }, { OUI_GOOGLE, GOOGLE_MKEEP_ALIVE_EVENT }, { OUI_BRCM, BRCM_VENDOR_EVENT_IDSUP_STATUS }, { OUI_BRCM, BRCM_VENDOR_EVENT_DRIVER_HANG } }; int wl_cfgvendor_attach(struct wiphy *wiphy, dhd_pub_t *dhd) { 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 /* (LINUX_VERSION_CODE > KERNEL_VERSION(3, 13, 0)) || defined(WL_VENDOR_EXT_SUPPORT) */