12be7d22fSVladimir Kondratiev /* 2c5a157e4SLior David * Copyright (c) 2012-2016 Qualcomm Atheros, Inc. 32be7d22fSVladimir Kondratiev * 42be7d22fSVladimir Kondratiev * Permission to use, copy, modify, and/or distribute this software for any 52be7d22fSVladimir Kondratiev * purpose with or without fee is hereby granted, provided that the above 62be7d22fSVladimir Kondratiev * copyright notice and this permission notice appear in all copies. 72be7d22fSVladimir Kondratiev * 82be7d22fSVladimir Kondratiev * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 92be7d22fSVladimir Kondratiev * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 102be7d22fSVladimir Kondratiev * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 112be7d22fSVladimir Kondratiev * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 122be7d22fSVladimir Kondratiev * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 132be7d22fSVladimir Kondratiev * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 142be7d22fSVladimir Kondratiev * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 152be7d22fSVladimir Kondratiev */ 162be7d22fSVladimir Kondratiev 17a82553bbSVladimir Kondratiev #include <linux/etherdevice.h> 182be7d22fSVladimir Kondratiev #include "wil6210.h" 192be7d22fSVladimir Kondratiev #include "wmi.h" 202be7d22fSVladimir Kondratiev 212be7d22fSVladimir Kondratiev #define CHAN60G(_channel, _flags) { \ 222be7d22fSVladimir Kondratiev .band = IEEE80211_BAND_60GHZ, \ 232be7d22fSVladimir Kondratiev .center_freq = 56160 + (2160 * (_channel)), \ 242be7d22fSVladimir Kondratiev .hw_value = (_channel), \ 252be7d22fSVladimir Kondratiev .flags = (_flags), \ 262be7d22fSVladimir Kondratiev .max_antenna_gain = 0, \ 272be7d22fSVladimir Kondratiev .max_power = 40, \ 282be7d22fSVladimir Kondratiev } 292be7d22fSVladimir Kondratiev 302be7d22fSVladimir Kondratiev static struct ieee80211_channel wil_60ghz_channels[] = { 312be7d22fSVladimir Kondratiev CHAN60G(1, 0), 322be7d22fSVladimir Kondratiev CHAN60G(2, 0), 332be7d22fSVladimir Kondratiev CHAN60G(3, 0), 342be7d22fSVladimir Kondratiev /* channel 4 not supported yet */ 352be7d22fSVladimir Kondratiev }; 362be7d22fSVladimir Kondratiev 372be7d22fSVladimir Kondratiev static struct ieee80211_supported_band wil_band_60ghz = { 382be7d22fSVladimir Kondratiev .channels = wil_60ghz_channels, 392be7d22fSVladimir Kondratiev .n_channels = ARRAY_SIZE(wil_60ghz_channels), 402be7d22fSVladimir Kondratiev .ht_cap = { 412be7d22fSVladimir Kondratiev .ht_supported = true, 422be7d22fSVladimir Kondratiev .cap = 0, /* TODO */ 432be7d22fSVladimir Kondratiev .ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K, /* TODO */ 442be7d22fSVladimir Kondratiev .ampdu_density = IEEE80211_HT_MPDU_DENSITY_8, /* TODO */ 452be7d22fSVladimir Kondratiev .mcs = { 462be7d22fSVladimir Kondratiev /* MCS 1..12 - SC PHY */ 472be7d22fSVladimir Kondratiev .rx_mask = {0xfe, 0x1f}, /* 1..12 */ 482be7d22fSVladimir Kondratiev .tx_params = IEEE80211_HT_MCS_TX_DEFINED, /* TODO */ 492be7d22fSVladimir Kondratiev }, 502be7d22fSVladimir Kondratiev }, 512be7d22fSVladimir Kondratiev }; 522be7d22fSVladimir Kondratiev 532be7d22fSVladimir Kondratiev static const struct ieee80211_txrx_stypes 542be7d22fSVladimir Kondratiev wil_mgmt_stypes[NUM_NL80211_IFTYPES] = { 552be7d22fSVladimir Kondratiev [NL80211_IFTYPE_STATION] = { 562be7d22fSVladimir Kondratiev .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | 572be7d22fSVladimir Kondratiev BIT(IEEE80211_STYPE_PROBE_RESP >> 4), 582be7d22fSVladimir Kondratiev .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | 592be7d22fSVladimir Kondratiev BIT(IEEE80211_STYPE_PROBE_REQ >> 4) 602be7d22fSVladimir Kondratiev }, 612be7d22fSVladimir Kondratiev [NL80211_IFTYPE_AP] = { 622be7d22fSVladimir Kondratiev .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | 632be7d22fSVladimir Kondratiev BIT(IEEE80211_STYPE_PROBE_RESP >> 4), 642be7d22fSVladimir Kondratiev .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | 652be7d22fSVladimir Kondratiev BIT(IEEE80211_STYPE_PROBE_REQ >> 4) 662be7d22fSVladimir Kondratiev }, 672be7d22fSVladimir Kondratiev [NL80211_IFTYPE_P2P_CLIENT] = { 682be7d22fSVladimir Kondratiev .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | 692be7d22fSVladimir Kondratiev BIT(IEEE80211_STYPE_PROBE_RESP >> 4), 702be7d22fSVladimir Kondratiev .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | 712be7d22fSVladimir Kondratiev BIT(IEEE80211_STYPE_PROBE_REQ >> 4) 722be7d22fSVladimir Kondratiev }, 732be7d22fSVladimir Kondratiev [NL80211_IFTYPE_P2P_GO] = { 742be7d22fSVladimir Kondratiev .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | 752be7d22fSVladimir Kondratiev BIT(IEEE80211_STYPE_PROBE_RESP >> 4), 762be7d22fSVladimir Kondratiev .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | 772be7d22fSVladimir Kondratiev BIT(IEEE80211_STYPE_PROBE_REQ >> 4) 782be7d22fSVladimir Kondratiev }, 792be7d22fSVladimir Kondratiev }; 802be7d22fSVladimir Kondratiev 812be7d22fSVladimir Kondratiev static const u32 wil_cipher_suites[] = { 822be7d22fSVladimir Kondratiev WLAN_CIPHER_SUITE_GCMP, 832be7d22fSVladimir Kondratiev }; 842be7d22fSVladimir Kondratiev 8558527421SVladimir Kondratiev static const char * const key_usage_str[] = { 8658527421SVladimir Kondratiev [WMI_KEY_USE_PAIRWISE] = "PTK", 8758527421SVladimir Kondratiev [WMI_KEY_USE_RX_GROUP] = "RX_GTK", 8858527421SVladimir Kondratiev [WMI_KEY_USE_TX_GROUP] = "TX_GTK", 8958527421SVladimir Kondratiev }; 9058527421SVladimir Kondratiev 912be7d22fSVladimir Kondratiev int wil_iftype_nl2wmi(enum nl80211_iftype type) 922be7d22fSVladimir Kondratiev { 932be7d22fSVladimir Kondratiev static const struct { 942be7d22fSVladimir Kondratiev enum nl80211_iftype nl; 952be7d22fSVladimir Kondratiev enum wmi_network_type wmi; 962be7d22fSVladimir Kondratiev } __nl2wmi[] = { 972be7d22fSVladimir Kondratiev {NL80211_IFTYPE_ADHOC, WMI_NETTYPE_ADHOC}, 982be7d22fSVladimir Kondratiev {NL80211_IFTYPE_STATION, WMI_NETTYPE_INFRA}, 992be7d22fSVladimir Kondratiev {NL80211_IFTYPE_AP, WMI_NETTYPE_AP}, 1002be7d22fSVladimir Kondratiev {NL80211_IFTYPE_P2P_CLIENT, WMI_NETTYPE_P2P}, 1012be7d22fSVladimir Kondratiev {NL80211_IFTYPE_P2P_GO, WMI_NETTYPE_P2P}, 1022be7d22fSVladimir Kondratiev {NL80211_IFTYPE_MONITOR, WMI_NETTYPE_ADHOC}, /* FIXME */ 1032be7d22fSVladimir Kondratiev }; 1042be7d22fSVladimir Kondratiev uint i; 1052be7d22fSVladimir Kondratiev 1062be7d22fSVladimir Kondratiev for (i = 0; i < ARRAY_SIZE(__nl2wmi); i++) { 1072be7d22fSVladimir Kondratiev if (__nl2wmi[i].nl == type) 1082be7d22fSVladimir Kondratiev return __nl2wmi[i].wmi; 1092be7d22fSVladimir Kondratiev } 1102be7d22fSVladimir Kondratiev 1112be7d22fSVladimir Kondratiev return -EOPNOTSUPP; 1122be7d22fSVladimir Kondratiev } 1132be7d22fSVladimir Kondratiev 1149eb82d43SVladimir Kondratiev int wil_cid_fill_sinfo(struct wil6210_priv *wil, int cid, 115ef28afdbSVladimir Kondratiev struct station_info *sinfo) 1162be7d22fSVladimir Kondratiev { 1172be7d22fSVladimir Kondratiev struct wmi_notify_req_cmd cmd = { 1183df2cd36SVladimir Kondratiev .cid = cid, 1192be7d22fSVladimir Kondratiev .interval_usec = 0, 1202be7d22fSVladimir Kondratiev }; 121ef28afdbSVladimir Kondratiev struct { 122b874ddecSLior David struct wmi_cmd_hdr wmi; 123ef28afdbSVladimir Kondratiev struct wmi_notify_req_done_event evt; 124ef28afdbSVladimir Kondratiev } __packed reply; 125c8b78b5fSVladimir Kondratiev struct wil_net_stats *stats = &wil->sta[cid].stats; 126ef28afdbSVladimir Kondratiev int rc; 1272be7d22fSVladimir Kondratiev 1282be7d22fSVladimir Kondratiev rc = wmi_call(wil, WMI_NOTIFY_REQ_CMDID, &cmd, sizeof(cmd), 129ef28afdbSVladimir Kondratiev WMI_NOTIFY_REQ_DONE_EVENTID, &reply, sizeof(reply), 20); 1302be7d22fSVladimir Kondratiev if (rc) 1312be7d22fSVladimir Kondratiev return rc; 1322be7d22fSVladimir Kondratiev 133c8b78b5fSVladimir Kondratiev wil_dbg_wmi(wil, "Link status for CID %d: {\n" 134c8b78b5fSVladimir Kondratiev " MCS %d TSF 0x%016llx\n" 135b8b33a3aSVladimir Kondratiev " BF status 0x%08x SNR 0x%08x SQI %d%%\n" 136c8b78b5fSVladimir Kondratiev " Tx Tpt %d goodput %d Rx goodput %d\n" 137c8b78b5fSVladimir Kondratiev " Sectors(rx:tx) my %d:%d peer %d:%d\n""}\n", 138c8b78b5fSVladimir Kondratiev cid, le16_to_cpu(reply.evt.bf_mcs), 139c8b78b5fSVladimir Kondratiev le64_to_cpu(reply.evt.tsf), reply.evt.status, 140c8b78b5fSVladimir Kondratiev le32_to_cpu(reply.evt.snr_val), 141b8b33a3aSVladimir Kondratiev reply.evt.sqi, 142c8b78b5fSVladimir Kondratiev le32_to_cpu(reply.evt.tx_tpt), 143c8b78b5fSVladimir Kondratiev le32_to_cpu(reply.evt.tx_goodput), 144c8b78b5fSVladimir Kondratiev le32_to_cpu(reply.evt.rx_goodput), 145c8b78b5fSVladimir Kondratiev le16_to_cpu(reply.evt.my_rx_sector), 146c8b78b5fSVladimir Kondratiev le16_to_cpu(reply.evt.my_tx_sector), 147c8b78b5fSVladimir Kondratiev le16_to_cpu(reply.evt.other_rx_sector), 148c8b78b5fSVladimir Kondratiev le16_to_cpu(reply.evt.other_tx_sector)); 149c8b78b5fSVladimir Kondratiev 1502be7d22fSVladimir Kondratiev sinfo->generation = wil->sinfo_gen; 1512be7d22fSVladimir Kondratiev 152319090bfSJohannes Berg sinfo->filled = BIT(NL80211_STA_INFO_RX_BYTES) | 153319090bfSJohannes Berg BIT(NL80211_STA_INFO_TX_BYTES) | 154319090bfSJohannes Berg BIT(NL80211_STA_INFO_RX_PACKETS) | 155319090bfSJohannes Berg BIT(NL80211_STA_INFO_TX_PACKETS) | 156319090bfSJohannes Berg BIT(NL80211_STA_INFO_RX_BITRATE) | 157319090bfSJohannes Berg BIT(NL80211_STA_INFO_TX_BITRATE) | 158319090bfSJohannes Berg BIT(NL80211_STA_INFO_RX_DROP_MISC) | 159319090bfSJohannes Berg BIT(NL80211_STA_INFO_TX_FAILED); 160c8b78b5fSVladimir Kondratiev 1612be7d22fSVladimir Kondratiev sinfo->txrate.flags = RATE_INFO_FLAGS_MCS | RATE_INFO_FLAGS_60G; 162ef28afdbSVladimir Kondratiev sinfo->txrate.mcs = le16_to_cpu(reply.evt.bf_mcs); 1632be7d22fSVladimir Kondratiev sinfo->rxrate.flags = RATE_INFO_FLAGS_MCS | RATE_INFO_FLAGS_60G; 164c8b78b5fSVladimir Kondratiev sinfo->rxrate.mcs = stats->last_mcs_rx; 165c8b78b5fSVladimir Kondratiev sinfo->rx_bytes = stats->rx_bytes; 166c8b78b5fSVladimir Kondratiev sinfo->rx_packets = stats->rx_packets; 167c8b78b5fSVladimir Kondratiev sinfo->rx_dropped_misc = stats->rx_dropped; 168c8b78b5fSVladimir Kondratiev sinfo->tx_bytes = stats->tx_bytes; 169c8b78b5fSVladimir Kondratiev sinfo->tx_packets = stats->tx_packets; 170c8b78b5fSVladimir Kondratiev sinfo->tx_failed = stats->tx_errors; 1712be7d22fSVladimir Kondratiev 1729419b6a2SVladimir Kondratiev if (test_bit(wil_status_fwconnected, wil->status)) { 173319090bfSJohannes Berg sinfo->filled |= BIT(NL80211_STA_INFO_SIGNAL); 174b8b33a3aSVladimir Kondratiev sinfo->signal = reply.evt.sqi; 1752be7d22fSVladimir Kondratiev } 1762be7d22fSVladimir Kondratiev 177ef28afdbSVladimir Kondratiev return rc; 178ef28afdbSVladimir Kondratiev } 179ef28afdbSVladimir Kondratiev 180ef28afdbSVladimir Kondratiev static int wil_cfg80211_get_station(struct wiphy *wiphy, 181ef28afdbSVladimir Kondratiev struct net_device *ndev, 1823b3a0162SJohannes Berg const u8 *mac, struct station_info *sinfo) 183ef28afdbSVladimir Kondratiev { 184ef28afdbSVladimir Kondratiev struct wil6210_priv *wil = wiphy_to_wil(wiphy); 185ef28afdbSVladimir Kondratiev int rc; 186ef28afdbSVladimir Kondratiev 187ef28afdbSVladimir Kondratiev int cid = wil_find_cid(wil, mac); 188ef28afdbSVladimir Kondratiev 189fa4a18e7SVladimir Kondratiev wil_dbg_misc(wil, "%s(%pM) CID %d\n", __func__, mac, cid); 190ef28afdbSVladimir Kondratiev if (cid < 0) 191c14c5d99SVladimir Kondratiev return cid; 192ef28afdbSVladimir Kondratiev 193ef28afdbSVladimir Kondratiev rc = wil_cid_fill_sinfo(wil, cid, sinfo); 194ef28afdbSVladimir Kondratiev 195ef28afdbSVladimir Kondratiev return rc; 196ef28afdbSVladimir Kondratiev } 197ef28afdbSVladimir Kondratiev 198ef28afdbSVladimir Kondratiev /* 199ef28afdbSVladimir Kondratiev * Find @idx-th active STA for station dump. 200ef28afdbSVladimir Kondratiev */ 201ef28afdbSVladimir Kondratiev static int wil_find_cid_by_idx(struct wil6210_priv *wil, int idx) 202ef28afdbSVladimir Kondratiev { 203ef28afdbSVladimir Kondratiev int i; 204ef28afdbSVladimir Kondratiev 205ef28afdbSVladimir Kondratiev for (i = 0; i < ARRAY_SIZE(wil->sta); i++) { 206ef28afdbSVladimir Kondratiev if (wil->sta[i].status == wil_sta_unused) 207ef28afdbSVladimir Kondratiev continue; 208ef28afdbSVladimir Kondratiev if (idx == 0) 209ef28afdbSVladimir Kondratiev return i; 210ef28afdbSVladimir Kondratiev idx--; 211ef28afdbSVladimir Kondratiev } 212ef28afdbSVladimir Kondratiev 213ef28afdbSVladimir Kondratiev return -ENOENT; 214ef28afdbSVladimir Kondratiev } 215ef28afdbSVladimir Kondratiev 216ef28afdbSVladimir Kondratiev static int wil_cfg80211_dump_station(struct wiphy *wiphy, 217ef28afdbSVladimir Kondratiev struct net_device *dev, int idx, 218ef28afdbSVladimir Kondratiev u8 *mac, struct station_info *sinfo) 219ef28afdbSVladimir Kondratiev { 220ef28afdbSVladimir Kondratiev struct wil6210_priv *wil = wiphy_to_wil(wiphy); 221ef28afdbSVladimir Kondratiev int rc; 222ef28afdbSVladimir Kondratiev int cid = wil_find_cid_by_idx(wil, idx); 223ef28afdbSVladimir Kondratiev 224ef28afdbSVladimir Kondratiev if (cid < 0) 225ef28afdbSVladimir Kondratiev return -ENOENT; 226ef28afdbSVladimir Kondratiev 227a82553bbSVladimir Kondratiev ether_addr_copy(mac, wil->sta[cid].addr); 228fa4a18e7SVladimir Kondratiev wil_dbg_misc(wil, "%s(%pM) CID %d\n", __func__, mac, cid); 229ef28afdbSVladimir Kondratiev 230ef28afdbSVladimir Kondratiev rc = wil_cid_fill_sinfo(wil, cid, sinfo); 231ef28afdbSVladimir Kondratiev 232ef28afdbSVladimir Kondratiev return rc; 2332be7d22fSVladimir Kondratiev } 2342be7d22fSVladimir Kondratiev 2352be7d22fSVladimir Kondratiev static int wil_cfg80211_change_iface(struct wiphy *wiphy, 2362be7d22fSVladimir Kondratiev struct net_device *ndev, 2372be7d22fSVladimir Kondratiev enum nl80211_iftype type, u32 *flags, 2382be7d22fSVladimir Kondratiev struct vif_params *params) 2392be7d22fSVladimir Kondratiev { 2402be7d22fSVladimir Kondratiev struct wil6210_priv *wil = wiphy_to_wil(wiphy); 2412be7d22fSVladimir Kondratiev struct wireless_dev *wdev = wil->wdev; 2422be7d22fSVladimir Kondratiev 2432be7d22fSVladimir Kondratiev switch (type) { 2442be7d22fSVladimir Kondratiev case NL80211_IFTYPE_STATION: 2452be7d22fSVladimir Kondratiev case NL80211_IFTYPE_AP: 2462be7d22fSVladimir Kondratiev case NL80211_IFTYPE_P2P_CLIENT: 2472be7d22fSVladimir Kondratiev case NL80211_IFTYPE_P2P_GO: 2482be7d22fSVladimir Kondratiev break; 2492be7d22fSVladimir Kondratiev case NL80211_IFTYPE_MONITOR: 2502be7d22fSVladimir Kondratiev if (flags) 2512be7d22fSVladimir Kondratiev wil->monitor_flags = *flags; 2522be7d22fSVladimir Kondratiev else 2532be7d22fSVladimir Kondratiev wil->monitor_flags = 0; 2542be7d22fSVladimir Kondratiev 2552be7d22fSVladimir Kondratiev break; 2562be7d22fSVladimir Kondratiev default: 2572be7d22fSVladimir Kondratiev return -EOPNOTSUPP; 2582be7d22fSVladimir Kondratiev } 2592be7d22fSVladimir Kondratiev 2602be7d22fSVladimir Kondratiev wdev->iftype = type; 2612be7d22fSVladimir Kondratiev 2622be7d22fSVladimir Kondratiev return 0; 2632be7d22fSVladimir Kondratiev } 2642be7d22fSVladimir Kondratiev 2652be7d22fSVladimir Kondratiev static int wil_cfg80211_scan(struct wiphy *wiphy, 2662be7d22fSVladimir Kondratiev struct cfg80211_scan_request *request) 2672be7d22fSVladimir Kondratiev { 2682be7d22fSVladimir Kondratiev struct wil6210_priv *wil = wiphy_to_wil(wiphy); 2692be7d22fSVladimir Kondratiev struct wireless_dev *wdev = wil->wdev; 2702be7d22fSVladimir Kondratiev struct { 2712be7d22fSVladimir Kondratiev struct wmi_start_scan_cmd cmd; 2722be7d22fSVladimir Kondratiev u16 chnl[4]; 2732be7d22fSVladimir Kondratiev } __packed cmd; 2742be7d22fSVladimir Kondratiev uint i, n; 275ed6f9dc6SVladimir Kondratiev int rc; 2762be7d22fSVladimir Kondratiev 2772be7d22fSVladimir Kondratiev if (wil->scan_request) { 2782be7d22fSVladimir Kondratiev wil_err(wil, "Already scanning\n"); 2792be7d22fSVladimir Kondratiev return -EAGAIN; 2802be7d22fSVladimir Kondratiev } 2812be7d22fSVladimir Kondratiev 2822be7d22fSVladimir Kondratiev /* check we are client side */ 2832be7d22fSVladimir Kondratiev switch (wdev->iftype) { 2842be7d22fSVladimir Kondratiev case NL80211_IFTYPE_STATION: 2852be7d22fSVladimir Kondratiev case NL80211_IFTYPE_P2P_CLIENT: 2862be7d22fSVladimir Kondratiev break; 2872be7d22fSVladimir Kondratiev default: 2882be7d22fSVladimir Kondratiev return -EOPNOTSUPP; 2892be7d22fSVladimir Kondratiev } 2902be7d22fSVladimir Kondratiev 2912be7d22fSVladimir Kondratiev /* FW don't support scan after connection attempt */ 2929419b6a2SVladimir Kondratiev if (test_bit(wil_status_dontscan, wil->status)) { 293e83eb2fcSVladimir Kondratiev wil_err(wil, "Can't scan now\n"); 2942be7d22fSVladimir Kondratiev return -EBUSY; 2952be7d22fSVladimir Kondratiev } 2962be7d22fSVladimir Kondratiev 2972a91d7d0SVladimir Kondratiev wil_dbg_misc(wil, "Start scan_request 0x%p\n", request); 2988e52fe30SHamad Kadmany wil_dbg_misc(wil, "SSID count: %d", request->n_ssids); 2998e52fe30SHamad Kadmany 3008e52fe30SHamad Kadmany for (i = 0; i < request->n_ssids; i++) { 3018e52fe30SHamad Kadmany wil_dbg_misc(wil, "SSID[%d]", i); 3028e52fe30SHamad Kadmany print_hex_dump_bytes("SSID ", DUMP_PREFIX_OFFSET, 3038e52fe30SHamad Kadmany request->ssids[i].ssid, 3048e52fe30SHamad Kadmany request->ssids[i].ssid_len); 3058e52fe30SHamad Kadmany } 3068e52fe30SHamad Kadmany 3078e52fe30SHamad Kadmany if (request->n_ssids) 3088e52fe30SHamad Kadmany rc = wmi_set_ssid(wil, request->ssids[0].ssid_len, 3098e52fe30SHamad Kadmany request->ssids[0].ssid); 3108e52fe30SHamad Kadmany else 3118e52fe30SHamad Kadmany rc = wmi_set_ssid(wil, 0, NULL); 3128e52fe30SHamad Kadmany 3138e52fe30SHamad Kadmany if (rc) { 3148e52fe30SHamad Kadmany wil_err(wil, "set SSID for scan request failed: %d\n", rc); 3158e52fe30SHamad Kadmany return rc; 3168e52fe30SHamad Kadmany } 3178e52fe30SHamad Kadmany 3182be7d22fSVladimir Kondratiev wil->scan_request = request; 319047e5d74SVladimir Kondratiev mod_timer(&wil->scan_timer, jiffies + WIL6210_SCAN_TO); 3202be7d22fSVladimir Kondratiev 3212be7d22fSVladimir Kondratiev memset(&cmd, 0, sizeof(cmd)); 32274997a53SLior David cmd.cmd.scan_type = WMI_ACTIVE_SCAN; 3232be7d22fSVladimir Kondratiev cmd.cmd.num_channels = 0; 3242be7d22fSVladimir Kondratiev n = min(request->n_channels, 4U); 3252be7d22fSVladimir Kondratiev for (i = 0; i < n; i++) { 3262be7d22fSVladimir Kondratiev int ch = request->channels[i]->hw_value; 3278fe59627SVladimir Kondratiev 3282be7d22fSVladimir Kondratiev if (ch == 0) { 3292be7d22fSVladimir Kondratiev wil_err(wil, 3302be7d22fSVladimir Kondratiev "Scan requested for unknown frequency %dMhz\n", 3312be7d22fSVladimir Kondratiev request->channels[i]->center_freq); 3322be7d22fSVladimir Kondratiev continue; 3332be7d22fSVladimir Kondratiev } 3342be7d22fSVladimir Kondratiev /* 0-based channel indexes */ 3352be7d22fSVladimir Kondratiev cmd.cmd.channel_list[cmd.cmd.num_channels++].channel = ch - 1; 3367743882dSVladimir Kondratiev wil_dbg_misc(wil, "Scan for ch %d : %d MHz\n", ch, 3372be7d22fSVladimir Kondratiev request->channels[i]->center_freq); 3382be7d22fSVladimir Kondratiev } 3392be7d22fSVladimir Kondratiev 34077c91295SVladimir Kondratiev if (request->ie_len) 34177c91295SVladimir Kondratiev print_hex_dump_bytes("Scan IE ", DUMP_PREFIX_OFFSET, 34277c91295SVladimir Kondratiev request->ie, request->ie_len); 34377c91295SVladimir Kondratiev else 34477c91295SVladimir Kondratiev wil_dbg_misc(wil, "Scan has no IE's\n"); 34577c91295SVladimir Kondratiev 3465421bf0cSVladimir Kondratiev rc = wmi_set_ie(wil, WMI_FRAME_PROBE_REQ, request->ie_len, request->ie); 3475421bf0cSVladimir Kondratiev if (rc) 34877c91295SVladimir Kondratiev goto out; 34977c91295SVladimir Kondratiev 35074997a53SLior David if (wil->discovery_mode && cmd.cmd.scan_type == WMI_ACTIVE_SCAN) { 35174997a53SLior David cmd.cmd.discovery_mode = 1; 35274997a53SLior David wil_dbg_misc(wil, "active scan with discovery_mode=1\n"); 35374997a53SLior David } 35474997a53SLior David 355ed6f9dc6SVladimir Kondratiev rc = wmi_send(wil, WMI_START_SCAN_CMDID, &cmd, sizeof(cmd.cmd) + 3562be7d22fSVladimir Kondratiev cmd.cmd.num_channels * sizeof(cmd.cmd.channel_list[0])); 357ed6f9dc6SVladimir Kondratiev 35877c91295SVladimir Kondratiev out: 359a2142086SVladimir Kondratiev if (rc) { 360a2142086SVladimir Kondratiev del_timer_sync(&wil->scan_timer); 361ed6f9dc6SVladimir Kondratiev wil->scan_request = NULL; 362a2142086SVladimir Kondratiev } 363ed6f9dc6SVladimir Kondratiev 364ed6f9dc6SVladimir Kondratiev return rc; 3652be7d22fSVladimir Kondratiev } 3662be7d22fSVladimir Kondratiev 367feeac225SVladimir Kondratiev static void wil_print_crypto(struct wil6210_priv *wil, 368feeac225SVladimir Kondratiev struct cfg80211_crypto_settings *c) 369feeac225SVladimir Kondratiev { 370feeac225SVladimir Kondratiev int i, n; 371feeac225SVladimir Kondratiev 372feeac225SVladimir Kondratiev wil_dbg_misc(wil, "WPA versions: 0x%08x cipher group 0x%08x\n", 373feeac225SVladimir Kondratiev c->wpa_versions, c->cipher_group); 374feeac225SVladimir Kondratiev wil_dbg_misc(wil, "Pairwise ciphers [%d] {\n", c->n_ciphers_pairwise); 375feeac225SVladimir Kondratiev n = min_t(int, c->n_ciphers_pairwise, ARRAY_SIZE(c->ciphers_pairwise)); 376feeac225SVladimir Kondratiev for (i = 0; i < n; i++) 377feeac225SVladimir Kondratiev wil_dbg_misc(wil, " [%d] = 0x%08x\n", i, 378feeac225SVladimir Kondratiev c->ciphers_pairwise[i]); 379feeac225SVladimir Kondratiev wil_dbg_misc(wil, "}\n"); 380feeac225SVladimir Kondratiev wil_dbg_misc(wil, "AKM suites [%d] {\n", c->n_akm_suites); 381feeac225SVladimir Kondratiev n = min_t(int, c->n_akm_suites, ARRAY_SIZE(c->akm_suites)); 382feeac225SVladimir Kondratiev for (i = 0; i < n; i++) 383feeac225SVladimir Kondratiev wil_dbg_misc(wil, " [%d] = 0x%08x\n", i, 384feeac225SVladimir Kondratiev c->akm_suites[i]); 385feeac225SVladimir Kondratiev wil_dbg_misc(wil, "}\n"); 386feeac225SVladimir Kondratiev wil_dbg_misc(wil, "Control port : %d, eth_type 0x%04x no_encrypt %d\n", 387feeac225SVladimir Kondratiev c->control_port, be16_to_cpu(c->control_port_ethertype), 388feeac225SVladimir Kondratiev c->control_port_no_encrypt); 389feeac225SVladimir Kondratiev } 390feeac225SVladimir Kondratiev 3918ca26163SVladimir Kondratiev static void wil_print_connect_params(struct wil6210_priv *wil, 3928ca26163SVladimir Kondratiev struct cfg80211_connect_params *sme) 3938ca26163SVladimir Kondratiev { 3948ca26163SVladimir Kondratiev wil_info(wil, "Connecting to:\n"); 3958ca26163SVladimir Kondratiev if (sme->channel) { 3968ca26163SVladimir Kondratiev wil_info(wil, " Channel: %d freq %d\n", 3978ca26163SVladimir Kondratiev sme->channel->hw_value, sme->channel->center_freq); 3988ca26163SVladimir Kondratiev } 3998ca26163SVladimir Kondratiev if (sme->bssid) 4008ca26163SVladimir Kondratiev wil_info(wil, " BSSID: %pM\n", sme->bssid); 4018ca26163SVladimir Kondratiev if (sme->ssid) 4028ca26163SVladimir Kondratiev print_hex_dump(KERN_INFO, " SSID: ", DUMP_PREFIX_OFFSET, 4038ca26163SVladimir Kondratiev 16, 1, sme->ssid, sme->ssid_len, true); 4048ca26163SVladimir Kondratiev wil_info(wil, " Privacy: %s\n", sme->privacy ? "secure" : "open"); 405eabb03b4SLior David wil_info(wil, " PBSS: %d\n", sme->pbss); 406feeac225SVladimir Kondratiev wil_print_crypto(wil, &sme->crypto); 4078ca26163SVladimir Kondratiev } 4088ca26163SVladimir Kondratiev 4092be7d22fSVladimir Kondratiev static int wil_cfg80211_connect(struct wiphy *wiphy, 4102be7d22fSVladimir Kondratiev struct net_device *ndev, 4112be7d22fSVladimir Kondratiev struct cfg80211_connect_params *sme) 4122be7d22fSVladimir Kondratiev { 4132be7d22fSVladimir Kondratiev struct wil6210_priv *wil = wiphy_to_wil(wiphy); 4142be7d22fSVladimir Kondratiev struct cfg80211_bss *bss; 4152be7d22fSVladimir Kondratiev struct wmi_connect_cmd conn; 4162be7d22fSVladimir Kondratiev const u8 *ssid_eid; 4172be7d22fSVladimir Kondratiev const u8 *rsn_eid; 4182be7d22fSVladimir Kondratiev int ch; 4192be7d22fSVladimir Kondratiev int rc = 0; 420eabb03b4SLior David enum ieee80211_bss_type bss_type = IEEE80211_BSS_TYPE_ESS; 4212be7d22fSVladimir Kondratiev 422344a7024SVladimir Kondratiev wil_print_connect_params(wil, sme); 423344a7024SVladimir Kondratiev 4249419b6a2SVladimir Kondratiev if (test_bit(wil_status_fwconnecting, wil->status) || 4259419b6a2SVladimir Kondratiev test_bit(wil_status_fwconnected, wil->status)) 4264cd9e837SVladimir Kondratiev return -EALREADY; 4274cd9e837SVladimir Kondratiev 428344a7024SVladimir Kondratiev if (sme->ie_len > WMI_MAX_IE_LEN) { 429344a7024SVladimir Kondratiev wil_err(wil, "IE too large (%td bytes)\n", sme->ie_len); 430344a7024SVladimir Kondratiev return -ERANGE; 431344a7024SVladimir Kondratiev } 432344a7024SVladimir Kondratiev 433344a7024SVladimir Kondratiev rsn_eid = sme->ie ? 434344a7024SVladimir Kondratiev cfg80211_find_ie(WLAN_EID_RSN, sme->ie, sme->ie_len) : 435344a7024SVladimir Kondratiev NULL; 43627aa6b71SVladimir Kondratiev if (sme->privacy && !rsn_eid) 43727aa6b71SVladimir Kondratiev wil_info(wil, "WSC connection\n"); 4388ca26163SVladimir Kondratiev 439eabb03b4SLior David if (sme->pbss) 440eabb03b4SLior David bss_type = IEEE80211_BSS_TYPE_PBSS; 44134d50519SLior David 4422be7d22fSVladimir Kondratiev bss = cfg80211_get_bss(wiphy, sme->channel, sme->bssid, 4432be7d22fSVladimir Kondratiev sme->ssid, sme->ssid_len, 444eabb03b4SLior David bss_type, IEEE80211_PRIVACY_ANY); 4452be7d22fSVladimir Kondratiev if (!bss) { 4462be7d22fSVladimir Kondratiev wil_err(wil, "Unable to find BSS\n"); 4472be7d22fSVladimir Kondratiev return -ENOENT; 4482be7d22fSVladimir Kondratiev } 4492be7d22fSVladimir Kondratiev 4502be7d22fSVladimir Kondratiev ssid_eid = ieee80211_bss_get_ie(bss, WLAN_EID_SSID); 4512be7d22fSVladimir Kondratiev if (!ssid_eid) { 4522be7d22fSVladimir Kondratiev wil_err(wil, "No SSID\n"); 4532be7d22fSVladimir Kondratiev rc = -ENOENT; 4542be7d22fSVladimir Kondratiev goto out; 4552be7d22fSVladimir Kondratiev } 456344a7024SVladimir Kondratiev wil->privacy = sme->privacy; 4572be7d22fSVladimir Kondratiev 458344a7024SVladimir Kondratiev if (wil->privacy) { 459230d8442SVladimir Kondratiev /* For secure assoc, remove old keys */ 460230d8442SVladimir Kondratiev rc = wmi_del_cipher_key(wil, 0, bss->bssid, 461230d8442SVladimir Kondratiev WMI_KEY_USE_PAIRWISE); 4622be7d22fSVladimir Kondratiev if (rc) { 463230d8442SVladimir Kondratiev wil_err(wil, "WMI_DELETE_CIPHER_KEY_CMD(PTK) failed\n"); 464230d8442SVladimir Kondratiev goto out; 465230d8442SVladimir Kondratiev } 466230d8442SVladimir Kondratiev rc = wmi_del_cipher_key(wil, 0, bss->bssid, 467230d8442SVladimir Kondratiev WMI_KEY_USE_RX_GROUP); 468230d8442SVladimir Kondratiev if (rc) { 469230d8442SVladimir Kondratiev wil_err(wil, "WMI_DELETE_CIPHER_KEY_CMD(GTK) failed\n"); 4702be7d22fSVladimir Kondratiev goto out; 4712be7d22fSVladimir Kondratiev } 472ac4acdb7SVladimir Kondratiev } 473ac4acdb7SVladimir Kondratiev 474ac4acdb7SVladimir Kondratiev /* WMI_SET_APPIE_CMD. ie may contain rsn info as well as other info 475ac4acdb7SVladimir Kondratiev * elements. Send it also in case it's empty, to erase previously set 476ac4acdb7SVladimir Kondratiev * ies in FW. 477ac4acdb7SVladimir Kondratiev */ 4782be7d22fSVladimir Kondratiev rc = wmi_set_ie(wil, WMI_FRAME_ASSOC_REQ, sme->ie_len, sme->ie); 4795421bf0cSVladimir Kondratiev if (rc) 4802be7d22fSVladimir Kondratiev goto out; 4812be7d22fSVladimir Kondratiev 4822be7d22fSVladimir Kondratiev /* WMI_CONNECT_CMD */ 4832be7d22fSVladimir Kondratiev memset(&conn, 0, sizeof(conn)); 484acc9780dSVladimir Kondratiev switch (bss->capability & WLAN_CAPABILITY_DMG_TYPE_MASK) { 4852be7d22fSVladimir Kondratiev case WLAN_CAPABILITY_DMG_TYPE_AP: 4862be7d22fSVladimir Kondratiev conn.network_type = WMI_NETTYPE_INFRA; 4872be7d22fSVladimir Kondratiev break; 4882be7d22fSVladimir Kondratiev case WLAN_CAPABILITY_DMG_TYPE_PBSS: 4892be7d22fSVladimir Kondratiev conn.network_type = WMI_NETTYPE_P2P; 4902be7d22fSVladimir Kondratiev break; 4912be7d22fSVladimir Kondratiev default: 4922be7d22fSVladimir Kondratiev wil_err(wil, "Unsupported BSS type, capability= 0x%04x\n", 4932be7d22fSVladimir Kondratiev bss->capability); 4942be7d22fSVladimir Kondratiev goto out; 4952be7d22fSVladimir Kondratiev } 496344a7024SVladimir Kondratiev if (wil->privacy) { 49727aa6b71SVladimir Kondratiev if (rsn_eid) { /* regular secure connection */ 4982be7d22fSVladimir Kondratiev conn.dot11_auth_mode = WMI_AUTH11_SHARED; 4992be7d22fSVladimir Kondratiev conn.auth_mode = WMI_AUTH_WPA2_PSK; 5002be7d22fSVladimir Kondratiev conn.pairwise_crypto_type = WMI_CRYPT_AES_GCMP; 5012be7d22fSVladimir Kondratiev conn.pairwise_crypto_len = 16; 502230d8442SVladimir Kondratiev conn.group_crypto_type = WMI_CRYPT_AES_GCMP; 503230d8442SVladimir Kondratiev conn.group_crypto_len = 16; 50427aa6b71SVladimir Kondratiev } else { /* WSC */ 50527aa6b71SVladimir Kondratiev conn.dot11_auth_mode = WMI_AUTH11_WSC; 50627aa6b71SVladimir Kondratiev conn.auth_mode = WMI_AUTH_NONE; 50727aa6b71SVladimir Kondratiev } 50827aa6b71SVladimir Kondratiev } else { /* insecure connection */ 5092be7d22fSVladimir Kondratiev conn.dot11_auth_mode = WMI_AUTH11_OPEN; 5102be7d22fSVladimir Kondratiev conn.auth_mode = WMI_AUTH_NONE; 5112be7d22fSVladimir Kondratiev } 5122be7d22fSVladimir Kondratiev 5132be7d22fSVladimir Kondratiev conn.ssid_len = min_t(u8, ssid_eid[1], 32); 5142be7d22fSVladimir Kondratiev memcpy(conn.ssid, ssid_eid+2, conn.ssid_len); 5152be7d22fSVladimir Kondratiev 5162be7d22fSVladimir Kondratiev ch = bss->channel->hw_value; 5172be7d22fSVladimir Kondratiev if (ch == 0) { 5182be7d22fSVladimir Kondratiev wil_err(wil, "BSS at unknown frequency %dMhz\n", 5192be7d22fSVladimir Kondratiev bss->channel->center_freq); 5202be7d22fSVladimir Kondratiev rc = -EOPNOTSUPP; 5212be7d22fSVladimir Kondratiev goto out; 5222be7d22fSVladimir Kondratiev } 5232be7d22fSVladimir Kondratiev conn.channel = ch - 1; 5242be7d22fSVladimir Kondratiev 525a82553bbSVladimir Kondratiev ether_addr_copy(conn.bssid, bss->bssid); 526a82553bbSVladimir Kondratiev ether_addr_copy(conn.dst_mac, bss->bssid); 527e83eb2fcSVladimir Kondratiev 5289419b6a2SVladimir Kondratiev set_bit(wil_status_fwconnecting, wil->status); 5292be7d22fSVladimir Kondratiev 5302be7d22fSVladimir Kondratiev rc = wmi_send(wil, WMI_CONNECT_CMDID, &conn, sizeof(conn)); 5312be7d22fSVladimir Kondratiev if (rc == 0) { 532c5e96c91SDedy Lansky netif_carrier_on(ndev); 5332be7d22fSVladimir Kondratiev /* Connect can take lots of time */ 5342be7d22fSVladimir Kondratiev mod_timer(&wil->connect_timer, 5352be7d22fSVladimir Kondratiev jiffies + msecs_to_jiffies(2000)); 536b338f74eSVladimir Kondratiev } else { 5379419b6a2SVladimir Kondratiev clear_bit(wil_status_fwconnecting, wil->status); 5382be7d22fSVladimir Kondratiev } 5392be7d22fSVladimir Kondratiev 5402be7d22fSVladimir Kondratiev out: 5415b112d3dSJohannes Berg cfg80211_put_bss(wiphy, bss); 5422be7d22fSVladimir Kondratiev 5432be7d22fSVladimir Kondratiev return rc; 5442be7d22fSVladimir Kondratiev } 5452be7d22fSVladimir Kondratiev 5462be7d22fSVladimir Kondratiev static int wil_cfg80211_disconnect(struct wiphy *wiphy, 5472be7d22fSVladimir Kondratiev struct net_device *ndev, 5482be7d22fSVladimir Kondratiev u16 reason_code) 5492be7d22fSVladimir Kondratiev { 5502be7d22fSVladimir Kondratiev int rc; 5512be7d22fSVladimir Kondratiev struct wil6210_priv *wil = wiphy_to_wil(wiphy); 5522be7d22fSVladimir Kondratiev 553de9084efSVladimir Kondratiev wil_dbg_misc(wil, "%s(reason=%d)\n", __func__, reason_code); 554de9084efSVladimir Kondratiev 55578771d76SVladimir Kondratiev if (!(test_bit(wil_status_fwconnecting, wil->status) || 55678771d76SVladimir Kondratiev test_bit(wil_status_fwconnected, wil->status))) { 55778771d76SVladimir Kondratiev wil_err(wil, "%s: Disconnect was called while disconnected\n", 55878771d76SVladimir Kondratiev __func__); 55978771d76SVladimir Kondratiev return 0; 56078771d76SVladimir Kondratiev } 56178771d76SVladimir Kondratiev 56278771d76SVladimir Kondratiev rc = wmi_call(wil, WMI_DISCONNECT_CMDID, NULL, 0, 56378771d76SVladimir Kondratiev WMI_DISCONNECT_EVENTID, NULL, 0, 56478771d76SVladimir Kondratiev WIL6210_DISCONNECT_TO_MS); 56578771d76SVladimir Kondratiev if (rc) 56678771d76SVladimir Kondratiev wil_err(wil, "%s: disconnect error %d\n", __func__, rc); 5672be7d22fSVladimir Kondratiev 5682be7d22fSVladimir Kondratiev return rc; 5692be7d22fSVladimir Kondratiev } 5702be7d22fSVladimir Kondratiev 5710b39aaf2SVladimir Kondratiev int wil_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, 5721647f12fSVladimir Kondratiev struct cfg80211_mgmt_tx_params *params, 5731647f12fSVladimir Kondratiev u64 *cookie) 5741647f12fSVladimir Kondratiev { 5751647f12fSVladimir Kondratiev const u8 *buf = params->buf; 5761647f12fSVladimir Kondratiev size_t len = params->len; 5771647f12fSVladimir Kondratiev struct wil6210_priv *wil = wiphy_to_wil(wiphy); 5781647f12fSVladimir Kondratiev int rc; 579304464f4SVladimir Kondratiev bool tx_status = false; 5801647f12fSVladimir Kondratiev struct ieee80211_mgmt *mgmt_frame = (void *)buf; 5811647f12fSVladimir Kondratiev struct wmi_sw_tx_req_cmd *cmd; 5821647f12fSVladimir Kondratiev struct { 583b874ddecSLior David struct wmi_cmd_hdr wmi; 5841647f12fSVladimir Kondratiev struct wmi_sw_tx_complete_event evt; 5851647f12fSVladimir Kondratiev } __packed evt; 5861647f12fSVladimir Kondratiev 5871647f12fSVladimir Kondratiev cmd = kmalloc(sizeof(*cmd) + len, GFP_KERNEL); 588304464f4SVladimir Kondratiev if (!cmd) { 589304464f4SVladimir Kondratiev rc = -ENOMEM; 590304464f4SVladimir Kondratiev goto out; 591304464f4SVladimir Kondratiev } 5921647f12fSVladimir Kondratiev 5931647f12fSVladimir Kondratiev memcpy(cmd->dst_mac, mgmt_frame->da, WMI_MAC_LEN); 5941647f12fSVladimir Kondratiev cmd->len = cpu_to_le16(len); 5951647f12fSVladimir Kondratiev memcpy(cmd->payload, buf, len); 5961647f12fSVladimir Kondratiev 5971647f12fSVladimir Kondratiev rc = wmi_call(wil, WMI_SW_TX_REQ_CMDID, cmd, sizeof(*cmd) + len, 5981647f12fSVladimir Kondratiev WMI_SW_TX_COMPLETE_EVENTID, &evt, sizeof(evt), 2000); 5991647f12fSVladimir Kondratiev if (rc == 0) 600304464f4SVladimir Kondratiev tx_status = !evt.evt.status; 6011647f12fSVladimir Kondratiev 6021647f12fSVladimir Kondratiev kfree(cmd); 603304464f4SVladimir Kondratiev out: 604304464f4SVladimir Kondratiev cfg80211_mgmt_tx_status(wdev, cookie ? *cookie : 0, buf, len, 605304464f4SVladimir Kondratiev tx_status, GFP_KERNEL); 6061647f12fSVladimir Kondratiev return rc; 6071647f12fSVladimir Kondratiev } 6081647f12fSVladimir Kondratiev 6092be7d22fSVladimir Kondratiev static int wil_cfg80211_set_channel(struct wiphy *wiphy, 6102be7d22fSVladimir Kondratiev struct cfg80211_chan_def *chandef) 6112be7d22fSVladimir Kondratiev { 6122be7d22fSVladimir Kondratiev struct wil6210_priv *wil = wiphy_to_wil(wiphy); 6132be7d22fSVladimir Kondratiev struct wireless_dev *wdev = wil->wdev; 6142be7d22fSVladimir Kondratiev 6152be7d22fSVladimir Kondratiev wdev->preset_chandef = *chandef; 6162be7d22fSVladimir Kondratiev 6172be7d22fSVladimir Kondratiev return 0; 6182be7d22fSVladimir Kondratiev } 6192be7d22fSVladimir Kondratiev 620230d8442SVladimir Kondratiev static enum wmi_key_usage wil_detect_key_usage(struct wil6210_priv *wil, 621230d8442SVladimir Kondratiev bool pairwise) 622230d8442SVladimir Kondratiev { 623230d8442SVladimir Kondratiev struct wireless_dev *wdev = wil->wdev; 624230d8442SVladimir Kondratiev enum wmi_key_usage rc; 625230d8442SVladimir Kondratiev 626230d8442SVladimir Kondratiev if (pairwise) { 627230d8442SVladimir Kondratiev rc = WMI_KEY_USE_PAIRWISE; 628230d8442SVladimir Kondratiev } else { 629230d8442SVladimir Kondratiev switch (wdev->iftype) { 630230d8442SVladimir Kondratiev case NL80211_IFTYPE_STATION: 631230d8442SVladimir Kondratiev rc = WMI_KEY_USE_RX_GROUP; 632230d8442SVladimir Kondratiev break; 633230d8442SVladimir Kondratiev case NL80211_IFTYPE_AP: 634230d8442SVladimir Kondratiev rc = WMI_KEY_USE_TX_GROUP; 635230d8442SVladimir Kondratiev break; 636230d8442SVladimir Kondratiev default: 637230d8442SVladimir Kondratiev /* TODO: Rx GTK or Tx GTK? */ 638230d8442SVladimir Kondratiev wil_err(wil, "Can't determine GTK type\n"); 639230d8442SVladimir Kondratiev rc = WMI_KEY_USE_RX_GROUP; 640230d8442SVladimir Kondratiev break; 641230d8442SVladimir Kondratiev } 642230d8442SVladimir Kondratiev } 643230d8442SVladimir Kondratiev wil_dbg_misc(wil, "%s() -> %s\n", __func__, key_usage_str[rc]); 644230d8442SVladimir Kondratiev 645230d8442SVladimir Kondratiev return rc; 646230d8442SVladimir Kondratiev } 647230d8442SVladimir Kondratiev 64858527421SVladimir Kondratiev static struct wil_tid_crypto_rx_single * 64958527421SVladimir Kondratiev wil_find_crypto_ctx(struct wil6210_priv *wil, u8 key_index, 65058527421SVladimir Kondratiev enum wmi_key_usage key_usage, const u8 *mac_addr) 65158527421SVladimir Kondratiev { 65258527421SVladimir Kondratiev int cid = -EINVAL; 65358527421SVladimir Kondratiev int tid = 0; 65458527421SVladimir Kondratiev struct wil_sta_info *s; 65558527421SVladimir Kondratiev struct wil_tid_crypto_rx *c; 65658527421SVladimir Kondratiev 65758527421SVladimir Kondratiev if (key_usage == WMI_KEY_USE_TX_GROUP) 65858527421SVladimir Kondratiev return NULL; /* not needed */ 65958527421SVladimir Kondratiev 66058527421SVladimir Kondratiev /* supplicant provides Rx group key in STA mode with NULL MAC address */ 66158527421SVladimir Kondratiev if (mac_addr) 66258527421SVladimir Kondratiev cid = wil_find_cid(wil, mac_addr); 66358527421SVladimir Kondratiev else if (key_usage == WMI_KEY_USE_RX_GROUP) 66458527421SVladimir Kondratiev cid = wil_find_cid_by_idx(wil, 0); 66558527421SVladimir Kondratiev if (cid < 0) { 66658527421SVladimir Kondratiev wil_err(wil, "No CID for %pM %s[%d]\n", mac_addr, 66758527421SVladimir Kondratiev key_usage_str[key_usage], key_index); 66858527421SVladimir Kondratiev return ERR_PTR(cid); 66958527421SVladimir Kondratiev } 67058527421SVladimir Kondratiev 67158527421SVladimir Kondratiev s = &wil->sta[cid]; 67258527421SVladimir Kondratiev if (key_usage == WMI_KEY_USE_PAIRWISE) 67358527421SVladimir Kondratiev c = &s->tid_crypto_rx[tid]; 67458527421SVladimir Kondratiev else 67558527421SVladimir Kondratiev c = &s->group_crypto_rx; 67658527421SVladimir Kondratiev 67758527421SVladimir Kondratiev return &c->key_id[key_index]; 67858527421SVladimir Kondratiev } 67958527421SVladimir Kondratiev 6802be7d22fSVladimir Kondratiev static int wil_cfg80211_add_key(struct wiphy *wiphy, 6812be7d22fSVladimir Kondratiev struct net_device *ndev, 6822be7d22fSVladimir Kondratiev u8 key_index, bool pairwise, 6832be7d22fSVladimir Kondratiev const u8 *mac_addr, 6842be7d22fSVladimir Kondratiev struct key_params *params) 6852be7d22fSVladimir Kondratiev { 68658527421SVladimir Kondratiev int rc; 6872be7d22fSVladimir Kondratiev struct wil6210_priv *wil = wiphy_to_wil(wiphy); 688230d8442SVladimir Kondratiev enum wmi_key_usage key_usage = wil_detect_key_usage(wil, pairwise); 68958527421SVladimir Kondratiev struct wil_tid_crypto_rx_single *cc = wil_find_crypto_ctx(wil, 69058527421SVladimir Kondratiev key_index, 69158527421SVladimir Kondratiev key_usage, 69258527421SVladimir Kondratiev mac_addr); 6932be7d22fSVladimir Kondratiev 69458527421SVladimir Kondratiev wil_dbg_misc(wil, "%s(%pM %s[%d] PN %*phN)\n", __func__, 69558527421SVladimir Kondratiev mac_addr, key_usage_str[key_usage], key_index, 69658527421SVladimir Kondratiev params->seq_len, params->seq); 697db8adcbfSVladimir Kondratiev 69858527421SVladimir Kondratiev if (IS_ERR(cc)) { 69958527421SVladimir Kondratiev wil_err(wil, "Not connected, %s(%pM %s[%d] PN %*phN)\n", 70058527421SVladimir Kondratiev __func__, mac_addr, key_usage_str[key_usage], key_index, 70158527421SVladimir Kondratiev params->seq_len, params->seq); 70258527421SVladimir Kondratiev return -EINVAL; 70358527421SVladimir Kondratiev } 70458527421SVladimir Kondratiev 70558527421SVladimir Kondratiev if (cc) 70658527421SVladimir Kondratiev cc->key_set = false; 70758527421SVladimir Kondratiev 70858527421SVladimir Kondratiev if (params->seq && params->seq_len != IEEE80211_GCMP_PN_LEN) { 70958527421SVladimir Kondratiev wil_err(wil, 71058527421SVladimir Kondratiev "Wrong PN len %d, %s(%pM %s[%d] PN %*phN)\n", 71158527421SVladimir Kondratiev params->seq_len, __func__, mac_addr, 71258527421SVladimir Kondratiev key_usage_str[key_usage], key_index, 71358527421SVladimir Kondratiev params->seq_len, params->seq); 71458527421SVladimir Kondratiev return -EINVAL; 71558527421SVladimir Kondratiev } 71658527421SVladimir Kondratiev 71758527421SVladimir Kondratiev rc = wmi_add_cipher_key(wil, key_index, mac_addr, params->key_len, 718230d8442SVladimir Kondratiev params->key, key_usage); 71958527421SVladimir Kondratiev if ((rc == 0) && cc) { 72058527421SVladimir Kondratiev if (params->seq) 72158527421SVladimir Kondratiev memcpy(cc->pn, params->seq, IEEE80211_GCMP_PN_LEN); 72258527421SVladimir Kondratiev else 72358527421SVladimir Kondratiev memset(cc->pn, 0, IEEE80211_GCMP_PN_LEN); 72458527421SVladimir Kondratiev cc->key_set = true; 72558527421SVladimir Kondratiev } 72658527421SVladimir Kondratiev 72758527421SVladimir Kondratiev return rc; 7282be7d22fSVladimir Kondratiev } 7292be7d22fSVladimir Kondratiev 7302be7d22fSVladimir Kondratiev static int wil_cfg80211_del_key(struct wiphy *wiphy, 7312be7d22fSVladimir Kondratiev struct net_device *ndev, 7322be7d22fSVladimir Kondratiev u8 key_index, bool pairwise, 7332be7d22fSVladimir Kondratiev const u8 *mac_addr) 7342be7d22fSVladimir Kondratiev { 7352be7d22fSVladimir Kondratiev struct wil6210_priv *wil = wiphy_to_wil(wiphy); 736230d8442SVladimir Kondratiev enum wmi_key_usage key_usage = wil_detect_key_usage(wil, pairwise); 73758527421SVladimir Kondratiev struct wil_tid_crypto_rx_single *cc = wil_find_crypto_ctx(wil, 73858527421SVladimir Kondratiev key_index, 73958527421SVladimir Kondratiev key_usage, 74058527421SVladimir Kondratiev mac_addr); 7412be7d22fSVladimir Kondratiev 74258527421SVladimir Kondratiev wil_dbg_misc(wil, "%s(%pM %s[%d])\n", __func__, mac_addr, 74358527421SVladimir Kondratiev key_usage_str[key_usage], key_index); 74458527421SVladimir Kondratiev 74558527421SVladimir Kondratiev if (IS_ERR(cc)) 74658527421SVladimir Kondratiev wil_info(wil, "Not connected, %s(%pM %s[%d])\n", __func__, 74758527421SVladimir Kondratiev mac_addr, key_usage_str[key_usage], key_index); 74858527421SVladimir Kondratiev 74958527421SVladimir Kondratiev if (!IS_ERR_OR_NULL(cc)) 75058527421SVladimir Kondratiev cc->key_set = false; 751db8adcbfSVladimir Kondratiev 752230d8442SVladimir Kondratiev return wmi_del_cipher_key(wil, key_index, mac_addr, key_usage); 7532be7d22fSVladimir Kondratiev } 7542be7d22fSVladimir Kondratiev 7552be7d22fSVladimir Kondratiev /* Need to be present or wiphy_new() will WARN */ 7562be7d22fSVladimir Kondratiev static int wil_cfg80211_set_default_key(struct wiphy *wiphy, 7572be7d22fSVladimir Kondratiev struct net_device *ndev, 7582be7d22fSVladimir Kondratiev u8 key_index, bool unicast, 7592be7d22fSVladimir Kondratiev bool multicast) 7602be7d22fSVladimir Kondratiev { 7612be7d22fSVladimir Kondratiev return 0; 7622be7d22fSVladimir Kondratiev } 7632be7d22fSVladimir Kondratiev 7641647f12fSVladimir Kondratiev static int wil_remain_on_channel(struct wiphy *wiphy, 7651647f12fSVladimir Kondratiev struct wireless_dev *wdev, 7661647f12fSVladimir Kondratiev struct ieee80211_channel *chan, 7671647f12fSVladimir Kondratiev unsigned int duration, 7681647f12fSVladimir Kondratiev u64 *cookie) 7691647f12fSVladimir Kondratiev { 7701647f12fSVladimir Kondratiev struct wil6210_priv *wil = wiphy_to_wil(wiphy); 7711647f12fSVladimir Kondratiev int rc; 7721647f12fSVladimir Kondratiev 7731647f12fSVladimir Kondratiev /* TODO: handle duration */ 7741647f12fSVladimir Kondratiev wil_info(wil, "%s(%d, %d ms)\n", __func__, chan->center_freq, duration); 7751647f12fSVladimir Kondratiev 7761647f12fSVladimir Kondratiev rc = wmi_set_channel(wil, chan->hw_value); 7771647f12fSVladimir Kondratiev if (rc) 7781647f12fSVladimir Kondratiev return rc; 7791647f12fSVladimir Kondratiev 7801647f12fSVladimir Kondratiev rc = wmi_rxon(wil, true); 7811647f12fSVladimir Kondratiev 7821647f12fSVladimir Kondratiev return rc; 7831647f12fSVladimir Kondratiev } 7841647f12fSVladimir Kondratiev 7851647f12fSVladimir Kondratiev static int wil_cancel_remain_on_channel(struct wiphy *wiphy, 7861647f12fSVladimir Kondratiev struct wireless_dev *wdev, 7871647f12fSVladimir Kondratiev u64 cookie) 7881647f12fSVladimir Kondratiev { 7891647f12fSVladimir Kondratiev struct wil6210_priv *wil = wiphy_to_wil(wiphy); 7901647f12fSVladimir Kondratiev int rc; 7911647f12fSVladimir Kondratiev 7921647f12fSVladimir Kondratiev wil_info(wil, "%s()\n", __func__); 7931647f12fSVladimir Kondratiev 7941647f12fSVladimir Kondratiev rc = wmi_rxon(wil, false); 7951647f12fSVladimir Kondratiev 7961647f12fSVladimir Kondratiev return rc; 7971647f12fSVladimir Kondratiev } 7981647f12fSVladimir Kondratiev 799c100c883SLior David /** 800c100c883SLior David * find a specific IE in a list of IEs 801c100c883SLior David * return a pointer to the beginning of IE in the list 802c100c883SLior David * or NULL if not found 803c100c883SLior David */ 804c100c883SLior David static const u8 *_wil_cfg80211_find_ie(const u8 *ies, u16 ies_len, const u8 *ie, 805c100c883SLior David u16 ie_len) 806c100c883SLior David { 807c100c883SLior David struct ieee80211_vendor_ie *vie; 808c100c883SLior David u32 oui; 809c100c883SLior David 810c100c883SLior David /* IE tag at offset 0, length at offset 1 */ 811c100c883SLior David if (ie_len < 2 || 2 + ie[1] > ie_len) 812c100c883SLior David return NULL; 813c100c883SLior David 814c100c883SLior David if (ie[0] != WLAN_EID_VENDOR_SPECIFIC) 815c100c883SLior David return cfg80211_find_ie(ie[0], ies, ies_len); 816c100c883SLior David 817c100c883SLior David /* make sure there is room for 3 bytes OUI + 1 byte OUI type */ 818c100c883SLior David if (ie[1] < 4) 819c100c883SLior David return NULL; 820c100c883SLior David vie = (struct ieee80211_vendor_ie *)ie; 821c100c883SLior David oui = vie->oui[0] << 16 | vie->oui[1] << 8 | vie->oui[2]; 822c100c883SLior David return cfg80211_find_vendor_ie(oui, vie->oui_type, ies, 823c100c883SLior David ies_len); 824c100c883SLior David } 825c100c883SLior David 826c100c883SLior David /** 827c100c883SLior David * merge the IEs in two lists into a single list. 828c100c883SLior David * do not include IEs from the second list which exist in the first list. 829c100c883SLior David * add only vendor specific IEs from second list to keep 830c100c883SLior David * the merged list sorted (since vendor-specific IE has the 831c100c883SLior David * highest tag number) 832c100c883SLior David * caller must free the allocated memory for merged IEs 833c100c883SLior David */ 834c100c883SLior David static int _wil_cfg80211_merge_extra_ies(const u8 *ies1, u16 ies1_len, 835c100c883SLior David const u8 *ies2, u16 ies2_len, 836c100c883SLior David u8 **merged_ies, u16 *merged_len) 837c100c883SLior David { 838c100c883SLior David u8 *buf, *dpos; 839c100c883SLior David const u8 *spos; 840c100c883SLior David 841c100c883SLior David if (ies1_len == 0 && ies2_len == 0) { 842c100c883SLior David *merged_ies = NULL; 843c100c883SLior David *merged_len = 0; 844c100c883SLior David return 0; 845c100c883SLior David } 846c100c883SLior David 847c100c883SLior David buf = kmalloc(ies1_len + ies2_len, GFP_KERNEL); 848c100c883SLior David if (!buf) 849c100c883SLior David return -ENOMEM; 850c100c883SLior David memcpy(buf, ies1, ies1_len); 851c100c883SLior David dpos = buf + ies1_len; 852c100c883SLior David spos = ies2; 853c100c883SLior David while (spos + 1 < ies2 + ies2_len) { 854c100c883SLior David /* IE tag at offset 0, length at offset 1 */ 855c100c883SLior David u16 ielen = 2 + spos[1]; 856c100c883SLior David 857c100c883SLior David if (spos + ielen > ies2 + ies2_len) 858c100c883SLior David break; 859c100c883SLior David if (spos[0] == WLAN_EID_VENDOR_SPECIFIC && 860c100c883SLior David !_wil_cfg80211_find_ie(ies1, ies1_len, spos, ielen)) { 861c100c883SLior David memcpy(dpos, spos, ielen); 862c100c883SLior David dpos += ielen; 863c100c883SLior David } 864c100c883SLior David spos += ielen; 865c100c883SLior David } 866c100c883SLior David 867c100c883SLior David *merged_ies = buf; 868c100c883SLior David *merged_len = dpos - buf; 869c100c883SLior David return 0; 870c100c883SLior David } 871c100c883SLior David 872ca959773SVladimir Kondratiev static void wil_print_bcon_data(struct cfg80211_beacon_data *b) 873ca959773SVladimir Kondratiev { 874ca959773SVladimir Kondratiev print_hex_dump_bytes("head ", DUMP_PREFIX_OFFSET, 875ca959773SVladimir Kondratiev b->head, b->head_len); 876ca959773SVladimir Kondratiev print_hex_dump_bytes("tail ", DUMP_PREFIX_OFFSET, 877ca959773SVladimir Kondratiev b->tail, b->tail_len); 878ca959773SVladimir Kondratiev print_hex_dump_bytes("BCON IE ", DUMP_PREFIX_OFFSET, 879ca959773SVladimir Kondratiev b->beacon_ies, b->beacon_ies_len); 880ca959773SVladimir Kondratiev print_hex_dump_bytes("PROBE ", DUMP_PREFIX_OFFSET, 881ca959773SVladimir Kondratiev b->probe_resp, b->probe_resp_len); 882ca959773SVladimir Kondratiev print_hex_dump_bytes("PROBE IE ", DUMP_PREFIX_OFFSET, 883ca959773SVladimir Kondratiev b->proberesp_ies, b->proberesp_ies_len); 884ca959773SVladimir Kondratiev print_hex_dump_bytes("ASSOC IE ", DUMP_PREFIX_OFFSET, 885ca959773SVladimir Kondratiev b->assocresp_ies, b->assocresp_ies_len); 886ca959773SVladimir Kondratiev } 887ca959773SVladimir Kondratiev 88833190ebfSVladimir Kondratiev /* internal functions for device reset and starting AP */ 88933190ebfSVladimir Kondratiev static int _wil_cfg80211_set_ies(struct wiphy *wiphy, 890cab5abbfSVladimir Kondratiev struct cfg80211_beacon_data *bcon) 89133190ebfSVladimir Kondratiev { 89233190ebfSVladimir Kondratiev int rc; 89333190ebfSVladimir Kondratiev struct wil6210_priv *wil = wiphy_to_wil(wiphy); 894c100c883SLior David u16 len = 0, proberesp_len = 0; 895c100c883SLior David u8 *ies = NULL, *proberesp = NULL; 89633190ebfSVladimir Kondratiev 897c100c883SLior David if (bcon->probe_resp) { 898c100c883SLior David struct ieee80211_mgmt *f = 899c100c883SLior David (struct ieee80211_mgmt *)bcon->probe_resp; 900c100c883SLior David size_t hlen = offsetof(struct ieee80211_mgmt, 901c100c883SLior David u.probe_resp.variable); 902c100c883SLior David proberesp = f->u.probe_resp.variable; 903c100c883SLior David proberesp_len = bcon->probe_resp_len - hlen; 904c100c883SLior David } 905c100c883SLior David rc = _wil_cfg80211_merge_extra_ies(proberesp, 906c100c883SLior David proberesp_len, 907c100c883SLior David bcon->proberesp_ies, 908c100c883SLior David bcon->proberesp_ies_len, 909c100c883SLior David &ies, &len); 910c100c883SLior David 9115421bf0cSVladimir Kondratiev if (rc) 912c100c883SLior David goto out; 91333190ebfSVladimir Kondratiev 914c100c883SLior David rc = wmi_set_ie(wil, WMI_FRAME_PROBE_RESP, len, ies); 915c100c883SLior David if (rc) 916c100c883SLior David goto out; 917c100c883SLior David 918c100c883SLior David if (bcon->assocresp_ies) 919c100c883SLior David rc = wmi_set_ie(wil, WMI_FRAME_ASSOC_RESP, 920c100c883SLior David bcon->assocresp_ies_len, bcon->assocresp_ies); 921c100c883SLior David else 922c100c883SLior David rc = wmi_set_ie(wil, WMI_FRAME_ASSOC_RESP, len, ies); 923cab5abbfSVladimir Kondratiev #if 0 /* to use beacon IE's, remove this #if 0 */ 9245421bf0cSVladimir Kondratiev if (rc) 925c100c883SLior David goto out; 9265421bf0cSVladimir Kondratiev 9275421bf0cSVladimir Kondratiev rc = wmi_set_ie(wil, WMI_FRAME_BEACON, bcon->tail_len, bcon->tail); 928cab5abbfSVladimir Kondratiev #endif 929c100c883SLior David out: 930c100c883SLior David kfree(ies); 9315421bf0cSVladimir Kondratiev return rc; 93233190ebfSVladimir Kondratiev } 93333190ebfSVladimir Kondratiev 93433190ebfSVladimir Kondratiev static int _wil_cfg80211_start_ap(struct wiphy *wiphy, 93533190ebfSVladimir Kondratiev struct net_device *ndev, 93633190ebfSVladimir Kondratiev const u8 *ssid, size_t ssid_len, u32 privacy, 93733190ebfSVladimir Kondratiev int bi, u8 chan, 938cab5abbfSVladimir Kondratiev struct cfg80211_beacon_data *bcon, 939eabb03b4SLior David u8 hidden_ssid, u32 pbss) 94033190ebfSVladimir Kondratiev { 94133190ebfSVladimir Kondratiev struct wil6210_priv *wil = wiphy_to_wil(wiphy); 94233190ebfSVladimir Kondratiev int rc; 94333190ebfSVladimir Kondratiev struct wireless_dev *wdev = ndev->ieee80211_ptr; 94433190ebfSVladimir Kondratiev u8 wmi_nettype = wil_iftype_nl2wmi(wdev->iftype); 94533190ebfSVladimir Kondratiev 946eabb03b4SLior David if (pbss) 947eabb03b4SLior David wmi_nettype = WMI_NETTYPE_P2P; 948eabb03b4SLior David 94933190ebfSVladimir Kondratiev wil_set_recovery_state(wil, fw_recovery_idle); 95033190ebfSVladimir Kondratiev 95133190ebfSVladimir Kondratiev mutex_lock(&wil->mutex); 95233190ebfSVladimir Kondratiev 95333190ebfSVladimir Kondratiev __wil_down(wil); 95433190ebfSVladimir Kondratiev rc = __wil_up(wil); 95533190ebfSVladimir Kondratiev if (rc) 95633190ebfSVladimir Kondratiev goto out; 95733190ebfSVladimir Kondratiev 95833190ebfSVladimir Kondratiev rc = wmi_set_ssid(wil, ssid_len, ssid); 95933190ebfSVladimir Kondratiev if (rc) 96033190ebfSVladimir Kondratiev goto out; 96133190ebfSVladimir Kondratiev 962cab5abbfSVladimir Kondratiev rc = _wil_cfg80211_set_ies(wiphy, bcon); 96333190ebfSVladimir Kondratiev if (rc) 96433190ebfSVladimir Kondratiev goto out; 96533190ebfSVladimir Kondratiev 96633190ebfSVladimir Kondratiev wil->privacy = privacy; 96733190ebfSVladimir Kondratiev wil->channel = chan; 96833190ebfSVladimir Kondratiev wil->hidden_ssid = hidden_ssid; 969eabb03b4SLior David wil->pbss = pbss; 97033190ebfSVladimir Kondratiev 97133190ebfSVladimir Kondratiev netif_carrier_on(ndev); 97233190ebfSVladimir Kondratiev 97333190ebfSVladimir Kondratiev rc = wmi_pcp_start(wil, bi, wmi_nettype, chan, hidden_ssid); 97433190ebfSVladimir Kondratiev if (rc) 97533190ebfSVladimir Kondratiev goto err_pcp_start; 97633190ebfSVladimir Kondratiev 97733190ebfSVladimir Kondratiev rc = wil_bcast_init(wil); 97833190ebfSVladimir Kondratiev if (rc) 97933190ebfSVladimir Kondratiev goto err_bcast; 98033190ebfSVladimir Kondratiev 98133190ebfSVladimir Kondratiev goto out; /* success */ 98233190ebfSVladimir Kondratiev 98333190ebfSVladimir Kondratiev err_bcast: 98433190ebfSVladimir Kondratiev wmi_pcp_stop(wil); 98533190ebfSVladimir Kondratiev err_pcp_start: 98633190ebfSVladimir Kondratiev netif_carrier_off(ndev); 98733190ebfSVladimir Kondratiev out: 98833190ebfSVladimir Kondratiev mutex_unlock(&wil->mutex); 98933190ebfSVladimir Kondratiev return rc; 99033190ebfSVladimir Kondratiev } 99133190ebfSVladimir Kondratiev 9921bd922fcSVladimir Kondratiev static int wil_cfg80211_change_beacon(struct wiphy *wiphy, 9931bd922fcSVladimir Kondratiev struct net_device *ndev, 9941bd922fcSVladimir Kondratiev struct cfg80211_beacon_data *bcon) 9951bd922fcSVladimir Kondratiev { 9961bd922fcSVladimir Kondratiev struct wil6210_priv *wil = wiphy_to_wil(wiphy); 9971bd922fcSVladimir Kondratiev int rc; 99833190ebfSVladimir Kondratiev u32 privacy = 0; 9991bd922fcSVladimir Kondratiev 10001bd922fcSVladimir Kondratiev wil_dbg_misc(wil, "%s()\n", __func__); 10011e7e5a0dSVladimir Kondratiev wil_print_bcon_data(bcon); 10021e7e5a0dSVladimir Kondratiev 1003c5a157e4SLior David if (bcon->tail && 1004c5a157e4SLior David cfg80211_find_ie(WLAN_EID_RSN, bcon->tail, 1005c5a157e4SLior David bcon->tail_len)) 100633190ebfSVladimir Kondratiev privacy = 1; 10071bd922fcSVladimir Kondratiev 100833190ebfSVladimir Kondratiev /* in case privacy has changed, need to restart the AP */ 100933190ebfSVladimir Kondratiev if (wil->privacy != privacy) { 101033190ebfSVladimir Kondratiev struct wireless_dev *wdev = ndev->ieee80211_ptr; 101133190ebfSVladimir Kondratiev 101233190ebfSVladimir Kondratiev wil_dbg_misc(wil, "privacy changed %d=>%d. Restarting AP\n", 101333190ebfSVladimir Kondratiev wil->privacy, privacy); 101433190ebfSVladimir Kondratiev 101533190ebfSVladimir Kondratiev rc = _wil_cfg80211_start_ap(wiphy, ndev, wdev->ssid, 101633190ebfSVladimir Kondratiev wdev->ssid_len, privacy, 101733190ebfSVladimir Kondratiev wdev->beacon_interval, 1018cab5abbfSVladimir Kondratiev wil->channel, bcon, 1019eabb03b4SLior David wil->hidden_ssid, 1020eabb03b4SLior David wil->pbss); 102133190ebfSVladimir Kondratiev } else { 1022cab5abbfSVladimir Kondratiev rc = _wil_cfg80211_set_ies(wiphy, bcon); 10231bd922fcSVladimir Kondratiev } 10241bd922fcSVladimir Kondratiev 102533190ebfSVladimir Kondratiev return rc; 10261bd922fcSVladimir Kondratiev } 10271bd922fcSVladimir Kondratiev 10282be7d22fSVladimir Kondratiev static int wil_cfg80211_start_ap(struct wiphy *wiphy, 10292be7d22fSVladimir Kondratiev struct net_device *ndev, 10302be7d22fSVladimir Kondratiev struct cfg80211_ap_settings *info) 10312be7d22fSVladimir Kondratiev { 103233190ebfSVladimir Kondratiev int rc; 10332be7d22fSVladimir Kondratiev struct wil6210_priv *wil = wiphy_to_wil(wiphy); 10342be7d22fSVladimir Kondratiev struct ieee80211_channel *channel = info->chandef.chan; 10352be7d22fSVladimir Kondratiev struct cfg80211_beacon_data *bcon = &info->beacon; 1036ca959773SVladimir Kondratiev struct cfg80211_crypto_settings *crypto = &info->crypto; 10378e52fe30SHamad Kadmany u8 hidden_ssid; 10382be7d22fSVladimir Kondratiev 1039ca959773SVladimir Kondratiev wil_dbg_misc(wil, "%s()\n", __func__); 1040ca959773SVladimir Kondratiev 10412be7d22fSVladimir Kondratiev if (!channel) { 10422be7d22fSVladimir Kondratiev wil_err(wil, "AP: No channel???\n"); 10432be7d22fSVladimir Kondratiev return -EINVAL; 10442be7d22fSVladimir Kondratiev } 10452be7d22fSVladimir Kondratiev 104633190ebfSVladimir Kondratiev switch (info->hidden_ssid) { 104733190ebfSVladimir Kondratiev case NL80211_HIDDEN_SSID_NOT_IN_USE: 104833190ebfSVladimir Kondratiev hidden_ssid = WMI_HIDDEN_SSID_DISABLED; 104933190ebfSVladimir Kondratiev break; 105033190ebfSVladimir Kondratiev 105133190ebfSVladimir Kondratiev case NL80211_HIDDEN_SSID_ZERO_LEN: 105233190ebfSVladimir Kondratiev hidden_ssid = WMI_HIDDEN_SSID_SEND_EMPTY; 105333190ebfSVladimir Kondratiev break; 105433190ebfSVladimir Kondratiev 105533190ebfSVladimir Kondratiev case NL80211_HIDDEN_SSID_ZERO_CONTENTS: 105633190ebfSVladimir Kondratiev hidden_ssid = WMI_HIDDEN_SSID_CLEAR; 105733190ebfSVladimir Kondratiev break; 105833190ebfSVladimir Kondratiev 105933190ebfSVladimir Kondratiev default: 106033190ebfSVladimir Kondratiev wil_err(wil, "AP: Invalid hidden SSID %d\n", info->hidden_ssid); 106133190ebfSVladimir Kondratiev return -EOPNOTSUPP; 106233190ebfSVladimir Kondratiev } 10637743882dSVladimir Kondratiev wil_dbg_misc(wil, "AP on Channel %d %d MHz, %s\n", channel->hw_value, 10642be7d22fSVladimir Kondratiev channel->center_freq, info->privacy ? "secure" : "open"); 1065ca959773SVladimir Kondratiev wil_dbg_misc(wil, "Privacy: %d auth_type %d\n", 1066ca959773SVladimir Kondratiev info->privacy, info->auth_type); 10678e52fe30SHamad Kadmany wil_dbg_misc(wil, "Hidden SSID mode: %d\n", 10688e52fe30SHamad Kadmany info->hidden_ssid); 1069ca959773SVladimir Kondratiev wil_dbg_misc(wil, "BI %d DTIM %d\n", info->beacon_interval, 1070ca959773SVladimir Kondratiev info->dtim_period); 1071eabb03b4SLior David wil_dbg_misc(wil, "PBSS %d\n", info->pbss); 10722be7d22fSVladimir Kondratiev print_hex_dump_bytes("SSID ", DUMP_PREFIX_OFFSET, 10732be7d22fSVladimir Kondratiev info->ssid, info->ssid_len); 1074ca959773SVladimir Kondratiev wil_print_bcon_data(bcon); 1075ca959773SVladimir Kondratiev wil_print_crypto(wil, crypto); 10762be7d22fSVladimir Kondratiev 107733190ebfSVladimir Kondratiev rc = _wil_cfg80211_start_ap(wiphy, ndev, 107833190ebfSVladimir Kondratiev info->ssid, info->ssid_len, info->privacy, 107933190ebfSVladimir Kondratiev info->beacon_interval, channel->hw_value, 1080eabb03b4SLior David bcon, hidden_ssid, info->pbss); 1081c33407a8SVladimir Kondratiev 10822be7d22fSVladimir Kondratiev return rc; 10832be7d22fSVladimir Kondratiev } 10842be7d22fSVladimir Kondratiev 10852be7d22fSVladimir Kondratiev static int wil_cfg80211_stop_ap(struct wiphy *wiphy, 10862be7d22fSVladimir Kondratiev struct net_device *ndev) 10872be7d22fSVladimir Kondratiev { 10882be7d22fSVladimir Kondratiev struct wil6210_priv *wil = wiphy_to_wil(wiphy); 10892be7d22fSVladimir Kondratiev 1090ca959773SVladimir Kondratiev wil_dbg_misc(wil, "%s()\n", __func__); 1091ca959773SVladimir Kondratiev 1092c5e96c91SDedy Lansky netif_carrier_off(ndev); 1093c33407a8SVladimir Kondratiev wil_set_recovery_state(wil, fw_recovery_idle); 1094c33407a8SVladimir Kondratiev 10959c3bde56SVladimir Kondratiev mutex_lock(&wil->mutex); 10969c3bde56SVladimir Kondratiev 109732a20d46SDedy Lansky wmi_pcp_stop(wil); 10982be7d22fSVladimir Kondratiev 109973d839aeSVladimir Kondratiev __wil_down(wil); 110073d839aeSVladimir Kondratiev 11019c3bde56SVladimir Kondratiev mutex_unlock(&wil->mutex); 110273d839aeSVladimir Kondratiev 110332a20d46SDedy Lansky return 0; 11042be7d22fSVladimir Kondratiev } 11052be7d22fSVladimir Kondratiev 11064d55a0a1SVladimir Kondratiev static int wil_cfg80211_del_station(struct wiphy *wiphy, 110789c771e5SJouni Malinen struct net_device *dev, 110889c771e5SJouni Malinen struct station_del_parameters *params) 11094d55a0a1SVladimir Kondratiev { 11104d55a0a1SVladimir Kondratiev struct wil6210_priv *wil = wiphy_to_wil(wiphy); 1111097638a0SVladimir Kondratiev 1112de9084efSVladimir Kondratiev wil_dbg_misc(wil, "%s(%pM, reason=%d)\n", __func__, params->mac, 1113de9084efSVladimir Kondratiev params->reason_code); 1114de9084efSVladimir Kondratiev 1115097638a0SVladimir Kondratiev mutex_lock(&wil->mutex); 11164821e6d8SVladimir Kondratiev wil6210_disconnect(wil, params->mac, params->reason_code, false); 1117097638a0SVladimir Kondratiev mutex_unlock(&wil->mutex); 1118097638a0SVladimir Kondratiev 11194d55a0a1SVladimir Kondratiev return 0; 11204d55a0a1SVladimir Kondratiev } 11214d55a0a1SVladimir Kondratiev 112240822a90SVladimir Kondratiev /* probe_client handling */ 112340822a90SVladimir Kondratiev static void wil_probe_client_handle(struct wil6210_priv *wil, 112440822a90SVladimir Kondratiev struct wil_probe_client_req *req) 112540822a90SVladimir Kondratiev { 112640822a90SVladimir Kondratiev struct net_device *ndev = wil_to_ndev(wil); 112740822a90SVladimir Kondratiev struct wil_sta_info *sta = &wil->sta[req->cid]; 112840822a90SVladimir Kondratiev /* assume STA is alive if it is still connected, 112940822a90SVladimir Kondratiev * else FW will disconnect it 113040822a90SVladimir Kondratiev */ 113140822a90SVladimir Kondratiev bool alive = (sta->status == wil_sta_connected); 113240822a90SVladimir Kondratiev 113340822a90SVladimir Kondratiev cfg80211_probe_status(ndev, sta->addr, req->cookie, alive, GFP_KERNEL); 113440822a90SVladimir Kondratiev } 113540822a90SVladimir Kondratiev 113640822a90SVladimir Kondratiev static struct list_head *next_probe_client(struct wil6210_priv *wil) 113740822a90SVladimir Kondratiev { 113840822a90SVladimir Kondratiev struct list_head *ret = NULL; 113940822a90SVladimir Kondratiev 114040822a90SVladimir Kondratiev mutex_lock(&wil->probe_client_mutex); 114140822a90SVladimir Kondratiev 114240822a90SVladimir Kondratiev if (!list_empty(&wil->probe_client_pending)) { 114340822a90SVladimir Kondratiev ret = wil->probe_client_pending.next; 114440822a90SVladimir Kondratiev list_del(ret); 114540822a90SVladimir Kondratiev } 114640822a90SVladimir Kondratiev 114740822a90SVladimir Kondratiev mutex_unlock(&wil->probe_client_mutex); 114840822a90SVladimir Kondratiev 114940822a90SVladimir Kondratiev return ret; 115040822a90SVladimir Kondratiev } 115140822a90SVladimir Kondratiev 115240822a90SVladimir Kondratiev void wil_probe_client_worker(struct work_struct *work) 115340822a90SVladimir Kondratiev { 115440822a90SVladimir Kondratiev struct wil6210_priv *wil = container_of(work, struct wil6210_priv, 115540822a90SVladimir Kondratiev probe_client_worker); 115640822a90SVladimir Kondratiev struct wil_probe_client_req *req; 115740822a90SVladimir Kondratiev struct list_head *lh; 115840822a90SVladimir Kondratiev 115940822a90SVladimir Kondratiev while ((lh = next_probe_client(wil)) != NULL) { 116040822a90SVladimir Kondratiev req = list_entry(lh, struct wil_probe_client_req, list); 116140822a90SVladimir Kondratiev 116240822a90SVladimir Kondratiev wil_probe_client_handle(wil, req); 116340822a90SVladimir Kondratiev kfree(req); 116440822a90SVladimir Kondratiev } 116540822a90SVladimir Kondratiev } 116640822a90SVladimir Kondratiev 116740822a90SVladimir Kondratiev void wil_probe_client_flush(struct wil6210_priv *wil) 116840822a90SVladimir Kondratiev { 116940822a90SVladimir Kondratiev struct wil_probe_client_req *req, *t; 117040822a90SVladimir Kondratiev 117140822a90SVladimir Kondratiev wil_dbg_misc(wil, "%s()\n", __func__); 117240822a90SVladimir Kondratiev 117340822a90SVladimir Kondratiev mutex_lock(&wil->probe_client_mutex); 117440822a90SVladimir Kondratiev 117540822a90SVladimir Kondratiev list_for_each_entry_safe(req, t, &wil->probe_client_pending, list) { 117640822a90SVladimir Kondratiev list_del(&req->list); 117740822a90SVladimir Kondratiev kfree(req); 117840822a90SVladimir Kondratiev } 117940822a90SVladimir Kondratiev 118040822a90SVladimir Kondratiev mutex_unlock(&wil->probe_client_mutex); 118140822a90SVladimir Kondratiev } 118240822a90SVladimir Kondratiev 118340822a90SVladimir Kondratiev static int wil_cfg80211_probe_client(struct wiphy *wiphy, 118440822a90SVladimir Kondratiev struct net_device *dev, 118540822a90SVladimir Kondratiev const u8 *peer, u64 *cookie) 118640822a90SVladimir Kondratiev { 118740822a90SVladimir Kondratiev struct wil6210_priv *wil = wiphy_to_wil(wiphy); 118840822a90SVladimir Kondratiev struct wil_probe_client_req *req; 118940822a90SVladimir Kondratiev int cid = wil_find_cid(wil, peer); 119040822a90SVladimir Kondratiev 119140822a90SVladimir Kondratiev wil_dbg_misc(wil, "%s(%pM => CID %d)\n", __func__, peer, cid); 119240822a90SVladimir Kondratiev 119340822a90SVladimir Kondratiev if (cid < 0) 119440822a90SVladimir Kondratiev return -ENOLINK; 119540822a90SVladimir Kondratiev 119640822a90SVladimir Kondratiev req = kzalloc(sizeof(*req), GFP_KERNEL); 119740822a90SVladimir Kondratiev if (!req) 119840822a90SVladimir Kondratiev return -ENOMEM; 119940822a90SVladimir Kondratiev 120040822a90SVladimir Kondratiev req->cid = cid; 120140822a90SVladimir Kondratiev req->cookie = cid; 120240822a90SVladimir Kondratiev 120340822a90SVladimir Kondratiev mutex_lock(&wil->probe_client_mutex); 120440822a90SVladimir Kondratiev list_add_tail(&req->list, &wil->probe_client_pending); 120540822a90SVladimir Kondratiev mutex_unlock(&wil->probe_client_mutex); 120640822a90SVladimir Kondratiev 120740822a90SVladimir Kondratiev *cookie = req->cookie; 120840822a90SVladimir Kondratiev queue_work(wil->wq_service, &wil->probe_client_worker); 120940822a90SVladimir Kondratiev return 0; 121040822a90SVladimir Kondratiev } 121140822a90SVladimir Kondratiev 121202beaf1aSVladimir Kondratiev static int wil_cfg80211_change_bss(struct wiphy *wiphy, 121302beaf1aSVladimir Kondratiev struct net_device *dev, 121402beaf1aSVladimir Kondratiev struct bss_parameters *params) 121502beaf1aSVladimir Kondratiev { 121602beaf1aSVladimir Kondratiev struct wil6210_priv *wil = wiphy_to_wil(wiphy); 121702beaf1aSVladimir Kondratiev 121802beaf1aSVladimir Kondratiev if (params->ap_isolate >= 0) { 121902beaf1aSVladimir Kondratiev wil_dbg_misc(wil, "%s(ap_isolate %d => %d)\n", __func__, 122002beaf1aSVladimir Kondratiev wil->ap_isolate, params->ap_isolate); 122102beaf1aSVladimir Kondratiev wil->ap_isolate = params->ap_isolate; 122202beaf1aSVladimir Kondratiev } 122302beaf1aSVladimir Kondratiev 122402beaf1aSVladimir Kondratiev return 0; 122502beaf1aSVladimir Kondratiev } 122602beaf1aSVladimir Kondratiev 12272be7d22fSVladimir Kondratiev static struct cfg80211_ops wil_cfg80211_ops = { 12282be7d22fSVladimir Kondratiev .scan = wil_cfg80211_scan, 12292be7d22fSVladimir Kondratiev .connect = wil_cfg80211_connect, 12302be7d22fSVladimir Kondratiev .disconnect = wil_cfg80211_disconnect, 12312be7d22fSVladimir Kondratiev .change_virtual_intf = wil_cfg80211_change_iface, 12322be7d22fSVladimir Kondratiev .get_station = wil_cfg80211_get_station, 1233ef28afdbSVladimir Kondratiev .dump_station = wil_cfg80211_dump_station, 12341647f12fSVladimir Kondratiev .remain_on_channel = wil_remain_on_channel, 12351647f12fSVladimir Kondratiev .cancel_remain_on_channel = wil_cancel_remain_on_channel, 12361647f12fSVladimir Kondratiev .mgmt_tx = wil_cfg80211_mgmt_tx, 12372be7d22fSVladimir Kondratiev .set_monitor_channel = wil_cfg80211_set_channel, 12382be7d22fSVladimir Kondratiev .add_key = wil_cfg80211_add_key, 12392be7d22fSVladimir Kondratiev .del_key = wil_cfg80211_del_key, 12402be7d22fSVladimir Kondratiev .set_default_key = wil_cfg80211_set_default_key, 12412be7d22fSVladimir Kondratiev /* AP mode */ 12421bd922fcSVladimir Kondratiev .change_beacon = wil_cfg80211_change_beacon, 12432be7d22fSVladimir Kondratiev .start_ap = wil_cfg80211_start_ap, 12442be7d22fSVladimir Kondratiev .stop_ap = wil_cfg80211_stop_ap, 12454d55a0a1SVladimir Kondratiev .del_station = wil_cfg80211_del_station, 124640822a90SVladimir Kondratiev .probe_client = wil_cfg80211_probe_client, 124702beaf1aSVladimir Kondratiev .change_bss = wil_cfg80211_change_bss, 12482be7d22fSVladimir Kondratiev }; 12492be7d22fSVladimir Kondratiev 12502be7d22fSVladimir Kondratiev static void wil_wiphy_init(struct wiphy *wiphy) 12512be7d22fSVladimir Kondratiev { 12528e52fe30SHamad Kadmany wiphy->max_scan_ssids = 1; 125377c91295SVladimir Kondratiev wiphy->max_scan_ie_len = WMI_MAX_IE_LEN; 12542be7d22fSVladimir Kondratiev wiphy->max_num_pmkids = 0 /* TODO: */; 12552be7d22fSVladimir Kondratiev wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | 12562be7d22fSVladimir Kondratiev BIT(NL80211_IFTYPE_AP) | 12572be7d22fSVladimir Kondratiev BIT(NL80211_IFTYPE_MONITOR); 12582be7d22fSVladimir Kondratiev /* TODO: enable P2P when integrated with supplicant: 12592be7d22fSVladimir Kondratiev * BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO) 12602be7d22fSVladimir Kondratiev */ 12612be7d22fSVladimir Kondratiev wiphy->flags |= WIPHY_FLAG_HAVE_AP_SME | 12622be7d22fSVladimir Kondratiev WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD; 12639cf10d62SVladimir Kondratiev dev_dbg(wiphy_dev(wiphy), "%s : flags = 0x%08x\n", 12642be7d22fSVladimir Kondratiev __func__, wiphy->flags); 12652be7d22fSVladimir Kondratiev wiphy->probe_resp_offload = 12662be7d22fSVladimir Kondratiev NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS | 12672be7d22fSVladimir Kondratiev NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2 | 12682be7d22fSVladimir Kondratiev NL80211_PROBE_RESP_OFFLOAD_SUPPORT_P2P; 12692be7d22fSVladimir Kondratiev 12702be7d22fSVladimir Kondratiev wiphy->bands[IEEE80211_BAND_60GHZ] = &wil_band_60ghz; 12712be7d22fSVladimir Kondratiev 12722be7d22fSVladimir Kondratiev /* TODO: figure this out */ 1273b8b33a3aSVladimir Kondratiev wiphy->signal_type = CFG80211_SIGNAL_TYPE_UNSPEC; 12742be7d22fSVladimir Kondratiev 12752be7d22fSVladimir Kondratiev wiphy->cipher_suites = wil_cipher_suites; 12762be7d22fSVladimir Kondratiev wiphy->n_cipher_suites = ARRAY_SIZE(wil_cipher_suites); 12772be7d22fSVladimir Kondratiev wiphy->mgmt_stypes = wil_mgmt_stypes; 1278713c8a29SVladimir Kondratiev wiphy->features |= NL80211_FEATURE_SK_TX_STATUS; 12792be7d22fSVladimir Kondratiev } 12802be7d22fSVladimir Kondratiev 12812be7d22fSVladimir Kondratiev struct wireless_dev *wil_cfg80211_init(struct device *dev) 12822be7d22fSVladimir Kondratiev { 12832be7d22fSVladimir Kondratiev int rc = 0; 12842be7d22fSVladimir Kondratiev struct wireless_dev *wdev; 12852be7d22fSVladimir Kondratiev 12869cf10d62SVladimir Kondratiev dev_dbg(dev, "%s()\n", __func__); 12879cf10d62SVladimir Kondratiev 12888fe59627SVladimir Kondratiev wdev = kzalloc(sizeof(*wdev), GFP_KERNEL); 12892be7d22fSVladimir Kondratiev if (!wdev) 12902be7d22fSVladimir Kondratiev return ERR_PTR(-ENOMEM); 12912be7d22fSVladimir Kondratiev 12922be7d22fSVladimir Kondratiev wdev->wiphy = wiphy_new(&wil_cfg80211_ops, 12932be7d22fSVladimir Kondratiev sizeof(struct wil6210_priv)); 12942be7d22fSVladimir Kondratiev if (!wdev->wiphy) { 12952be7d22fSVladimir Kondratiev rc = -ENOMEM; 12962be7d22fSVladimir Kondratiev goto out; 12972be7d22fSVladimir Kondratiev } 12982be7d22fSVladimir Kondratiev 12992be7d22fSVladimir Kondratiev set_wiphy_dev(wdev->wiphy, dev); 13002be7d22fSVladimir Kondratiev wil_wiphy_init(wdev->wiphy); 13012be7d22fSVladimir Kondratiev 13022be7d22fSVladimir Kondratiev rc = wiphy_register(wdev->wiphy); 13032be7d22fSVladimir Kondratiev if (rc < 0) 13042be7d22fSVladimir Kondratiev goto out_failed_reg; 13052be7d22fSVladimir Kondratiev 13062be7d22fSVladimir Kondratiev return wdev; 13072be7d22fSVladimir Kondratiev 13082be7d22fSVladimir Kondratiev out_failed_reg: 13092be7d22fSVladimir Kondratiev wiphy_free(wdev->wiphy); 13102be7d22fSVladimir Kondratiev out: 13112be7d22fSVladimir Kondratiev kfree(wdev); 13122be7d22fSVladimir Kondratiev 13132be7d22fSVladimir Kondratiev return ERR_PTR(rc); 13142be7d22fSVladimir Kondratiev } 13152be7d22fSVladimir Kondratiev 13162be7d22fSVladimir Kondratiev void wil_wdev_free(struct wil6210_priv *wil) 13172be7d22fSVladimir Kondratiev { 13182be7d22fSVladimir Kondratiev struct wireless_dev *wdev = wil_to_wdev(wil); 13192be7d22fSVladimir Kondratiev 13209cf10d62SVladimir Kondratiev dev_dbg(wil_to_dev(wil), "%s()\n", __func__); 13219cf10d62SVladimir Kondratiev 13222be7d22fSVladimir Kondratiev if (!wdev) 13232be7d22fSVladimir Kondratiev return; 13242be7d22fSVladimir Kondratiev 13252be7d22fSVladimir Kondratiev wiphy_unregister(wdev->wiphy); 13262be7d22fSVladimir Kondratiev wiphy_free(wdev->wiphy); 13272be7d22fSVladimir Kondratiev kfree(wdev); 13282be7d22fSVladimir Kondratiev } 1329