linux/drivers/net/wireless/ath/wil6210/cfg80211.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2012 Qualcomm Atheros, Inc.
   3 *
   4 * Permission to use, copy, modify, and/or distribute this software for any
   5 * purpose with or without fee is hereby granted, provided that the above
   6 * copyright notice and this permission notice appear in all copies.
   7 *
   8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
   9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15 */
  16
  17#include "wil6210.h"
  18#include "wmi.h"
  19
  20#define CHAN60G(_channel, _flags) {                             \
  21        .band                   = IEEE80211_BAND_60GHZ,         \
  22        .center_freq            = 56160 + (2160 * (_channel)),  \
  23        .hw_value               = (_channel),                   \
  24        .flags                  = (_flags),                     \
  25        .max_antenna_gain       = 0,                            \
  26        .max_power              = 40,                           \
  27}
  28
  29static struct ieee80211_channel wil_60ghz_channels[] = {
  30        CHAN60G(1, 0),
  31        CHAN60G(2, 0),
  32        CHAN60G(3, 0),
  33/* channel 4 not supported yet */
  34};
  35
  36static struct ieee80211_supported_band wil_band_60ghz = {
  37        .channels = wil_60ghz_channels,
  38        .n_channels = ARRAY_SIZE(wil_60ghz_channels),
  39        .ht_cap = {
  40                .ht_supported = true,
  41                .cap = 0, /* TODO */
  42                .ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K, /* TODO */
  43                .ampdu_density = IEEE80211_HT_MPDU_DENSITY_8, /* TODO */
  44                .mcs = {
  45                                /* MCS 1..12 - SC PHY */
  46                        .rx_mask = {0xfe, 0x1f}, /* 1..12 */
  47                        .tx_params = IEEE80211_HT_MCS_TX_DEFINED, /* TODO */
  48                },
  49        },
  50};
  51
  52static const struct ieee80211_txrx_stypes
  53wil_mgmt_stypes[NUM_NL80211_IFTYPES] = {
  54        [NL80211_IFTYPE_STATION] = {
  55                .tx = BIT(IEEE80211_STYPE_ACTION >> 4) |
  56                BIT(IEEE80211_STYPE_PROBE_RESP >> 4),
  57                .rx = BIT(IEEE80211_STYPE_ACTION >> 4) |
  58                BIT(IEEE80211_STYPE_PROBE_REQ >> 4)
  59        },
  60        [NL80211_IFTYPE_AP] = {
  61                .tx = BIT(IEEE80211_STYPE_ACTION >> 4) |
  62                BIT(IEEE80211_STYPE_PROBE_RESP >> 4),
  63                .rx = BIT(IEEE80211_STYPE_ACTION >> 4) |
  64                BIT(IEEE80211_STYPE_PROBE_REQ >> 4)
  65        },
  66        [NL80211_IFTYPE_P2P_CLIENT] = {
  67                .tx = BIT(IEEE80211_STYPE_ACTION >> 4) |
  68                BIT(IEEE80211_STYPE_PROBE_RESP >> 4),
  69                .rx = BIT(IEEE80211_STYPE_ACTION >> 4) |
  70                BIT(IEEE80211_STYPE_PROBE_REQ >> 4)
  71        },
  72        [NL80211_IFTYPE_P2P_GO] = {
  73                .tx = BIT(IEEE80211_STYPE_ACTION >> 4) |
  74                BIT(IEEE80211_STYPE_PROBE_RESP >> 4),
  75                .rx = BIT(IEEE80211_STYPE_ACTION >> 4) |
  76                BIT(IEEE80211_STYPE_PROBE_REQ >> 4)
  77        },
  78};
  79
  80static const u32 wil_cipher_suites[] = {
  81        WLAN_CIPHER_SUITE_GCMP,
  82};
  83
  84int wil_iftype_nl2wmi(enum nl80211_iftype type)
  85{
  86        static const struct {
  87                enum nl80211_iftype nl;
  88                enum wmi_network_type wmi;
  89        } __nl2wmi[] = {
  90                {NL80211_IFTYPE_ADHOC,          WMI_NETTYPE_ADHOC},
  91                {NL80211_IFTYPE_STATION,        WMI_NETTYPE_INFRA},
  92                {NL80211_IFTYPE_AP,             WMI_NETTYPE_AP},
  93                {NL80211_IFTYPE_P2P_CLIENT,     WMI_NETTYPE_P2P},
  94                {NL80211_IFTYPE_P2P_GO,         WMI_NETTYPE_P2P},
  95                {NL80211_IFTYPE_MONITOR,        WMI_NETTYPE_ADHOC}, /* FIXME */
  96        };
  97        uint i;
  98
  99        for (i = 0; i < ARRAY_SIZE(__nl2wmi); i++) {
 100                if (__nl2wmi[i].nl == type)
 101                        return __nl2wmi[i].wmi;
 102        }
 103
 104        return -EOPNOTSUPP;
 105}
 106
 107static int wil_cfg80211_get_station(struct wiphy *wiphy,
 108                                    struct net_device *ndev,
 109                                    u8 *mac, struct station_info *sinfo)
 110{
 111        struct wil6210_priv *wil = wiphy_to_wil(wiphy);
 112        int rc;
 113        struct wmi_notify_req_cmd cmd = {
 114                .cid = 0,
 115                .interval_usec = 0,
 116        };
 117
 118        if (memcmp(mac, wil->dst_addr[0], ETH_ALEN))
 119                return -ENOENT;
 120
 121        /* WMI_NOTIFY_REQ_DONE_EVENTID handler fills wil->stats.bf_mcs */
 122        rc = wmi_call(wil, WMI_NOTIFY_REQ_CMDID, &cmd, sizeof(cmd),
 123                      WMI_NOTIFY_REQ_DONE_EVENTID, NULL, 0, 20);
 124        if (rc)
 125                return rc;
 126
 127        sinfo->generation = wil->sinfo_gen;
 128
 129        sinfo->filled |= STATION_INFO_TX_BITRATE;
 130        sinfo->txrate.flags = RATE_INFO_FLAGS_MCS | RATE_INFO_FLAGS_60G;
 131        sinfo->txrate.mcs = wil->stats.bf_mcs;
 132        sinfo->filled |= STATION_INFO_RX_BITRATE;
 133        sinfo->rxrate.flags = RATE_INFO_FLAGS_MCS | RATE_INFO_FLAGS_60G;
 134        sinfo->rxrate.mcs = wil->stats.last_mcs_rx;
 135
 136        if (test_bit(wil_status_fwconnected, &wil->status)) {
 137                sinfo->filled |= STATION_INFO_SIGNAL;
 138                sinfo->signal = 12; /* TODO: provide real value */
 139        }
 140
 141        return 0;
 142}
 143
 144static int wil_cfg80211_change_iface(struct wiphy *wiphy,
 145                                     struct net_device *ndev,
 146                                     enum nl80211_iftype type, u32 *flags,
 147                                     struct vif_params *params)
 148{
 149        struct wil6210_priv *wil = wiphy_to_wil(wiphy);
 150        struct wireless_dev *wdev = wil->wdev;
 151
 152        switch (type) {
 153        case NL80211_IFTYPE_STATION:
 154        case NL80211_IFTYPE_AP:
 155        case NL80211_IFTYPE_P2P_CLIENT:
 156        case NL80211_IFTYPE_P2P_GO:
 157                break;
 158        case NL80211_IFTYPE_MONITOR:
 159                if (flags)
 160                        wil->monitor_flags = *flags;
 161                else
 162                        wil->monitor_flags = 0;
 163
 164                break;
 165        default:
 166                return -EOPNOTSUPP;
 167        }
 168
 169        wdev->iftype = type;
 170
 171        return 0;
 172}
 173
 174static int wil_cfg80211_scan(struct wiphy *wiphy,
 175                             struct cfg80211_scan_request *request)
 176{
 177        struct wil6210_priv *wil = wiphy_to_wil(wiphy);
 178        struct wireless_dev *wdev = wil->wdev;
 179        struct {
 180                struct wmi_start_scan_cmd cmd;
 181                u16 chnl[4];
 182        } __packed cmd;
 183        uint i, n;
 184
 185        if (wil->scan_request) {
 186                wil_err(wil, "Already scanning\n");
 187                return -EAGAIN;
 188        }
 189
 190        /* check we are client side */
 191        switch (wdev->iftype) {
 192        case NL80211_IFTYPE_STATION:
 193        case NL80211_IFTYPE_P2P_CLIENT:
 194                break;
 195        default:
 196                return -EOPNOTSUPP;
 197        }
 198
 199        /* FW don't support scan after connection attempt */
 200        if (test_bit(wil_status_dontscan, &wil->status)) {
 201                wil_err(wil, "Scan after connect attempt not supported\n");
 202                return -EBUSY;
 203        }
 204
 205        wil->scan_request = request;
 206
 207        memset(&cmd, 0, sizeof(cmd));
 208        cmd.cmd.num_channels = 0;
 209        n = min(request->n_channels, 4U);
 210        for (i = 0; i < n; i++) {
 211                int ch = request->channels[i]->hw_value;
 212                if (ch == 0) {
 213                        wil_err(wil,
 214                                "Scan requested for unknown frequency %dMhz\n",
 215                                request->channels[i]->center_freq);
 216                        continue;
 217                }
 218                /* 0-based channel indexes */
 219                cmd.cmd.channel_list[cmd.cmd.num_channels++].channel = ch - 1;
 220                wil_dbg_misc(wil, "Scan for ch %d  : %d MHz\n", ch,
 221                             request->channels[i]->center_freq);
 222        }
 223
 224        return wmi_send(wil, WMI_START_SCAN_CMDID, &cmd, sizeof(cmd.cmd) +
 225                        cmd.cmd.num_channels * sizeof(cmd.cmd.channel_list[0]));
 226}
 227
 228static int wil_cfg80211_connect(struct wiphy *wiphy,
 229                                struct net_device *ndev,
 230                                struct cfg80211_connect_params *sme)
 231{
 232        struct wil6210_priv *wil = wiphy_to_wil(wiphy);
 233        struct cfg80211_bss *bss;
 234        struct wmi_connect_cmd conn;
 235        const u8 *ssid_eid;
 236        const u8 *rsn_eid;
 237        int ch;
 238        int rc = 0;
 239
 240        bss = cfg80211_get_bss(wiphy, sme->channel, sme->bssid,
 241                               sme->ssid, sme->ssid_len,
 242                               WLAN_CAPABILITY_ESS, WLAN_CAPABILITY_ESS);
 243        if (!bss) {
 244                wil_err(wil, "Unable to find BSS\n");
 245                return -ENOENT;
 246        }
 247
 248        ssid_eid = ieee80211_bss_get_ie(bss, WLAN_EID_SSID);
 249        if (!ssid_eid) {
 250                wil_err(wil, "No SSID\n");
 251                rc = -ENOENT;
 252                goto out;
 253        }
 254
 255        rsn_eid = sme->ie ?
 256                        cfg80211_find_ie(WLAN_EID_RSN, sme->ie, sme->ie_len) :
 257                        NULL;
 258        if (rsn_eid) {
 259                if (sme->ie_len > WMI_MAX_IE_LEN) {
 260                        rc = -ERANGE;
 261                        wil_err(wil, "IE too large (%td bytes)\n",
 262                                sme->ie_len);
 263                        goto out;
 264                }
 265                /*
 266                 * For secure assoc, send:
 267                 * (1) WMI_DELETE_CIPHER_KEY_CMD
 268                 * (2) WMI_SET_APPIE_CMD
 269                 */
 270                rc = wmi_del_cipher_key(wil, 0, bss->bssid);
 271                if (rc) {
 272                        wil_err(wil, "WMI_DELETE_CIPHER_KEY_CMD failed\n");
 273                        goto out;
 274                }
 275                /* WMI_SET_APPIE_CMD */
 276                rc = wmi_set_ie(wil, WMI_FRAME_ASSOC_REQ, sme->ie_len, sme->ie);
 277                if (rc) {
 278                        wil_err(wil, "WMI_SET_APPIE_CMD failed\n");
 279                        goto out;
 280                }
 281        }
 282
 283        /* WMI_CONNECT_CMD */
 284        memset(&conn, 0, sizeof(conn));
 285        switch (bss->capability & WLAN_CAPABILITY_DMG_TYPE_MASK) {
 286        case WLAN_CAPABILITY_DMG_TYPE_AP:
 287                conn.network_type = WMI_NETTYPE_INFRA;
 288                break;
 289        case WLAN_CAPABILITY_DMG_TYPE_PBSS:
 290                conn.network_type = WMI_NETTYPE_P2P;
 291                break;
 292        default:
 293                wil_err(wil, "Unsupported BSS type, capability= 0x%04x\n",
 294                        bss->capability);
 295                goto out;
 296        }
 297        if (rsn_eid) {
 298                conn.dot11_auth_mode = WMI_AUTH11_SHARED;
 299                conn.auth_mode = WMI_AUTH_WPA2_PSK;
 300                conn.pairwise_crypto_type = WMI_CRYPT_AES_GCMP;
 301                conn.pairwise_crypto_len = 16;
 302        } else {
 303                conn.dot11_auth_mode = WMI_AUTH11_OPEN;
 304                conn.auth_mode = WMI_AUTH_NONE;
 305        }
 306
 307        conn.ssid_len = min_t(u8, ssid_eid[1], 32);
 308        memcpy(conn.ssid, ssid_eid+2, conn.ssid_len);
 309
 310        ch = bss->channel->hw_value;
 311        if (ch == 0) {
 312                wil_err(wil, "BSS at unknown frequency %dMhz\n",
 313                        bss->channel->center_freq);
 314                rc = -EOPNOTSUPP;
 315                goto out;
 316        }
 317        conn.channel = ch - 1;
 318
 319        memcpy(conn.bssid, bss->bssid, ETH_ALEN);
 320        memcpy(conn.dst_mac, bss->bssid, ETH_ALEN);
 321        /*
 322         * FW don't support scan after connection attempt
 323         */
 324        set_bit(wil_status_dontscan, &wil->status);
 325        set_bit(wil_status_fwconnecting, &wil->status);
 326
 327        rc = wmi_send(wil, WMI_CONNECT_CMDID, &conn, sizeof(conn));
 328        if (rc == 0) {
 329                /* Connect can take lots of time */
 330                mod_timer(&wil->connect_timer,
 331                          jiffies + msecs_to_jiffies(2000));
 332        } else {
 333                clear_bit(wil_status_dontscan, &wil->status);
 334                clear_bit(wil_status_fwconnecting, &wil->status);
 335        }
 336
 337 out:
 338        cfg80211_put_bss(wiphy, bss);
 339
 340        return rc;
 341}
 342
 343static int wil_cfg80211_disconnect(struct wiphy *wiphy,
 344                                   struct net_device *ndev,
 345                                   u16 reason_code)
 346{
 347        int rc;
 348        struct wil6210_priv *wil = wiphy_to_wil(wiphy);
 349
 350        rc = wmi_send(wil, WMI_DISCONNECT_CMDID, NULL, 0);
 351
 352        return rc;
 353}
 354
 355static int wil_cfg80211_set_channel(struct wiphy *wiphy,
 356                                    struct cfg80211_chan_def *chandef)
 357{
 358        struct wil6210_priv *wil = wiphy_to_wil(wiphy);
 359        struct wireless_dev *wdev = wil->wdev;
 360
 361        wdev->preset_chandef = *chandef;
 362
 363        return 0;
 364}
 365
 366static int wil_cfg80211_add_key(struct wiphy *wiphy,
 367                                struct net_device *ndev,
 368                                u8 key_index, bool pairwise,
 369                                const u8 *mac_addr,
 370                                struct key_params *params)
 371{
 372        struct wil6210_priv *wil = wiphy_to_wil(wiphy);
 373
 374        /* group key is not used */
 375        if (!pairwise)
 376                return 0;
 377
 378        return wmi_add_cipher_key(wil, key_index, mac_addr,
 379                                  params->key_len, params->key);
 380}
 381
 382static int wil_cfg80211_del_key(struct wiphy *wiphy,
 383                                struct net_device *ndev,
 384                                u8 key_index, bool pairwise,
 385                                const u8 *mac_addr)
 386{
 387        struct wil6210_priv *wil = wiphy_to_wil(wiphy);
 388
 389        /* group key is not used */
 390        if (!pairwise)
 391                return 0;
 392
 393        return wmi_del_cipher_key(wil, key_index, mac_addr);
 394}
 395
 396/* Need to be present or wiphy_new() will WARN */
 397static int wil_cfg80211_set_default_key(struct wiphy *wiphy,
 398                                        struct net_device *ndev,
 399                                        u8 key_index, bool unicast,
 400                                        bool multicast)
 401{
 402        return 0;
 403}
 404
 405static int wil_fix_bcon(struct wil6210_priv *wil,
 406                        struct cfg80211_beacon_data *bcon)
 407{
 408        struct ieee80211_mgmt *f = (struct ieee80211_mgmt *)bcon->probe_resp;
 409        size_t hlen = offsetof(struct ieee80211_mgmt, u.probe_resp.variable);
 410        int rc = 0;
 411
 412        if (bcon->probe_resp_len <= hlen)
 413                return 0;
 414
 415        if (!bcon->proberesp_ies) {
 416                bcon->proberesp_ies = f->u.probe_resp.variable;
 417                bcon->proberesp_ies_len = bcon->probe_resp_len - hlen;
 418                rc = 1;
 419        }
 420        if (!bcon->assocresp_ies) {
 421                bcon->assocresp_ies = f->u.probe_resp.variable;
 422                bcon->assocresp_ies_len = bcon->probe_resp_len - hlen;
 423                rc = 1;
 424        }
 425
 426        return rc;
 427}
 428
 429static int wil_cfg80211_start_ap(struct wiphy *wiphy,
 430                                 struct net_device *ndev,
 431                                 struct cfg80211_ap_settings *info)
 432{
 433        int rc = 0;
 434        struct wil6210_priv *wil = wiphy_to_wil(wiphy);
 435        struct wireless_dev *wdev = ndev->ieee80211_ptr;
 436        struct ieee80211_channel *channel = info->chandef.chan;
 437        struct cfg80211_beacon_data *bcon = &info->beacon;
 438        u8 wmi_nettype = wil_iftype_nl2wmi(wdev->iftype);
 439
 440        if (!channel) {
 441                wil_err(wil, "AP: No channel???\n");
 442                return -EINVAL;
 443        }
 444
 445        wil_dbg_misc(wil, "AP on Channel %d %d MHz, %s\n", channel->hw_value,
 446                     channel->center_freq, info->privacy ? "secure" : "open");
 447        print_hex_dump_bytes("SSID ", DUMP_PREFIX_OFFSET,
 448                             info->ssid, info->ssid_len);
 449
 450        if (wil_fix_bcon(wil, bcon))
 451                wil_dbg_misc(wil, "Fixed bcon\n");
 452
 453        rc = wil_reset(wil);
 454        if (rc)
 455                return rc;
 456
 457        /* Rx VRING. */
 458        rc = wil_rx_init(wil);
 459        if (rc)
 460                return rc;
 461
 462        rc = wmi_set_ssid(wil, info->ssid_len, info->ssid);
 463        if (rc)
 464                return rc;
 465
 466        /* MAC address - pre-requisite for other commands */
 467        wmi_set_mac_address(wil, ndev->dev_addr);
 468
 469        /* IE's */
 470        /* bcon 'head IE's are not relevant for 60g band */
 471        /*
 472         * FW do not form regular beacon, so bcon IE's are not set
 473         * For the DMG bcon, when it will be supported, bcon IE's will
 474         * be reused; add something like:
 475         * wmi_set_ie(wil, WMI_FRAME_BEACON, bcon->beacon_ies_len,
 476         * bcon->beacon_ies);
 477         */
 478        wmi_set_ie(wil, WMI_FRAME_PROBE_RESP, bcon->proberesp_ies_len,
 479                   bcon->proberesp_ies);
 480        wmi_set_ie(wil, WMI_FRAME_ASSOC_RESP, bcon->assocresp_ies_len,
 481                   bcon->assocresp_ies);
 482
 483        wil->secure_pcp = info->privacy;
 484
 485        rc = wmi_pcp_start(wil, info->beacon_interval, wmi_nettype,
 486                           channel->hw_value);
 487        if (rc)
 488                return rc;
 489
 490
 491        netif_carrier_on(ndev);
 492
 493        return rc;
 494}
 495
 496static int wil_cfg80211_stop_ap(struct wiphy *wiphy,
 497                                struct net_device *ndev)
 498{
 499        int rc = 0;
 500        struct wil6210_priv *wil = wiphy_to_wil(wiphy);
 501
 502        rc = wmi_pcp_stop(wil);
 503
 504        return rc;
 505}
 506
 507static struct cfg80211_ops wil_cfg80211_ops = {
 508        .scan = wil_cfg80211_scan,
 509        .connect = wil_cfg80211_connect,
 510        .disconnect = wil_cfg80211_disconnect,
 511        .change_virtual_intf = wil_cfg80211_change_iface,
 512        .get_station = wil_cfg80211_get_station,
 513        .set_monitor_channel = wil_cfg80211_set_channel,
 514        .add_key = wil_cfg80211_add_key,
 515        .del_key = wil_cfg80211_del_key,
 516        .set_default_key = wil_cfg80211_set_default_key,
 517        /* AP mode */
 518        .start_ap = wil_cfg80211_start_ap,
 519        .stop_ap = wil_cfg80211_stop_ap,
 520};
 521
 522static void wil_wiphy_init(struct wiphy *wiphy)
 523{
 524        /* TODO: set real value */
 525        wiphy->max_scan_ssids = 10;
 526        wiphy->max_num_pmkids = 0 /* TODO: */;
 527        wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
 528                                 BIT(NL80211_IFTYPE_AP) |
 529                                 BIT(NL80211_IFTYPE_MONITOR);
 530        /* TODO: enable P2P when integrated with supplicant:
 531         * BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO)
 532         */
 533        wiphy->flags |= WIPHY_FLAG_HAVE_AP_SME |
 534                        WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD;
 535        dev_warn(wiphy_dev(wiphy), "%s : flags = 0x%08x\n",
 536                 __func__, wiphy->flags);
 537        wiphy->probe_resp_offload =
 538                NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS |
 539                NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2 |
 540                NL80211_PROBE_RESP_OFFLOAD_SUPPORT_P2P;
 541
 542        wiphy->bands[IEEE80211_BAND_60GHZ] = &wil_band_60ghz;
 543
 544        /* TODO: figure this out */
 545        wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM;
 546
 547        wiphy->cipher_suites = wil_cipher_suites;
 548        wiphy->n_cipher_suites = ARRAY_SIZE(wil_cipher_suites);
 549        wiphy->mgmt_stypes = wil_mgmt_stypes;
 550}
 551
 552struct wireless_dev *wil_cfg80211_init(struct device *dev)
 553{
 554        int rc = 0;
 555        struct wireless_dev *wdev;
 556
 557        wdev = kzalloc(sizeof(struct wireless_dev), GFP_KERNEL);
 558        if (!wdev)
 559                return ERR_PTR(-ENOMEM);
 560
 561        wdev->wiphy = wiphy_new(&wil_cfg80211_ops,
 562                                sizeof(struct wil6210_priv));
 563        if (!wdev->wiphy) {
 564                rc = -ENOMEM;
 565                goto out;
 566        }
 567
 568        set_wiphy_dev(wdev->wiphy, dev);
 569        wil_wiphy_init(wdev->wiphy);
 570
 571        rc = wiphy_register(wdev->wiphy);
 572        if (rc < 0)
 573                goto out_failed_reg;
 574
 575        return wdev;
 576
 577out_failed_reg:
 578        wiphy_free(wdev->wiphy);
 579out:
 580        kfree(wdev);
 581
 582        return ERR_PTR(rc);
 583}
 584
 585void wil_wdev_free(struct wil6210_priv *wil)
 586{
 587        struct wireless_dev *wdev = wil_to_wdev(wil);
 588
 589        if (!wdev)
 590                return;
 591
 592        wiphy_unregister(wdev->wiphy);
 593        wiphy_free(wdev->wiphy);
 594        kfree(wdev);
 595}
 596