Integrates the driver with cfg80211 subsystem for standard wireless configuration, and handling notifications from the driver. Also has the Function prototypes and structures for cfg80211 integration. Signed-off-by: Gokul Sivakumar --- .../net/wireless/infineon/inffmac/cfg80211.c | 6749 +++++++++++++++++ .../net/wireless/infineon/inffmac/cfg80211.h | 604 ++ 2 files changed, 7353 insertions(+) create mode 100644 drivers/net/wireless/infineon/inffmac/cfg80211.c create mode 100644 drivers/net/wireless/infineon/inffmac/cfg80211.h diff --git a/drivers/net/wireless/infineon/inffmac/cfg80211.c b/drivers/net/wireless/infineon/inffmac/cfg80211.c new file mode 100644 index 000000000000..11e2a6f08024 --- /dev/null +++ b/drivers/net/wireless/infineon/inffmac/cfg80211.c @@ -0,0 +1,6749 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Copyright (c) 2025, Infineon Technologies AG, or an affiliate of Infineon Technologies AG. + * All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "defs.h" +#include "hw_ids.h" +#include "core.h" +#include "debug.h" +#include "tracepoint.h" +#include "fwil_types.h" +#include "p2p.h" +#include "btcoex.h" +#include "pno.h" +#include "fwsignal.h" +#include "cfg80211.h" +#include "feature.h" +#include "fwil.h" +#include "proto.h" +#include "vendor.h" +#include "vendor_inf.h" +#include "bus.h" +#include "common.h" +#include "he.h" +#include "eht.h" +#include "twt.h" +#include "offload.h" +#include "pmsr.h" + +#define INFF_ND_INFO_TIMEOUT msecs_to_jiffies(2000) + +#define MGMT_AUTH_FRAME_DWELL_TIME 4000 +#define MGMT_AUTH_FRAME_WAIT_TIME (MGMT_AUTH_FRAME_DWELL_TIME + 100) + +#define INFF_ASSOC_PARAMS_FIXED_SIZE \ + (sizeof(struct inff_assoc_params_le) - sizeof(u16)) + +#define INFF_MAX_CHANSPEC_LIST \ + (INFF_DCMD_MEDLEN / sizeof(__le32) - 1) + +#define RATE_TO_BASE100KBPS(rate) (((rate) * 10) / 2) +#define RATETAB_ENT(_bitrate, _rateid, _flags) \ + { \ + .bitrate = (_bitrate), \ + .hw_value = (_rateid), \ + .flags = (_flags), \ + } + +static struct ieee80211_rate __wl_rates[] = { + RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_1M), INF_RATE_1M, 0), + RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_2M), INF_RATE_2M, IEEE80211_RATE_SHORT_PREAMBLE), + RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_5M5), INF_RATE_5M5, IEEE80211_RATE_SHORT_PREAMBLE), + RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_11M), INF_RATE_11M, IEEE80211_RATE_SHORT_PREAMBLE), + RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_6M), INF_RATE_6M, 0), + RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_9M), INF_RATE_9M, 0), + RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_12M), INF_RATE_12M, 0), + RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_18M), INF_RATE_18M, 0), + RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_24M), INF_RATE_24M, 0), + RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_36M), INF_RATE_36M, 0), + RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_48M), INF_RATE_48M, 0), + RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_54M), INF_RATE_54M, 0), +}; + +#define wl_g_rates (__wl_rates + 0) +#define wl_g_rates_size ARRAY_SIZE(__wl_rates) +#define wl_a_rates (__wl_rates + 4) +#define wl_a_rates_size (wl_g_rates_size - 4) + +#define CHAN2G(_channel, _freq) { \ + .band = NL80211_BAND_2GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_channel), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + +#define CH5G_FREQ(_channel) (5000 + (5 * (_channel))) +#define CHAN5G(_channel, _freq) { \ + .band = NL80211_BAND_5GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_channel), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + +#define CH6G_FREQ(_channel) (5950 + (5 * (_channel))) +#define CHAN6G(_channel, _freq) { \ + .band = NL80211_BAND_6GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_channel), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + +static struct ieee80211_channel __wl_2ghz_channels[] = { + CHAN2G(1, 2412), CHAN2G(2, 2417), CHAN2G(3, 2422), CHAN2G(4, 2427), + CHAN2G(5, 2432), CHAN2G(6, 2437), CHAN2G(7, 2442), CHAN2G(8, 2447), + CHAN2G(9, 2452), CHAN2G(10, 2457), CHAN2G(11, 2462), CHAN2G(12, 2467), + CHAN2G(13, 2472), CHAN2G(14, 2484) +}; + +static struct ieee80211_channel __wl_5ghz_channels[] = { + CHAN5G(34, CH5G_FREQ(34)), CHAN5G(36, CH5G_FREQ(36)), CHAN5G(38, CH5G_FREQ(38)), + CHAN5G(40, CH5G_FREQ(40)), CHAN5G(42, CH5G_FREQ(42)), CHAN5G(44, CH5G_FREQ(44)), + CHAN5G(46, CH5G_FREQ(46)), CHAN5G(48, CH5G_FREQ(48)), CHAN5G(52, CH5G_FREQ(52)), + CHAN5G(56, CH5G_FREQ(56)), CHAN5G(60, CH5G_FREQ(60)), CHAN5G(64, CH5G_FREQ(64)), + CHAN5G(100, CH5G_FREQ(100)), CHAN5G(104, CH5G_FREQ(104)), CHAN5G(108, CH5G_FREQ(108)), + CHAN5G(112, CH5G_FREQ(112)), CHAN5G(116, CH5G_FREQ(116)), CHAN5G(120, CH5G_FREQ(120)), + CHAN5G(124, CH5G_FREQ(124)), CHAN5G(128, CH5G_FREQ(128)), CHAN5G(132, CH5G_FREQ(132)), + CHAN5G(136, CH5G_FREQ(136)), CHAN5G(140, CH5G_FREQ(140)), CHAN5G(144, CH5G_FREQ(144)), + CHAN5G(149, CH5G_FREQ(149)), CHAN5G(153, CH5G_FREQ(153)), CHAN5G(157, CH5G_FREQ(157)), + CHAN5G(161, CH5G_FREQ(161)), CHAN5G(165, CH5G_FREQ(165)) +}; + +static struct ieee80211_channel __wl_6ghz_channels[] = { + CHAN6G(1, CH6G_FREQ(1)), CHAN6G(5, CH6G_FREQ(5)), CHAN6G(9, CH6G_FREQ(9)), + CHAN6G(13, CH6G_FREQ(13)), CHAN6G(17, CH6G_FREQ(17)), + CHAN6G(21, CH6G_FREQ(21)), CHAN6G(25, CH6G_FREQ(25)), CHAN6G(29, CH6G_FREQ(29)), + CHAN6G(33, CH6G_FREQ(33)), CHAN6G(37, CH6G_FREQ(37)), + CHAN6G(41, CH6G_FREQ(41)), CHAN6G(45, CH6G_FREQ(45)), CHAN6G(49, CH6G_FREQ(49)), + CHAN6G(53, CH6G_FREQ(53)), CHAN6G(57, CH6G_FREQ(57)), + CHAN6G(61, CH6G_FREQ(61)), CHAN6G(65, CH6G_FREQ(65)), CHAN6G(69, CH6G_FREQ(69)), + CHAN6G(73, CH6G_FREQ(73)), CHAN6G(77, CH6G_FREQ(77)), + CHAN6G(81, CH6G_FREQ(81)), CHAN6G(85, CH6G_FREQ(85)), CHAN6G(89, CH6G_FREQ(89)), + CHAN6G(93, CH6G_FREQ(93)), CHAN6G(97, CH6G_FREQ(97)), + CHAN6G(101, CH6G_FREQ(101)), CHAN6G(105, CH6G_FREQ(105)), CHAN6G(109, CH6G_FREQ(109)), + CHAN6G(113, CH6G_FREQ(113)), CHAN6G(117, CH6G_FREQ(117)), + CHAN6G(121, CH6G_FREQ(121)), CHAN6G(125, CH6G_FREQ(125)), CHAN6G(129, CH6G_FREQ(129)), + CHAN6G(133, CH6G_FREQ(133)), CHAN6G(137, CH6G_FREQ(137)), + CHAN6G(141, CH6G_FREQ(141)), CHAN6G(145, CH6G_FREQ(145)), CHAN6G(149, CH6G_FREQ(149)), + CHAN6G(153, CH6G_FREQ(153)), CHAN6G(157, CH6G_FREQ(157)), + CHAN6G(161, CH6G_FREQ(161)), CHAN6G(165, CH6G_FREQ(165)), CHAN6G(169, CH6G_FREQ(169)), + CHAN6G(173, CH6G_FREQ(173)), CHAN6G(177, CH6G_FREQ(177)), + CHAN6G(181, CH6G_FREQ(181)), CHAN6G(185, CH6G_FREQ(185)), CHAN6G(189, CH6G_FREQ(189)), + CHAN6G(193, CH6G_FREQ(193)), CHAN6G(197, CH6G_FREQ(197)), + CHAN6G(201, CH6G_FREQ(201)), CHAN6G(205, CH6G_FREQ(205)), CHAN6G(209, CH6G_FREQ(209)), + CHAN6G(213, CH6G_FREQ(213)), CHAN6G(217, CH6G_FREQ(217)), + CHAN6G(221, CH6G_FREQ(221)), CHAN6G(225, CH6G_FREQ(225)), CHAN6G(229, CH6G_FREQ(229)), + CHAN6G(233, CH6G_FREQ(233)) +}; + +/* Band templates duplicated per wiphy. The channel info + * above is added to the band during setup. + */ +static const struct ieee80211_supported_band __wl_band_2ghz = { + .band = NL80211_BAND_2GHZ, + .bitrates = wl_g_rates, + .n_bitrates = wl_g_rates_size, +}; + +static const struct ieee80211_supported_band __wl_band_5ghz = { + .band = NL80211_BAND_5GHZ, + .bitrates = wl_a_rates, + .n_bitrates = wl_a_rates_size, +}; + +static struct ieee80211_supported_band __wl_band_6ghz = { + .band = NL80211_BAND_6GHZ, + .bitrates = wl_a_rates, + .n_bitrates = wl_a_rates_size, +}; + +/* This is to override regulatory domains defined in cfg80211 module (reg.c) + * By default world regulatory domain defined in reg.c puts the flags + * NL80211_RRF_NO_IR for 5GHz channels (for * 36..48 and 149..165). + * With respect to these flags, wpa_supplicant doesn't * start p2p + * operations on 5GHz channels. All the changes in world regulatory + * domain are to be done here. + */ +static const struct ieee80211_regdomain inff_regdom = { + .n_reg_rules = 5, + .alpha2 = "99", + .reg_rules = { + /* IEEE 802.11b/g, channels 1..11 */ + REG_RULE(2412 - 10, 2472 + 10, 40, 6, 20, 0), + /* If any */ + /* IEEE 802.11 channel 14 - Only JP enables + * this and for 802.11b only + */ + REG_RULE(2484 - 10, 2484 + 10, 20, 6, 20, 0), + /* IEEE 802.11a, channel 36..64 */ + REG_RULE(5150 - 10, 5350 + 10, 160, 6, 20, 0), + /* IEEE 802.11a, channel 100..165 */ + REG_RULE(5470 - 10, 5850 + 10, 160, 6, 20, 0), + /* IEEE 802.11ax, 6E */ + REG_RULE(5935 - 10, 7115 + 10, 160, 6, 20, 0), + } +}; + +/* Note: inff_cipher_suites is an array of int defining which cipher suites + * are supported. A pointer to this array and the number of entries is passed + * on to upper layers. AES_CMAC defines whether or not the driver supports MFP. + * So the cipher suite AES_CMAC has to be the last one in the array, and when + * device does not support MFP then the number of suites will be decreased by 4 + */ +static const u32 inff_cipher_suites[] = { + WLAN_CIPHER_SUITE_WEP40, + WLAN_CIPHER_SUITE_WEP104, + WLAN_CIPHER_SUITE_TKIP, + WLAN_CIPHER_SUITE_CCMP, + WLAN_CIPHER_SUITE_CCMP_256, + WLAN_CIPHER_SUITE_GCMP, + WLAN_CIPHER_SUITE_GCMP_256, + /* Keep as last entry: */ + WLAN_CIPHER_SUITE_AES_CMAC, + WLAN_CIPHER_SUITE_BIP_CMAC_256, + WLAN_CIPHER_SUITE_BIP_GMAC_128, + WLAN_CIPHER_SUITE_BIP_GMAC_256 +}; + +static int inff_setup_wiphybands(struct inff_cfg80211_info *cfg); + +u8 nl80211_band_to_fwil(enum nl80211_band band) +{ + switch (band) { + case NL80211_BAND_2GHZ: + return WLC_BAND_2G; + case NL80211_BAND_5GHZ: + return WLC_BAND_5G; + case NL80211_BAND_6GHZ: + return WLC_BAND_6G; + default: + WARN_ON(1); + break; + } + return 0; +} + +u16 chandef_to_chanspec(struct inff_d11inf *d11inf, struct cfg80211_chan_def *ch) +{ + struct inff_chan ch_inf; + s32 primary_offset; + + inff_dbg(TRACE, "chandef: control %d center %d width %d\n", + ch->chan->center_freq, ch->center_freq1, ch->width); + ch_inf.chnum = ieee80211_frequency_to_channel(ch->center_freq1); + primary_offset = ch->chan->center_freq - ch->center_freq1; + switch (ch->width) { + case NL80211_CHAN_WIDTH_20: + case NL80211_CHAN_WIDTH_20_NOHT: + ch_inf.bw = INFF_CHAN_BW_20; + WARN_ON(primary_offset != 0); + break; + case NL80211_CHAN_WIDTH_40: + ch_inf.bw = INFF_CHAN_BW_40; + if (primary_offset > 0) + ch_inf.sb = INFF_CHAN_SB_U; + else + ch_inf.sb = INFF_CHAN_SB_L; + break; + case NL80211_CHAN_WIDTH_80: + ch_inf.bw = INFF_CHAN_BW_80; + if (primary_offset == -30) + ch_inf.sb = INFF_CHAN_SB_LL; + else if (primary_offset == -10) + ch_inf.sb = INFF_CHAN_SB_LU; + else if (primary_offset == 10) + ch_inf.sb = INFF_CHAN_SB_UL; + else + ch_inf.sb = INFF_CHAN_SB_UU; + break; + case NL80211_CHAN_WIDTH_160: + ch_inf.bw = INFF_CHAN_BW_160; + if (primary_offset == -70) + ch_inf.sb = INFF_CHAN_SB_LLL; + else if (primary_offset == -50) + ch_inf.sb = INFF_CHAN_SB_LLU; + else if (primary_offset == -30) + ch_inf.sb = INFF_CHAN_SB_LUL; + else if (primary_offset == -10) + ch_inf.sb = INFF_CHAN_SB_LUU; + else if (primary_offset == 10) + ch_inf.sb = INFF_CHAN_SB_ULL; + else if (primary_offset == 30) + ch_inf.sb = INFF_CHAN_SB_ULU; + else if (primary_offset == 50) + ch_inf.sb = INFF_CHAN_SB_UUL; + else + ch_inf.sb = INFF_CHAN_SB_UUU; + break; + case NL80211_CHAN_WIDTH_80P80: + case NL80211_CHAN_WIDTH_5: + case NL80211_CHAN_WIDTH_10: + default: + WARN_ON_ONCE(1); + } + switch (ch->chan->band) { + case NL80211_BAND_2GHZ: + ch_inf.band = INFF_CHAN_BAND_2G; + break; + case NL80211_BAND_5GHZ: + ch_inf.band = INFF_CHAN_BAND_5G; + break; + case NL80211_BAND_6GHZ: + ch_inf.band = INFF_CHAN_BAND_6G; + break; + case NL80211_BAND_60GHZ: + default: + WARN_ON_ONCE(1); + } + d11inf->encchspec(&ch_inf); + + inff_dbg(TRACE, "chanspec: 0x%x\n", ch_inf.chspec); + return ch_inf.chspec; +} + +u16 channel_to_chanspec(struct inff_d11inf *d11inf, + struct ieee80211_channel *ch) +{ + struct inff_chan ch_inf; + + switch (ch->band) { + case NL80211_BAND_2GHZ: + ch_inf.band = INFF_CHAN_BAND_2G; + break; + case NL80211_BAND_5GHZ: + ch_inf.band = INFF_CHAN_BAND_5G; + break; + case NL80211_BAND_6GHZ: + ch_inf.band = INFF_CHAN_BAND_6G; + break; + case NL80211_BAND_60GHZ: + default: + WARN_ON_ONCE(1); + } + ch_inf.chnum = ieee80211_frequency_to_channel(ch->center_freq); + ch_inf.bw = INFF_CHAN_BW_20; + d11inf->encchspec(&ch_inf); + + return ch_inf.chspec; +} + +static int inff_vif_change_validate(struct inff_cfg80211_info *cfg, + struct inff_cfg80211_vif *vif, + enum nl80211_iftype new_type) +{ + struct inff_cfg80211_vif *pos; + bool check_combos = false; + int ret = 0; + struct iface_combination_params params = { + .num_different_channels = 1, + }; + + list_for_each_entry(pos, &cfg->vif_list, list) + if (pos == vif) { + params.iftype_num[new_type]++; + } else { + /* concurrent interfaces so need check combinations */ + check_combos = true; + params.iftype_num[pos->wdev.iftype]++; + } + + if (check_combos) + ret = cfg80211_check_combinations(cfg->wiphy, ¶ms); + + return ret; +} + +static int inff_vif_add_validate(struct inff_cfg80211_info *cfg, + enum nl80211_iftype new_type) +{ + struct inff_cfg80211_vif *pos; + struct iface_combination_params params = { + .num_different_channels = 1, + }; + + list_for_each_entry(pos, &cfg->vif_list, list) + params.iftype_num[pos->wdev.iftype]++; + + params.iftype_num[new_type]++; + return cfg80211_check_combinations(cfg->wiphy, ¶ms); +} + +void +inff_cfg80211_update_proto_addr_mode(struct wireless_dev *wdev) +{ + struct inff_cfg80211_vif *vif; + struct inff_if *ifp; + + vif = container_of(wdev, struct inff_cfg80211_vif, wdev); + ifp = vif->ifp; + + if (wdev->iftype == NL80211_IFTYPE_ADHOC || + wdev->iftype == NL80211_IFTYPE_AP || + wdev->iftype == NL80211_IFTYPE_P2P_GO) + inff_proto_configure_addr_mode(ifp->drvr, ifp->ifidx, + ADDR_DIRECT); + else + inff_proto_configure_addr_mode(ifp->drvr, ifp->ifidx, + ADDR_INDIRECT); +} + +static struct wireless_dev *inff_cfg80211_add_iface(struct wiphy *wiphy, + const char *name, + unsigned char name_assign_type, + enum nl80211_iftype type, + struct vif_params *params) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_pub *drvr = cfg->pub; + struct wireless_dev *wdev = NULL; + int err; + + inff_dbg(TRACE, "enter: %s type %d\n", name, type); + err = inff_vif_add_validate(wiphy_to_cfg(wiphy), type); + if (err) { + iphy_err(drvr, "iface validation failed: err=%d\n", err); + return ERR_PTR(err); + } + switch (type) { + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_AP_VLAN: + case NL80211_IFTYPE_WDS: + case NL80211_IFTYPE_MESH_POINT: + return ERR_PTR(-EOPNOTSUPP); + case NL80211_IFTYPE_MONITOR: + return inff_mon_add_vif(wiphy, name); + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_AP: + wdev = inff_apsta_add_vif(wiphy, name, params, type); + break; + case NL80211_IFTYPE_P2P_CLIENT: + case NL80211_IFTYPE_P2P_GO: + case NL80211_IFTYPE_P2P_DEVICE: + wdev = inff_p2p_add_vif(wiphy, name, name_assign_type, type, params); + break; + case NL80211_IFTYPE_WLAN_SENSE: + wdev = inff_wlan_sense_add_vif(wiphy, name, name_assign_type, type, params); + break; + case NL80211_IFTYPE_UNSPECIFIED: + default: + return ERR_PTR(-EOPNOTSUPP); + } + + if (IS_ERR_OR_NULL(wdev)) + iphy_err(drvr, "add iface %s type %d failed: err=%d\n", name, + type, (int)PTR_ERR(wdev)); + else + inff_cfg80211_update_proto_addr_mode(wdev); + + return wdev; +} + +void inff_set_mpc(struct inff_if *ifp, int mpc) +{ + struct inff_pub *drvr = ifp->drvr; + s32 err = 0; + + ifp->drvr->req_mpc = mpc; + if (check_vif_up(ifp->vif)) { + err = inff_fil_iovar_int_set(ifp, + "mpc", + ifp->drvr->req_mpc); + if (err) { + iphy_err(drvr, "fail to set mpc\n"); + return; + } + inff_dbg(INFO, "MPC : %d\n", mpc); + } +} + +static +int inff_cfg80211_del_iface(struct wiphy *wiphy, struct wireless_dev *wdev) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct net_device *ndev = wdev->netdev; + + if (ndev && ndev == cfg_to_ndev(cfg)) + return -EOPNOTSUPP; + + /* vif event pending in firmware */ + if (inff_cfg80211_vif_event_armed(cfg)) + return -EBUSY; + + if (ndev) { + if (test_bit(INFF_SCAN_STATUS_BUSY, &cfg->scan_status) && + cfg->escan_info.ifp == netdev_priv(ndev)) + inff_notify_escan_complete(cfg, netdev_priv(ndev), + true, true); + + inff_fil_iovar_int_set(netdev_priv(ndev), "mpc", 1); + } + + switch (wdev->iftype) { + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_AP_VLAN: + case NL80211_IFTYPE_WDS: + case NL80211_IFTYPE_MESH_POINT: + return -EOPNOTSUPP; + case NL80211_IFTYPE_MONITOR: + return inff_mon_del_vif(wiphy, wdev); + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_AP: + return inff_cfg80211_del_apsta_iface(wiphy, wdev); + case NL80211_IFTYPE_P2P_CLIENT: + case NL80211_IFTYPE_P2P_GO: + case NL80211_IFTYPE_P2P_DEVICE: + return inff_p2p_del_vif(wiphy, wdev); + case NL80211_IFTYPE_WLAN_SENSE: + return inff_wlan_sense_del_vif(wiphy, wdev); + case NL80211_IFTYPE_UNSPECIFIED: + default: + return -EOPNOTSUPP; + } + + return -EOPNOTSUPP; +} + +static s32 +inff_cfg80211_change_iface(struct wiphy *wiphy, struct net_device *ndev, + enum nl80211_iftype type, + struct vif_params *params) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_if *ifp = netdev_priv(ndev); + struct inff_cfg80211_vif *vif = ifp->vif; + struct inff_pub *drvr = cfg->pub; + s32 infra = 0; + s32 ap = 0; + s32 err = 0; + + inff_dbg(TRACE, "Enter, bsscfgidx=%d, type=%d\n", ifp->bsscfgidx, + type); + + /* WAR: There are a number of p2p interface related problems which + * need to be handled initially (before doing the validate). + * wpa_supplicant tends to do iface changes on p2p device/client/go + * which are not always possible/allowed. However we need to return + * OK otherwise the wpa_supplicant won't start. The situation differs + * on configuration and setup (p2pon=1 module param). The first check + * is to see if the request is a change to station for p2p iface. + */ + if (type == NL80211_IFTYPE_STATION && + (vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT || + vif->wdev.iftype == NL80211_IFTYPE_P2P_GO || + vif->wdev.iftype == NL80211_IFTYPE_P2P_DEVICE)) { + inff_dbg(TRACE, "Ignoring cmd for p2p if\n"); + /* Now depending on whether module param p2pon=1 was used the + * response needs to be either 0 or EOPNOTSUPP. The reason is + * that if p2pon=1 is used, but a newer supplicant is used then + * we should return an error, as this combination won't work. + * In other situations 0 is returned and supplicant will start + * normally. It will give a trace in cfg80211, but it is the + * only way to get it working. Unfortunately this will result + * in situation where we won't support new supplicant in + * combination with module param p2pon=1, but that is the way + * it is. If the user tries this then unloading of driver might + * fail/lock. + */ + if (cfg->p2p.p2pdev_dynamically) + return -EOPNOTSUPP; + else + return 0; + } + err = inff_vif_change_validate(wiphy_to_cfg(wiphy), vif, type); + if (err) { + iphy_err(drvr, "iface validation failed: err=%d\n", err); + return err; + } + switch (type) { + case NL80211_IFTYPE_MONITOR: + case NL80211_IFTYPE_WDS: + iphy_err(drvr, "type (%d) : currently we do not support this type\n", + type); + return -EOPNOTSUPP; + case NL80211_IFTYPE_ADHOC: + infra = 0; + break; + case NL80211_IFTYPE_STATION: + infra = 1; + break; + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + ap = 1; + break; + default: + err = -EINVAL; + goto done; + } + + if (ap) { + if (type == NL80211_IFTYPE_P2P_GO) { + inff_dbg(INFO, "IF Type = P2P GO\n"); + err = inff_p2p_ifchange(cfg, INFF_FIL_P2P_IF_GO); + } + if (!err) + inff_dbg(INFO, "IF Type = AP\n"); + } else { + err = inff_fil_cmd_int_set(ifp, INFF_C_SET_INFRA, infra); + if (err) { + iphy_err(drvr, "WLC_SET_INFRA error (%d)\n", err); + err = -EAGAIN; + goto done; + } + inff_dbg(INFO, "IF Type = %s\n", inff_is_ibssmode(vif) ? + "Adhoc" : "Infra"); + } + ndev->ieee80211_ptr->iftype = type; + + inff_cfg80211_update_proto_addr_mode(&vif->wdev); + inff_setup_wiphybands(cfg); + +done: + inff_dbg(TRACE, "Exit\n"); + + return err; +} + +static s32 inff_set_rts(struct net_device *ndev, u32 rts_threshold) +{ + struct inff_if *ifp = netdev_priv(ndev); + struct inff_pub *drvr = ifp->drvr; + s32 err = 0; + + err = inff_fil_iovar_int_set(ifp, "rtsthresh", rts_threshold); + if (err) + iphy_err(drvr, "Error (%d)\n", err); + + return err; +} + +static s32 inff_set_frag(struct net_device *ndev, u32 frag_threshold) +{ + struct inff_if *ifp = netdev_priv(ndev); + struct inff_pub *drvr = ifp->drvr; + s32 err = 0; + + err = inff_fil_iovar_int_set(ifp, "fragthresh", + frag_threshold); + if (err) + iphy_err(drvr, "Error (%d)\n", err); + + return err; +} + +static s32 inff_set_retry(struct net_device *ndev, u32 retry, bool l) +{ + struct inff_if *ifp = netdev_priv(ndev); + struct inff_pub *drvr = ifp->drvr; + s32 err = 0; + u32 cmd = (l ? INFF_C_SET_LRL : INFF_C_SET_SRL); + + err = inff_fil_cmd_int_set(ifp, cmd, retry); + if (err) { + iphy_err(drvr, "cmd (%d) , error (%d)\n", cmd, err); + return err; + } + return err; +} + +static s32 inff_cfg80211_set_wiphy_params(struct wiphy *wiphy, int radio_idx, + u32 changed) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct net_device *ndev = cfg_to_ndev(cfg); + struct inff_if *ifp = netdev_priv(ndev); + s32 err = 0; + + inff_dbg(TRACE, "Enter\n"); + if (!check_vif_up(ifp->vif)) + return -EIO; + + if (changed & WIPHY_PARAM_RTS_THRESHOLD && + cfg->conf->rts_threshold != wiphy->rts_threshold) { + cfg->conf->rts_threshold = wiphy->rts_threshold; + err = inff_set_rts(ndev, cfg->conf->rts_threshold); + if (!err) + goto done; + } + if (changed & WIPHY_PARAM_FRAG_THRESHOLD && + cfg->conf->frag_threshold != wiphy->frag_threshold) { + cfg->conf->frag_threshold = wiphy->frag_threshold; + err = inff_set_frag(ndev, cfg->conf->frag_threshold); + if (!err) + goto done; + } + if (changed & WIPHY_PARAM_RETRY_LONG && + cfg->conf->retry_long != wiphy->retry_long) { + cfg->conf->retry_long = wiphy->retry_long; + err = inff_set_retry(ndev, cfg->conf->retry_long, true); + if (!err) + goto done; + } + if (changed & WIPHY_PARAM_RETRY_SHORT && + cfg->conf->retry_short != wiphy->retry_short) { + cfg->conf->retry_short = wiphy->retry_short; + err = inff_set_retry(ndev, cfg->conf->retry_short, false); + if (!err) + goto done; + } + +done: + inff_dbg(TRACE, "Exit\n"); + return err; +} + +static int inff_set_sae_password(struct inff_if *ifp, const u8 *pwd_data, + u16 pwd_len) +{ + struct inff_pub *drvr = ifp->drvr; + struct inff_wsec_sae_pwd_le sae_pwd; + int err; + + if (pwd_len > INFF_WSEC_MAX_SAE_PASSWORD_LEN) { + iphy_err(drvr, "sae_password must be less than %d\n", + INFF_WSEC_MAX_SAE_PASSWORD_LEN); + return -EINVAL; + } + + sae_pwd.key_len = cpu_to_le16(pwd_len); + memcpy(sae_pwd.key, pwd_data, pwd_len); + + err = inff_fil_iovar_data_set(ifp, "sae_password", &sae_pwd, + sizeof(sae_pwd)); + if (err < 0) + iphy_err(drvr, "failed to set SAE password in firmware (len=%u)\n", + pwd_len); + + return err; +} + +void inff_link_down(struct inff_cfg80211_vif *vif, u16 reason, + bool locally_generated) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(vif->wdev.wiphy); + struct inff_pub *drvr = cfg->pub; + bool bus_up = drvr->bus_if->state == INFF_BUS_UP; + s32 err = 0; + + inff_dbg(TRACE, "Enter\n"); + + if (test_and_clear_bit(INFF_VIF_STATUS_CONNECTED, &vif->sme_state)) { + if (bus_up) { + inff_dbg(INFO, "Call WLC_DISASSOC to stop excess roaming\n"); + err = inff_fil_cmd_data_set(vif->ifp, + INFF_C_DISASSOC, NULL, 0); + if (err) { + iphy_err(drvr, "WLC_DISASSOC failed (%d)\n", + err); + } else { + if (inff_feat_is_enabled(vif->ifp, INFF_FEAT_TWT)) { + /* Cleanup TWT Session list */ + inff_twt_cleanup_all_sess(vif->ifp); + } + } + } + + if (vif->wdev.iftype == NL80211_IFTYPE_STATION || + vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT) + cfg80211_disconnected(vif->wdev.netdev, reason, NULL, 0, + locally_generated, GFP_KERNEL); + } + clear_bit(INFF_VIF_STATUS_CONNECTING, &vif->sme_state); + clear_bit(INFF_VIF_STATUS_EAP_SUCCESS, &vif->sme_state); + clear_bit(INFF_VIF_STATUS_ASSOC_SUCCESS, &vif->sme_state); + clear_bit(INFF_SCAN_STATUS_SUPPRESS, &cfg->scan_status); + inff_btcoex_set_mode(vif, INFF_BTCOEX_ENABLED, 0); + if (vif->profile.use_fwsup != INFF_PROFILE_FWSUP_NONE) { + if (bus_up) + inff_set_pmk(vif->ifp, NULL, 0); + vif->profile.use_fwsup = INFF_PROFILE_FWSUP_NONE; + } + + inff_dbg(TRACE, "Exit\n"); +} + +static s32 +inff_cfg80211_join_ibss(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_ibss_params *params) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_if *ifp = netdev_priv(ndev); + struct inff_cfg80211_profile *profile = &ifp->vif->profile; + struct inff_pub *drvr = cfg->pub; + struct inff_join_params join_params; + size_t join_params_size = 0; + s32 err = 0; + s32 wsec = 0; + s32 bcnprd; + u16 chanspec; + u32 ssid_len; + + inff_dbg(TRACE, "Enter\n"); + if (!check_vif_up(ifp->vif)) + return -EIO; + + if (params->ssid) { + inff_dbg(CONN, "SSID: %s\n", params->ssid); + } else { + inff_dbg(CONN, "SSID: NULL, Not supported\n"); + return -EOPNOTSUPP; + } + + set_bit(INFF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state); + + if (params->bssid) + inff_dbg(CONN, "BSSID: %pM\n", params->bssid); + else + inff_dbg(CONN, "No BSSID specified\n"); + + if (params->chandef.chan) + inff_dbg(CONN, "channel: %d\n", + params->chandef.chan->center_freq); + else + inff_dbg(CONN, "no channel specified\n"); + + if (params->channel_fixed) + inff_dbg(CONN, "fixed channel required\n"); + else + inff_dbg(CONN, "no fixed channel required\n"); + + if (params->ie && params->ie_len) + inff_dbg(CONN, "ie len: %d\n", params->ie_len); + else + inff_dbg(CONN, "no ie specified\n"); + + if (params->beacon_interval) + inff_dbg(CONN, "beacon interval: %d\n", + params->beacon_interval); + else + inff_dbg(CONN, "no beacon interval specified\n"); + + if (params->basic_rates) + inff_dbg(CONN, "basic rates: %08X\n", params->basic_rates); + else + inff_dbg(CONN, "no basic rates specified\n"); + + if (params->privacy) + inff_dbg(CONN, "privacy required\n"); + else + inff_dbg(CONN, "no privacy required\n"); + + /* Configure Privacy for starter */ + if (params->privacy) + wsec |= WEP_ENABLED; + + err = inff_fil_iovar_int_set(ifp, "wsec", wsec); + if (err) { + iphy_err(drvr, "wsec failed (%d)\n", err); + goto done; + } + + /* Configure Beacon Interval for starter */ + if (params->beacon_interval) + bcnprd = params->beacon_interval; + else + bcnprd = 100; + + err = inff_fil_cmd_int_set(ifp, INFF_C_SET_BCNPRD, bcnprd); + if (err) { + iphy_err(drvr, "WLC_SET_BCNPRD failed (%d)\n", err); + goto done; + } + + /* Configure required join parameter */ + memset(&join_params, 0, sizeof(struct inff_join_params)); + + /* SSID */ + ssid_len = min_t(u32, params->ssid_len, IEEE80211_MAX_SSID_LEN); + memcpy(join_params.ssid_le.SSID, params->ssid, ssid_len); + join_params.ssid_le.SSID_len = cpu_to_le32(ssid_len); + join_params_size = sizeof(join_params.ssid_le); + + /* BSSID */ + if (params->bssid) { + memcpy(join_params.params_le.bssid, params->bssid, ETH_ALEN); + join_params_size += INFF_ASSOC_PARAMS_FIXED_SIZE; + memcpy(profile->bssid, params->bssid, ETH_ALEN); + } else { + eth_broadcast_addr(join_params.params_le.bssid); + eth_zero_addr(profile->bssid); + } + + /* Channel */ + if (params->chandef.chan) { + u32 target_channel; + + cfg->channel = + ieee80211_frequency_to_channel(params->chandef.chan->center_freq); + if (params->channel_fixed) { + /* adding chanspec */ + chanspec = chandef_to_chanspec(&cfg->d11inf, + ¶ms->chandef); + join_params.params_le.chanspec_list = + cpu_to_le16(chanspec); + join_params.params_le.chanspec_num = cpu_to_le32(1); + join_params_size += sizeof(join_params.params_le); + } + + /* set channel for starter */ + target_channel = cfg->channel; + err = inff_fil_cmd_int_set(ifp, INFF_C_SET_CHANNEL, + target_channel); + if (err) { + iphy_err(drvr, "WLC_SET_CHANNEL failed (%d)\n", err); + goto done; + } + } else { + cfg->channel = 0; + } + + cfg->ibss_starter = false; + + err = inff_fil_cmd_data_set(ifp, INFF_C_SET_SSID, + &join_params, join_params_size); + if (err) { + iphy_err(drvr, "WLC_SET_SSID failed (%d)\n", err); + goto done; + } + +done: + if (err) + clear_bit(INFF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state); + inff_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +inff_cfg80211_leave_ibss(struct wiphy *wiphy, struct net_device *ndev) +{ + struct inff_if *ifp = netdev_priv(ndev); + + inff_dbg(TRACE, "Enter\n"); + if (!check_vif_up(ifp->vif)) { + /* When driver is being unloaded, it can end up here. If an + * error is returned then later on a debug trace in the wireless + * core module will be printed. To avoid this 0 is returned. + */ + return 0; + } + + inff_link_down(ifp->vif, WLAN_REASON_DEAUTH_LEAVING, true); + inff_net_setcarrier(ifp, false); + + inff_dbg(TRACE, "Exit\n"); + + return 0; +} + +static s32 inff_set_wpa_version(struct net_device *ndev, + struct cfg80211_connect_params *sme) +{ + struct inff_if *ifp = netdev_priv(ndev); + struct inff_cfg80211_profile *profile = ndev_to_prof(ndev); + struct inff_pub *drvr = ifp->drvr; + struct inff_cfg80211_security *sec; + s32 val = 0; + s32 err = 0; + + if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_1) { + val = WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED; + } else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_2) { + if (sme->crypto.akm_suites[0] == WLAN_AKM_SUITE_SAE) + val = WPA3_AUTH_SAE_PSK; + else if (sme->crypto.akm_suites[0] == WLAN_AKM_SUITE_OWE) + val = WPA3_AUTH_OWE; + else if (sme->crypto.akm_suites[0] == WLAN_AKM_SUITE_8021X_SHA256) + val = WPA3_AUTH_1X_SHA256; + else + val = WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED; + } else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_3) { + if (sme->crypto.akm_suites[0] == WLAN_AKM_SUITE_FT_OVER_SAE) + val = WPA3_AUTH_SAE_FBT; + else if (sme->crypto.akm_suites[0] == WLAN_AKM_SUITE_FT_8021X_SHA384) + val = WPA3_AUTH_SAE_FT_1X; + else + val = WPA3_AUTH_SAE_PSK; + } else { + val = WPA_AUTH_DISABLED; + } + inff_dbg(CONN, "setting wpa_auth to 0x%0x\n", val); + err = inff_fil_bsscfg_int_set(ifp, "wpa_auth", val); + if (err) { + iphy_err(drvr, "set wpa_auth failed (%d)\n", err); + return err; + } + sec = &profile->sec; + sec->wpa_versions = sme->crypto.wpa_versions; + return err; +} + +static s32 inff_set_auth_type(struct net_device *ndev, + struct cfg80211_connect_params *sme) +{ + struct inff_if *ifp = netdev_priv(ndev); + struct inff_cfg80211_profile *profile = ndev_to_prof(ndev); + struct inff_pub *drvr = ifp->drvr; + struct inff_cfg80211_security *sec; + s32 val = 0; + s32 err = 0; + + switch (sme->auth_type) { + case NL80211_AUTHTYPE_OPEN_SYSTEM: + val = 0; + inff_dbg(CONN, "open system\n"); + break; + case NL80211_AUTHTYPE_SHARED_KEY: + val = 1; + inff_dbg(CONN, "shared key\n"); + break; + case NL80211_AUTHTYPE_SAE: + val = 3; + inff_dbg(CONN, "SAE authentication\n"); + break; + default: + val = 2; + inff_dbg(CONN, "automatic, auth type (%d)\n", sme->auth_type); + break; + } + + err = inff_fil_bsscfg_int_set(ifp, "auth", val); + if (err) { + iphy_err(drvr, "set auth failed (%d)\n", err); + return err; + } + sec = &profile->sec; + sec->auth_type = sme->auth_type; + return err; +} + +static s32 +inff_set_wsec_mode(struct net_device *ndev, + struct cfg80211_connect_params *sme) +{ + struct inff_if *ifp = netdev_priv(ndev); + struct inff_cfg80211_profile *profile = ndev_to_prof(ndev); + struct inff_pub *drvr = ifp->drvr; + struct inff_cfg80211_security *sec; + s32 pval = 0; + s32 gval = 0; + s32 wsec; + s32 err = 0; + u32 algos = 0, mask = 0; + + if (sme->crypto.n_ciphers_pairwise) { + switch (sme->crypto.ciphers_pairwise[0]) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + pval = WEP_ENABLED; + break; + case WLAN_CIPHER_SUITE_TKIP: + pval = TKIP_ENABLED; + break; + case WLAN_CIPHER_SUITE_CCMP: + pval = AES_ENABLED; + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + pval = AES_ENABLED; + break; + case WLAN_CIPHER_SUITE_GCMP_256: + if (!inff_feat_is_enabled(ifp, INFF_FEAT_GCMP)) { + inff_err("the low layer not support GCMP\n"); + return -EOPNOTSUPP; + } + pval = AES_ENABLED; + algos = KEY_ALGO_MASK(CRYPTO_ALGO_AES_GCM256); + mask = algos | KEY_ALGO_MASK(CRYPTO_ALGO_AES_CCM); + break; + default: + iphy_err(drvr, "invalid cipher pairwise (%d)\n", + sme->crypto.ciphers_pairwise[0]); + return -EINVAL; + } + } + if (sme->crypto.cipher_group) { + switch (sme->crypto.cipher_group) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + gval = WEP_ENABLED; + break; + case WLAN_CIPHER_SUITE_TKIP: + gval = TKIP_ENABLED; + break; + case WLAN_CIPHER_SUITE_CCMP: + gval = AES_ENABLED; + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + gval = AES_ENABLED; + break; + case WLAN_CIPHER_SUITE_GCMP_256: + if (!inff_feat_is_enabled(ifp, INFF_FEAT_GCMP)) { + inff_err("the low layer not support GCMP\n"); + return -EOPNOTSUPP; + } + gval = AES_ENABLED; + algos = KEY_ALGO_MASK(CRYPTO_ALGO_AES_GCM256); + mask = algos | KEY_ALGO_MASK(CRYPTO_ALGO_AES_CCM); + break; + default: + iphy_err(drvr, "invalid cipher group (%d)\n", + sme->crypto.cipher_group); + return -EINVAL; + } + } + + inff_dbg(CONN, "pval (%d) gval (%d)\n", pval, gval); + inff_dbg(CONN, "algos (0x%x) mask (0x%x)\n", algos, mask); + /* In case of privacy, but no security and WPS then simulate */ + /* setting AES. WPS-2.0 allows no security */ + if (inff_find_wpsie(sme->ie, sme->ie_len) && !pval && !gval && + sme->privacy) + pval = AES_ENABLED; + + wsec = pval | gval; + err = inff_fil_bsscfg_int_set(ifp, "wsec", wsec); + if (err) { + iphy_err(drvr, "error (%d)\n", err); + return err; + } + + if (inff_feat_is_enabled(ifp, INFF_FEAT_GCMP)) { + inff_dbg(CONN, + "set_wsec_info algos (0x%x) mask (0x%x)\n", + algos, mask); + err = wl_set_wsec_info_algos(ifp, algos, mask); + if (err) { + inff_err("set wsec_info error (%d)\n", err); + return err; + } + } + + sec = &profile->sec; + sec->cipher_pairwise = sme->crypto.ciphers_pairwise[0]; + sec->cipher_group = sme->crypto.cipher_group; + + return err; +} + +static s32 +inff_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) +{ + struct inff_if *ifp = netdev_priv(ndev); + struct inff_cfg80211_profile *profile = &ifp->vif->profile; + struct inff_pub *drvr = ifp->drvr; + s32 val; + s32 err; + s32 okc_enable; + const struct inff_tlv *rsn_ie; + const u8 *ie; + u32 ie_len; + u32 offset; + u16 rsn_cap; + u32 mfp; + u16 count; + u16 pmkid_count; + const u8 *group_mgmt_cs = NULL; + + profile->use_fwsup = INFF_PROFILE_FWSUP_NONE; + profile->is_ft = false; + profile->is_okc = false; + + if (!sme->crypto.n_akm_suites) + return 0; + + err = inff_fil_bsscfg_int_get(netdev_priv(ndev), "wpa_auth", &val); + if (err) { + iphy_err(drvr, "could not get wpa_auth (%d)\n", err); + return err; + } + if (val & (WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED)) { + switch (sme->crypto.akm_suites[0]) { + case WLAN_AKM_SUITE_8021X: + val = WPA_AUTH_UNSPECIFIED; + if (sme->want_1x) + profile->use_fwsup = INFF_PROFILE_FWSUP_1X; + else + profile->use_fwsup = INFF_PROFILE_FWSUP_ROAM; + break; + case WLAN_AKM_SUITE_PSK: + val = WPA_AUTH_PSK; + break; + default: + iphy_err(drvr, "invalid akm suite (%d)\n", + sme->crypto.akm_suites[0]); + return -EINVAL; + } + } else if (val & (WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED | WPA3_AUTH_OWE)) { + switch (sme->crypto.akm_suites[0]) { + case WLAN_AKM_SUITE_8021X: + val = WPA2_AUTH_UNSPECIFIED; + if (sme->want_1x) + profile->use_fwsup = INFF_PROFILE_FWSUP_1X; + else + profile->use_fwsup = INFF_PROFILE_FWSUP_ROAM; + break; + case WLAN_AKM_SUITE_8021X_SHA256: + val = WPA2_AUTH_1X_SHA256; + if (sme->want_1x) + profile->use_fwsup = INFF_PROFILE_FWSUP_1X; + else + profile->use_fwsup = INFF_PROFILE_FWSUP_ROAM; + break; + case WLAN_AKM_SUITE_PSK_SHA256: + val = WPA2_AUTH_PSK_SHA256; + break; + case WLAN_AKM_SUITE_PSK: + val = WPA2_AUTH_PSK; + break; + case WLAN_AKM_SUITE_FT_8021X: + val = WPA2_AUTH_UNSPECIFIED | WPA2_AUTH_FT; + profile->is_ft = true; + if (sme->want_1x) + profile->use_fwsup = INFF_PROFILE_FWSUP_1X; + else + profile->use_fwsup = INFF_PROFILE_FWSUP_ROAM; + break; + case WLAN_AKM_SUITE_FT_PSK: + val = WPA2_AUTH_PSK | WPA2_AUTH_FT; + profile->is_ft = true; + if (inff_feat_is_enabled(ifp, INFF_FEAT_FWSUP)) + profile->use_fwsup = INFF_PROFILE_FWSUP_PSK; + else + profile->use_fwsup = INFF_PROFILE_FWSUP_ROAM; + break; + case WLAN_AKM_SUITE_WFA_DPP: + val = WFA_AUTH_DPP; + profile->use_fwsup = INFF_PROFILE_FWSUP_NONE; + break; + case WLAN_AKM_SUITE_OWE: + val = WPA3_AUTH_OWE; + profile->use_fwsup = INFF_PROFILE_FWSUP_ROAM; + break; + case WLAN_AKM_SUITE_8021X_SUITE_B_192: + val = WPA3_AUTH_1X_SUITE_B_SHA384; + if (sme->want_1x) + profile->use_fwsup = INFF_PROFILE_FWSUP_1X; + else + profile->use_fwsup = INFF_PROFILE_FWSUP_ROAM; + + /*Disable intrnal sup for SuiteB*/ + if (inff_feat_is_enabled(ifp, INFF_FEAT_FWSUP)) + profile->use_fwsup = INFF_PROFILE_FWSUP_NONE; + break; + default: + iphy_err(drvr, "invalid akm suite (%d)\n", + sme->crypto.akm_suites[0]); + return -EINVAL; + } + } else if (val & (WPA3_AUTH_SAE_PSK | WPA3_AUTH_SAE_FBT)) { + switch (sme->crypto.akm_suites[0]) { + case WLAN_AKM_SUITE_SAE: + val = WPA3_AUTH_SAE_PSK; + if (sme->crypto.sae_pwd) { + inff_dbg(INFO, "using SAE offload\n"); + profile->use_fwsup = INFF_PROFILE_FWSUP_SAE; + } + break; + case WLAN_AKM_SUITE_FT_OVER_SAE: + val = WPA3_AUTH_SAE_FBT; + profile->is_ft = true; + if (sme->crypto.sae_pwd) { + inff_dbg(INFO, "using SAE offload\n"); + profile->use_fwsup = INFF_PROFILE_FWSUP_SAE; + } else { + profile->use_fwsup = INFF_PROFILE_FWSUP_ROAM; + } + break; + default: + iphy_err(drvr, "invalid akm suite (%d)\n", + sme->crypto.akm_suites[0]); + return -EINVAL; + } + } + + if (profile->use_fwsup == INFF_PROFILE_FWSUP_1X || + profile->use_fwsup == INFF_PROFILE_FWSUP_ROAM) { + inff_dbg(INFO, "using 1X offload\n"); + err = inff_fil_bsscfg_int_get(netdev_priv(ndev), "okc_enable", + &okc_enable); + if (err) { + iphy_err(drvr, "get okc_enable failed (%d)\n", err); + } else { + inff_dbg(INFO, "get okc_enable (%d)\n", okc_enable); + profile->is_okc = okc_enable; + } + } else if (profile->use_fwsup != INFF_PROFILE_FWSUP_SAE && + (val == WPA3_AUTH_SAE_PSK)) { + inff_dbg(INFO, "not using SAE offload\n"); + err = inff_fil_bsscfg_int_get(netdev_priv(ndev), "okc_enable", + &okc_enable); + if (err) { + iphy_err(drvr, "get okc_enable failed (%d)\n", err); + } else { + inff_dbg(INFO, "get okc_enable (%d)\n", okc_enable); + profile->is_okc = okc_enable; + } + } + + if (!inff_feat_is_enabled(ifp, INFF_FEAT_MFP)) + goto skip_mfp_config; + /* The MFP mode (1 or 2) needs to be determined, parse IEs. The + * IE will not be verified, just a quick search for MFP config + */ + rsn_ie = inff_parse_tlvs((const u8 *)sme->ie, sme->ie_len, + WLAN_EID_RSN); + if (!rsn_ie) + goto skip_mfp_config; + ie = (const u8 *)rsn_ie; + ie_len = rsn_ie->len + TLV_HDR_LEN; + /* Skip unicast suite */ + offset = TLV_HDR_LEN + WPA_IE_VERSION_LEN + WPA_IE_MIN_OUI_LEN; + if (offset + WPA_IE_SUITE_COUNT_LEN >= ie_len) + goto skip_mfp_config; + /* Skip multicast suite */ + count = ie[offset] + (ie[offset + 1] << 8); + offset += WPA_IE_SUITE_COUNT_LEN + (count * WPA_IE_MIN_OUI_LEN); + if (offset + WPA_IE_SUITE_COUNT_LEN >= ie_len) + goto skip_mfp_config; + /* Skip auth key management suite(s) */ + count = ie[offset] + (ie[offset + 1] << 8); + offset += WPA_IE_SUITE_COUNT_LEN + (count * WPA_IE_MIN_OUI_LEN); + if (offset + WPA_IE_SUITE_COUNT_LEN > ie_len) + goto skip_mfp_config; + /* Ready to read capabilities */ + mfp = INFF_MFP_NONE; + rsn_cap = ie[offset] + (ie[offset + 1] << 8); + if (rsn_cap & RSN_CAP_MFPR_MASK) + mfp = INFF_MFP_REQUIRED; + else if (rsn_cap & RSN_CAP_MFPC_MASK) + mfp = INFF_MFP_CAPABLE; + + /* In case of dpp, very low tput is observed if MFPC is set in + * firmmare. Firmware needs to ensure that MFPC is not set when + * MFPR was requested from fmac. However since this change being + * specific to DPP, fmac needs to set wpa_auth prior to mfp, so + * that firmware can use this info to prevent MFPC being set in + * case of dpp. + */ + if (val == WFA_AUTH_DPP) { + inff_dbg(CONN, "setting wpa_auth to 0x%0x\n", val); + err = inff_fil_bsscfg_int_set(netdev_priv(ndev), "wpa_auth", val); + if (err) { + iphy_err(drvr, "could not set wpa_auth (%d)\n", err); + return err; + } + } + inff_fil_bsscfg_int_set(netdev_priv(ndev), "mfp", mfp); + + offset += RSN_CAP_LEN; + if (mfp && (ie_len - offset >= RSN_PMKID_COUNT_LEN)) { + pmkid_count = ie[offset] + (ie[offset + 1] << 8); + offset += RSN_PMKID_COUNT_LEN + (pmkid_count * WLAN_PMKID_LEN); + if (ie_len - offset >= WPA_IE_MIN_OUI_LEN) { + group_mgmt_cs = &ie[offset]; + if (memcmp(group_mgmt_cs, RSN_OUI, TLV_OUI_LEN) == 0) { + inff_fil_bsscfg_data_set(ifp, "bip", + (void *)group_mgmt_cs, + WPA_IE_MIN_OUI_LEN); + } + } + } + +skip_mfp_config: + if (val != WFA_AUTH_DPP) { + inff_dbg(CONN, "setting wpa_auth to 0x%0x\n", val); + err = inff_fil_bsscfg_int_set(netdev_priv(ndev), "wpa_auth", val); + if (err) { + iphy_err(drvr, "could not set wpa_auth (%d)\n", err); + return err; + } + } + + return err; +} + +static s32 +inff_set_sharedkey(struct net_device *ndev, + struct cfg80211_connect_params *sme) +{ + struct inff_if *ifp = netdev_priv(ndev); + struct inff_pub *drvr = ifp->drvr; + struct inff_cfg80211_profile *profile = ndev_to_prof(ndev); + struct inff_cfg80211_security *sec; + struct inff_wsec_key key; + s32 val; + s32 err = 0; + + inff_dbg(CONN, "key len (%d)\n", sme->key_len); + + if (sme->key_len == 0) + return 0; + + sec = &profile->sec; + inff_dbg(CONN, "wpa_versions 0x%x cipher_pairwise 0x%x\n", + sec->wpa_versions, sec->cipher_pairwise); + + if (sec->wpa_versions & (NL80211_WPA_VERSION_1 | NL80211_WPA_VERSION_2 | + NL80211_WPA_VERSION_3)) + return 0; + + if (!(sec->cipher_pairwise & + (WLAN_CIPHER_SUITE_WEP40 | WLAN_CIPHER_SUITE_WEP104))) + return 0; + + memset(&key, 0, sizeof(key)); + key.len = (u32)sme->key_len; + key.index = (u32)sme->key_idx; + if (key.len > sizeof(key.data)) { + iphy_err(drvr, "Too long key length (%u)\n", key.len); + return -EINVAL; + } + memcpy(key.data, sme->key, key.len); + key.flags = INFF_PRIMARY_KEY; + switch (sec->cipher_pairwise) { + case WLAN_CIPHER_SUITE_WEP40: + key.algo = CRYPTO_ALGO_WEP1; + break; + case WLAN_CIPHER_SUITE_WEP104: + key.algo = CRYPTO_ALGO_WEP128; + break; + default: + iphy_err(drvr, "Invalid algorithm (%d)\n", + sme->crypto.ciphers_pairwise[0]); + return -EINVAL; + } + /* Set the new key/index */ + inff_dbg(CONN, "key length (%d) key index (%d) algo (%d)\n", + key.len, key.index, key.algo); + inff_dbg(CONN, "key \"%s\"\n", key.data); + err = send_key_to_dongle(ifp, &key); + if (err) + return err; + + if (sec->auth_type == NL80211_AUTHTYPE_SHARED_KEY) { + inff_dbg(CONN, "set auth_type to shared key\n"); + val = WL_AUTH_SHARED_KEY; /* shared key */ + err = inff_fil_bsscfg_int_set(ifp, "auth", val); + if (err) + iphy_err(drvr, "set auth failed (%d)\n", err); + } + return err; +} + +static +enum nl80211_auth_type inff_war_auth_type(struct inff_if *ifp, + enum nl80211_auth_type type) +{ + if (type == NL80211_AUTHTYPE_AUTOMATIC && + inff_feat_is_quirk_enabled(ifp, INFF_FEAT_QUIRK_AUTO_AUTH)) { + inff_dbg(CONN, "WAR: use OPEN instead of AUTO\n"); + type = NL80211_AUTHTYPE_OPEN_SYSTEM; + } + return type; +} + +static void inff_set_join_pref(struct inff_if *ifp, + struct cfg80211_bss_selection *bss_select) +{ + struct inff_pub *drvr = ifp->drvr; + struct inff_join_pref_params join_pref_params[2]; + enum nl80211_band band; + int err, i = 0; + + join_pref_params[i].len = 2; + join_pref_params[i].rssi_gain = 0; + + if (bss_select->behaviour != NL80211_BSS_SELECT_ATTR_BAND_PREF) + inff_fil_cmd_int_set(ifp, INFF_C_SET_ASSOC_PREFER, WLC_BAND_AUTO); + + switch (bss_select->behaviour) { + case __NL80211_BSS_SELECT_ATTR_INVALID: + inff_c_set_joinpref_default(ifp); + return; + case NL80211_BSS_SELECT_ATTR_BAND_PREF: + join_pref_params[i].type = INFF_JOIN_PREF_BAND; + band = bss_select->param.band_pref; + join_pref_params[i].band = nl80211_band_to_fwil(band); + i++; + break; + case NL80211_BSS_SELECT_ATTR_RSSI_ADJUST: + join_pref_params[i].type = INFF_JOIN_PREF_RSSI_DELTA; + band = bss_select->param.adjust.band; + join_pref_params[i].band = nl80211_band_to_fwil(band); + join_pref_params[i].rssi_gain = bss_select->param.adjust.delta; + i++; + break; + case NL80211_BSS_SELECT_ATTR_RSSI: + default: + break; + } + join_pref_params[i].type = INFF_JOIN_PREF_RSSI; + join_pref_params[i].len = 2; + join_pref_params[i].rssi_gain = 0; + join_pref_params[i].band = 0; + err = inff_fil_iovar_data_set(ifp, "join_pref", join_pref_params, + sizeof(join_pref_params)); + if (err) + iphy_err(drvr, "Set join_pref error (%d)\n", err); +} + +static bool +wl_cfg80211_is_oce_ap(struct inff_if *ifp, + struct wiphy *wiphy, const u8 *bssid_hint) +{ + struct inff_pub *drvr = ifp->drvr; + const struct inff_tlv *ie; + const struct cfg80211_bss_ies *ies; + struct cfg80211_bss *bss; + const u8 *parse = NULL; + u32 len; + + bss = cfg80211_get_bss(wiphy, NULL, bssid_hint, 0, 0, + IEEE80211_BSS_TYPE_ANY, IEEE80211_PRIVACY_ANY); + if (!bss) { + iphy_err(drvr, "Unable to find AP in the cache"); + return false; + } + + if (rcu_access_pointer(bss->ies)) { + ies = rcu_access_pointer(bss->ies); + parse = ies->data; + len = ies->len; + } else { + iphy_err(drvr, "ies is NULL"); + return false; + } + + while ((ie = inff_parse_tlvs(parse, len, WLAN_EID_VENDOR_SPECIFIC))) { + if (wl_cfgoce_is_oce_ie((const u8 *)ie, + (u8 const **)&parse, &len) == true) { + return true; + } + } + inff_dbg(TRACE, "OCE IE NOT found"); + return false; +} + +static s32 +inff_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_connect_params *sme) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_if *ifp = netdev_priv(ndev); + struct inff_cfg80211_profile *profile = &ifp->vif->profile; + struct ieee80211_channel *chan = sme->channel; + struct inff_pub *drvr = ifp->drvr; + struct inff_join_params join_params; + size_t join_params_size; + const struct inff_tlv *rsn_ie; + const struct inff_vs_tlv *wpa_ie; + const void *ie; + u32 ie_len; + struct inff_ext_join_params_le *ext_join_params; + u16 chanspec; + s32 err = 0; + u32 ssid_len; + bool skip_hints = ifp->drvr->settings->fw_ap_select; + + inff_dbg(TRACE, "Enter\n"); + + if (cfg->pfn_enable && cfg->pfn_connection) { + err = inff_fil_cmd_data_set(ifp, + INFF_C_DISASSOC, NULL, 0); + if (err) { + inff_err("INFF_C_DISASSOC error:%d\n", err); + return -1; + } + cfg->pfn_connection = 0; + + /* Disable pfn */ + err = inff_fil_iovar_int_set(ifp, "pfn", 0); + if (err < 0) { + inff_err("pfn disable error:%d\n", err); + } else { + /* clear pfn */ + err = inff_fil_iovar_data_set(ifp, "pfnclear", NULL, 0); + if (err) + inff_err("pfnclear error:%d\n", err); + } + } + + if (!check_vif_up(ifp->vif)) + return -EIO; + + if (!sme->ssid) { + iphy_err(drvr, "Invalid ssid\n"); + return -EOPNOTSUPP; + } + + /* override bssid_hint for oce networks */ + skip_hints = (skip_hints && + wl_cfg80211_is_oce_ap(ifp, wiphy, sme->bssid_hint)); + if (skip_hints) { + /* Let fw choose the best AP */ + inff_dbg(TRACE, "Skipping bssid & channel hint\n"); + } else { + if (sme->channel_hint) + chan = sme->channel_hint; + + if (sme->bssid_hint) + sme->bssid = sme->bssid_hint; + } + + /* FT Cert: Handling the roam request from supplicant for FT roaming */ + if (sme->prev_bssid && sme->bssid && + inff_feat_is_enabled(ifp, INFF_FEAT_FBT) && + wpa_akm_ft(sme->crypto.akm_suites[0])) { + /* Only reassoc IOVAR required for Roam skip additional IOVAR */ + struct inff_assoc_params_le ext_roam_params; + + inff_dbg(CONN, "Trying to REASSOC For FT\n"); + memset(&ext_roam_params, 0, sizeof(ext_roam_params)); + memcpy(&ext_roam_params.bssid, sme->bssid, ETH_ALEN); + set_bit(INFF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state); + + err = inff_fil_cmd_data_set(ifp, INFF_C_REASSOC, + &ext_roam_params, + sizeof(ext_roam_params)); + goto done; + } + + if (ifp->vif == cfg->p2p.bss_idx[P2PAPI_BSSCFG_PRIMARY].vif) { + /* A normal (non P2P) connection request setup. */ + ie = NULL; + ie_len = 0; + /* find the WPA_IE */ + wpa_ie = inff_find_wpaie((u8 *)sme->ie, sme->ie_len); + if (wpa_ie) { + ie = wpa_ie; + ie_len = wpa_ie->len + TLV_HDR_LEN; + } else { + /* find the RSN_IE */ + rsn_ie = inff_parse_tlvs((const u8 *)sme->ie, + sme->ie_len, + WLAN_EID_RSN); + if (rsn_ie) { + ie = rsn_ie; + ie_len = rsn_ie->len + TLV_HDR_LEN; + } + } + inff_fil_iovar_data_set(ifp, "wpaie", ie, ie_len); + } + + err = inff_vif_set_mgmt_ie(ifp->vif, INFF_VNDR_IE_ASSOCREQ_FLAG, + sme->ie, sme->ie_len); + if (err) + iphy_err(drvr, "Set Assoc REQ IE Failed\n"); + else + inff_dbg(TRACE, "Applied Vndr IEs for Assoc request\n"); + + set_bit(INFF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state); + + if (chan) { + cfg->channel = + ieee80211_frequency_to_channel(chan->center_freq); + chanspec = channel_to_chanspec(&cfg->d11inf, chan); + inff_dbg(CONN, "channel=%d, center_req=%d, chanspec=0x%04x\n", + cfg->channel, chan->center_freq, chanspec); + } else { + cfg->channel = 0; + chanspec = 0; + } + + inff_dbg(INFO, "ie (%p), ie_len (%zd)\n", sme->ie, sme->ie_len); + + err = inff_set_wpa_version(ndev, sme); + if (err) { + iphy_err(drvr, "wl_set_wpa_version failed (%d)\n", err); + goto done; + } + + sme->auth_type = inff_war_auth_type(ifp, sme->auth_type); + err = inff_set_auth_type(ndev, sme); + if (err) { + iphy_err(drvr, "wl_set_auth_type failed (%d)\n", err); + goto done; + } + + err = inff_set_wsec_mode(ndev, sme); + if (err) { + iphy_err(drvr, "wl_set_set_cipher failed (%d)\n", err); + goto done; + } + + err = inff_set_key_mgmt(ndev, sme); + if (err) { + iphy_err(drvr, "wl_set_key_mgmt failed (%d)\n", err); + goto done; + } + + err = inff_set_sharedkey(ndev, sme); + if (err) { + iphy_err(drvr, "inff_set_sharedkey failed (%d)\n", err); + goto done; + } + + if (inff_feat_is_enabled(ifp, INFF_FEAT_FWSUP)) { + if (sme->crypto.psk) { + if (profile->use_fwsup != INFF_PROFILE_FWSUP_SAE) { + if (WARN_ON(profile->use_fwsup != + INFF_PROFILE_FWSUP_NONE)) { + err = -EINVAL; + goto done; + } + inff_dbg(INFO, "using PSK offload\n"); + profile->use_fwsup = INFF_PROFILE_FWSUP_PSK; + } + } + + if (profile->use_fwsup != INFF_PROFILE_FWSUP_NONE) { + /* enable firmware supplicant for this interface */ + err = inff_fil_iovar_int_set(ifp, "sup_wpa", 1); + if (err < 0) { + iphy_err(drvr, "failed to enable fw supplicant\n"); + goto done; + } + } else { + err = inff_fil_iovar_int_set(ifp, "sup_wpa", 0); + } + + if (profile->use_fwsup == INFF_PROFILE_FWSUP_PSK) + err = inff_set_pmk(ifp, sme->crypto.psk, + INFF_WSEC_MAX_PSK_LEN); + + /* if upper layer has passed sae_password, + * set it to firmware for the potential transit up roaming use. + */ + if (sme->crypto.sae_pwd && inff_feat_is_enabled(ifp, INFF_FEAT_SAE)) { + /* clean up user-space RSNE */ + if (inff_fil_iovar_data_set(ifp, "wpaie", NULL, 0)) { + iphy_err(drvr, "failed to clean up user-space RSNE\n"); + goto done; + } + err = inff_set_sae_password(ifp, sme->crypto.sae_pwd, + sme->crypto.sae_pwd_len); + if (!err && sme->crypto.psk) + err = inff_set_pmk(ifp, sme->crypto.psk, + INFF_WSEC_MAX_PSK_LEN); + } + if (err) + goto done; + + if (inff_feat_is_enabled(ifp, INFF_FEAT_OWE) && + sme->crypto.akm_suites[0] == WLAN_AKM_SUITE_OWE) { + /* clean up user-space RSNE */ + if (inff_fil_iovar_data_set(ifp, "wpaie", NULL, 0)) { + iphy_err(drvr, "failed to clean up user-space RSNE\n"); + goto done; + } + } + } + /* Join with specific BSSID and cached SSID + * If SSID is zero join based on BSSID only + */ + join_params_size = offsetof(struct inff_ext_join_params_le, assoc_le) + + offsetof(struct inff_assoc_params_le, chanspec_list); + if (cfg->channel) + join_params_size += sizeof(u16); + ext_join_params = kzalloc(sizeof(*ext_join_params), GFP_KERNEL); + if (!ext_join_params) { + err = -ENOMEM; + goto done; + } + ssid_len = min_t(u32, sme->ssid_len, IEEE80211_MAX_SSID_LEN); + ext_join_params->ssid_le.SSID_len = cpu_to_le32(ssid_len); + memcpy(&ext_join_params->ssid_le.SSID, sme->ssid, ssid_len); + if (ssid_len < IEEE80211_MAX_SSID_LEN) + inff_dbg(CONN, "SSID \"%s\", len (%d)\n", + ext_join_params->ssid_le.SSID, ssid_len); + + /* Set up join scan parameters */ + ext_join_params->scan_le.scan_type = -1; + ext_join_params->scan_le.home_time = cpu_to_le32(-1); + + if (sme->bssid) + memcpy(&ext_join_params->assoc_le.bssid, sme->bssid, ETH_ALEN); + else + eth_broadcast_addr(ext_join_params->assoc_le.bssid); + + if (cfg->channel) { + ext_join_params->assoc_le.chanspec_num = cpu_to_le32(1); + ext_join_params->assoc_le.chanspec_list = + cpu_to_le16(chanspec); + + /* Increase dwell time to receive probe response or detect + * beacon from target AP at a noisy air only during connect + * command. + */ + if (INFF_CHSPEC_IS6G(chanspec)) { + ext_join_params->scan_le.active_time = + cpu_to_le32(INFF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS_6E); + ext_join_params->scan_le.passive_time = + cpu_to_le32(INFF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS_6E); + } else { + ext_join_params->scan_le.active_time = + cpu_to_le32(INFF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS); + ext_join_params->scan_le.passive_time = + cpu_to_le32(INFF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS); + } + + /* To sync with presence period of VSDB GO send probe request + * more frequently. Probe request will be stopped when it gets + * probe response from target AP/GO. + */ + ext_join_params->scan_le.nprobes = + cpu_to_le32(INFF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS / + INFF_SCAN_JOIN_PROBE_INTERVAL_MS); + } else { + ext_join_params->scan_le.active_time = cpu_to_le32(-1); + ext_join_params->scan_le.passive_time = cpu_to_le32(-1); + ext_join_params->scan_le.nprobes = cpu_to_le32(-1); + } + + inff_set_join_pref(ifp, &sme->bss_select); + + /* The internal supplicant judges to use assoc or reassoc itself. + * it is not necessary to specify REASSOC + */ + if (sme->prev_bssid && !inff_feat_is_enabled(ifp, INFF_FEAT_FWSUP)) { + inff_dbg(CONN, "Trying to REASSOC\n"); + join_params_size = sizeof(ext_join_params->assoc_le); + err = inff_fil_cmd_data_set(ifp, INFF_C_REASSOC, + &ext_join_params->assoc_le, join_params_size); + } else { + err = inff_fil_bsscfg_data_set(ifp, "join", ext_join_params, + join_params_size); + } + kfree(ext_join_params); + if (!err) + /* This is it. join command worked, we are done */ + goto done; + + /* join command failed, fallback to set ssid */ + memset(&join_params, 0, sizeof(join_params)); + join_params_size = sizeof(join_params.ssid_le); + + memcpy(&join_params.ssid_le.SSID, sme->ssid, ssid_len); + join_params.ssid_le.SSID_len = cpu_to_le32(ssid_len); + + if (sme->bssid) + memcpy(join_params.params_le.bssid, sme->bssid, ETH_ALEN); + else + eth_broadcast_addr(join_params.params_le.bssid); + + if (cfg->channel) { + join_params.params_le.chanspec_list = cpu_to_le16(chanspec); + join_params.params_le.chanspec_num = cpu_to_le32(1); + join_params_size += sizeof(join_params.params_le); + } + err = inff_fil_cmd_data_set(ifp, INFF_C_SET_SSID, + &join_params, join_params_size); + if (err) + iphy_err(drvr, "INFF_C_SET_SSID failed (%d)\n", err); + +done: + if (err) + clear_bit(INFF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state); + inff_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +inff_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *ndev, + u16 reason_code) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_if *ifp = netdev_priv(ndev); + struct inff_cfg80211_profile *profile = &ifp->vif->profile; + struct inff_pub *drvr = cfg->pub; + struct inff_scb_val_le scbval; + s32 err = 0; + + inff_dbg(TRACE, "Enter. Reason code = %d\n", reason_code); + if (!check_vif_up(ifp->vif)) + return -EIO; + + clear_bit(INFF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state); + clear_bit(INFF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state); + clear_bit(INFF_VIF_STATUS_EAP_SUCCESS, &ifp->vif->sme_state); + clear_bit(INFF_VIF_STATUS_ASSOC_SUCCESS, &ifp->vif->sme_state); + cfg80211_disconnected(ndev, reason_code, NULL, 0, true, GFP_KERNEL); + + if (cfg->pfn_enable) { + cfg->pfn_connection = 0; + pfn_send_network_blob_fw(wiphy, &ifp->vif->wdev); + } + + memcpy(&scbval.ea, &profile->bssid, ETH_ALEN); + scbval.val = cpu_to_le32(reason_code); + err = inff_fil_cmd_data_set(ifp, INFF_C_DISASSOC, + &scbval, sizeof(scbval)); + if (err) { + iphy_err(drvr, "error (%d)\n", err); + } else { + if (inff_feat_is_enabled(ifp, INFF_FEAT_TWT)) { + /* Cleanup TWT Session list */ + inff_twt_cleanup_all_sess(ifp); + } + } + + inff_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +inff_cfg80211_set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, + int radio_idx, enum nl80211_tx_power_setting type, + s32 mbm) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct net_device *ndev = cfg_to_ndev(cfg); + struct inff_if *ifp = netdev_priv(ndev); + struct inff_pub *drvr = cfg->pub; + s32 err; + s32 disable; + u32 qdbm = 127; + + inff_dbg(TRACE, "Enter %d %d\n", type, mbm); + if (!check_vif_up(ifp->vif)) + return -EIO; + + switch (type) { + case NL80211_TX_POWER_AUTOMATIC: + break; + case NL80211_TX_POWER_LIMITED: + case NL80211_TX_POWER_FIXED: + if (mbm < 0) { + iphy_err(drvr, "TX_POWER_FIXED - dbm is negative\n"); + err = -EINVAL; + goto done; + } + qdbm = MBM_TO_DBM(4 * mbm); + if (qdbm > 127) + qdbm = 127; + qdbm |= WL_TXPWR_OVERRIDE; + break; + default: + iphy_err(drvr, "Unsupported type %d\n", type); + err = -EINVAL; + goto done; + } + /* Make sure radio is off or on as far as software is concerned */ + disable = WL_RADIO_SW_DISABLE << 16; + err = inff_fil_cmd_int_set(ifp, INFF_C_SET_RADIO, disable); + if (err) + iphy_err(drvr, "WLC_SET_RADIO error (%d)\n", err); + + err = inff_fil_iovar_int_set(ifp, "qtxpower", qdbm); + if (err) + iphy_err(drvr, "qtxpower error (%d)\n", err); + +done: + inff_dbg(TRACE, "Exit %ld (qdbm)\n", qdbm & ~WL_TXPWR_OVERRIDE); + return err; +} + +static s32 +inff_cfg80211_get_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, + int radio_idx, unsigned int link_id, s32 *dbm) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_cfg80211_vif *vif = wdev_to_vif(wdev); + struct inff_pub *drvr = cfg->pub; + s32 qdbm = 0; + s32 err; + + inff_dbg(TRACE, "Enter\n"); + if (!check_vif_up(vif)) + return -EIO; + + err = inff_fil_iovar_int_get(vif->ifp, "qtxpower", &qdbm); + if (err) { + iphy_err(drvr, "error (%d)\n", err); + goto done; + } + *dbm = (qdbm & ~WL_TXPWR_OVERRIDE) / 4; + +done: + inff_dbg(TRACE, "Exit (0x%x %d)\n", qdbm, *dbm); + return err; +} + +static s32 +inff_cfg80211_config_default_key(struct wiphy *wiphy, struct net_device *ndev, + int link_id, u8 key_idx, bool unicast, + bool multicast) +{ + struct inff_if *ifp = netdev_priv(ndev); + struct inff_pub *drvr = ifp->drvr; + u32 index; + u32 wsec; + s32 err = 0; + + inff_dbg(TRACE, "Enter\n"); + inff_dbg(CONN, "key index (%d)\n", key_idx); + if (!check_vif_up(ifp->vif)) + return -EIO; + + err = inff_fil_bsscfg_int_get(ifp, "wsec", &wsec); + if (err) { + iphy_err(drvr, "WLC_GET_WSEC error (%d)\n", err); + goto done; + } + + if (wsec & WEP_ENABLED) { + /* Just select a new current key */ + index = key_idx; + err = inff_fil_cmd_int_set(ifp, + INFF_C_SET_KEY_PRIMARY, index); + if (err) + iphy_err(drvr, "error (%d)\n", err); + } +done: + inff_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +inff_cfg80211_del_key(struct wiphy *wiphy, struct net_device *ndev, + int link_id, u8 key_idx, bool pairwise, + const u8 *mac_addr) +{ + struct inff_if *ifp = netdev_priv(ndev); + struct inff_wsec_key *key; + s32 err; + + inff_dbg(TRACE, "Enter\n"); + inff_dbg(CONN, "key index (%d)\n", key_idx); + + if (!check_vif_up(ifp->vif)) + return -EIO; + + if (key_idx >= INFF_MAX_DEFAULT_KEYS) { + /* we ignore this key index in this case */ + return -EINVAL; + } + + key = &ifp->vif->profile.key[key_idx]; + + if (key->algo == CRYPTO_ALGO_OFF) { + inff_dbg(CONN, "Ignore clearing of (never configured) key\n"); + return -EINVAL; + } + + memset(key, 0, sizeof(*key)); + key->index = (u32)key_idx; + key->flags = INFF_PRIMARY_KEY; + + /* Clear the key/index */ + err = send_key_to_dongle(ifp, key); + + inff_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +inff_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev, + int link_id, u8 key_idx, bool pairwise, + const u8 *mac_addr, struct key_params *params) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_if *ifp = netdev_priv(ndev); + struct inff_pub *drvr = cfg->pub; + struct inff_wsec_key *key; + s32 val; + s32 wsec; + s32 err; + u8 keybuf[8]; + bool ext_key; + u32 algos = 0, mask = 0; + + inff_dbg(TRACE, "Enter\n"); + inff_dbg(CONN, "key index (%d)\n", key_idx); + if (!check_vif_up(ifp->vif)) + return -EIO; + + if (key_idx >= INFF_MAX_DEFAULT_KEYS) { + /* we ignore this key index in this case */ + iphy_err(drvr, "invalid key index (%d)\n", key_idx); + return -EINVAL; + } + + if (params->key_len == 0) + return inff_cfg80211_del_key(wiphy, ndev, -1, key_idx, + pairwise, mac_addr); + + if (params->key_len > sizeof(key->data)) { + iphy_err(drvr, "Too long key length (%u)\n", params->key_len); + return -EINVAL; + } + + ext_key = false; + if (mac_addr && params->cipher != WLAN_CIPHER_SUITE_WEP40 && + params->cipher != WLAN_CIPHER_SUITE_WEP104) { + inff_dbg(TRACE, "Ext key, mac %pM", mac_addr); + ext_key = true; + } + + key = &ifp->vif->profile.key[key_idx]; + memset(key, 0, sizeof(*key)); + if ((ext_key) && (!is_multicast_ether_addr(mac_addr))) + memcpy((char *)&key->ea, (void *)mac_addr, ETH_ALEN); + key->len = params->key_len; + key->index = key_idx; + memcpy(key->data, params->key, key->len); + if (!ext_key) + key->flags = INFF_PRIMARY_KEY; + + if (params->seq && params->seq_len == 6) { + /* rx iv */ + u8 *ivptr; + + ivptr = (u8 *)params->seq; + key->rxiv.hi = (ivptr[5] << 24) | (ivptr[4] << 16) | + (ivptr[3] << 8) | ivptr[2]; + key->rxiv.lo = (ivptr[1] << 8) | ivptr[0]; + key->iv_initialized = true; + } + + switch (params->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + key->algo = CRYPTO_ALGO_WEP1; + val = WEP_ENABLED; + inff_dbg(CONN, "WLAN_CIPHER_SUITE_WEP40\n"); + break; + case WLAN_CIPHER_SUITE_WEP104: + key->algo = CRYPTO_ALGO_WEP128; + val = WEP_ENABLED; + inff_dbg(CONN, "WLAN_CIPHER_SUITE_WEP104\n"); + break; + case WLAN_CIPHER_SUITE_TKIP: + if (!inff_is_apmode(ifp->vif)) { + inff_dbg(CONN, "Swapping RX/TX MIC key\n"); + memcpy(keybuf, &key->data[24], sizeof(keybuf)); + memcpy(&key->data[24], &key->data[16], sizeof(keybuf)); + memcpy(&key->data[16], keybuf, sizeof(keybuf)); + } + key->algo = CRYPTO_ALGO_TKIP; + val = TKIP_ENABLED; + inff_dbg(CONN, "WLAN_CIPHER_SUITE_TKIP\n"); + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + key->algo = CRYPTO_ALGO_AES_CCM; + val = AES_ENABLED; + inff_dbg(CONN, "WLAN_CIPHER_SUITE_AES_CMAC\n"); + break; + case WLAN_CIPHER_SUITE_CCMP: + key->algo = CRYPTO_ALGO_AES_CCM; + val = AES_ENABLED; + inff_dbg(CONN, "WLAN_CIPHER_SUITE_CCMP\n"); + break; + case WLAN_CIPHER_SUITE_GCMP_256: + if (!inff_feat_is_enabled(ifp, INFF_FEAT_GCMP)) { + inff_err("the low layer not support GCMP\n"); + err = -EOPNOTSUPP; + goto done; + } + key->algo = CRYPTO_ALGO_AES_GCM256; + val = AES_ENABLED; + inff_dbg(CONN, "WLAN_CIPHER_SUITE_GCMP_256\n"); + algos = KEY_ALGO_MASK(CRYPTO_ALGO_AES_GCM256); + mask = algos | KEY_ALGO_MASK(CRYPTO_ALGO_AES_CCM); + break; + case WLAN_CIPHER_SUITE_BIP_GMAC_256: + if (!inff_feat_is_enabled(ifp, INFF_FEAT_GCMP)) { + inff_err("the low layer not support GCMP\n"); + err = -EOPNOTSUPP; + goto done; + } + key->algo = CRYPTO_ALGO_BIP_GMAC256; + val = AES_ENABLED; + algos = KEY_ALGO_MASK(CRYPTO_ALGO_BIP_GMAC256); + mask = algos | KEY_ALGO_MASK(CRYPTO_ALGO_AES_CCM); + inff_dbg(CONN, "WLAN_CIPHER_SUITE_BIP_GMAC_256\n"); + break; + default: + iphy_err(drvr, "Invalid cipher (0x%x)\n", params->cipher); + err = -EINVAL; + goto done; + } + + err = send_key_to_dongle(ifp, key); + if (ext_key || err) + goto done; + + err = inff_fil_bsscfg_int_get(ifp, "wsec", &wsec); + if (err) { + iphy_err(drvr, "get wsec error (%d)\n", err); + goto done; + } + wsec |= val; + err = inff_fil_bsscfg_int_set(ifp, "wsec", wsec); + if (err) { + iphy_err(drvr, "set wsec error (%d)\n", err); + goto done; + } + + if (inff_feat_is_enabled(ifp, INFF_FEAT_GCMP)) { + inff_dbg(CONN, + "set_wsdec_info algos (0x%x) mask (0x%x)\n", + algos, mask); + err = wl_set_wsec_info_algos(ifp, algos, mask); + if (err) { + inff_err("set wsec_info error (%d)\n", err); + return err; + } + } + +done: + inff_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +inff_cfg80211_get_key(struct wiphy *wiphy, struct net_device *ndev, + int link_id, u8 key_idx, bool pairwise, + const u8 *mac_addr, void *cookie, + void (*callback)(void *cookie, + struct key_params *params)) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct key_params params; + struct inff_if *ifp = netdev_priv(ndev); + struct inff_cfg80211_profile *profile = &ifp->vif->profile; + struct inff_pub *drvr = cfg->pub; + struct inff_cfg80211_security *sec; + s32 wsec; + s32 err = 0; + + inff_dbg(TRACE, "Enter\n"); + inff_dbg(CONN, "key index (%d)\n", key_idx); + if (!check_vif_up(ifp->vif)) + return -EIO; + + memset(¶ms, 0, sizeof(params)); + + err = inff_fil_bsscfg_int_get(ifp, "wsec", &wsec); + if (err) { + iphy_err(drvr, "WLC_GET_WSEC error (%d)\n", err); + /* Ignore this error, may happen during DISASSOC */ + err = -EAGAIN; + goto done; + } + if (wsec & WEP_ENABLED) { + sec = &profile->sec; + if (sec->cipher_pairwise & WLAN_CIPHER_SUITE_WEP40) { + params.cipher = WLAN_CIPHER_SUITE_WEP40; + inff_dbg(CONN, "WLAN_CIPHER_SUITE_WEP40\n"); + } else if (sec->cipher_pairwise & WLAN_CIPHER_SUITE_WEP104) { + params.cipher = WLAN_CIPHER_SUITE_WEP104; + inff_dbg(CONN, "WLAN_CIPHER_SUITE_WEP104\n"); + } + } else if (wsec & TKIP_ENABLED) { + params.cipher = WLAN_CIPHER_SUITE_TKIP; + inff_dbg(CONN, "WLAN_CIPHER_SUITE_TKIP\n"); + } else if (wsec & AES_ENABLED) { + params.cipher = WLAN_CIPHER_SUITE_AES_CMAC; + inff_dbg(CONN, "WLAN_CIPHER_SUITE_AES_CMAC\n"); + } else { + iphy_err(drvr, "Invalid algo (0x%x)\n", wsec); + err = -EINVAL; + goto done; + } + callback(cookie, ¶ms); + +done: + inff_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +inff_cfg80211_config_default_mgmt_key(struct wiphy *wiphy, + struct net_device *ndev, int link_id, + u8 key_idx) +{ + struct inff_if *ifp = netdev_priv(ndev); + + inff_dbg(TRACE, "Enter key_idx %d\n", key_idx); + + if (inff_feat_is_enabled(ifp, INFF_FEAT_MFP)) + return 0; + + inff_dbg(INFO, "Not supported\n"); + + return -EOPNOTSUPP; +} + +static void inff_convert_sta_flags(u32 fw_sta_flags, struct station_info *si) +{ + struct nl80211_sta_flag_update *sfu; + + inff_dbg(TRACE, "flags %08x\n", fw_sta_flags); + si->filled |= BIT_ULL(NL80211_STA_INFO_STA_FLAGS); + sfu = &si->sta_flags; + sfu->mask = BIT(NL80211_STA_FLAG_WME) | + BIT(NL80211_STA_FLAG_AUTHENTICATED) | + BIT(NL80211_STA_FLAG_ASSOCIATED) | + BIT(NL80211_STA_FLAG_AUTHORIZED); + if (fw_sta_flags & INFF_STA_WME) + sfu->set |= BIT(NL80211_STA_FLAG_WME); + if (fw_sta_flags & INFF_STA_AUTHE) + sfu->set |= BIT(NL80211_STA_FLAG_AUTHENTICATED); + if (fw_sta_flags & INFF_STA_ASSOC) + sfu->set |= BIT(NL80211_STA_FLAG_ASSOCIATED); + if (fw_sta_flags & INFF_STA_AUTHO) + sfu->set |= BIT(NL80211_STA_FLAG_AUTHORIZED); +} + +static void inff_fill_bss_param(struct inff_if *ifp, struct station_info *si) +{ + struct inff_pub *drvr = ifp->drvr; + struct { + __le32 len; + struct inff_bss_info_le bss_le; + } *buf; + u16 capability; + int err; + + buf = kzalloc(WL_BSS_INFO_MAX, GFP_KERNEL); + if (!buf) + return; + + buf->len = cpu_to_le32(WL_BSS_INFO_MAX); + err = inff_fil_cmd_data_get(ifp, INFF_C_GET_BSS_INFO, buf, + WL_BSS_INFO_MAX); + if (err) { + iphy_err(drvr, "Failed to get bss info (%d)\n", err); + goto out_kfree; + } + si->filled |= BIT_ULL(NL80211_STA_INFO_BSS_PARAM); + si->bss_param.beacon_interval = le16_to_cpu(buf->bss_le.beacon_period); + si->bss_param.dtim_period = buf->bss_le.dtim_period; + capability = le16_to_cpu(buf->bss_le.capability); + if (capability & IEEE80211_HT_STBC_PARAM_DUAL_CTS_PROT) + si->bss_param.flags |= BSS_PARAM_FLAGS_CTS_PROT; + if (capability & WLAN_CAPABILITY_SHORT_PREAMBLE) + si->bss_param.flags |= BSS_PARAM_FLAGS_SHORT_PREAMBLE; + if (capability & WLAN_CAPABILITY_SHORT_SLOT_TIME) + si->bss_param.flags |= BSS_PARAM_FLAGS_SHORT_SLOT_TIME; + +out_kfree: + kfree(buf); +} + +static s32 +inff_cfg80211_get_station_ibss(struct inff_if *ifp, + struct station_info *sinfo, + const u8 *mac) +{ + struct inff_pub *drvr = ifp->drvr; + struct inff_scb_val_le scbval; + struct inff_pktcnt_le pktcnt; + s32 err; + u32 rate; + u32 rssi; + + /* Get the current tx rate */ + err = inff_fil_cmd_int_get(ifp, INFF_C_GET_RATE, &rate); + if (err < 0) { + iphy_err(drvr, "INFF_C_GET_RATE error (%d)\n", err); + return err; + } + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE); + sinfo->txrate.legacy = rate * 5; + + memset(&scbval, 0, sizeof(scbval)); + memcpy(&scbval.ea[0], mac, ETH_ALEN); + err = inff_fil_cmd_data_get(ifp, INFF_C_GET_RSSI, &scbval, + sizeof(scbval)); + if (err) { + iphy_err(drvr, "INFF_C_GET_RSSI error (%d)\n", err); + return err; + } + rssi = le32_to_cpu(scbval.val); + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_SIGNAL); + sinfo->signal = rssi; + + err = inff_fil_cmd_data_get(ifp, INFF_C_GET_GET_PKTCNTS, &pktcnt, + sizeof(pktcnt)); + if (err) { + iphy_err(drvr, "INFF_C_GET_GET_PKTCNTS error (%d)\n", err); + return err; + } + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_PACKETS) | + BIT_ULL(NL80211_STA_INFO_RX_DROP_MISC) | + BIT_ULL(NL80211_STA_INFO_TX_PACKETS) | + BIT_ULL(NL80211_STA_INFO_TX_FAILED); + sinfo->rx_packets = le32_to_cpu(pktcnt.rx_good_pkt); + sinfo->rx_dropped_misc = le32_to_cpu(pktcnt.rx_bad_pkt); + sinfo->tx_packets = le32_to_cpu(pktcnt.tx_good_pkt); + sinfo->tx_failed = le32_to_cpu(pktcnt.tx_bad_pkt); + + return 0; +} + +static s32 +inff_cfg80211_get_station(struct wiphy *wiphy, struct net_device *ndev, + const u8 *mac, struct station_info *sinfo) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_if *ifp = netdev_priv(ndev); + struct inff_pub *drvr = cfg->pub; + struct inff_scb_val_le scb_val; + s32 err = 0; + struct inff_sta_info_le sta_info_le; + u32 sta_flags; + u32 is_tdls_peer; + s32 total_rssi_avg = 0; + s32 total_rssi = 0; + s32 count_rssi = 0; + int rssi; + u32 i; + + inff_dbg(TRACE, "Enter, MAC %pM\n", mac); + if (!check_vif_up(ifp->vif)) + return -EIO; + + if (inff_is_ibssmode(ifp->vif)) + return inff_cfg80211_get_station_ibss(ifp, sinfo, mac); + + memset(&sta_info_le, 0, sizeof(sta_info_le)); + memcpy(&sta_info_le, mac, ETH_ALEN); + err = inff_fil_iovar_data_get(ifp, "tdls_sta_info", + &sta_info_le, + sizeof(sta_info_le)); + is_tdls_peer = !err; + if (err) { + err = inff_fil_iovar_data_get(ifp, "sta_info", + &sta_info_le, + sizeof(sta_info_le)); + if (err < 0) { + iphy_err(drvr, "GET STA INFO failed, %d\n", err); + goto done; + } + } + inff_dbg(TRACE, "version %d\n", le16_to_cpu(sta_info_le.ver)); + sinfo->filled = BIT_ULL(NL80211_STA_INFO_INACTIVE_TIME); + sinfo->inactive_time = le32_to_cpu(sta_info_le.idle) * 1000; + sta_flags = le32_to_cpu(sta_info_le.flags); + inff_convert_sta_flags(sta_flags, sinfo); + sinfo->sta_flags.mask |= BIT(NL80211_STA_FLAG_TDLS_PEER); + if (is_tdls_peer) + sinfo->sta_flags.set |= BIT(NL80211_STA_FLAG_TDLS_PEER); + else + sinfo->sta_flags.set &= ~BIT(NL80211_STA_FLAG_TDLS_PEER); + if (sta_flags & INFF_STA_ASSOC) { + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_CONNECTED_TIME); + sinfo->connected_time = le32_to_cpu(sta_info_le.in); + inff_fill_bss_param(ifp, sinfo); + } + if (sta_flags & INFF_STA_SCBSTATS) { + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_FAILED); + sinfo->tx_failed = le32_to_cpu(sta_info_le.tx_failures); + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_PACKETS); + sinfo->tx_packets = le32_to_cpu(sta_info_le.tx_pkts); + sinfo->tx_packets += le32_to_cpu(sta_info_le.tx_mcast_pkts); + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_PACKETS); + sinfo->rx_packets = le32_to_cpu(sta_info_le.rx_ucast_pkts); + sinfo->rx_packets += le32_to_cpu(sta_info_le.rx_mcast_pkts); + if (sinfo->tx_packets) { + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE); + sinfo->txrate.legacy = + le32_to_cpu(sta_info_le.tx_rate) / 100; + } + if (sinfo->rx_packets) { + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_BITRATE); + sinfo->rxrate.legacy = + le32_to_cpu(sta_info_le.rx_rate) / 100; + } + if (le16_to_cpu(sta_info_le.ver) >= 4) { + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BYTES); + sinfo->tx_bytes = le64_to_cpu(sta_info_le.tx_tot_bytes); + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_BYTES); + sinfo->rx_bytes = le64_to_cpu(sta_info_le.rx_tot_bytes); + } + for (i = 0; i < INFF_ANT_MAX; i++) { + if (sta_info_le.rssi[i] == 0 || + sta_info_le.rx_lastpkt_rssi[i] == 0) + continue; + sinfo->chains |= BIT(count_rssi); + sinfo->chain_signal[count_rssi] = + sta_info_le.rx_lastpkt_rssi[i]; + sinfo->chain_signal_avg[count_rssi] = + sta_info_le.rssi[i]; + total_rssi += sta_info_le.rx_lastpkt_rssi[i]; + total_rssi_avg += sta_info_le.rssi[i]; + count_rssi++; + } + if (count_rssi) { + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_SIGNAL); + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_SIGNAL_AVG); + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL); + sinfo->filled |= + BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL_AVG); + sinfo->signal = total_rssi / count_rssi; + sinfo->signal_avg = total_rssi_avg / count_rssi; + } else if (test_bit(INFF_VIF_STATUS_CONNECTED, + &ifp->vif->sme_state)) { + memset(&scb_val, 0, sizeof(scb_val)); + memcpy(&scb_val.ea[0], mac, ETH_ALEN); + err = inff_fil_cmd_data_get(ifp, INFF_C_GET_RSSI, + &scb_val, sizeof(scb_val)); + if (err) { + iphy_err(drvr, "Could not get rssi (%d)\n", + err); + goto done; + } else { + rssi = le32_to_cpu(scb_val.val); + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_SIGNAL); + sinfo->signal = rssi; + inff_dbg(CONN, "RSSI %d dBm\n", rssi); + } + } + } +done: + inff_dbg(TRACE, "Exit\n"); + return err; +} + +static int +inff_cfg80211_dump_station(struct wiphy *wiphy, struct net_device *ndev, + int idx, u8 *mac, struct station_info *sinfo) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_if *ifp = netdev_priv(ndev); + struct inff_pub *drvr = cfg->pub; + s32 err; + + inff_dbg(TRACE, "Enter, idx %d\n", idx); + + if (idx == 0) { + cfg->assoclist.count = cpu_to_le32(INFF_MAX_ASSOCLIST); + err = inff_fil_cmd_data_get(ifp, INFF_C_GET_ASSOCLIST, + &cfg->assoclist, + sizeof(cfg->assoclist)); + if (err) { + /* GET_ASSOCLIST unsupported by firmware of older chips */ + if (err == -EBADE) + iphy_info_once(drvr, "INFF_C_GET_ASSOCLIST unsupported\n"); + else + iphy_err(drvr, "INFF_C_GET_ASSOCLIST failed, err=%d\n", + err); + + cfg->assoclist.count = 0; + return -EOPNOTSUPP; + } + } + if (idx < le32_to_cpu(cfg->assoclist.count)) { + memcpy(mac, cfg->assoclist.mac[idx], ETH_ALEN); + return inff_cfg80211_get_station(wiphy, ndev, mac, sinfo); + } + return -ENOENT; +} + +static s32 +inff_cfg80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *ndev, + bool enabled, s32 timeout) +{ + s32 pm; + s32 err = 0; + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_if *ifp = netdev_priv(ndev); + struct inff_pub *drvr = cfg->pub; + + inff_dbg(TRACE, "Enter\n"); + + /* + * Powersave enable/disable request is coming from the + * cfg80211 even before the interface is up. In that + * scenario, driver will be storing the power save + * preference in cfg struct to apply this to + * FW later while initializing the dongle + */ + cfg->pwr_save = enabled; + if (!check_vif_up(ifp->vif)) { + inff_dbg(INFO, "Device is not ready, storing the value in cfg_info struct\n"); + goto done; + } + + pm = enabled ? ifp->drvr->settings->default_pm : PM_OFF; + /* Do not enable the power save after assoc if it is a p2p interface */ + if (ifp->vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT) { + inff_dbg(INFO, "Do not enable power save for P2P clients\n"); + pm = PM_OFF; + } + + inff_dbg(INFO, "power save %s\n", (pm ? "enabled" : "disabled")); + + err = inff_fil_cmd_int_set(ifp, INFF_C_SET_PM, pm); + if (err) { + if (err == -ENODEV) + iphy_err(drvr, "net_device is not ready yet\n"); + else + iphy_err(drvr, "error (%d)\n", err); + } + +done: + inff_dbg(TRACE, "Exit\n"); + return err; +} + +s32 inff_inform_single_bss(struct inff_cfg80211_info *cfg, + struct inff_bss_info_le *bi) +{ + struct wiphy *wiphy = cfg_to_wiphy(cfg); + struct inff_pub *drvr = cfg->pub; + struct cfg80211_bss *bss; + enum nl80211_band band; + struct inff_chan ch; + u16 channel; + u32 freq; + u16 notify_capability; + u16 notify_interval; + u8 *notify_ie; + size_t notify_ielen; + struct cfg80211_inform_bss bss_data = {}; + const struct inff_tlv *ssid = NULL; + + if (le32_to_cpu(bi->length) > WL_BSS_INFO_MAX) { + iphy_err(drvr, "Bss info is larger than buffer. Discarding\n"); + return -EINVAL; + } + + ch.chspec = le16_to_cpu(bi->chanspec); + cfg->d11inf.decchspec(&ch); + + if (!bi->ctl_ch) + bi->ctl_ch = ch.control_ch_num; + + channel = bi->ctl_ch; + band = inff_d11_chan_band_to_nl80211(ch.band); + + freq = ieee80211_channel_to_frequency(channel, band); + if (!freq) + return -EINVAL; + + bss_data.chan = ieee80211_get_channel(wiphy, freq); + if (!bss_data.chan) + return -EINVAL; + + bss_data.boottime_ns = ktime_to_ns(ktime_get_boottime()); + + notify_capability = le16_to_cpu(bi->capability); + notify_interval = le16_to_cpu(bi->beacon_period); + notify_ie = (u8 *)bi + le16_to_cpu(bi->ie_offset); + notify_ielen = le32_to_cpu(bi->ie_length); + bss_data.signal = (s16)le16_to_cpu(bi->RSSI) * 100; + + ssid = inff_parse_tlvs(notify_ie, notify_ielen, WLAN_EID_SSID); + if (ssid && ssid->data[0] == '\0' && ssid->len == bi->SSID_len) { + /* Update SSID for hidden AP */ + memcpy((u8 *)ssid->data, bi->SSID, bi->SSID_len); + } + + inff_dbg(CONN, "bssid: %pM\n", bi->BSSID); + inff_dbg(CONN, "Channel: %d(%d)\n", channel, freq); + inff_dbg(CONN, "Capability: %X\n", notify_capability); + inff_dbg(CONN, "Beacon interval: %d\n", notify_interval); + inff_dbg(CONN, "Signal: %d\n", bss_data.signal); + + bss = cfg80211_inform_bss_data(wiphy, &bss_data, + CFG80211_BSS_FTYPE_PRESP, + (const u8 *)bi->BSSID, + 0, notify_capability, + notify_interval, notify_ie, + notify_ielen, GFP_KERNEL); + + if (!bss) + return -ENOMEM; + + cfg80211_put_bss(wiphy, bss); + + return 0; +} + +static struct inff_bss_info_le * +next_bss_le(struct inff_scan_results *list, struct inff_bss_info_le *bss) +{ + if (!bss) + return list->bss_info_le; + return (struct inff_bss_info_le *)((unsigned long)bss + + le32_to_cpu(bss->length)); +} + +s32 inff_inform_bss(struct inff_cfg80211_info *cfg) +{ + struct inff_pub *drvr = cfg->pub; + struct inff_scan_results *bss_list; + struct inff_bss_info_le *bi = NULL; /* must be initialized */ + s32 err = 0; + int i; + + bss_list = (struct inff_scan_results *)cfg->escan_info.escan_buf; + if (bss_list->count != 0 && + bss_list->version != INFF_BSS_INFO_VERSION) { + iphy_err(drvr, "Version %d != WL_BSS_INFO_VERSION\n", + bss_list->version); + return -EOPNOTSUPP; + } + inff_dbg(SCAN, "scanned AP count (%d)\n", bss_list->count); + for (i = 0; i < bss_list->count; i++) { + bi = next_bss_le(bss_list, bi); + err = inff_inform_single_bss(cfg, bi); + if (err) + break; + } + return err; +} + +s32 inff_inform_ibss(struct inff_cfg80211_info *cfg, + struct net_device *ndev, const u8 *bssid) +{ + struct wiphy *wiphy = cfg_to_wiphy(cfg); + struct inff_pub *drvr = cfg->pub; + struct ieee80211_channel *notify_channel; + struct inff_bss_info_le *bi = NULL; + struct ieee80211_supported_band *band; + struct cfg80211_bss *bss; + struct inff_chan ch; + u8 *buf = NULL; + s32 err = 0; + u32 freq; + u16 notify_capability; + u16 notify_interval; + u8 *notify_ie; + size_t notify_ielen; + s32 notify_signal; + + inff_dbg(TRACE, "Enter\n"); + + buf = kzalloc(WL_BSS_INFO_MAX, GFP_KERNEL); + if (!buf) { + err = -ENOMEM; + goto cleanup; + } + + *(__le32 *)buf = cpu_to_le32(WL_BSS_INFO_MAX); + + err = inff_fil_cmd_data_get(netdev_priv(ndev), INFF_C_GET_BSS_INFO, + buf, WL_BSS_INFO_MAX); + if (err) { + iphy_err(drvr, "WLC_GET_BSS_INFO failed: %d\n", err); + goto cleanup; + } + + bi = (struct inff_bss_info_le *)(buf + 4); + + ch.chspec = le16_to_cpu(bi->chanspec); + cfg->d11inf.decchspec(&ch); + + band = wiphy->bands[inff_d11_chan_band_to_nl80211(ch.band)]; + freq = ieee80211_channel_to_frequency(ch.control_ch_num, band->band); + if (!freq) { + err = -EINVAL; + goto cleanup; + } + cfg->channel = freq; + notify_channel = ieee80211_get_channel(wiphy, freq); + if (!notify_channel) { + err = -EINVAL; + goto cleanup; + } + + notify_capability = le16_to_cpu(bi->capability); + notify_interval = le16_to_cpu(bi->beacon_period); + notify_ie = (u8 *)bi + le16_to_cpu(bi->ie_offset); + notify_ielen = le32_to_cpu(bi->ie_length); + notify_signal = (s16)le16_to_cpu(bi->RSSI) * 100; + + inff_dbg(CONN, "channel: %d(%d)\n", ch.control_ch_num, freq); + inff_dbg(CONN, "capability: %X\n", notify_capability); + inff_dbg(CONN, "beacon interval: %d\n", notify_interval); + inff_dbg(CONN, "signal: %d\n", notify_signal); + + bss = cfg80211_inform_bss(wiphy, notify_channel, + CFG80211_BSS_FTYPE_UNKNOWN, bssid, 0, + notify_capability, notify_interval, + notify_ie, notify_ielen, notify_signal, + GFP_KERNEL); + + if (!bss) { + err = -ENOMEM; + goto cleanup; + } + + cfg80211_put_bss(wiphy, bss); + +cleanup: + + kfree(buf); + + inff_dbg(TRACE, "Exit\n"); + + return err; +} + +static const struct inff_bss_info_le * +inff_update_bss_info(struct inff_cfg80211_info *cfg, struct inff_if *ifp) +{ + struct inff_pub *drvr = cfg->pub; + struct inff_bss_info_le *bi = NULL; + s32 err = 0; + u8 null_mac[6] = {0}; + + inff_dbg(TRACE, "Enter\n"); + if (inff_is_ibssmode(ifp->vif)) + return NULL; + + *(__le32 *)cfg->extra_buf = cpu_to_le32(WL_EXTRA_BUF_MAX); + err = inff_fil_cmd_data_get(ifp, INFF_C_GET_BSS_INFO, + cfg->extra_buf, WL_EXTRA_BUF_MAX); + if (err) { + iphy_err(drvr, "Could not get bss info %d\n", err); + goto update_bss_info_out; + } + bi = (struct inff_bss_info_le *)(cfg->extra_buf + 4); + + if (!memcmp(null_mac, bi->BSSID, ETH_ALEN)) { + iphy_err(drvr, "NULL mac, don't update bss\n"); + goto update_bss_info_out; + } + + err = inff_inform_single_bss(cfg, bi); + + inff_dbg(TRACE, "Exit"); + return bi; + +update_bss_info_out: + inff_dbg(TRACE, "Exit"); + return NULL; +} + +s32 +inff_compare_update_same_bss(struct inff_cfg80211_info *cfg, + struct inff_bss_info_le *bss, + struct inff_bss_info_le *bss_info_le) +{ + struct inff_chan ch_bss, ch_bss_info_le; + + ch_bss.chspec = le16_to_cpu(bss->chanspec); + cfg->d11inf.decchspec(&ch_bss); + ch_bss_info_le.chspec = le16_to_cpu(bss_info_le->chanspec); + cfg->d11inf.decchspec(&ch_bss_info_le); + + if (!memcmp(&bss_info_le->BSSID, &bss->BSSID, ETH_ALEN) && + ch_bss.band == ch_bss_info_le.band && + bss_info_le->SSID_len == bss->SSID_len && + !memcmp(bss_info_le->SSID, bss->SSID, bss_info_le->SSID_len)) { + if ((bss->flags & INFF_BSS_RSSI_ON_CHANNEL) == + (bss_info_le->flags & INFF_BSS_RSSI_ON_CHANNEL)) { + s16 bss_rssi = le16_to_cpu(bss->RSSI); + s16 bss_info_rssi = le16_to_cpu(bss_info_le->RSSI); + + /* preserve max RSSI if the measurements are + * both on-channel or both off-channel + */ + if (bss_info_rssi > bss_rssi) + bss->RSSI = bss_info_le->RSSI; + } else if ((bss->flags & INFF_BSS_RSSI_ON_CHANNEL) && + (bss_info_le->flags & INFF_BSS_RSSI_ON_CHANNEL) == 0) { + /* preserve the on-channel rssi measurement + * if the new measurement is off channel + */ + bss->RSSI = bss_info_le->RSSI; + bss->flags |= INFF_BSS_RSSI_ON_CHANNEL; + } + return 1; + } + return 0; +} + +struct inff_pno_net_info_le * +inff_get_netinfo_array(struct inff_pno_scanresults_le *pfn_v1) +{ + struct inff_pno_scanresults_v2_le *pfn_v2; + struct inff_pno_net_info_le *netinfo; + + switch (pfn_v1->version) { + default: + WARN_ON(1); + fallthrough; + case cpu_to_le32(1): + netinfo = (struct inff_pno_net_info_le *)(pfn_v1 + 1); + break; + case cpu_to_le32(2): + pfn_v2 = (struct inff_pno_scanresults_v2_le *)pfn_v1; + netinfo = (struct inff_pno_net_info_le *)(pfn_v2 + 1); + break; + } + + return netinfo; +} + +static s32 inff_config_wowl_pattern(struct inff_if *ifp, u8 cmd[4], + u8 *pattern, u32 patternsize, u8 *mask, + u32 packet_offset) +{ + struct inff_fil_wowl_pattern_le *filter; + u32 masksize; + u32 patternoffset; + u8 *buf; + u32 bufsize; + s32 ret; + + masksize = (patternsize + 7) / 8; + patternoffset = sizeof(*filter) - sizeof(filter->cmd) + masksize; + + bufsize = sizeof(*filter) + patternsize + masksize; + buf = kzalloc(bufsize, GFP_KERNEL); + if (!buf) + return -ENOMEM; + filter = (struct inff_fil_wowl_pattern_le *)buf; + + memcpy(filter->cmd, cmd, 4); + filter->masksize = cpu_to_le32(masksize); + filter->offset = cpu_to_le32(packet_offset); + filter->patternoffset = cpu_to_le32(patternoffset); + filter->patternsize = cpu_to_le32(patternsize); + filter->type = cpu_to_le32(INFF_WOWL_PATTERN_TYPE_BITMAP); + + if ((mask) && (masksize)) + memcpy(buf + sizeof(*filter), mask, masksize); + if ((pattern) && (patternsize)) + memcpy(buf + sizeof(*filter) + masksize, pattern, patternsize); + + ret = inff_fil_iovar_data_set(ifp, "wowl_pattern", buf, bufsize); + + kfree(buf); + return ret; +} + +static s32 +inff_wowl_nd_results(struct inff_if *ifp, const struct inff_event_msg *e, + void *data) +{ + struct inff_pub *drvr = ifp->drvr; + struct inff_cfg80211_info *cfg = drvr->config; + struct inff_pno_scanresults_le *pfn_result; + struct inff_pno_net_info_le *netinfo; + + inff_dbg(SCAN, "Enter\n"); + + if (e->datalen < (sizeof(*pfn_result) + sizeof(*netinfo))) { + inff_dbg(SCAN, "Event data too small. Ignore\n"); + return 0; + } + + pfn_result = (struct inff_pno_scanresults_le *)data; + + if (e->event_code == INFF_E_PFN_NET_LOST) { + inff_dbg(SCAN, "PFN NET LOST event. Ignore\n"); + return 0; + } + + if (le32_to_cpu(pfn_result->count) < 1) { + iphy_err(drvr, "Invalid result count, expected 1 (%d)\n", + le32_to_cpu(pfn_result->count)); + return -EINVAL; + } + + netinfo = inff_get_netinfo_array(pfn_result); + if (netinfo->SSID_len > IEEE80211_MAX_SSID_LEN) + netinfo->SSID_len = IEEE80211_MAX_SSID_LEN; + memcpy(cfg->wowl.nd->ssid.ssid, netinfo->SSID, netinfo->SSID_len); + cfg->wowl.nd->ssid.ssid_len = netinfo->SSID_len; + cfg->wowl.nd->n_channels = 1; + cfg->wowl.nd->channels[0] = + ieee80211_channel_to_frequency(netinfo->channel, + netinfo->channel <= CH_MAX_2G_CHANNEL ? + NL80211_BAND_2GHZ : NL80211_BAND_5GHZ); + cfg->wowl.nd_info->n_matches = 1; + cfg->wowl.nd_info->matches[0] = cfg->wowl.nd; + + /* Inform (the resume task) that the net detect information was recvd */ + cfg->wowl.nd_data_completed = true; + wake_up(&cfg->wowl.nd_data_wait); + + return 0; +} + +#ifdef CONFIG_PM + +static void inff_report_wowl_wakeind(struct wiphy *wiphy, struct inff_if *ifp) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_pub *drvr = cfg->pub; + struct inff_wowl_wakeind_le wake_ind_le; + struct cfg80211_wowlan_wakeup wakeup_data; + struct cfg80211_wowlan_wakeup *wakeup; + u32 wakeind; + s32 err; + long time_left; + + err = inff_fil_iovar_data_get(ifp, "wowl_wakeind", &wake_ind_le, + sizeof(wake_ind_le)); + if (err) { + iphy_err(drvr, "Get wowl_wakeind failed, err = %d\n", err); + return; + } + + wakeind = le32_to_cpu(wake_ind_le.ucode_wakeind); + if (wakeind & (INFF_WOWL_MAGIC | INFF_WOWL_DIS | INFF_WOWL_BCN | + INFF_WOWL_RETR | INFF_WOWL_NET | + INFF_WOWL_PFN_FOUND)) { + wakeup = &wakeup_data; + memset(&wakeup_data, 0, sizeof(wakeup_data)); + wakeup_data.pattern_idx = -1; + + if (wakeind & INFF_WOWL_MAGIC) { + inff_dbg(INFO, "WOWL Wake indicator: INFF_WOWL_MAGIC\n"); + wakeup_data.magic_pkt = true; + } + if (wakeind & INFF_WOWL_DIS) { + inff_dbg(INFO, "WOWL Wake indicator: INFF_WOWL_DIS\n"); + wakeup_data.disconnect = true; + } + if (wakeind & INFF_WOWL_BCN) { + inff_dbg(INFO, "WOWL Wake indicator: INFF_WOWL_BCN\n"); + wakeup_data.disconnect = true; + } + if (wakeind & INFF_WOWL_RETR) { + inff_dbg(INFO, "WOWL Wake indicator: INFF_WOWL_RETR\n"); + wakeup_data.disconnect = true; + } + if (wakeind & INFF_WOWL_NET) { + inff_dbg(INFO, "WOWL Wake indicator: INFF_WOWL_NET\n"); + /* For now always map to pattern 0, no API to get + * correct information available at the moment. + */ + wakeup_data.pattern_idx = 0; + } + if (wakeind & INFF_WOWL_PFN_FOUND) { + inff_dbg(INFO, "WOWL Wake indicator: INFF_WOWL_PFN_FOUND\n"); + time_left = wait_event_timeout(cfg->wowl.nd_data_wait, + cfg->wowl.nd_data_completed, + INFF_ND_INFO_TIMEOUT); + if (!time_left) + iphy_err(drvr, "No result for wowl net detect\n"); + else + wakeup_data.net_detect = cfg->wowl.nd_info; + } + if (wakeind & INFF_WOWL_GTK_FAILURE) { + inff_dbg(INFO, "WOWL Wake indicator: INFF_WOWL_GTK_FAILURE\n"); + wakeup_data.gtk_rekey_failure = true; + } + } else { + wakeup = NULL; + } + cfg80211_report_wowlan_wakeup(&ifp->vif->wdev, wakeup, GFP_KERNEL); +} + +#else + +static void inff_report_wowl_wakeind(struct wiphy *wiphy, struct inff_if *ifp) +{ +} + +#endif /* CONFIG_PM */ + +static s32 inff_cfg80211_resume(struct wiphy *wiphy) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct net_device *ndev = cfg_to_ndev(cfg); + struct inff_if *ifp = netdev_priv(ndev); + struct inff_pub *drvr = ifp->drvr; + struct inff_bus *bus_if = drvr->bus_if; + struct inff_cfg80211_info *config = drvr->config; + int retry = INFF_PM_WAIT_MAXRETRY; + s32 power_mode; + + power_mode = cfg->pwr_save ? ifp->drvr->settings->default_pm : PM_OFF; + + inff_dbg(TRACE, "Enter\n"); + + config->pm_state = INFF_CFG80211_PM_STATE_RESUMING; + + if (cfg->wowl.active) { + /* wait for bus resumed */ + while (retry && bus_if->state != INFF_BUS_UP) { + usleep_range(10000, 20000); + retry--; + } + if (!retry && bus_if->state != INFF_BUS_UP) + inff_err("timed out wait for bus resume\n"); + + inff_report_wowl_wakeind(wiphy, ifp); + inff_fil_iovar_int_set(ifp, "wowl_clear", 0); + inff_config_wowl_pattern(ifp, "clr", NULL, 0, NULL, 0); + if (!inff_feat_is_enabled(ifp, INFF_FEAT_WOWL_ARP_ND)) + inff_offload_configure_arp_nd(ifp, true); + inff_fil_cmd_int_set(ifp, INFF_C_SET_PM, + power_mode); + cfg->wowl.active = false; + if (cfg->wowl.nd_enabled) { + inff_cfg80211_sched_scan_stop(cfg->wiphy, ifp->ndev, 0); + inff_fweh_unregister(cfg->pub, INFF_E_PFN_NET_FOUND); + inff_fweh_register(cfg->pub, INFF_E_PFN_NET_FOUND, + inff_notify_sched_scan_results); + cfg->wowl.nd_enabled = false; + } + + /* disable packet filters */ + inff_pktfilter_enable(ifp->ndev, false); + } + /* During resume, disable all offload modules which are enabled + * previously while entering suspend. + */ + if (inff_cfg80211_get_iftype(ifp) == NL80211_IFTYPE_STATION && + inff_feat_is_enabled(ifp, INFF_FEAT_OFFLOADS)) + inff_offload_enable(ifp, inff_offload_feat, false); + + config->pm_state = INFF_CFG80211_PM_STATE_RESUMED; + return 0; +} + +static void inff_configure_wowl(struct inff_cfg80211_info *cfg, + struct inff_if *ifp, + struct cfg80211_wowlan *wowl) +{ + u32 wowl_config; + struct inff_wowl_wakeind_le wowl_wakeind; + u32 i; + + inff_dbg(TRACE, "Suspend, wowl config.\n"); + + if (!inff_feat_is_enabled(ifp, INFF_FEAT_WOWL_ARP_ND)) + inff_offload_configure_arp_nd(ifp, false); + inff_fil_cmd_int_set(ifp, INFF_C_SET_PM, PM_MAX); + + wowl_config = 0; + if (wowl->disconnect) + wowl_config = INFF_WOWL_DIS | INFF_WOWL_BCN | INFF_WOWL_RETR; + if (wowl->magic_pkt) + wowl_config |= INFF_WOWL_MAGIC; + if (wowl->patterns && wowl->n_patterns) { + wowl_config |= INFF_WOWL_NET; + for (i = 0; i < wowl->n_patterns; i++) { + inff_config_wowl_pattern(ifp, "add", + (u8 *)wowl->patterns[i].pattern, + wowl->patterns[i].pattern_len, + (u8 *)wowl->patterns[i].mask, + wowl->patterns[i].pkt_offset); + } + } + if (wowl->nd_config) { + inff_cfg80211_sched_scan_start(cfg->wiphy, ifp->ndev, + wowl->nd_config); + wowl_config |= INFF_WOWL_PFN_FOUND; + + cfg->wowl.nd_data_completed = false; + cfg->wowl.nd_enabled = true; + /* Now reroute the event for PFN to the wowl function. */ + inff_fweh_unregister(cfg->pub, INFF_E_PFN_NET_FOUND); + inff_fweh_register(cfg->pub, INFF_E_PFN_NET_FOUND, + inff_wowl_nd_results); + } + if (wowl->gtk_rekey_failure) + wowl_config |= INFF_WOWL_GTK_FAILURE; + if (!test_bit(INFF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state)) + wowl_config |= INFF_WOWL_UNASSOC; + + memcpy(&wowl_wakeind, "clear", 6); + inff_fil_iovar_data_set(ifp, "wowl_wakeind", &wowl_wakeind, + sizeof(wowl_wakeind)); + inff_fil_iovar_int_set(ifp, "wowl", wowl_config); + inff_fil_iovar_int_set(ifp, "wowl_activate", 1); + inff_bus_wowl_config(cfg->pub->bus_if, true); + cfg->wowl.active = true; + + /* enable packet filters */ + inff_pktfilter_enable(ifp->ndev, true); +} + +static int inff_keepalive_start(struct inff_if *ifp, unsigned int interval) +{ + struct inff_mkeep_alive_pkt_le kalive = {0}; + int ret = 0; + + /* Configure Null function/data keepalive */ + kalive.version = cpu_to_le16(1); + kalive.period_msec = cpu_to_le32(interval * MSEC_PER_SEC); + kalive.len_bytes = cpu_to_le16(0); + kalive.keep_alive_id = 0; + + ret = inff_fil_iovar_data_set(ifp, "mkeep_alive", &kalive, sizeof(kalive)); + if (ret) + inff_err("keep-alive packet config failed, ret=%d\n", ret); + + return ret; +} + +static s32 inff_cfg80211_suspend(struct wiphy *wiphy, + struct cfg80211_wowlan *wowl) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct net_device *ndev = cfg_to_ndev(cfg); + struct inff_if *ifp = netdev_priv(ndev); + struct inff_cfg80211_vif *vif; + struct inff_cfg80211_info *config = ifp->drvr->config; + + inff_dbg(TRACE, "Enter\n"); + + config->pm_state = INFF_CFG80211_PM_STATE_SUSPENDING; + + /* if the primary net_device is not READY there is nothing + * we can do but pray resume goes smoothly. + */ + if (!check_vif_up(ifp->vif)) + goto exit; + + /* Stop scheduled scan */ + if (inff_feat_is_enabled(ifp, INFF_FEAT_PNO)) + inff_cfg80211_sched_scan_stop(wiphy, ndev, 0); + + /* end any scanning */ + if (test_bit(INFF_SCAN_STATUS_BUSY, &cfg->scan_status)) + inff_abort_scanning(cfg); + + /* Enable offload features that were not in default (LOW) or user selected + * power profile but should be offloaded to fw in suspend as host goes to + * sleep. These will be disabled on resume. + */ + if (inff_cfg80211_get_iftype(ifp) == NL80211_IFTYPE_STATION && + inff_feat_is_enabled(ifp, INFF_FEAT_OFFLOADS)) + inff_offload_enable(ifp, inff_offload_feat, true); + + if (!wowl || !test_bit(INFF_VIF_STATUS_CONNECTED, + &ifp->vif->sme_state)) { + inff_bus_wowl_config(cfg->pub->bus_if, false); + list_for_each_entry(vif, &cfg->vif_list, list) { + if (!test_bit(INFF_VIF_STATUS_READY, &vif->sme_state)) + continue; + /* While going to suspend if associated with AP + * disassociate from AP to save power while system is + * in suspended state + */ + inff_link_down(vif, WLAN_REASON_UNSPECIFIED, true); + /* Make sure WPA_Supplicant receives all the event + * generated due to DISASSOC call to the fw to keep + * the state fw and WPA_Supplicant state consistent + */ + inff_delay(500); + } + /* Configure MPC */ + inff_set_mpc(ifp, 1); + + } else { + if (inff_feat_is_enabled(ifp, INFF_FEAT_WOWL)) { + /* Configure WOWL parameters */ + inff_configure_wowl(cfg, ifp, wowl); + + /* Prevent disassociation due to inactivity with keep-alive */ + inff_keepalive_start(ifp, 30); + } + if (inff_cfg80211_get_iftype(ifp) == NL80211_IFTYPE_STATION && + inff_feat_is_enabled(ifp, INFF_FEAT_OFFLOADS)) + inff_offload_enable(ifp, INFF_OFFLOAD_WOWLPF, true); + } + +exit: + /* set cfg80211 pm state to cfg80211 suspended state */ + config->pm_state = INFF_CFG80211_PM_STATE_SUSPENDED; + + /* clear any scanning activity */ + cfg->scan_status = 0; + + inff_dbg(TRACE, "Exit\n"); + return 0; +} + +static s32 +inff_cfg80211_set_pmksa(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_pmksa *pmksa) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_if *ifp = netdev_priv(ndev); + struct inff_pub *drvr = cfg->pub; + s32 err; + + inff_dbg(TRACE, "Enter\n"); + if (!check_vif_up(ifp->vif)) + return -EIO; + + inff_dbg(CONN, "set_pmksa - PMK bssid: %pM =\n", pmksa->bssid); + inff_dbg(CONN, "%*ph\n", WLAN_PMKID_LEN, pmksa->pmkid); + + err = inff_update_pmksa(cfg, ifp, pmksa->bssid, pmksa->pmkid, PMKSA_SET); + if (err < 0) { + iphy_err(drvr, + "PMKSA_SET inff_update_pmksa failed: ret=%d\n", + err); + goto exit; + } + + if (pmksa->pmk_len && pmksa->pmk_len < INFF_WSEC_PMK_LEN_SUITEB_192) { + /* external supplicant stores SUITEB-192 PMK */ + if (ifp->vif->profile.is_okc) { + err = inff_fil_iovar_data_set(ifp, "okc_info_pmk", pmksa->pmk, + pmksa->pmk_len); + if (err < 0) + iphy_err(drvr, "okc_info_pmk iovar failed: ret=%d\n", err); + } else { + inff_set_pmk(ifp, pmksa->pmk, pmksa->pmk_len); + } + } + +exit: + inff_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +inff_cfg80211_del_pmksa(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_pmksa *pmksa) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_if *ifp = netdev_priv(ndev); + struct inff_pub *drvr = cfg->pub; + s32 err; + + inff_dbg(TRACE, "Enter\n"); + if (!check_vif_up(ifp->vif)) + return -EIO; + + inff_dbg(CONN, "del_pmksa - PMK bssid = %pM\n", pmksa->bssid); + + /* TODO: implement PMKID_V2 */ + err = inff_update_pmksa(cfg, ifp, pmksa->bssid, pmksa->pmkid, PMKSA_DELETE); + if (err < 0) { + iphy_err(drvr, + "PMKSA_DELETE inff_update_pmksa failed: ret=%d\n", + err); + return err; + } + + inff_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +inff_cfg80211_flush_pmksa(struct wiphy *wiphy, struct net_device *ndev) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_if *ifp = netdev_priv(ndev); + s32 err; + + inff_dbg(TRACE, "Enter\n"); + if (!check_vif_up(ifp->vif)) + return -EIO; + + memset(&cfg->pmk_list, 0, sizeof(cfg->pmk_list)); + err = inff_update_pmklist(cfg, ifp); + + inff_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +inff_cfg80211_start_ap(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_ap_settings *settings) +{ + s32 ie_offset; + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_if *ifp = netdev_priv(ndev); + struct inff_pub *drvr = cfg->pub; + struct inff_cfg80211_profile *profile = &ifp->vif->profile; + struct cfg80211_crypto_settings *crypto = &settings->crypto; + const struct inff_tlv *ssid_ie; + const struct inff_tlv *country_ie; + struct inff_ssid_le ssid_le; + s32 err = -EPERM; + struct inff_join_params join_params; + enum nl80211_iftype dev_role; + struct inff_fil_bss_enable_le bss_enable; + u16 chanspec = chandef_to_chanspec(&cfg->d11inf, &settings->chandef); + bool mbss = false; + int is_11d; + bool supports_11d; + bool closednet; + struct inff_p2p_info *p2p = &cfg->p2p; + + inff_dbg(TRACE, "ctrlchn=%d, center=%d, bw=%d, beacon_interval=%d, dtim_period=%d,\n", + settings->chandef.chan->hw_value, + settings->chandef.center_freq1, settings->chandef.width, + settings->beacon_interval, settings->dtim_period); + inff_dbg(TRACE, "ssid=%s(%zu), auth_type=%d, inactivity_timeout=%d\n", + settings->ssid, settings->ssid_len, settings->auth_type, + settings->inactivity_timeout); + dev_role = ifp->vif->wdev.iftype; + + if (dev_role == NL80211_IFTYPE_AP && + inff_feat_is_enabled(ifp, INFF_FEAT_MBSS)) { + struct inff_cfg80211_vif *vif_walk; + + list_for_each_entry(vif_walk, &cfg->vif_list, list) { + if (inff_is_apmode(vif_walk) && + check_vif_up(vif_walk) && + vif_walk != ifp->vif) { + /* found a vif is with the 1st AP type, + * and it doesn't equal to the currect vif calls start_ap. + * then it is mbss case. + */ + mbss = true; + break; + } + } + } + inff_dbg(TRACE, "mbss %s\n", mbss ? "enabled" : "disabled"); + + /* store current 11d setting */ + if (inff_fil_cmd_int_get(ifp, INFF_C_GET_REGULATORY, + &ifp->vif->is_11d)) { + is_11d = false; + supports_11d = false; + } else { + country_ie = inff_parse_tlvs((u8 *)settings->beacon.tail, + settings->beacon.tail_len, + WLAN_EID_COUNTRY); + is_11d = country_ie ? 1 : 0; + supports_11d = true; + } + + memset(&ssid_le, 0, sizeof(ssid_le)); + if (!settings->ssid || settings->ssid_len == 0) { + ie_offset = DOT11_MGMT_HDR_LEN + DOT11_BCN_PRB_FIXED_LEN; + ssid_ie = inff_parse_tlvs((u8 *)&settings->beacon.head[ie_offset], + settings->beacon.head_len - ie_offset, + WLAN_EID_SSID); + if (!ssid_ie || ssid_ie->len > IEEE80211_MAX_SSID_LEN) + return -EINVAL; + + memcpy(ssid_le.SSID, ssid_ie->data, ssid_ie->len); + ssid_le.SSID_len = cpu_to_le32(ssid_ie->len); + inff_dbg(TRACE, "SSID is (%s) in Head\n", ssid_le.SSID); + } else { + memcpy(ssid_le.SSID, settings->ssid, settings->ssid_len); + ssid_le.SSID_len = cpu_to_le32((u32)settings->ssid_len); + } + + if (!mbss) { + inff_set_mpc(ifp, 0); + inff_offload_configure_arp_nd(ifp, false); + } + + /* Parameters shared by all radio interfaces */ + if (!mbss) { + if ((supports_11d) && is_11d != ifp->vif->is_11d) { + err = inff_fil_cmd_int_set(ifp, INFF_C_SET_REGULATORY, + is_11d); + if (err < 0) { + iphy_err(drvr, "Regulatory Set Error, %d\n", + err); + goto exit; + } + } + if (settings->beacon_interval) { + err = inff_fil_cmd_int_set(ifp, INFF_C_SET_BCNPRD, + settings->beacon_interval); + if (err < 0) { + iphy_err(drvr, "Beacon Interval Set Error, %d\n", + err); + goto exit; + } + } + if (settings->dtim_period) { + err = inff_fil_cmd_int_set(ifp, INFF_C_SET_DTIMPRD, + settings->dtim_period); + if (err < 0) { + iphy_err(drvr, "DTIM Interval Set Error, %d\n", + err); + goto exit; + } + } + + if (dev_role == NL80211_IFTYPE_AP && + (ifp->ifidx == 0 || + (!inff_feat_is_enabled(ifp, INFF_FEAT_RSDB) && + !inff_feat_is_enabled(ifp, INFF_FEAT_MCHAN)))) { + err = inff_fil_cmd_int_set(ifp, INFF_C_DOWN, 1); + if (err < 0) { + iphy_err(drvr, "INFF_C_DOWN error %d\n", + err); + goto exit; + } + inff_fil_iovar_int_set(ifp, "apsta", 0); + } + + err = inff_fil_cmd_int_set(ifp, INFF_C_SET_INFRA, 1); + if (err < 0) { + iphy_err(drvr, "SET INFRA error %d\n", err); + goto exit; + } + } else if (WARN_ON(supports_11d && (is_11d != ifp->vif->is_11d))) { + /* Multiple-BSS should use same 11d configuration */ + err = -EINVAL; + goto exit; + } + ifp->isap = false; + /* Interface specific setup */ + if (dev_role == NL80211_IFTYPE_AP) { + u32 is_up; + + if ((inff_feat_is_enabled(ifp, INFF_FEAT_MBSS)) && !mbss) { + err = inff_fil_cmd_int_get(ifp, INFF_C_GET_UP, &is_up); + if (err < 0) { + iphy_err(drvr, "INFF_C_GET_UP error (%d)\n", err); + goto exit; + } + + /* mbss must be set in DOWN state. */ + if (is_up) { + err = inff_fil_cmd_int_set(ifp, INFF_C_DOWN, 1); + if (err < 0) { + iphy_err(drvr, "INFF_C_DOWN error (%d)\n", err); + goto exit; + } + } + err = inff_fil_iovar_int_set(ifp, "mbss", 1); + if (err < 0) { + iphy_err(drvr, "set mbss error (%d)\n", err); + goto exit; + } + } + + if (!test_bit(INFF_VIF_STATUS_AP_CREATED, &ifp->vif->sme_state)) { + bss_enable.bsscfgidx = cpu_to_le32(ifp->bsscfgidx); + bss_enable.enable = cpu_to_le32(WL_IOV_OP_MANUAL_AP_BSSCFG_CREATE); + err = inff_fil_iovar_data_set(ifp, "bss", &bss_enable, + sizeof(bss_enable)); + if (err < 0) { + iphy_err(drvr, "bss_enable config failed %d\n", err); + goto exit; + } + } + + err = inff_fil_cmd_int_set(ifp, INFF_C_SET_AP, 1); + if (err < 0) { + iphy_err(drvr, "setting AP mode failed %d\n", + err); + goto exit; + } + + /* Firmware 10.x requires setting channel after enabling + * AP and before bringing interface up. + */ + err = inff_fil_iovar_int_set(ifp, "chanspec", chanspec); + if (err < 0) { + iphy_err(drvr, "Set Channel failed: chspec=%d, %d\n", + chanspec, err); + goto exit; + } + + err = inff_fil_cmd_int_get(ifp, INFF_C_GET_UP, &is_up); + if (err < 0) { + iphy_err(drvr, "INFF_C_GET_UP error (%d)\n", err); + goto exit; + } + + if (!is_up) + err = inff_fil_cmd_int_set(ifp, INFF_C_UP, 1); + if (err < 0) { + iphy_err(drvr, "INFF_C_UP error (%d)\n", err); + goto exit; + } + + if (crypto->psk) { + inff_dbg(INFO, "using PSK offload\n"); + profile->use_fwauth |= BIT(INFF_PROFILE_FWAUTH_PSK); + err = inff_set_pmk(ifp, crypto->psk, + INFF_WSEC_MAX_PSK_LEN); + if (err < 0) + goto exit; + } + if (crypto->sae_pwd) { + inff_dbg(INFO, "using SAE offload\n"); + profile->use_fwauth |= BIT(INFF_PROFILE_FWAUTH_SAE); + err = inff_set_sae_password(ifp, crypto->sae_pwd, + crypto->sae_pwd_len); + if (err < 0) + goto exit; + } + if (profile->use_fwauth == 0) + profile->use_fwauth = BIT(INFF_PROFILE_FWAUTH_NONE); + + err = inff_parse_configure_security(ifp, settings, + NL80211_IFTYPE_AP); + if (err < 0) { + iphy_err(drvr, "inff_parse_configure_security error\n"); + goto exit; + } + + /* On DOWN the firmware removes the WEP keys, reconfigure + * them if they were set. + */ + inff_cfg80211_reconfigure_wep(ifp); + + memset(&join_params, 0, sizeof(join_params)); + /* join parameters starts with ssid */ + memcpy(&join_params.ssid_le, &ssid_le, sizeof(ssid_le)); + /* create softap */ + err = inff_fil_cmd_data_set(ifp, INFF_C_SET_SSID, + &join_params, sizeof(join_params)); + if (err < 0) { + iphy_err(drvr, "SET SSID error (%d)\n", err); + goto exit; + } + + closednet = + (settings->hidden_ssid != NL80211_HIDDEN_SSID_NOT_IN_USE); + err = inff_fil_iovar_int_set(ifp, "closednet", closednet); + if (err) { + iphy_err(drvr, "%s closednet error (%d)\n", + (closednet ? "enabled" : "disabled"), + err); + goto exit; + } + ifp->isap = true; + inff_dbg(TRACE, "AP mode configuration complete\n"); + } else if (dev_role == NL80211_IFTYPE_P2P_GO) { + err = inff_fil_iovar_int_set(ifp, "chanspec", chanspec); + if (err < 0) { + iphy_err(drvr, "Set Channel failed: chspec=%d, %d\n", + chanspec, err); + goto exit; + } + + err = inff_parse_configure_security(ifp, settings, + NL80211_IFTYPE_P2P_GO); + if (err < 0) { + inff_err("inff_parse_configure_security error\n"); + goto exit; + } + + err = inff_fil_bsscfg_data_set(ifp, "ssid", &ssid_le, + sizeof(ssid_le)); + if (err < 0) { + iphy_err(drvr, "setting ssid failed %d\n", err); + goto exit; + } + bss_enable.bsscfgidx = cpu_to_le32(ifp->bsscfgidx); + bss_enable.enable = cpu_to_le32(WL_IOV_OP_BSSCFG_ENABLE); + err = inff_fil_iovar_data_set(ifp, "bss", &bss_enable, + sizeof(bss_enable)); + if (err < 0) { + iphy_err(drvr, "bss_enable config failed %d\n", err); + goto exit; + } + + p2p->afx_hdl.my_listen_chan = chanspec; + ifp->isap = true; + inff_dbg(TRACE, "GO mode configuration complete\n"); + } else { + WARN_ON(1); + } + + /* Set HE BSS Color */ + if (settings->beacon.he_bss_color.enabled) + inff_he_set_bss_color(ifp, settings->beacon.he_bss_color.color); + + inff_vif_clear_mgmt_ies(ifp->vif); + inff_config_ap_mgmt_ie(ifp->vif, &settings->beacon); + set_bit(INFF_VIF_STATUS_AP_CREATED, &ifp->vif->sme_state); + inff_net_setcarrier(ifp, true); + +exit: + if ((err) && !mbss) { + inff_set_mpc(ifp, 1); + inff_offload_configure_arp_nd(ifp, true); + } else { + cfg->num_softap++; + inff_dbg(TRACE, "Num of SoftAP %u\n", cfg->num_softap); + } + return err; +} + +static int inff_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *ndev, + unsigned int link_id) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_if *ifp = netdev_priv(ndev); + struct inff_pub *drvr = cfg->pub; + struct inff_cfg80211_profile *profile = &ifp->vif->profile; + s32 err; + struct inff_fil_bss_enable_le bss_enable; + struct inff_join_params join_params; + s32 apsta = 0; + + inff_dbg(TRACE, "Enter\n"); + + if (ifp->vif->wdev.iftype == NL80211_IFTYPE_AP) { + /* Due to most likely deauths outstanding we sleep */ + /* first to make sure they get processed by fw. */ + msleep(400); + + if (profile->use_fwauth != BIT(INFF_PROFILE_FWAUTH_NONE)) { + if (profile->use_fwauth & BIT(INFF_PROFILE_FWAUTH_PSK)) + inff_set_pmk(ifp, NULL, 0); + if (profile->use_fwauth & BIT(INFF_PROFILE_FWAUTH_SAE)) + inff_set_sae_password(ifp, NULL, 0); + profile->use_fwauth = BIT(INFF_PROFILE_FWAUTH_NONE); + } + + cfg->num_softap--; + + /* Clear bss configuration and SSID */ + bss_enable.bsscfgidx = cpu_to_le32(ifp->bsscfgidx); + bss_enable.enable = cpu_to_le32(WL_IOV_OP_BSSCFG_DISABLE); + err = inff_fil_iovar_data_set(ifp, "bss", &bss_enable, + sizeof(bss_enable)); + if (err < 0) + inff_err("bss_enable config failed %d\n", err); + + memset(&join_params, 0, sizeof(join_params)); + err = inff_fil_cmd_data_set(ifp, INFF_C_SET_SSID, + &join_params, sizeof(join_params)); + if (err < 0) + iphy_err(drvr, "SET SSID error (%d)\n", err); + + if (cfg->num_softap) { + inff_dbg(TRACE, "Num of SoftAP %u\n", cfg->num_softap); + return 0; + } + + /* First BSS doesn't get a full reset */ + if (ifp->bsscfgidx == 0) + inff_fil_iovar_int_set(ifp, "closednet", 0); + + err = inff_fil_iovar_int_get(ifp, "apsta", &apsta); + if (err < 0) + inff_err("wl apsta failed (%d)\n", err); + + if (!apsta) { + err = inff_fil_cmd_int_set(ifp, INFF_C_DOWN, 1); + if (err < 0) + iphy_err(drvr, "INFF_C_DOWN error %d\n", err); + err = inff_fil_cmd_int_set(ifp, INFF_C_SET_AP, 0); + if (err < 0) + iphy_err(drvr, "Set AP mode error %d\n", err); + } + if (inff_feat_is_enabled(ifp, INFF_FEAT_MBSS)) + inff_fil_iovar_int_set(ifp, "mbss", 0); + inff_fil_cmd_int_set(ifp, INFF_C_SET_REGULATORY, + ifp->vif->is_11d); + /* Bring device back up so it can be used again */ + err = inff_fil_cmd_int_set(ifp, INFF_C_UP, 1); + if (err < 0) + iphy_err(drvr, "INFF_C_UP error %d\n", err); + + inff_vif_clear_mgmt_ies(ifp->vif); + } else { + bss_enable.bsscfgidx = cpu_to_le32(ifp->bsscfgidx); + bss_enable.enable = cpu_to_le32(WL_IOV_OP_BSSCFG_DISABLE); + err = inff_fil_iovar_data_set(ifp, "bss", &bss_enable, + sizeof(bss_enable)); + if (err < 0) + iphy_err(drvr, "bss_enable config failed %d\n", err); + } + inff_set_mpc(ifp, 1); + clear_bit(INFF_VIF_STATUS_AP_CREATED, &ifp->vif->sme_state); + inff_offload_configure_arp_nd(ifp, true); + inff_net_setcarrier(ifp, false); + + return err; +} + +static s32 +inff_cfg80211_change_beacon(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_ap_update *info) +{ + struct inff_if *ifp = netdev_priv(ndev); + + inff_dbg(TRACE, "Enter\n"); + + return inff_config_ap_mgmt_ie(ifp->vif, &info->beacon); +} + +static int +inff_cfg80211_del_station(struct wiphy *wiphy, struct net_device *ndev, + struct station_del_parameters *params) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_pub *drvr = cfg->pub; + struct inff_scb_val_le scbval; + struct inff_if *ifp = netdev_priv(ndev); + s32 err; + + if (!params->mac) + return -EFAULT; + + inff_dbg(TRACE, "Enter %pM\n", params->mac); + + if (ifp->vif == cfg->p2p.bss_idx[P2PAPI_BSSCFG_DEVICE].vif) + ifp = cfg->p2p.bss_idx[P2PAPI_BSSCFG_PRIMARY].vif->ifp; + if (!check_vif_up(ifp->vif)) + return -EIO; + + memcpy(&scbval.ea, params->mac, ETH_ALEN); + scbval.val = cpu_to_le32(params->reason_code); + err = inff_fil_cmd_data_set(ifp, INFF_C_SCB_DEAUTHENTICATE_FOR_REASON, + &scbval, sizeof(scbval)); + if (err) + iphy_err(drvr, "SCB_DEAUTHENTICATE_FOR_REASON failed %d\n", + err); + + inff_dbg(TRACE, "Exit\n"); + return err; +} + +static int +inff_cfg80211_change_station(struct wiphy *wiphy, struct net_device *ndev, + const u8 *mac, struct station_parameters *params) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_pub *drvr = cfg->pub; + struct inff_if *ifp = netdev_priv(ndev); + s32 err; + + inff_dbg(TRACE, "Enter, MAC %pM, mask 0x%04x set 0x%04x\n", mac, + params->sta_flags_mask, params->sta_flags_set); + + /* Ignore all 00 MAC */ + if (is_zero_ether_addr(mac)) + return 0; + + if (!(params->sta_flags_mask & BIT(NL80211_STA_FLAG_AUTHORIZED))) + return 0; + + if (params->sta_flags_set & BIT(NL80211_STA_FLAG_AUTHORIZED)) + err = inff_fil_cmd_data_set(ifp, INFF_C_SET_SCB_AUTHORIZE, + (void *)mac, ETH_ALEN); + else + err = inff_fil_cmd_data_set(ifp, INFF_C_SET_SCB_DEAUTHORIZE, + (void *)mac, ETH_ALEN); + if (err < 0) + iphy_err(drvr, "Setting SCB (de-)authorize failed, %d\n", err); + + return err; +} + +static void +inff_cfg80211_update_mgmt_frame_registrations(struct wiphy *wiphy, + struct wireless_dev *wdev, + struct mgmt_frame_regs *upd) +{ + struct inff_cfg80211_vif *vif; + + vif = container_of(wdev, struct inff_cfg80211_vif, wdev); + + vif->mgmt_rx_reg = upd->interface_stypes; +} + +static int +inff_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, + struct cfg80211_mgmt_tx_params *params, u64 *cookie) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct ieee80211_channel *chan = params->chan; + struct inff_pub *drvr = cfg->pub; + const u8 *buf = params->buf; + size_t len = params->len; + const struct ieee80211_mgmt *mgmt; + struct inff_cfg80211_vif *vif; + s32 err = 0; + s32 ie_offset; + s32 ie_len; + struct inff_fil_action_frame_le *action_frame; + struct inff_fil_af_params_le *af_params; + bool ack = false; + __le32 hw_ch; + struct inff_mf_params_le *mf_params; + u32 mf_params_len; + s32 timeout; + + inff_dbg(TRACE, "Enter\n"); + + *cookie = 0; + + mgmt = (const struct ieee80211_mgmt *)buf; + + if (!ieee80211_is_mgmt(mgmt->frame_control)) { + iphy_err(drvr, "Driver only allows MGMT packet type\n"); + return -EPERM; + } + + vif = container_of(wdev, struct inff_cfg80211_vif, wdev); + + if (ieee80211_is_probe_resp(mgmt->frame_control)) { + /* Right now the only reason to get a probe response */ + /* is for p2p listen response or for p2p GO from */ + /* wpa_supplicant. Unfortunately the probe is send */ + /* on primary ndev, while dongle wants it on the p2p */ + /* vif. Since this is only reason for a probe */ + /* response to be sent, the vif is taken from cfg. */ + /* If ever desired to send proberesp for non p2p */ + /* response then data should be checked for */ + /* "DIRECT-". Note in future supplicant will take */ + /* dedicated p2p wdev to do this and then this 'hack'*/ + /* is not needed anymore. */ + ie_offset = DOT11_MGMT_HDR_LEN + + DOT11_BCN_PRB_FIXED_LEN; + ie_len = len - ie_offset; + if (vif == cfg->p2p.bss_idx[P2PAPI_BSSCFG_PRIMARY].vif) + vif = cfg->p2p.bss_idx[P2PAPI_BSSCFG_DEVICE].vif; + err = inff_vif_set_mgmt_ie(vif, + INFF_VNDR_IE_PRBRSP_FLAG, + &buf[ie_offset], + ie_len); + cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, true, + GFP_KERNEL); + } else if (ieee80211_is_action(mgmt->frame_control)) { + if (len > INFF_FIL_ACTION_FRAME_SIZE + DOT11_MGMT_HDR_LEN) { + iphy_err(drvr, "invalid action frame length\n"); + err = -EINVAL; + goto exit; + } + af_params = kzalloc(sizeof(*af_params), GFP_KERNEL); + if (!af_params) { + err = -ENOMEM; + goto exit; + } + action_frame = &af_params->action_frame; + /* Add the packet Id */ + action_frame->packet_id = cpu_to_le32(*cookie); + /* Add BSSID */ + memcpy(&action_frame->da[0], &mgmt->da[0], ETH_ALEN); + memcpy(&af_params->bssid[0], &mgmt->bssid[0], ETH_ALEN); + /* Add the length exepted for 802.11 header */ + action_frame->len = cpu_to_le16(len - DOT11_MGMT_HDR_LEN); + /* Add the channel. Use the one specified as parameter if any or + * the current one (got from the firmware) otherwise + */ + if (chan) { + hw_ch = cpu_to_le32(chan->hw_value); + } else { + err = inff_fil_cmd_data_get(vif->ifp, + INFF_C_GET_CHANNEL, + &hw_ch, sizeof(hw_ch)); + if (err) { + iphy_err(drvr, + "unable to get current hw channel\n"); + goto free_af_params; + } + } + af_params->channel = hw_ch; + + af_params->dwell_time = cpu_to_le32(params->wait); + memcpy(action_frame->data, &buf[DOT11_MGMT_HDR_LEN], + le16_to_cpu(action_frame->len)); + + inff_dbg(TRACE, "Action frame, cookie=%lld, len=%d, channel=%d\n", + *cookie, le16_to_cpu(action_frame->len), + le32_to_cpu(af_params->channel)); + + ack = inff_p2p_send_action_frame(cfg, cfg_to_ndev(cfg), + af_params, vif, chan); + + cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, ack, + GFP_KERNEL); +free_af_params: + kfree(af_params); + } else if (ieee80211_is_auth(mgmt->frame_control)) { + reinit_completion(&vif->mgmt_tx); + clear_bit(INFF_MGMT_TX_ACK, &vif->mgmt_tx_status); + clear_bit(INFF_MGMT_TX_NOACK, &vif->mgmt_tx_status); + clear_bit(INFF_MGMT_TX_OFF_CHAN_COMPLETED, + &vif->mgmt_tx_status); + + mf_params_len = offsetof(struct inff_mf_params_le, data) + + (len - DOT11_MGMT_HDR_LEN); + mf_params = kzalloc(mf_params_len, GFP_KERNEL); + if (!mf_params) { + err = -ENOMEM; + goto exit; + } + + mf_params->dwell_time = cpu_to_le32(MGMT_AUTH_FRAME_DWELL_TIME); + mf_params->len = cpu_to_le16(len - DOT11_MGMT_HDR_LEN); + mf_params->frame_control = mgmt->frame_control; + + if (chan) { + hw_ch = cpu_to_le16(chan->hw_value); + } else { + err = inff_fil_cmd_data_get(vif->ifp, INFF_C_GET_CHANNEL, + &hw_ch, sizeof(hw_ch)); + if (err) { + iphy_err(drvr, "unable to get current hw channel\n"); + goto free_mf_params; + } + } + mf_params->channel = hw_ch; + + memcpy(&mf_params->da[0], &mgmt->da[0], ETH_ALEN); + memcpy(&mf_params->bssid[0], &mgmt->bssid[0], ETH_ALEN); + *cookie = (u64)mf_params->data; + mf_params->packet_id = cpu_to_le32(*cookie); + unsafe_memcpy(mf_params->data, &buf[DOT11_MGMT_HDR_LEN], + le16_to_cpu(mf_params->len), /* alloc enough buf*/); + + inff_dbg(TRACE, "Auth frame, cookie=%d, fc=%04x, len=%d, channel=%d\n", + le32_to_cpu(mf_params->packet_id), + le16_to_cpu(mf_params->frame_control), + le16_to_cpu(mf_params->len), + le16_to_cpu(mf_params->channel)); + + vif->mgmt_tx_id = le32_to_cpu(mf_params->packet_id); + set_bit(INFF_MGMT_TX_SEND_FRAME, &vif->mgmt_tx_status); + + err = inff_fil_bsscfg_data_set(vif->ifp, "mgmt_frame", + mf_params, mf_params_len); + if (err) { + iphy_err(drvr, "Failed to send Auth frame: err=%d\n", + err); + goto tx_status; + } + + timeout = + wait_for_completion_timeout(&vif->mgmt_tx, + MGMT_AUTH_FRAME_WAIT_TIME); + if (test_bit(INFF_MGMT_TX_ACK, &vif->mgmt_tx_status)) { + inff_dbg(TRACE, "TX Auth frame operation is success\n"); + ack = true; + } else { + iphy_err(drvr, "TX Auth frame operation is failed: status=%ld)\n", + vif->mgmt_tx_status); + } + +tx_status: + cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, ack, + GFP_KERNEL); +free_mf_params: + kfree(mf_params); + } else { + inff_dbg(TRACE, "Unhandled, fc=%04x!!\n", mgmt->frame_control); + inff_dbg_hex_dump(true, buf, len, "payload, len=%zu\n", len); + } + +exit: + return err; +} + +static int inff_cfg80211_set_cqm_rssi_range_config(struct wiphy *wiphy, + struct net_device *ndev, + s32 rssi_low, s32 rssi_high) +{ + struct inff_cfg80211_vif *vif; + struct inff_if *ifp; + int err = 0; + + inff_dbg(TRACE, "low=%d high=%d", rssi_low, rssi_high); + + ifp = netdev_priv(ndev); + vif = ifp->vif; + + if (rssi_low != vif->cqm_rssi_low || rssi_high != vif->cqm_rssi_high) { + /* The firmware will send an event when the RSSI is less than or + * equal to a configured level and the previous RSSI event was + * less than or equal to a different level. Set a third level + * so that we also detect the transition from rssi <= rssi_high + * to rssi > rssi_high. + */ + struct inff_rssi_event_le config = { + .rate_limit_msec = cpu_to_le32(0), + .rssi_level_num = 3, + .rssi_levels = { + clamp_val(rssi_low, S8_MIN, S8_MAX - 2), + clamp_val(rssi_high, S8_MIN + 1, S8_MAX - 1), + S8_MAX, + }, + }; + + err = inff_fil_iovar_data_set(ifp, "rssi_event", &config, + sizeof(config)); + if (err) { + err = -EINVAL; + } else { + vif->cqm_rssi_low = rssi_low; + vif->cqm_rssi_high = rssi_high; + } + } + + return err; +} + +static int +inff_cfg80211_cancel_remain_on_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, + u64 cookie) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_pub *drvr = cfg->pub; + struct inff_cfg80211_vif *vif; + int err = 0; + + inff_dbg(TRACE, "Enter p2p listen cancel\n"); + + vif = cfg->p2p.bss_idx[P2PAPI_BSSCFG_DEVICE].vif; + if (!vif) { + iphy_err(drvr, "No p2p device available for probe response\n"); + err = -ENODEV; + goto exit; + } + inff_p2p_cancel_remain_on_channel(vif->ifp); +exit: + return err; +} + +int inff_cfg80211_get_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, + unsigned int link_id, + struct cfg80211_chan_def *chandef) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_cfg80211_vif *vif = wdev_to_vif(wdev); + struct net_device *ndev = wdev->netdev; + struct inff_pub *drvr = cfg->pub; + struct inff_chan ch; + enum nl80211_band band = 0; + enum nl80211_chan_width width = 0; + u32 chanspec; + int freq, err; + + if (!ndev || drvr->bus_if->state != INFF_BUS_UP) + return -ENODEV; + if (!check_vif_up(vif)) + return -EIO; + err = inff_fil_iovar_int_get(netdev_priv(ndev), "chanspec", &chanspec); + if (err) { + iphy_err(drvr, "chanspec failed (%d)\n", err); + return err; + } + + ch.chspec = chanspec; + cfg->d11inf.decchspec(&ch); + band = inff_d11_chan_band_to_nl80211(ch.band); + + switch (ch.bw) { + case INFF_CHAN_BW_80: + width = NL80211_CHAN_WIDTH_80; + break; + case INFF_CHAN_BW_40: + width = NL80211_CHAN_WIDTH_40; + break; + case INFF_CHAN_BW_20: + width = NL80211_CHAN_WIDTH_20; + break; + case INFF_CHAN_BW_80P80: + width = NL80211_CHAN_WIDTH_80P80; + break; + case INFF_CHAN_BW_160: + width = NL80211_CHAN_WIDTH_160; + break; + } + + freq = ieee80211_channel_to_frequency(ch.control_ch_num, band); + if (!freq) + return -EINVAL; + chandef->chan = ieee80211_get_channel(wiphy, freq); + if (!chandef->chan) + return -EINVAL; + chandef->width = width; + chandef->center_freq1 = ieee80211_channel_to_frequency(ch.chnum, band); + chandef->center_freq2 = 0; + + return 0; +} + +static int inff_cfg80211_crit_proto_start(struct wiphy *wiphy, + struct wireless_dev *wdev, + enum nl80211_crit_proto_id proto, + u16 duration) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_cfg80211_vif *vif; + + vif = container_of(wdev, struct inff_cfg80211_vif, wdev); + + /* only DHCP support for now */ + if (proto != NL80211_CRIT_PROTO_DHCP) + return -EINVAL; + + /* suppress and abort scanning */ + set_bit(INFF_SCAN_STATUS_SUPPRESS, &cfg->scan_status); + inff_abort_scanning(cfg); + + return inff_btcoex_set_mode(vif, INFF_BTCOEX_DISABLED, duration); +} + +static void inff_cfg80211_crit_proto_stop(struct wiphy *wiphy, + struct wireless_dev *wdev) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_cfg80211_vif *vif; + + vif = container_of(wdev, struct inff_cfg80211_vif, wdev); + + inff_btcoex_set_mode(vif, INFF_BTCOEX_ENABLED, 0); + clear_bit(INFF_SCAN_STATUS_SUPPRESS, &cfg->scan_status); +} + +static s32 +inff_notify_tdls_peer_event(struct inff_if *ifp, + const struct inff_event_msg *e, void *data) +{ + switch (e->reason) { + case INFF_E_REASON_TDLS_PEER_DISCOVERED: + inff_dbg(TRACE, "TDLS Peer Discovered\n"); + break; + case INFF_E_REASON_TDLS_PEER_CONNECTED: + inff_dbg(TRACE, "TDLS Peer Connected\n"); + inff_proto_add_tdls_peer(ifp->drvr, ifp->ifidx, (u8 *)e->addr); + break; + case INFF_E_REASON_TDLS_PEER_DISCONNECTED: + inff_dbg(TRACE, "TDLS Peer Disconnected\n"); + inff_proto_delete_peer(ifp->drvr, ifp->ifidx, (u8 *)e->addr); + break; + } + + return 0; +} + +static int inff_convert_nl80211_tdls_oper(enum nl80211_tdls_operation oper) +{ + int ret; + + switch (oper) { + case NL80211_TDLS_DISCOVERY_REQ: + ret = INFF_TDLS_MANUAL_EP_DISCOVERY; + break; + case NL80211_TDLS_SETUP: + ret = INFF_TDLS_MANUAL_EP_CREATE; + break; + case NL80211_TDLS_TEARDOWN: + ret = INFF_TDLS_MANUAL_EP_DELETE; + break; + default: + inff_err("unsupported operation: %d\n", oper); + ret = -EOPNOTSUPP; + } + return ret; +} + +static int inff_cfg80211_tdls_oper(struct wiphy *wiphy, + struct net_device *ndev, const u8 *peer, + enum nl80211_tdls_operation oper) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_pub *drvr = cfg->pub; + struct inff_if *ifp; + struct inff_tdls_iovar_le info; + int ret = 0; + + ret = inff_convert_nl80211_tdls_oper(oper); + if (ret < 0) + return ret; + + ifp = netdev_priv(ndev); + memset(&info, 0, sizeof(info)); + info.mode = (u8)ret; + if (peer) + memcpy(info.ea, peer, ETH_ALEN); + + ret = inff_fil_iovar_data_set(ifp, "tdls_endpoint", + &info, sizeof(info)); + if (ret < 0) + iphy_err(drvr, "tdls_endpoint iovar failed: ret=%d\n", ret); + + return ret; +} + +static int +inff_cfg80211_update_conn_params(struct wiphy *wiphy, + struct net_device *ndev, + struct cfg80211_connect_params *sme, + u32 changed) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_pub *drvr = cfg->pub; + struct inff_if *ifp; + int err; + + if (!(changed & UPDATE_ASSOC_IES)) + return 0; + + ifp = netdev_priv(ndev); + err = inff_vif_set_mgmt_ie(ifp->vif, INFF_VNDR_IE_ASSOCREQ_FLAG, + sme->ie, sme->ie_len); + if (err) + iphy_err(drvr, "Set Assoc REQ IE Failed\n"); + else + inff_dbg(TRACE, "Applied Vndr IEs for Assoc request\n"); + + return err; +} + +static int inff_cfg80211_set_pmk(struct wiphy *wiphy, struct net_device *dev, + const struct cfg80211_pmk_conf *conf) +{ + struct inff_if *ifp; + struct inff_pub *drvr; + int ret; + + inff_dbg(TRACE, "enter\n"); + + /* expect using firmware supplicant for 1X */ + ifp = netdev_priv(dev); + drvr = ifp->drvr; + if (WARN_ON(ifp->vif->profile.use_fwsup != INFF_PROFILE_FWSUP_1X && + ifp->vif->profile.use_fwsup != INFF_PROFILE_FWSUP_ROAM && + !ifp->vif->profile.is_ft && + !ifp->vif->profile.is_okc)) + return -EINVAL; + + if (conf->pmk_len > INFF_WSEC_MAX_PMK_LEN) + return -ERANGE; + + if (ifp->vif->profile.is_okc) { + ret = inff_fil_iovar_data_set(ifp, "okc_info_pmk", conf->pmk, + conf->pmk_len); + if (ret < 0) + iphy_err(drvr, "okc_info_pmk iovar failed: ret=%d\n", ret); + } + + return inff_set_pmk(ifp, conf->pmk, conf->pmk_len); +} + +static int inff_cfg80211_del_pmk(struct wiphy *wiphy, struct net_device *dev, + const u8 *aa) +{ + struct inff_if *ifp; + + inff_dbg(TRACE, "enter\n"); + ifp = netdev_priv(dev); + if (WARN_ON(ifp->vif->profile.use_fwsup != INFF_PROFILE_FWSUP_1X)) + return -EINVAL; + + return inff_set_pmk(ifp, NULL, 0); +} + +static int +inff_cfg80211_change_bss(struct wiphy *wiphy, struct net_device *dev, + struct bss_parameters *params) +{ + struct inff_if *ifp; + int ret = 0; + u32 ap_isolate, val; + + inff_dbg(TRACE, "Enter\n"); + ifp = netdev_priv(dev); + if (params->ap_isolate >= 0) { + ap_isolate = (u32)params->ap_isolate; + ret = inff_fil_iovar_int_set(ifp, "ap_isolate", ap_isolate); + if (ret < 0) + inff_err("ap_isolate iovar failed: ret=%d\n", ret); + } + + /* Get ap_isolate value from firmware to detemine whether fmac */ + /* driver supports packet forwarding. */ + if (inff_fil_iovar_int_get(ifp, "ap_isolate", &val) == 0) { + ifp->fmac_pkt_fwd_en = + ((params->ap_isolate == 0) && (val == 1)) ? + true : false; + } else { + inff_err("get ap_isolate iovar failed: ret=%d\n", ret); + ifp->fmac_pkt_fwd_en = false; + } + + return ret; +} + +static int +inff_cfg80211_external_auth(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_external_auth_params *params) +{ + struct inff_if *ifp; + struct inff_pub *drvr; + struct inff_auth_req_status_le auth_status; + int ret = 0; + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + + inff_dbg(TRACE, "Enter\n"); + + ifp = netdev_priv(dev); + drvr = ifp->drvr; + if (params->status == WLAN_STATUS_SUCCESS) { + auth_status.flags = cpu_to_le16(INFF_EXTAUTH_SUCCESS); + } else { + iphy_err(drvr, "External authentication failed: status=%d\n", + params->status); + auth_status.flags = cpu_to_le16(INFF_EXTAUTH_FAIL); + } + + memcpy(auth_status.peer_mac, params->bssid, ETH_ALEN); + auth_status.ssid_len = cpu_to_le32(min_t(u8, params->ssid.ssid_len, + IEEE80211_MAX_SSID_LEN)); + memcpy(auth_status.ssid, params->ssid.ssid, auth_status.ssid_len); + memset(auth_status.pmkid, 0, WLAN_PMKID_LEN); + if (params->pmkid) + memcpy(auth_status.pmkid, params->pmkid, WLAN_PMKID_LEN); + + ret = inff_fil_iovar_data_set(ifp, "auth_status", &auth_status, + sizeof(auth_status)); + if (ret < 0) + iphy_err(drvr, "auth_status iovar failed: ret=%d\n", ret); + + if (params->pmkid) { + ret = inff_update_pmksa(cfg, + ifp, + params->bssid, + params->pmkid, + PMKSA_SET); + if (ret < 0) { + iphy_err(drvr, + "PMKSA_SET inff_update_pmksa failed: ret=%d\n", + ret); + } + } + + return ret; +} + +static int +inff_cfg80211_set_bitrate(struct wiphy *wiphy, struct net_device *ndev, + unsigned int link_id, const u8 *addr, + const struct cfg80211_bitrate_mask *mask) +{ + struct inff_if *ifp = netdev_priv(ndev); + s32 ret = TIME_OK; + u8 he, band; + + ret = inff_he_get_enable(ifp, &he, sizeof(he)); + if (!ret && !he) { + inff_dbg(INFO, "Only HE mode rate setting supported\n"); + return -EOPNOTSUPP; + } + + for (band = 0; band < NUM_NL80211_BANDS; band++) { + if (band != NL80211_BAND_2GHZ && band != NL80211_BAND_5GHZ && + band != NL80211_BAND_6GHZ) { + continue; + } + + /* Skip setting HE rates if legacy rate set is called from userspace. + * Also if any one of 2.4, 5 or 6GHz is being called then other two will have + * an invalid he mask of 0xFFF so skip setting he rates for other two bands. + */ + if (!mask->control[band].he_mcs[0] || mask->control[band].he_mcs[0] == 0xFFF) + continue; + + ret = inff_he_set_bitrate(ifp, mask, band); + if (ret) + break; + } + + return ret; +} + +static int +inff_cfg80211_set_cqm_rssi_config(struct wiphy *wiphy, struct net_device *dev, + s32 rssi_thold, u32 rssi_hyst) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_if *ifp; + struct wl_rssi_event rssi; + int err = 0; + + ifp = netdev_priv(dev); + if (rssi_thold == cfg->cqm_info.rssi_threshold) + return err; + + if (rssi_thold == 0) { + rssi.rate_limit_msec = cpu_to_le32(0); + rssi.num_rssi_levels = 0; + rssi.version = WL_RSSI_EVENT_VERSION_NEW; + } else { + rssi.rate_limit_msec = cpu_to_le32(0); + rssi.num_rssi_levels = 3; + rssi.rssi_levels[0] = S8_MIN; + rssi.rssi_levels[1] = rssi_thold; + rssi.rssi_levels[2] = S8_MAX; + rssi.version = WL_RSSI_EVENT_VERSION_OLD; + } + + err = inff_fil_iovar_data_set(ifp, "rssi_event", &rssi, sizeof(rssi)); + if (err < 0) { + inff_err("set rssi_event iovar failed (%d)\n", err); + } else { + cfg->cqm_info.enable = rssi_thold ? 1 : 0; + cfg->cqm_info.rssi_threshold = rssi_thold; + } + + inff_dbg(TRACE, "enable = %d, rssi_threshold = %d\n", + cfg->cqm_info.enable, cfg->cqm_info.rssi_threshold); + return err; +} + +static int +inff_cfg80211_update_owe_info(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_update_owe_info *owe_info) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_if *ifp; + int err = 0; + struct inff_owe_info_buf *owe_info_buf; + u8 *curr_ie_buf; + struct parsed_extension_ies owe_ecdh_ie; + struct parsed_ext_ie_info *extie_info; + u32 del_add_ie_buf_len = 0; + u32 total_owe_info_len = 0; + u32 pmkid_offset = 0; + struct inff_pub *drvr; + + ifp = netdev_priv(dev); + if (owe_info) { + owe_info_buf = kzalloc(WL_EXTRA_BUF_MAX, GFP_KERNEL); + if (!owe_info_buf) + return -ENOMEM; + + curr_ie_buf = owe_info_buf->ecdh_ie_info; + + memcpy(owe_info_buf->peer_mac, owe_info->peer, ETH_ALEN); + owe_info_buf->status_le16 = cpu_to_le16(owe_info->status); + + owe_info_buf->with_ecdh = false; + if (owe_info->ie) { + drvr = ifp->drvr; + if (inff_has_pmkid(owe_info->ie, + owe_info->ie_len, + &pmkid_offset)) { + err = inff_update_pmksa(cfg, + ifp, + owe_info_buf->peer_mac, + owe_info->ie + pmkid_offset, + PMKSA_SET); + if (err < 0) { + iphy_err(drvr, + "PMKSA_SET inff_update_pmksa failed: ret=%d\n", + err); + return err; + } + + owe_info_buf->with_pmkid = true; + memcpy(owe_info_buf->pmkid, + owe_info->ie + pmkid_offset, + WLAN_PMKID_LEN); + } + + inff_parse_extension_ies(owe_info->ie, owe_info->ie_len, &owe_ecdh_ie); + if (owe_ecdh_ie.count > 1) { + inff_err("more ecdh_cnt found in info: %d\n", owe_ecdh_ie.count); + return -EINVAL; + } + + if (owe_ecdh_ie.count == 1) { + owe_info_buf->with_ecdh = true; + extie_info = &owe_ecdh_ie.ie_info[0]; + + del_add_ie_buf_len = inff_vndr_ie(curr_ie_buf, + INFF_VNDR_IE_ASSOCRSP_FLAG | + INFF_VNDR_IE_CUSTOM_FLAG, + extie_info->ie_ptr, + extie_info->ie_len, + "add"); + } + } + + total_owe_info_len = sizeof(struct inff_owe_info_buf) + del_add_ie_buf_len; + err = inff_fil_bsscfg_data_set(ifp, "owe_info", owe_info_buf, + total_owe_info_len); + + kfree(owe_info_buf); + } + + if (err) + inff_err("update owe_info error :%d\n", err); + + return err; +} + +static struct cfg80211_ops inff_cfg80211_ops = { + .add_virtual_intf = inff_cfg80211_add_iface, + .del_virtual_intf = inff_cfg80211_del_iface, + .change_virtual_intf = inff_cfg80211_change_iface, + .scan = inff_cfg80211_scan, + .set_wiphy_params = inff_cfg80211_set_wiphy_params, + .join_ibss = inff_cfg80211_join_ibss, + .leave_ibss = inff_cfg80211_leave_ibss, + .get_station = inff_cfg80211_get_station, + .dump_station = inff_cfg80211_dump_station, + .set_tx_power = inff_cfg80211_set_tx_power, + .get_tx_power = inff_cfg80211_get_tx_power, + .add_key = inff_cfg80211_add_key, + .del_key = inff_cfg80211_del_key, + .get_key = inff_cfg80211_get_key, + .set_default_key = inff_cfg80211_config_default_key, + .set_default_mgmt_key = inff_cfg80211_config_default_mgmt_key, + .set_power_mgmt = inff_cfg80211_set_power_mgmt, + .connect = inff_cfg80211_connect, + .disconnect = inff_cfg80211_disconnect, + .suspend = inff_cfg80211_suspend, + .resume = inff_cfg80211_resume, + .set_pmksa = inff_cfg80211_set_pmksa, + .del_pmksa = inff_cfg80211_del_pmksa, + .flush_pmksa = inff_cfg80211_flush_pmksa, + .start_ap = inff_cfg80211_start_ap, + .stop_ap = inff_cfg80211_stop_ap, + .change_beacon = inff_cfg80211_change_beacon, + .set_bitrate_mask = inff_cfg80211_set_bitrate, + .del_station = inff_cfg80211_del_station, + .change_station = inff_cfg80211_change_station, + .sched_scan_start = inff_cfg80211_sched_scan_start, + .sched_scan_stop = inff_cfg80211_sched_scan_stop, + .update_mgmt_frame_registrations = + inff_cfg80211_update_mgmt_frame_registrations, + .mgmt_tx = inff_cfg80211_mgmt_tx, + .set_cqm_rssi_range_config = inff_cfg80211_set_cqm_rssi_range_config, + .remain_on_channel = inff_p2p_remain_on_channel, + .cancel_remain_on_channel = inff_cfg80211_cancel_remain_on_channel, + .get_channel = inff_cfg80211_get_channel, + .start_p2p_device = inff_p2p_start_device, + .stop_p2p_device = inff_p2p_stop_device, + .crit_proto_start = inff_cfg80211_crit_proto_start, + .crit_proto_stop = inff_cfg80211_crit_proto_stop, + .tdls_oper = inff_cfg80211_tdls_oper, + .update_connect_params = inff_cfg80211_update_conn_params, + .set_pmk = inff_cfg80211_set_pmk, + .del_pmk = inff_cfg80211_del_pmk, + .change_bss = inff_cfg80211_change_bss, + .external_auth = inff_cfg80211_external_auth, + .set_cqm_rssi_config = inff_cfg80211_set_cqm_rssi_config, + .update_owe_info = inff_cfg80211_update_owe_info, + .start_pmsr = inff_cfg80211_start_pmsr, + .abort_pmsr = inff_cfg80211_abort_pmsr, + .start_wlan_sense = inff_wlan_sense_start, + .stop_wlan_sense = inff_wlan_sense_stop, +}; + +struct cfg80211_ops *inff_cfg80211_get_ops(struct inff_mp_device *settings) +{ + struct cfg80211_ops *ops; + + ops = kmemdup(&inff_cfg80211_ops, sizeof(inff_cfg80211_ops), + GFP_KERNEL); + + if (ops && settings->roamoff) + ops->update_connect_params = NULL; + + return ops; +} + +void inff_cfg80211_free_netdev(struct net_device *ndev) +{ + struct inff_cfg80211_vif *vif; + struct inff_if *ifp; + + ifp = netdev_priv(ndev); + vif = ifp->vif; + + if (vif) { + inff_free_vif(vif); + ifp->vif = NULL; + } +} + +bool inff_is_linkup(struct inff_cfg80211_vif *vif, + const struct inff_event_msg *e) +{ + u32 event = e->event_code; + u32 status = e->status; + + if ((vif->profile.use_fwsup == INFF_PROFILE_FWSUP_PSK || + vif->profile.use_fwsup == INFF_PROFILE_FWSUP_SAE) && + event == INFF_E_PSK_SUP && + status == INFF_E_STATUS_FWSUP_COMPLETED) + set_bit(INFF_VIF_STATUS_EAP_SUCCESS, &vif->sme_state); + if (event == INFF_E_LINK && status == INFF_E_STATUS_SUCCESS && + (e->flags & INFF_EVENT_MSG_LINK)) { + inff_dbg(CONN, "Processing set ssid\n"); + memcpy(vif->profile.bssid, e->addr, ETH_ALEN); + if (vif->profile.use_fwsup != INFF_PROFILE_FWSUP_PSK && + vif->profile.use_fwsup != INFF_PROFILE_FWSUP_SAE) + return true; + + set_bit(INFF_VIF_STATUS_ASSOC_SUCCESS, &vif->sme_state); + } + + if (test_bit(INFF_VIF_STATUS_EAP_SUCCESS, &vif->sme_state) && + test_bit(INFF_VIF_STATUS_ASSOC_SUCCESS, &vif->sme_state)) { + clear_bit(INFF_VIF_STATUS_EAP_SUCCESS, &vif->sme_state); + clear_bit(INFF_VIF_STATUS_ASSOC_SUCCESS, &vif->sme_state); + return true; + } + return false; +} + +bool inff_is_linkdown(struct inff_cfg80211_vif *vif, + const struct inff_event_msg *e) +{ + u32 event = e->event_code; + u16 flags = e->flags; + u32 status = e->status; + + if (event == INFF_E_DEAUTH || event == INFF_E_DEAUTH_IND || + event == INFF_E_DISASSOC_IND || + (event == INFF_E_LINK && !(flags & INFF_EVENT_MSG_LINK)) || + (event == INFF_E_SET_SSID && status != INFF_E_STATUS_SUCCESS)) { + inff_dbg(CONN, "Processing link down\n"); + clear_bit(INFF_VIF_STATUS_EAP_SUCCESS, &vif->sme_state); + clear_bit(INFF_VIF_STATUS_ASSOC_SUCCESS, &vif->sme_state); + return true; + } + return false; +} + +bool inff_is_nonetwork(struct inff_cfg80211_info *cfg, + const struct inff_event_msg *e) +{ + u32 event = e->event_code; + u32 status = e->status; + + if (event == INFF_E_LINK && status == INFF_E_STATUS_NO_NETWORKS) { + inff_dbg(CONN, "Processing Link %s & no network found\n", + e->flags & INFF_EVENT_MSG_LINK ? "up" : "down"); + return true; + } + + if (event == INFF_E_SET_SSID && status != INFF_E_STATUS_SUCCESS) { + inff_dbg(CONN, "Processing connecting & no network found\n"); + return true; + } + + if (event == INFF_E_PSK_SUP && + status != INFF_E_STATUS_FWSUP_COMPLETED) { + inff_dbg(CONN, "Processing failed supplicant state: %u\n", + status); + return true; + } + + return false; +} + +u8 inff_map_prio_to_prec(void *config, u8 prio) +{ + struct inff_cfg80211_info *cfg = (struct inff_cfg80211_info *)config; + + if (!cfg) + return (prio == PRIO_8021D_NONE || prio == PRIO_8021D_BE) ? + (prio ^ 2) : prio; + + /* For those AC(s) with ACM flag set to 1, convert its 4-level priority + * to an 8-level precedence which is the same as BE's + */ + if (prio > PRIO_8021D_EE && + cfg->ac_priority[prio] == cfg->ac_priority[PRIO_8021D_BE]) + return cfg->ac_priority[prio] * 2; + + /* Conversion of 4-level priority to 8-level precedence */ + if (prio == PRIO_8021D_BE || prio == PRIO_8021D_BK || + prio == PRIO_8021D_CL || prio == PRIO_8021D_VO) + return cfg->ac_priority[prio] * 2; + else + return cfg->ac_priority[prio] * 2 + 1; +} + +u8 inff_map_prio_to_aci(void *config, u8 prio) +{ + /* Prio here refers to the 802.1d priority in range of 0 to 7. + * ACI here refers to the WLAN AC Index in range of 0 to 3. + * This function will return ACI corresponding to input prio. + */ + struct inff_cfg80211_info *cfg = (struct inff_cfg80211_info *)config; + + if (cfg) + return cfg->ac_priority[prio]; + + return prio; +} + +static void inff_init_wmm_prio(u8 *priority) +{ + /* Initialize AC priority array to default + * 802.1d priority as per following table: + * 802.1d prio 0,3 maps to BE + * 802.1d prio 1,2 maps to BK + * 802.1d prio 4,5 maps to VI + * 802.1d prio 6,7 maps to VO + */ + priority[0] = INFF_FWS_FIFO_AC_BE; + priority[3] = INFF_FWS_FIFO_AC_BE; + priority[1] = INFF_FWS_FIFO_AC_BK; + priority[2] = INFF_FWS_FIFO_AC_BK; + priority[4] = INFF_FWS_FIFO_AC_VI; + priority[5] = INFF_FWS_FIFO_AC_VI; + priority[6] = INFF_FWS_FIFO_AC_VO; + priority[7] = INFF_FWS_FIFO_AC_VO; +} + +void inff_wifi_prioritize_acparams(const + struct inff_cfg80211_edcf_acparam *acp, u8 *priority) +{ + u8 aci; + u8 aifsn; + u8 ecwmin; + u8 ecwmax; + u8 acm; + u8 ranking_basis[EDCF_AC_COUNT]; + u8 aci_prio[EDCF_AC_COUNT]; /* AC_BE, AC_BK, AC_VI, AC_VO */ + u8 index; + + for (aci = 0; aci < EDCF_AC_COUNT; aci++, acp++) { + aifsn = acp->ACI & EDCF_AIFSN_MASK; + acm = (acp->ACI & EDCF_ACM_MASK) ? 1 : 0; + ecwmin = acp->ECW & EDCF_ECWMIN_MASK; + ecwmax = (acp->ECW & EDCF_ECWMAX_MASK) >> EDCF_ECWMAX_SHIFT; + inff_dbg(CONN, "ACI %d aifsn %d acm %d ecwmin %d ecwmax %d\n", + aci, aifsn, acm, ecwmin, ecwmax); + /* Default AC_VO will be the lowest ranking value */ + ranking_basis[aci] = aifsn + ecwmin + ecwmax; + /* Initialise priority starting at 0 (AC_BE) */ + aci_prio[aci] = 0; + + /* If ACM is set, STA can't use this AC as per 802.11. + * Change the ranking to BE + */ + if (aci != AC_BE && aci != AC_BK && acm == 1) + ranking_basis[aci] = ranking_basis[AC_BE]; + } + + /* Ranking method which works for AC priority + * swapping when values for cwmin, cwmax and aifsn are varied + * Compare each aci_prio against each other aci_prio + */ + for (aci = 0; aci < EDCF_AC_COUNT; aci++) { + for (index = 0; index < EDCF_AC_COUNT; index++) { + if (index != aci) { + /* Smaller ranking value has higher priority, + * so increment priority for each ACI which has + * a higher ranking value + */ + if (ranking_basis[aci] < ranking_basis[index]) + aci_prio[aci]++; + } + } + } + + /* By now, aci_prio[] will be in range of 0 to 3. + * Use ACI prio to get the new priority value for + * each 802.1d traffic type, in this range. + */ + if (!(aci_prio[AC_BE] == aci_prio[AC_BK] && + aci_prio[AC_BK] == aci_prio[AC_VI] && + aci_prio[AC_VI] == aci_prio[AC_VO])) { + /* 802.1d 0,3 maps to BE */ + priority[0] = aci_prio[AC_BE]; + priority[3] = aci_prio[AC_BE]; + + /* 802.1d 1,2 maps to BK */ + priority[1] = aci_prio[AC_BK]; + priority[2] = aci_prio[AC_BK]; + + /* 802.1d 4,5 maps to VO */ + priority[4] = aci_prio[AC_VI]; + priority[5] = aci_prio[AC_VI]; + + /* 802.1d 6,7 maps to VO */ + priority[6] = aci_prio[AC_VO]; + priority[7] = aci_prio[AC_VO]; + } else { + /* Initialize to default priority */ + inff_init_wmm_prio(priority); + } + + inff_dbg(CONN, "Adj prio BE 0->%d, BK 1->%d, BK 2->%d, BE 3->%d\n", + priority[0], priority[1], priority[2], priority[3]); + + inff_dbg(CONN, "Adj prio VI 4->%d, VI 5->%d, VO 6->%d, VO 7->%d\n", + priority[4], priority[5], priority[6], priority[7]); +} + +s32 +inff_bss_roaming_done(struct inff_cfg80211_info *cfg, + struct net_device *ndev, + const struct inff_event_msg *e) +{ + struct inff_if *ifp = netdev_priv(ndev); + struct inff_cfg80211_profile *profile = &ifp->vif->profile; + struct inff_cfg80211_connect_info *conn_info = cfg_to_conn(cfg); + struct wiphy *wiphy = cfg_to_wiphy(cfg); + struct ieee80211_channel *notify_channel = NULL; + struct ieee80211_supported_band *band; + struct inff_bss_info_le *bi; + struct inff_chan ch; + struct cfg80211_roam_info roam_info = {}; + u32 freq; + s32 err = 0; + + inff_dbg(TRACE, "Enter\n"); + + inff_get_assoc_ies(cfg, ifp); + memcpy(profile->bssid, e->addr, ETH_ALEN); + bi = (struct inff_bss_info_le *)inff_update_bss_info(cfg, ifp); + if (!bi) + goto done; + + ch.chspec = le16_to_cpu(bi->chanspec); + cfg->d11inf.decchspec(&ch); + + band = wiphy->bands[inff_d11_chan_band_to_nl80211(ch.band)]; + freq = ieee80211_channel_to_frequency(ch.control_ch_num, band->band); + if (!freq) + err = -EINVAL; + notify_channel = ieee80211_get_channel(wiphy, freq); + if (!notify_channel) + err = -EINVAL; + +done: + + roam_info.links[0].channel = notify_channel; + roam_info.links[0].bssid = profile->bssid; + roam_info.req_ie = conn_info->req_ie; + roam_info.req_ie_len = conn_info->req_ie_len; + roam_info.resp_ie = conn_info->resp_ie; + roam_info.resp_ie_len = conn_info->resp_ie_len; + + cfg80211_roamed(ndev, &roam_info, GFP_KERNEL); + inff_dbg(CONN, "Report roaming result\n"); + + if (((profile->use_fwsup == INFF_PROFILE_FWSUP_1X || + profile->use_fwsup == INFF_PROFILE_FWSUP_ROAM) && + (inff_has_pmkid(roam_info.req_ie, roam_info.req_ie_len, NULL) || + profile->is_ft || profile->is_okc)) || + profile->use_fwsup == INFF_PROFILE_FWSUP_PSK || + profile->use_fwsup == INFF_PROFILE_FWSUP_SAE) { + cfg80211_port_authorized(ndev, profile->bssid, NULL, 0, GFP_KERNEL); + inff_dbg(CONN, "Report port authorized\n"); + } + + clear_bit(INFF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state); + set_bit(INFF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state); + inff_dbg(TRACE, "Exit\n"); + return err; +} + +s32 +inff_bss_connect_done(struct inff_cfg80211_info *cfg, + struct net_device *ndev, const struct inff_event_msg *e, + bool completed) +{ + struct inff_if *ifp = netdev_priv(ndev); + struct inff_cfg80211_profile *profile = &ifp->vif->profile; + struct inff_cfg80211_connect_info *conn_info = cfg_to_conn(cfg); + struct cfg80211_connect_resp_params conn_params; + + inff_dbg(TRACE, "Enter cfg->pfn_enable %d\n", cfg->pfn_enable); + + if (test_and_clear_bit(INFF_VIF_STATUS_CONNECTING, + &ifp->vif->sme_state)) { + memset(&conn_params, 0, sizeof(conn_params)); + if (completed) { + inff_get_assoc_ies(cfg, ifp); + inff_update_bss_info(cfg, ifp); + set_bit(INFF_VIF_STATUS_CONNECTED, + &ifp->vif->sme_state); + conn_params.status = WLAN_STATUS_SUCCESS; + } else { + clear_bit(INFF_VIF_STATUS_EAP_SUCCESS, + &ifp->vif->sme_state); + clear_bit(INFF_VIF_STATUS_ASSOC_SUCCESS, + &ifp->vif->sme_state); + conn_params.status = WLAN_STATUS_AUTH_TIMEOUT; + } + conn_params.links[0].bssid = profile->bssid; + conn_params.req_ie = conn_info->req_ie; + conn_params.req_ie_len = conn_info->req_ie_len; + conn_params.resp_ie = conn_info->resp_ie; + conn_params.resp_ie_len = conn_info->resp_ie_len; + + cfg80211_connect_done(ndev, &conn_params, GFP_KERNEL); + inff_dbg(CONN, "Report connect result\n"); + + if ((profile->use_fwsup == INFF_PROFILE_FWSUP_1X && + inff_has_pmkid(conn_params.req_ie, conn_params.req_ie_len, NULL)) || + profile->use_fwsup == INFF_PROFILE_FWSUP_PSK || + profile->use_fwsup == INFF_PROFILE_FWSUP_SAE) { + cfg80211_port_authorized(ndev, profile->bssid, NULL, 0, GFP_KERNEL); + inff_dbg(CONN, "Report port authorized\n"); + } + + } else if (cfg->pfn_enable && completed) { + cfg->pfn_connection = 1; + } + inff_dbg(TRACE, "Exit\n"); + return 0; +} + +static void inff_init_conf(struct inff_cfg80211_conf *conf) +{ + conf->frag_threshold = (u32)-1; + conf->rts_threshold = (u32)-1; + conf->retry_short = (u32)-1; + conf->retry_long = (u32)-1; +} + +static void inff_deinit_priv_mem(struct inff_cfg80211_info *cfg) +{ + kfree(cfg->conf); + cfg->conf = NULL; + kfree(cfg->extra_buf); + cfg->extra_buf = NULL; + kfree(cfg->wowl.nd); + cfg->wowl.nd = NULL; + kfree(cfg->wowl.nd_info); + cfg->wowl.nd_info = NULL; + kfree(cfg->escan_info.escan_buf); + cfg->escan_info.escan_buf = NULL; +} + +static s32 inff_init_priv_mem(struct inff_cfg80211_info *cfg) +{ + cfg->conf = kzalloc(sizeof(*cfg->conf), GFP_KERNEL); + if (!cfg->conf) + goto init_priv_mem_out; + cfg->extra_buf = kzalloc(WL_EXTRA_BUF_MAX, GFP_KERNEL); + if (!cfg->extra_buf) + goto init_priv_mem_out; + cfg->wowl.nd = kzalloc(sizeof(*cfg->wowl.nd) + sizeof(u32), GFP_KERNEL); + if (!cfg->wowl.nd) + goto init_priv_mem_out; + cfg->wowl.nd_info = kzalloc(sizeof(*cfg->wowl.nd_info) + + sizeof(struct cfg80211_wowlan_nd_match *), + GFP_KERNEL); + if (!cfg->wowl.nd_info) + goto init_priv_mem_out; + cfg->escan_info.escan_buf = kzalloc(INFF_ESCAN_BUF_SIZE, GFP_KERNEL); + if (!cfg->escan_info.escan_buf) + goto init_priv_mem_out; + + return 0; + +init_priv_mem_out: + inff_deinit_priv_mem(cfg); + + return -ENOMEM; +} + +static s32 wl_init_priv(struct inff_cfg80211_info *cfg) +{ + s32 err = 0; + + cfg->scan_request = NULL; + cfg->pwr_save = true; + cfg->dongle_up = false; /* dongle is not up yet */ + err = inff_init_priv_mem(cfg); + if (err) + return err; + inff_register_event_handlers(cfg); + mutex_init(&cfg->usr_sync); + inff_init_escan(cfg); + inff_init_conf(cfg->conf); + inff_init_wmm_prio(cfg->ac_priority); + init_completion(&cfg->vif_disabled); + return err; +} + +static void wl_deinit_priv(struct inff_cfg80211_info *cfg) +{ + cfg->dongle_up = false; /* dongle down */ + inff_abort_scanning(cfg); + inff_deinit_priv_mem(cfg); + inff_clear_assoc_ies(cfg); +} + +static void init_vif_event(struct inff_cfg80211_vif_event *event) +{ + init_waitqueue_head(&event->vif_wq); + spin_lock_init(&event->vif_event_lock); +} + +static s32 inff_dongle_roam(struct inff_if *ifp) +{ + struct inff_pub *drvr = ifp->drvr; + s32 err; + u32 bcn_timeout; + __le32 roamtrigger[2]; + __le32 roam_delta[2]; + __le32 bandlist[4]; + u32 n_bands; + int i; + + /* Configure beacon timeout value based upon roaming setting */ + if (ifp->drvr->settings->roamoff < INFF_ROAMOFF_DISABLE || + ifp->drvr->settings->roamoff >= INFF_ROAMOFF_MAX) { + iphy_err(drvr, + "roamoff setting is incorrect (%d), reset it\n", + ifp->drvr->settings->roamoff); + ifp->drvr->settings->roamoff = INFF_ROAMOFF_DISABLE; + } + + if (ifp->drvr->settings->roamoff) + bcn_timeout = INFF_DEFAULT_BCN_TIMEOUT_ROAM_OFF; + else + bcn_timeout = INFF_DEFAULT_BCN_TIMEOUT_ROAM_ON; + err = inff_fil_iovar_int_set(ifp, "bcn_timeout", bcn_timeout); + if (err) { + iphy_err(drvr, "bcn_timeout error (%d)\n", err); + goto roam_setup_done; + } + + /* Enable/Disable built-in roaming to allow supplicant to take care of + * roaming. + */ + inff_dbg(INFO, "Internal Roaming = %s, Mode:%d\n", + ifp->drvr->settings->roamoff ? "Off" : "On", ifp->drvr->settings->roamoff); + err = inff_fil_iovar_int_set(ifp, "roam_off", + ifp->drvr->settings->roamoff ? 1 : 0); + if (err) { + iphy_err(drvr, "roam_off error (%d)\n", err); + goto roam_setup_done; + } + + err = inff_fil_cmd_data_get(ifp, INFF_C_GET_BANDLIST, &bandlist, + sizeof(bandlist)); + if (err) { + iphy_err(drvr, "could not obtain band info: err=%d\n", err); + goto roam_setup_done; + } + /* To enhance compatibility set each band's roam properties instead of + * using all band. BAND_5G is 1, BAND_2G is 2 and BAND_6G is 3. + */ + n_bands = le32_to_cpu(bandlist[0]); + for (i = 1; i <= n_bands; i++) { + roamtrigger[0] = cpu_to_le32(WL_ROAM_TRIGGER_LEVEL); + roamtrigger[1] = cpu_to_le32(bandlist[i]); + err = inff_fil_cmd_data_set(ifp, INFF_C_SET_ROAM_TRIGGER, + (void *)roamtrigger, sizeof(roamtrigger)); + if (err) + iphy_err(drvr, "WLC_SET_ROAM_TRIGGER error (%d), band %d\n", + err, bandlist[i]); + + roam_delta[0] = cpu_to_le32(WL_ROAM_DELTA); + roam_delta[1] = cpu_to_le32(bandlist[i]); + err = inff_fil_cmd_data_set(ifp, INFF_C_SET_ROAM_DELTA, + (void *)roam_delta, sizeof(roam_delta)); + if (err) + iphy_err(drvr, "WLC_SET_ROAM_DELTA error (%d), band %d\n", + err, bandlist[i]); + } + + return 0; + +roam_setup_done: + return err; +} + +static s32 +inff_dongle_scantime(struct inff_if *ifp) +{ + struct inff_pub *drvr = ifp->drvr; + s32 err = 0; + + err = inff_fil_cmd_int_set(ifp, INFF_C_SET_SCAN_CHANNEL_TIME, + INFF_SCAN_CHANNEL_TIME); + if (err) { + iphy_err(drvr, "Scan assoc time error (%d)\n", err); + goto dongle_scantime_out; + } + err = inff_fil_cmd_int_set(ifp, INFF_C_SET_SCAN_UNASSOC_TIME, + INFF_SCAN_UNASSOC_TIME); + if (err) { + iphy_err(drvr, "Scan unassoc time error (%d)\n", err); + goto dongle_scantime_out; + } + + err = inff_fil_cmd_int_set(ifp, INFF_C_SET_SCAN_PASSIVE_TIME, + INFF_SCAN_PASSIVE_TIME); + if (err) { + iphy_err(drvr, "Scan passive time error (%d)\n", err); + goto dongle_scantime_out; + } + +dongle_scantime_out: + return err; +} + +static void inff_update_bw40_channel_flag(struct ieee80211_channel *channel, + struct inff_chan *ch) +{ + u32 ht40_flag; + + ht40_flag = channel->flags & IEEE80211_CHAN_NO_HT40; + if (ch->sb == INFF_CHAN_SB_U) { + if (ht40_flag == IEEE80211_CHAN_NO_HT40) + channel->flags &= ~IEEE80211_CHAN_NO_HT40; + channel->flags |= IEEE80211_CHAN_NO_HT40PLUS; + } else { + /* It should be one of + * IEEE80211_CHAN_NO_HT40 or + * IEEE80211_CHAN_NO_HT40PLUS + */ + channel->flags &= ~IEEE80211_CHAN_NO_HT40; + if (ht40_flag == IEEE80211_CHAN_NO_HT40) + channel->flags |= IEEE80211_CHAN_NO_HT40MINUS; + } +} + +static void inff_wiphy_reset_band_and_channel(struct wiphy *wiphy) +{ + enum nl80211_band band; + struct ieee80211_supported_band *wiphy_band = NULL; + + for (band = 0; band < NUM_NL80211_BANDS; band++) { + wiphy_band = wiphy->bands[band]; + if (!wiphy_band) + continue; + + kfree(wiphy_band->channels); + wiphy_band->channels = NULL; + kfree(wiphy_band); + wiphy->bands[band] = NULL; + } +} + +static int inff_fill_band_with_default_chanlist(struct wiphy *wiphy, struct inff_if *ifp) +{ + struct inff_pub *drvr = ifp->drvr; + struct ieee80211_supported_band *band; + int err, i; + __le32 bandlist[4]; + u32 n_bands; + + err = inff_fil_cmd_data_get(ifp, INFF_C_GET_BANDLIST, &bandlist, + sizeof(bandlist)); + if (err) { + iphy_err(drvr, "could not obtain band info: err=%d\n", err); + return err; + } + + inff_wiphy_reset_band_and_channel(wiphy); + + n_bands = le32_to_cpu(bandlist[0]); + for (i = 1; i <= n_bands && i < ARRAY_SIZE(bandlist); i++) { + if (bandlist[i] == cpu_to_le32(WLC_BAND_2G)) { + band = kmemdup(&__wl_band_2ghz, sizeof(__wl_band_2ghz), + GFP_KERNEL); + if (!band) + goto mem_err; + band->channels = kmemdup(&__wl_2ghz_channels, + sizeof(__wl_2ghz_channels), + GFP_KERNEL); + if (!band->channels) + goto mem_err; + + /* restore 2G channels info */ + band->n_channels = ARRAY_SIZE(__wl_2ghz_channels); + wiphy->bands[NL80211_BAND_2GHZ] = band; + } else if (bandlist[i] == cpu_to_le32(WLC_BAND_5G)) { + band = kmemdup(&__wl_band_5ghz, sizeof(__wl_band_5ghz), + GFP_KERNEL); + if (!band) + goto mem_err; + band->channels = kmemdup(&__wl_5ghz_channels, + sizeof(__wl_5ghz_channels), + GFP_KERNEL); + if (!band->channels) + goto mem_err; + + /* restore 5G channels info */ + band->n_channels = ARRAY_SIZE(__wl_5ghz_channels); + wiphy->bands[NL80211_BAND_5GHZ] = band; + } else if (bandlist[i] == cpu_to_le32(WLC_BAND_6G) && + inff_feat_is_6ghz_enabled(ifp)) { + band = kmemdup(&__wl_band_6ghz, sizeof(__wl_band_6ghz), + GFP_KERNEL); + if (!band) + goto mem_err; + band->channels = kmemdup(&__wl_6ghz_channels, + sizeof(__wl_6ghz_channels), + GFP_KERNEL); + if (!band->channels) + goto mem_err; + + /* restore 6G channels info */ + band->n_channels = ARRAY_SIZE(__wl_6ghz_channels); + wiphy->bands[NL80211_BAND_6GHZ] = band; + } + } + + if (wiphy->bands[NL80211_BAND_5GHZ] && + inff_feat_is_enabled(ifp, INFF_FEAT_DOT11H)) + wiphy_ext_feature_set(wiphy, + NL80211_EXT_FEATURE_DFS_OFFLOAD); + + return 0; + +mem_err: + inff_wiphy_reset_band_and_channel(wiphy); + return -ENOMEM; +} + +static void inff_wiphy_rm_disabled_band_and_channel(struct wiphy *wiphy) +{ + enum nl80211_band band; + struct ieee80211_supported_band *wiphy_band = NULL; + struct ieee80211_channel *cur = NULL; + struct ieee80211_channel *next = NULL; + u32 n_ch = 0; + u32 i, j; + + for (band = 0; band < NUM_NL80211_BANDS; band++) { + wiphy_band = wiphy->bands[band]; + if (!wiphy_band) + continue; + + n_ch = wiphy_band->n_channels; + for (i = 0; i < n_ch;) { + cur = &wiphy_band->channels[i]; + if (cur->flags == IEEE80211_CHAN_DISABLED) { + for (j = i; j < n_ch - 1; j++) { + cur = &wiphy_band->channels[j]; + next = &wiphy_band->channels[j + 1]; + memcpy(cur, next, sizeof(*cur)); + } + n_ch--; + } else { + i++; + } + } + + wiphy_band->n_channels = n_ch; + if (!n_ch) { + kfree(wiphy_band->channels); + wiphy_band->channels = NULL; + kfree(wiphy_band); + wiphy->bands[band] = NULL; + } + } +} + +static int inff_construct_chaninfo(struct inff_cfg80211_info *cfg, + u32 bw_cap[]) +{ + struct wiphy *wiphy = cfg_to_wiphy(cfg); + struct inff_pub *drvr = cfg->pub; + struct inff_if *ifp = inff_get_ifp(drvr, 0); + struct ieee80211_supported_band *band; + struct ieee80211_channel *channel; + struct inff_chanspec_list *list; + struct inff_chan ch; + int err; + u8 *pbuf; + u32 i, j; + u32 total; + u32 chaninfo; + + pbuf = kzalloc(INFF_DCMD_MEDLEN, GFP_KERNEL); + + if (!pbuf) + return -ENOMEM; + + list = (struct inff_chanspec_list *)pbuf; + + err = inff_fil_iovar_data_get(ifp, "chanspecs", pbuf, + INFF_DCMD_MEDLEN); + if (err) { + iphy_err(drvr, "get chanspecs error (%d)\n", err); + goto fail_pbuf; + } + + err = inff_fill_band_with_default_chanlist(wiphy, ifp); + if (err) { + iphy_err(drvr, "could not retrore band and channels: err=%d\n", err); + goto fail_pbuf; + } + + band = wiphy->bands[NL80211_BAND_2GHZ]; + if (band) { + for (i = 0; i < band->n_channels; i++) + band->channels[i].flags = IEEE80211_CHAN_DISABLED; + } + band = wiphy->bands[NL80211_BAND_5GHZ]; + if (band) { + for (i = 0; i < band->n_channels; i++) + band->channels[i].flags = IEEE80211_CHAN_DISABLED; + } + band = wiphy->bands[NL80211_BAND_6GHZ]; + if (band) { + for (i = 0; i < band->n_channels; i++) + band->channels[i].flags = IEEE80211_CHAN_DISABLED; + } + + total = le32_to_cpu(list->count); + if (total > INFF_MAX_CHANSPEC_LIST) { + iphy_err(drvr, "Invalid count of channel Spec. (%u)\n", + total); + err = -EINVAL; + goto fail_pbuf; + } + + for (i = 0; i < total; i++) { + ch.chspec = (u16)le32_to_cpu(list->element[i]); + cfg->d11inf.decchspec(&ch); + + if (ch.band == INFF_CHAN_BAND_2G) { + band = wiphy->bands[NL80211_BAND_2GHZ]; + } else if (ch.band == INFF_CHAN_BAND_5G) { + band = wiphy->bands[NL80211_BAND_5GHZ]; + } else if (ch.band == INFF_CHAN_BAND_6G) { + if (inff_feat_is_6ghz_enabled(ifp)) { + band = wiphy->bands[NL80211_BAND_6GHZ]; + } else { + inff_dbg(INFO, "Disabled channel Spec. 0x%x.\n", + ch.chspec); + continue; + } + } else { + iphy_err(drvr, "Invalid channel Spec. 0x%x.\n", + ch.chspec); + continue; + } + if (!band) + continue; + if (!(bw_cap[band->band] & WLC_BW_40MHZ_BIT) && + ch.bw == INFF_CHAN_BW_40) + continue; + if (!(bw_cap[band->band] & WLC_BW_80MHZ_BIT) && + ch.bw == INFF_CHAN_BW_80) + continue; + + channel = NULL; + for (j = 0; j < band->n_channels; j++) { + if (band->channels[j].hw_value == ch.control_ch_num) { + channel = &band->channels[j]; + break; + } + } + if (!channel) { + /* It seems firmware supports some channel we never + * considered. Something new in IEEE standard? + */ + iphy_err(drvr, "Ignoring unexpected firmware channel %d\n", + ch.control_ch_num); + continue; + } + + if (channel->orig_flags & IEEE80211_CHAN_DISABLED) + continue; + + /* assuming the chanspecs order is HT20, + * HT40 upper, HT40 lower, and VHT80. + */ + switch (ch.bw) { + case INFF_CHAN_BW_160: + channel->flags &= ~IEEE80211_CHAN_NO_160MHZ; + break; + case INFF_CHAN_BW_80: + channel->flags &= ~IEEE80211_CHAN_NO_80MHZ; + break; + case INFF_CHAN_BW_40: + inff_update_bw40_channel_flag(channel, &ch); + break; + default: + wiphy_warn(wiphy, "Firmware reported unsupported bandwidth %d\n", + ch.bw); + fallthrough; + case INFF_CHAN_BW_20: + /* enable the channel and disable other bandwidths + * for now as mentioned order assure they are enabled + * for subsequent chanspecs. + */ + channel->flags = IEEE80211_CHAN_NO_HT40 | + IEEE80211_CHAN_NO_80MHZ | + IEEE80211_CHAN_NO_160MHZ; + ch.bw = INFF_CHAN_BW_20; + cfg->d11inf.encchspec(&ch); + chaninfo = ch.chspec; + err = inff_fil_bsscfg_int_get(ifp, "per_chan_info", + &chaninfo); + if (!err) { + if (chaninfo & WL_CHAN_RADAR) + channel->flags |= + (IEEE80211_CHAN_RADAR | + IEEE80211_CHAN_NO_IR); + if (chaninfo & WL_CHAN_PASSIVE) + channel->flags |= + IEEE80211_CHAN_NO_IR; + } + } + } + + /* Remove disabled channels and band to avoid unexpected restore. */ + inff_wiphy_rm_disabled_band_and_channel(wiphy); + +fail_pbuf: + kfree(pbuf); + return err; +} + +static int inff_enable_bw40_2g(struct inff_cfg80211_info *cfg) +{ + struct inff_pub *drvr = cfg->pub; + struct inff_if *ifp = inff_get_ifp(drvr, 0); + struct ieee80211_supported_band *band; + struct inff_fil_bwcap_le band_bwcap; + struct inff_chanspec_list *list; + u8 *pbuf; + u32 val; + int err; + struct inff_chan ch; + u32 num_chan; + int i, j; + + /* verify support for bw_cap command */ + val = WLC_BAND_5G; + err = inff_fil_iovar_int_get(ifp, "bw_cap", &val); + + if (!err) { + /* only set 2G bandwidth using bw_cap command */ + band_bwcap.band = cpu_to_le32(WLC_BAND_2G); + band_bwcap.bw_cap = cpu_to_le32(WLC_BW_CAP_40MHZ); + err = inff_fil_iovar_data_set(ifp, "bw_cap", &band_bwcap, + sizeof(band_bwcap)); + } else { + inff_dbg(INFO, "fallback to mimo_bw_cap\n"); + val = WLC_N_BW_40ALL; + err = inff_fil_iovar_int_set(ifp, "mimo_bw_cap", val); + } + + if (!err) { + /* update channel info in 2G band */ + pbuf = kzalloc(INFF_DCMD_MEDLEN, GFP_KERNEL); + + if (!pbuf) + return -ENOMEM; + + ch.band = INFF_CHAN_BAND_2G; + ch.bw = INFF_CHAN_BW_40; + ch.sb = INFF_CHAN_SB_NONE; + ch.chnum = 0; + cfg->d11inf.encchspec(&ch); + + /* pass encoded chanspec in query */ + *(__le16 *)pbuf = cpu_to_le16(ch.chspec); + + err = inff_fil_iovar_data_get(ifp, "chanspecs", pbuf, + INFF_DCMD_MEDLEN); + if (err) { + iphy_err(drvr, "get chanspecs error (%d)\n", err); + kfree(pbuf); + return err; + } + + band = cfg_to_wiphy(cfg)->bands[NL80211_BAND_2GHZ]; + list = (struct inff_chanspec_list *)pbuf; + num_chan = le32_to_cpu(list->count); + if (num_chan > INFF_MAX_CHANSPEC_LIST) { + iphy_err(drvr, "Invalid count of channel Spec. (%u)\n", + num_chan); + kfree(pbuf); + return -EINVAL; + } + + for (i = 0; i < num_chan; i++) { + ch.chspec = (u16)le32_to_cpu(list->element[i]); + cfg->d11inf.decchspec(&ch); + if (WARN_ON(ch.band != INFF_CHAN_BAND_2G)) + continue; + if (WARN_ON(ch.bw != INFF_CHAN_BW_40)) + continue; + for (j = 0; j < band->n_channels; j++) { + if (band->channels[j].hw_value == ch.control_ch_num) + break; + } + if (WARN_ON(j == band->n_channels)) + continue; + + inff_update_bw40_channel_flag(&band->channels[j], &ch); + } + kfree(pbuf); + } + return err; +} + +static void inff_get_bwcap(struct inff_if *ifp, u32 bw_cap[]) +{ + struct inff_pub *drvr = ifp->drvr; + u32 band, mimo_bwcap; + int err; + + band = WLC_BAND_2G; + err = inff_fil_iovar_int_get(ifp, "bw_cap", &band); + if (!err) { + bw_cap[NL80211_BAND_2GHZ] = band; + band = WLC_BAND_5G; + err = inff_fil_iovar_int_get(ifp, "bw_cap", &band); + if (!err) { + bw_cap[NL80211_BAND_5GHZ] = band; + + if (!inff_feat_is_6ghz_enabled(ifp)) + return; + + band = WLC_BAND_6G; + err = inff_fil_iovar_int_get(ifp, "bw_cap", &band); + if (!err) { + bw_cap[NL80211_BAND_6GHZ] = band; + return; + } + return; + } + WARN_ON(1); + return; + } + inff_dbg(INFO, "fallback to mimo_bw_cap info\n"); + mimo_bwcap = 0; + err = inff_fil_iovar_int_get(ifp, "mimo_bw_cap", &mimo_bwcap); + if (err) + /* assume 20MHz if firmware does not give a clue */ + mimo_bwcap = WLC_N_BW_20ALL; + + switch (mimo_bwcap) { + case WLC_N_BW_40ALL: + bw_cap[NL80211_BAND_2GHZ] |= WLC_BW_40MHZ_BIT; + fallthrough; + case WLC_N_BW_20IN2G_40IN5G: + bw_cap[NL80211_BAND_5GHZ] |= WLC_BW_40MHZ_BIT; + fallthrough; + case WLC_N_BW_20ALL: + bw_cap[NL80211_BAND_2GHZ] |= WLC_BW_20MHZ_BIT; + bw_cap[NL80211_BAND_5GHZ] |= WLC_BW_20MHZ_BIT; + break; + default: + iphy_err(drvr, "invalid mimo_bw_cap value\n"); + } +} + +static void inff_ht_update_wiphy_cap(struct inff_if *ifp, + u32 bw_cap[2], u32 nchain) +{ + struct inff_pub *drvr = ifp->drvr; + struct wiphy *wiphy = drvr->wiphy; + struct ieee80211_supported_band *band; + u32 nmode = 0, sgi_rx = 1; + int i = 0; + + /* HT mode */ + if (inff_fil_iovar_int_get(ifp, "nmode", &nmode)) + iphy_err(drvr, "nmode error\n"); + + if (!nmode) + return; + + inff_dbg(INFO, "HT Enabled\n"); + + if (inff_fil_iovar_int_get(ifp, "sgi_rx", &sgi_rx)) + iphy_err(drvr, "sgi_rx error\n"); + + /* Update HT Capab for each Band */ + for (i = 0; i < ARRAY_SIZE(wiphy->bands); i++) { + band = wiphy->bands[i]; + if (!band) + continue; + + switch (band->band) { + case NL80211_BAND_6GHZ: + break; + + case NL80211_BAND_5GHZ: + /* Band 5GHz supports HT, so */ + fallthrough; + + case NL80211_BAND_2GHZ: + /* Band 2GHz supports HT */ + band->ht_cap.ht_supported = true; + + /* Bit 0 represents HT SGI 20MHz */ + if ((sgi_rx & BIT(0)) && + (bw_cap[band->band] & WLC_BW_20MHZ_BIT)) + band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_20; + + /* Bit 1 represents HT SGI 40MHz */ + if ((sgi_rx & BIT(1)) && + (bw_cap[band->band] & WLC_BW_40MHZ_BIT)) { + band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_40; + band->ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; + } + + band->ht_cap.cap |= IEEE80211_HT_CAP_DSSSCCK40; + band->ht_cap.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; + band->ht_cap.ampdu_density = IEEE80211_HT_MPDU_DENSITY_16; + memset(band->ht_cap.mcs.rx_mask, 0xff, nchain); + band->ht_cap.mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; + break; + + default: + break; + } + } +} + +static __le16 inff_vht_get_mcs_map(u32 nchain, enum ieee80211_vht_mcs_support supp) +{ + u16 mcs_map; + int i; + + for (i = 0, mcs_map = 0xFFFF; i < nchain; i++) + mcs_map = (mcs_map << 2) | supp; + + return cpu_to_le16(mcs_map); +} + +static void inff_vht_update_wiphy_cap(struct inff_if *ifp, + u32 bw_cap[2], u32 nchain) +{ + struct inff_pub *drvr = ifp->drvr; + struct wiphy *wiphy = drvr->wiphy; + struct ieee80211_supported_band *band; + u32 vhtmode = 0; + u32 txstreams = 0; + u32 txbf_bfe_cap = 0; + u32 txbf_bfr_cap = 0; + __le16 mcs_map; + int i = 0; + + /* VHT mode */ + if (inff_fil_iovar_int_get(ifp, "vhtmode", &vhtmode)) + iphy_err(drvr, "vhtmode error\n"); + + if (!vhtmode) + return; + + inff_dbg(INFO, "VHT Enabled\n"); + + /* Create a MAP with MCS for each Stream */ + mcs_map = inff_vht_get_mcs_map(nchain, IEEE80211_VHT_MCS_SUPPORT_0_9); + + if (inff_fil_iovar_int_get(ifp, "txstreams", &txstreams)) + iphy_err(drvr, "txstreams error\n"); + + /* Beamforming support information */ + if (inff_fil_iovar_int_get(ifp, "txbf_bfe_cap", &txbf_bfe_cap)) + iphy_err(drvr, "txbf_bfe_cap error\n"); + if (inff_fil_iovar_int_get(ifp, "txbf_bfr_cap", &txbf_bfr_cap)) + iphy_err(drvr, "txbf_bfw_cap error\n"); + + /* Update VHT Capab for each Band */ + for (i = 0; i < ARRAY_SIZE(wiphy->bands); i++) { + band = wiphy->bands[i]; + if (!band) + continue; + + switch (band->band) { + case NL80211_BAND_6GHZ: + break; + + case NL80211_BAND_5GHZ: + /* Band 5GHz supports VHT */ + band->vht_cap.vht_supported = true; + band->vht_cap.vht_mcs.rx_mcs_map = mcs_map; + band->vht_cap.vht_mcs.tx_mcs_map = mcs_map; + + if (bw_cap[band->band] & WLC_BW_80MHZ_BIT) + band->vht_cap.cap |= IEEE80211_VHT_CAP_SHORT_GI_80; + + if (txbf_bfe_cap & INFF_TXBF_SU_BFE_CAP) + band->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE; + if (txbf_bfe_cap & INFF_TXBF_MU_BFE_CAP) + band->vht_cap.cap |= IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE; + if (txbf_bfr_cap & INFF_TXBF_SU_BFR_CAP) + band->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE; + if (txbf_bfr_cap & INFF_TXBF_MU_BFR_CAP) + band->vht_cap.cap |= IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE; + + if ((txbf_bfe_cap || txbf_bfr_cap) && txstreams > 1) { + band->vht_cap.cap |= + (2 << IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT); + band->vht_cap.cap |= ((txstreams - 1) << + IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_SHIFT); + band->vht_cap.cap |= + IEEE80211_VHT_CAP_VHT_LINK_ADAPTATION_VHT_MRQ_MFB; + } + break; + + case NL80211_BAND_2GHZ: + break; + + default: + break; + } + } +} + +static int inff_setup_wiphybands(struct inff_cfg80211_info *cfg) +{ + struct inff_pub *drvr = cfg->pub; + struct inff_if *ifp = inff_get_ifp(drvr, 0); + u32 rxchain, nchain = 1; + u32 bw_cap[4] = { WLC_BW_20MHZ_BIT, /* 2GHz */ + WLC_BW_20MHZ_BIT, /* 5GHz */ + 0, /* 60GHz */ + 0 }; /* 6GHz */ + int err; + + inff_get_bwcap(ifp, bw_cap); + inff_dbg(INFO, "bw_cap=[2G(%d), 5G(%d), 6G(%d)]\n", + bw_cap[NL80211_BAND_2GHZ], bw_cap[NL80211_BAND_5GHZ], + bw_cap[NL80211_BAND_6GHZ]); + + err = inff_fil_iovar_int_get(ifp, "rxchain", &rxchain); + if (err) + iphy_err(drvr, "rxchain error (%d)\n", err); + else + for (nchain = 0; rxchain; nchain++) + rxchain = rxchain & (rxchain - 1); + inff_dbg(INFO, "nchain=%d\n", nchain); + + err = inff_construct_chaninfo(cfg, bw_cap); + if (err) { + iphy_err(drvr, "inff_construct_chaninfo failed (%d)\n", err); + return err; + } + + /* HT Capability Registration */ + inff_ht_update_wiphy_cap(ifp, bw_cap, nchain); + + /* VHT Capability Registration */ + inff_vht_update_wiphy_cap(ifp, bw_cap, nchain); + + /* HE Capability Registration */ + inff_he_update_wiphy_cap(ifp); + + /* EHT Capability Registration */ + inff_eht_update_wiphy_cap(ifp); + + return 0; +} + +static const struct ieee80211_txrx_stypes +inff_txrx_stypes[NUM_NL80211_IFTYPES] = { + [NL80211_IFTYPE_STATION] = { + .tx = 0xffff, + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_AUTH >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) + }, + [NL80211_IFTYPE_P2P_CLIENT] = { + .tx = 0xffff, + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) + }, + [NL80211_IFTYPE_P2P_GO] = { + .tx = 0xffff, + .rx = BIT(IEEE80211_STYPE_ASSOC_REQ >> 4) | + BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) | + BIT(IEEE80211_STYPE_DISASSOC >> 4) | + BIT(IEEE80211_STYPE_AUTH >> 4) | + BIT(IEEE80211_STYPE_DEAUTH >> 4) | + BIT(IEEE80211_STYPE_ACTION >> 4) + }, + [NL80211_IFTYPE_P2P_DEVICE] = { + .tx = 0xffff, + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) + }, + [NL80211_IFTYPE_AP] = { + .tx = 0xffff, + .rx = BIT(IEEE80211_STYPE_ASSOC_REQ >> 4) | + BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) | + BIT(IEEE80211_STYPE_DISASSOC >> 4) | + BIT(IEEE80211_STYPE_AUTH >> 4) | + BIT(IEEE80211_STYPE_DEAUTH >> 4) | + BIT(IEEE80211_STYPE_ACTION >> 4) + } +}; + +/** + * inff_setup_ifmodes() - determine interface modes and combinations. + * + * @wiphy: wiphy object. + * @ifp: interface object needed for feat module api. + * + * The interface modes and combinations are determined dynamically here + * based on firmware functionality. + * + * no p2p and no mbss: + * + * #STA <= 1, #AP <= 1, channels = 1, 2 total + * + * no p2p and mbss: + * + * #STA <= 1, #AP <= 1, channels = 1, 2 total + * #AP <= 4, matching BI, channels = 1, 4 total + * + * no p2p and rsdb: + * #STA <= 1, #AP <= 2, channels = 2, 4 total + * + * p2p, no mchan, and mbss: + * + * #STA <= 1, #P2P-DEV <= 1, #{P2P-CL, P2P-GO} <= 1, channels = 1, 3 total + * #STA <= 1, #P2P-DEV <= 1, #AP <= 1, #P2P-CL <= 1, channels = 1, 4 total + * #AP <= 4, matching BI, channels = 1, 4 total + * + * p2p, mchan, and mbss: + * + * #STA <= 2, #P2P-DEV <= 1, #{P2P-CL, P2P-GO} <= 1, channels = 2, 3 total + * #STA <= 1, #P2P-DEV <= 1, #AP <= 1, #P2P-CL <= 1, channels = 1, 4 total + * #AP <= 4, matching BI, channels = 1, 4 total + * + * p2p, rsdb, and no mbss: + * #STA <= 1, #P2P-DEV <= 1, #{P2P-CL, P2P-GO} <= 2, AP <= 2, + * channels = 2, 4 total + * + * Return: 0 on success, negative errno on failure + */ +static int inff_setup_ifmodes(struct wiphy *wiphy, struct inff_if *ifp) +{ + struct ieee80211_iface_combination *combo = NULL; + struct ieee80211_iface_limit *c0_limits = NULL; + struct ieee80211_iface_limit *p2p_limits = NULL; + struct ieee80211_iface_limit *mbss_limits = NULL; + bool mon_flag, mbss, p2p, rsdb, mchan; + bool wlan_sense; + int i, c, n_combos, n_limits, p2p_num_infs; + + mon_flag = inff_feat_is_enabled(ifp, INFF_FEAT_MONITOR_FLAG); + mbss = inff_feat_is_enabled(ifp, INFF_FEAT_MBSS); + p2p = inff_feat_is_enabled(ifp, INFF_FEAT_P2P); + rsdb = inff_feat_is_enabled(ifp, INFF_FEAT_RSDB); + mchan = inff_feat_is_enabled(ifp, INFF_FEAT_MCHAN); + wlan_sense = inff_feat_is_enabled(ifp, INFF_FEAT_WLAN_SENSE); + + n_combos = 1 + !!(p2p && !rsdb) + !!mbss; + combo = kcalloc(n_combos, sizeof(*combo), GFP_KERNEL); + if (!combo) + goto err; + + wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC) | + BIT(NL80211_IFTYPE_AP); + if (mon_flag) + wiphy->interface_modes |= BIT(NL80211_IFTYPE_MONITOR); + if (p2p) + wiphy->interface_modes |= BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_P2P_GO) | + BIT(NL80211_IFTYPE_P2P_DEVICE); + if (wlan_sense) + wiphy->interface_modes |= BIT(NL80211_IFTYPE_WLAN_SENSE); + + c = 0; + i = 0; + n_limits = 1 + mon_flag + (p2p ? 2 : 0) + (rsdb || !p2p); + n_limits += wlan_sense; + + c0_limits = kcalloc(n_limits, sizeof(*c0_limits), GFP_KERNEL); + if (!c0_limits) + goto err; + + combo[c].num_different_channels = 1 + (rsdb || (p2p && mchan)); + c0_limits[i].max = 1 + (p2p && mchan); + c0_limits[i++].types = BIT(NL80211_IFTYPE_STATION); + if (mon_flag) { + c0_limits[i].max = 1; + c0_limits[i++].types = BIT(NL80211_IFTYPE_MONITOR); + } + if (p2p) { + c0_limits[i].max = 1; + c0_limits[i++].types = BIT(NL80211_IFTYPE_P2P_DEVICE); + c0_limits[i].max = 1 + rsdb; + c0_limits[i++].types = BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_P2P_GO); + } + if (wlan_sense) { + c0_limits[i].max = 1; + c0_limits[i++].types = BIT(NL80211_IFTYPE_WLAN_SENSE); + } + if (p2p && rsdb) { + c0_limits[i].max = 2; + c0_limits[i++].types = BIT(NL80211_IFTYPE_AP); + combo[c].max_interfaces = 4; + } else if (p2p) { + combo[c].max_interfaces = i; + } else if (rsdb) { + c0_limits[i].max = 2; + c0_limits[i++].types = BIT(NL80211_IFTYPE_AP); + combo[c].max_interfaces = 3; + } else { + c0_limits[i].max = 1; + c0_limits[i++].types = BIT(NL80211_IFTYPE_AP); + combo[c].max_interfaces = i; + } + combo[c].n_limits = i; + combo[c].limits = c0_limits; + + if (p2p && !rsdb) { + c++; + i = 0; + p2p_num_infs = 4; + if (wlan_sense) + p2p_num_infs += 1; + + p2p_limits = kcalloc(p2p_num_infs, sizeof(*p2p_limits), GFP_KERNEL); + if (!p2p_limits) + goto err; + p2p_limits[i].max = 1; + p2p_limits[i++].types = BIT(NL80211_IFTYPE_STATION); + p2p_limits[i].max = 1; + p2p_limits[i++].types = BIT(NL80211_IFTYPE_AP); + p2p_limits[i].max = 1; + p2p_limits[i++].types = BIT(NL80211_IFTYPE_P2P_CLIENT); + p2p_limits[i].max = 1; + p2p_limits[i++].types = BIT(NL80211_IFTYPE_P2P_DEVICE); + if (wlan_sense) { + p2p_limits[i].max = 1; + p2p_limits[i++].types = BIT(NL80211_IFTYPE_WLAN_SENSE); + } + + combo[c].num_different_channels = 1; + combo[c].max_interfaces = i; + combo[c].n_limits = i; + combo[c].limits = p2p_limits; + } + + if (mbss) { + c++; + i = 0; + n_limits = 1 + mon_flag; + mbss_limits = kcalloc(n_limits, sizeof(*mbss_limits), + GFP_KERNEL); + if (!mbss_limits) + goto err; + mbss_limits[i].max = 4; + mbss_limits[i++].types = BIT(NL80211_IFTYPE_AP); + if (mon_flag) { + mbss_limits[i].max = 1; + mbss_limits[i++].types = BIT(NL80211_IFTYPE_MONITOR); + } + combo[c].beacon_int_infra_match = true; + combo[c].num_different_channels = 1; + combo[c].max_interfaces = 4 + mon_flag; + combo[c].n_limits = i; + combo[c].limits = mbss_limits; + } + + wiphy->n_iface_combinations = n_combos; + wiphy->iface_combinations = combo; + return 0; + +err: + kfree(c0_limits); + kfree(p2p_limits); + kfree(mbss_limits); + kfree(combo); + return -ENOMEM; +} + +#ifdef CONFIG_PM +static const struct wiphy_wowlan_support inff_wowlan_support = { + .flags = WIPHY_WOWLAN_MAGIC_PKT | WIPHY_WOWLAN_DISCONNECT, + .n_patterns = INFF_WOWL_MAXPATTERNS, + .pattern_max_len = INFF_WOWL_MAXPATTERNSIZE, + .pattern_min_len = 1, + .max_pkt_offset = 1500, +}; +#endif + +static void inff_wiphy_wowl_params(struct wiphy *wiphy, struct inff_if *ifp) +{ +#ifdef CONFIG_PM + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct wiphy_wowlan_support *wowl; + struct cfg80211_wowlan *inff_wowlan_config = NULL; + + wowl = kmemdup(&inff_wowlan_support, sizeof(inff_wowlan_support), + GFP_KERNEL); + if (!wowl) { + wiphy->wowlan = &inff_wowlan_support; + return; + } + + if (inff_feat_is_enabled(ifp, INFF_FEAT_PNO)) { + if (inff_feat_is_enabled(ifp, INFF_FEAT_WOWL_ND)) { + wowl->flags |= WIPHY_WOWLAN_NET_DETECT; + wowl->max_nd_match_sets = INFF_PNO_MAX_PFN_COUNT; + init_waitqueue_head(&cfg->wowl.nd_data_wait); + } + } + + /* for backward compatibility, retain INFF_FEAT_WOWL_GTK */ + if (inff_feat_is_enabled(ifp, INFF_FEAT_WOWL_GTK) || + inff_feat_is_enabled(ifp, INFF_FEAT_GTKO)) + wowl->flags |= WIPHY_WOWLAN_SUPPORTS_GTK_REKEY; + + if (inff_feat_is_enabled(ifp, INFF_FEAT_WOWL_GTK)) + wowl->flags |= WIPHY_WOWLAN_GTK_REKEY_FAILURE; + + wiphy->wowlan = wowl; + + /* wowlan_config structure report for kernels */ + inff_wowlan_config = kzalloc(sizeof(*inff_wowlan_config), + GFP_KERNEL); + if (inff_wowlan_config) { + inff_wowlan_config->any = false; + inff_wowlan_config->disconnect = true; + inff_wowlan_config->eap_identity_req = true; + inff_wowlan_config->four_way_handshake = true; + inff_wowlan_config->rfkill_release = false; + inff_wowlan_config->patterns = NULL; + inff_wowlan_config->n_patterns = 0; + inff_wowlan_config->tcp = NULL; + if (inff_feat_is_enabled(ifp, INFF_FEAT_WOWL_GTK)) + inff_wowlan_config->gtk_rekey_failure = true; + else + inff_wowlan_config->gtk_rekey_failure = false; + } else { + inff_err("Can not allocate memory for inff_wowlan_config\n"); + } + wiphy->wowlan_config = inff_wowlan_config; +#endif +} + +static int inff_setup_wiphy(struct wiphy *wiphy, struct inff_if *ifp) +{ + struct inff_pub *drvr = ifp->drvr; + const struct ieee80211_iface_combination *combo; + u16 max_interfaces = 0; + bool gscan; + int err, i; + enum nl80211_band band; + + wiphy->max_scan_ssids = WL_NUM_SCAN_MAX; + wiphy->max_scan_ie_len = INFF_SCAN_IE_LEN_MAX; + wiphy->max_num_pmkids = INFF_MAXPMKID; + + err = inff_setup_ifmodes(wiphy, ifp); + if (err) + return err; + + for (i = 0, combo = wiphy->iface_combinations; + i < wiphy->n_iface_combinations; i++, combo++) { + max_interfaces = max(max_interfaces, combo->max_interfaces); + } + + for (i = 0; i < max_interfaces && i < ARRAY_SIZE(drvr->addresses); + i++) { + u8 *addr = drvr->addresses[i].addr; + + memcpy(addr, drvr->mac, ETH_ALEN); + if (i) { + addr[0] |= BIT(1); + addr[ETH_ALEN - 1] ^= i; + } + } + wiphy->addresses = drvr->addresses; + wiphy->n_addresses = i; + + wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; + wiphy->cipher_suites = inff_cipher_suites; + wiphy->n_cipher_suites = ARRAY_SIZE(inff_cipher_suites); + if (!inff_feat_is_enabled(ifp, INFF_FEAT_MFP)) + wiphy->n_cipher_suites -= 4; + wiphy->bss_select_support = BIT(NL80211_BSS_SELECT_ATTR_RSSI) | + BIT(NL80211_BSS_SELECT_ATTR_BAND_PREF) | + BIT(NL80211_BSS_SELECT_ATTR_RSSI_ADJUST); + + wiphy->bss_param_support = WIPHY_BSS_PARAM_AP_ISOLATE; + + wiphy->flags |= WIPHY_FLAG_NETNS_OK | + WIPHY_FLAG_PS_ON_BY_DEFAULT | + WIPHY_FLAG_HAVE_AP_SME | + WIPHY_FLAG_OFFCHAN_TX | + WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL | + WIPHY_FLAG_SPLIT_SCAN_6GHZ; + if (inff_feat_is_enabled(ifp, INFF_FEAT_MLO)) + wiphy->flags |= WIPHY_FLAG_SUPPORTS_MLO; + + if (inff_feat_is_enabled(ifp, INFF_FEAT_TDLS)) + wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS; + if (ifp->drvr->settings->roamoff == INFF_ROAMOFF_DISABLE) + wiphy->flags |= WIPHY_FLAG_SUPPORTS_FW_ROAM; + if (inff_feat_is_enabled(ifp, INFF_FEAT_FWSUP)) { + wiphy_ext_feature_set(wiphy, + NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_PSK); + wiphy_ext_feature_set(wiphy, + NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_1X); + if (inff_feat_is_enabled(ifp, INFF_FEAT_OWE)) + wiphy_ext_feature_set(wiphy, + NL80211_EXT_FEATURE_OWE_OFFLOAD); + if (inff_feat_is_enabled(ifp, INFF_FEAT_SAE)) + wiphy_ext_feature_set(wiphy, + NL80211_EXT_FEATURE_SAE_OFFLOAD); + } + if (inff_feat_is_enabled(ifp, INFF_FEAT_FWAUTH)) { + wiphy_ext_feature_set(wiphy, + NL80211_EXT_FEATURE_4WAY_HANDSHAKE_AP_PSK); + if (inff_feat_is_enabled(ifp, INFF_FEAT_SAE)) + wiphy_ext_feature_set(wiphy, + NL80211_EXT_FEATURE_SAE_OFFLOAD_AP); + } + if (inff_feat_is_enabled(ifp, INFF_FEAT_SAE_EXT)) { + wiphy->features |= NL80211_FEATURE_SAE; + wiphy_ext_feature_set(wiphy, + NL80211_EXT_FEATURE_AP_PMKSA_CACHING); + } + wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_CQM_RSSI_LIST); + + wiphy->mgmt_stypes = inff_txrx_stypes; + wiphy->max_remain_on_channel_duration = 5000; + if (inff_feat_is_enabled(ifp, INFF_FEAT_PNO)) { + gscan = inff_feat_is_enabled(ifp, INFF_FEAT_GSCAN); + inff_pno_wiphy_params(wiphy, gscan); + } + /* vendor commands/events support */ + wiphy->vendor_commands = inff_vendor_cmds; + wiphy->n_vendor_commands = get_inff_num_vndr_cmds(); + + inff_wiphy_wowl_params(wiphy, ifp); + + /* first entry in bandlist is number of bands */ + for (band = 0; band < NUM_NL80211_BANDS; band++) + wiphy->bands[band] = NULL; + err = inff_fill_band_with_default_chanlist(wiphy, ifp); + if (err) { + iphy_err(drvr, "could not retrore band and channels: err=%d\n", err); + return err; + } + + wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_CQM_RSSI_LIST); + + inff_wiphy_pmsr_params(wiphy, ifp); + + wiphy_read_of_freq_limits(wiphy); + return 0; +} + +static s32 inff_config_dongle(struct inff_cfg80211_info *cfg) +{ + struct inff_pub *drvr = cfg->pub; + struct net_device *ndev; + struct wireless_dev *wdev; + struct inff_if *ifp; + s32 power_mode; + s32 eap_restrict; + s32 err = 0; + u32 wowl_config = 0; + + if (cfg->dongle_up) + return err; + + ndev = cfg_to_ndev(cfg); + wdev = ndev->ieee80211_ptr; + ifp = netdev_priv(ndev); + + /* make sure RF is ready for work */ + inff_fil_cmd_int_set(ifp, INFF_C_UP, 0); + + inff_dongle_scantime(ifp); + + power_mode = cfg->pwr_save ? ifp->drvr->settings->default_pm : PM_OFF; + err = inff_fil_cmd_int_set(ifp, INFF_C_SET_PM, power_mode); + if (err) + goto default_conf_out; + inff_dbg(INFO, "power save set to %s\n", + (power_mode ? "enabled" : "disabled")); + + err = inff_dongle_roam(ifp); + if (err) + goto default_conf_out; + + eap_restrict = ifp->drvr->settings->eap_restrict; + if (eap_restrict) { + err = inff_fil_iovar_int_set(ifp, "eap_restrict", + eap_restrict); + if (err) + inff_info("eap_restrict error (%d)\n", err); + } + err = inff_cfg80211_change_iface(wdev->wiphy, ndev, wdev->iftype, + NULL); + if (err) + goto default_conf_out; + + /* Configure user based power profile for offloads. + * Default profile is LOW_PWR. + */ + if (wdev->iftype == NL80211_IFTYPE_STATION && + inff_feat_is_enabled(ifp, INFF_FEAT_OFFLOADS)) { + inff_offload_config(ifp, inff_offload_feat, + inff_offload_prof, false); + + if (inff_feat_is_enabled(ifp, INFF_FEAT_ULP)) { + wowl_config = INFF_WOWL_DIS | INFF_WOWL_BCN; + err = inff_fil_iovar_int_set(ifp, "wowl", wowl_config); + if (err < 0) + inff_err("wowl_flags DIS,BCN not set"); + } + } else { + inff_offload_configure_arp_nd(ifp, true); + } + + err = inff_fil_cmd_int_set(ifp, INFF_C_SET_FAKEFRAG, 1); + if (err) { + iphy_err(drvr, "failed to set frameburst mode\n"); + goto default_conf_out; + } + + cfg->dongle_up = true; +default_conf_out: + + return err; +} + +static s32 __inff_cfg80211_up(struct inff_if *ifp) +{ + set_bit(INFF_VIF_STATUS_READY, &ifp->vif->sme_state); + + return inff_config_dongle(ifp->drvr->config); +} + +static s32 __inff_cfg80211_down(struct inff_if *ifp) +{ + struct inff_cfg80211_info *cfg = ifp->drvr->config; + /* Disable all offloads started on inff_config_dongle before + * link is brought down. + */ + if (inff_cfg80211_get_iftype(ifp) == NL80211_IFTYPE_STATION && + inff_feat_is_enabled(ifp, INFF_FEAT_OFFLOADS)) + inff_offload_config(ifp, inff_offload_feat, + inff_offload_prof, true); + + /* + * While going down, if associated with AP disassociate + * from AP to save power + */ + if (check_vif_up(ifp->vif)) { + inff_link_down(ifp->vif, WLAN_REASON_UNSPECIFIED, true); + + /* Make sure WPA_Supplicant receives all the event + * generated due to DISASSOC call to the fw to keep + * the state fw and WPA_Supplicant state consistent + */ + inff_delay(500); + cfg->dongle_up = false; + } + + inff_abort_scanning(cfg); + clear_bit(INFF_VIF_STATUS_READY, &ifp->vif->sme_state); + + return 0; +} + +s32 inff_cfg80211_up(struct net_device *ndev) +{ + struct inff_if *ifp = netdev_priv(ndev); + struct inff_cfg80211_info *cfg = ifp->drvr->config; + s32 err = 0; + + mutex_lock(&cfg->usr_sync); + err = __inff_cfg80211_up(ifp); + mutex_unlock(&cfg->usr_sync); + + return err; +} + +s32 inff_cfg80211_down(struct net_device *ndev) +{ + struct inff_if *ifp = netdev_priv(ndev); + struct inff_cfg80211_info *cfg = ifp->drvr->config; + s32 err = 0; + + mutex_lock(&cfg->usr_sync); + err = __inff_cfg80211_down(ifp); + mutex_unlock(&cfg->usr_sync); + + return err; +} + +bool inff_get_vif_state_any(struct inff_cfg80211_info *cfg, + unsigned long state) +{ + struct inff_cfg80211_vif *vif; + + list_for_each_entry(vif, &cfg->vif_list, list) { + if (test_bit(state, &vif->sme_state)) + return true; + } + return false; +} + +static inline bool vif_event_equals(struct inff_cfg80211_vif_event *event, + u8 action) +{ + u8 evt_action; + + spin_lock(&event->vif_event_lock); + evt_action = event->action; + spin_unlock(&event->vif_event_lock); + return evt_action == action; +} + +void inff_cfg80211_arm_vif_event(struct inff_cfg80211_info *cfg, + struct inff_cfg80211_vif *vif) +{ + struct inff_cfg80211_vif_event *event = &cfg->vif_event; + + spin_lock(&event->vif_event_lock); + event->vif = vif; + event->action = 0; + spin_unlock(&event->vif_event_lock); +} + +bool inff_cfg80211_vif_event_armed(struct inff_cfg80211_info *cfg) +{ + struct inff_cfg80211_vif_event *event = &cfg->vif_event; + bool armed; + + spin_lock(&event->vif_event_lock); + armed = (event->vif) ? true : false; + spin_unlock(&event->vif_event_lock); + + return armed; +} + +int inff_cfg80211_wait_vif_event(struct inff_cfg80211_info *cfg, + u8 action, ulong timeout) +{ + struct inff_cfg80211_vif_event *event = &cfg->vif_event; + + return wait_event_timeout(event->vif_wq, + vif_event_equals(event, action), timeout); +} + +static s32 inff_translate_country_code(struct inff_pub *drvr, char alpha2[2], + struct inff_fil_country_le *ccreq) +{ + if (alpha2[0] == ccreq->country_abbrev[0] && + alpha2[1] == ccreq->country_abbrev[1]) { + inff_dbg(INFO, "Country code already set\n"); + return -EAGAIN; + } + + inff_dbg(INFO, "For Country code, using ISO3166 code and 0 rev\n"); + memset(ccreq, 0, sizeof(*ccreq)); + ccreq->country_abbrev[0] = alpha2[0]; + ccreq->country_abbrev[1] = alpha2[1]; + ccreq->ccode[0] = alpha2[0]; + ccreq->ccode[1] = alpha2[1]; + + return 0; +} + +static void inff_cfg80211_reg_notifier(struct wiphy *wiphy, + struct regulatory_request *req) +{ + struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct inff_if *ifp = inff_get_ifp(cfg->pub, 0); + struct inff_pub *drvr = cfg->pub; + struct inff_fil_country_le ccreq; + s32 err; + int i; + + /* The country code gets set to "00" by default at boot, ignore */ + if (req->alpha2[0] == '0' && req->alpha2[1] == '0') + return; + + /* ignore non-ISO3166 country codes */ + for (i = 0; i < 2; i++) + if (req->alpha2[i] < 'A' || req->alpha2[i] > 'Z') { + iphy_err(drvr, "not an ISO3166 code (0x%02x 0x%02x)\n", + req->alpha2[0], req->alpha2[1]); + return; + } + + inff_dbg(INFO, "Enter: initiator=%d, alpha=%c%c\n", req->initiator, + req->alpha2[0], req->alpha2[1]); + + err = inff_fil_iovar_data_get(ifp, "country", &ccreq, sizeof(ccreq)); + if (err) { + iphy_err(drvr, "Country code iovar returned err = %d\n", err); + return; + } + + err = inff_translate_country_code(ifp->drvr, req->alpha2, &ccreq); + if (err) + return; + + /* Abort on-going scan before changing ccode */ + inff_abort_scanning(cfg); + + err = inff_fil_iovar_data_set(ifp, "country", &ccreq, sizeof(ccreq)); + if (err) { + iphy_err(drvr, "Firmware rejected country setting\n"); + return; + } + inff_setup_wiphybands(cfg); +} + +static void inff_free_wiphy(struct wiphy *wiphy) +{ + int i; + + if (!wiphy) + return; + + if (wiphy->iface_combinations) { + for (i = 0; i < wiphy->n_iface_combinations; i++) + kfree(wiphy->iface_combinations[i].limits); + } + kfree(wiphy->iface_combinations); + if (wiphy->bands[NL80211_BAND_2GHZ]) { + kfree(wiphy->bands[NL80211_BAND_2GHZ]->channels); + kfree(wiphy->bands[NL80211_BAND_2GHZ]); + } + if (wiphy->bands[NL80211_BAND_5GHZ]) { + kfree(wiphy->bands[NL80211_BAND_5GHZ]->channels); + kfree(wiphy->bands[NL80211_BAND_5GHZ]); + } + if (wiphy->bands[NL80211_BAND_6GHZ]) { + kfree(wiphy->bands[NL80211_BAND_6GHZ]->channels); + kfree(wiphy->bands[NL80211_BAND_6GHZ]); + } + +#if IS_ENABLED(CONFIG_PM) + if (wiphy->wowlan != &inff_wowlan_support) + kfree(wiphy->wowlan); +#endif + kfree(wiphy->pmsr_capa); +} + +struct inff_cfg80211_info *inff_cfg80211_attach(struct inff_pub *drvr, + struct cfg80211_ops *ops, + bool p2pdev_forced) +{ + struct wiphy *wiphy = drvr->wiphy; + struct net_device *ndev = inff_get_ifp(drvr, 0)->ndev; + struct inff_cfg80211_info *cfg; + struct inff_cfg80211_vif *vif; + struct inff_if *ifp; + s32 err = 0; + s32 io_type; + u16 *cap = NULL; + + if (!ndev) { + iphy_err(drvr, "ndev is invalid\n"); + return NULL; + } + + cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return NULL; + + cfg->wiphy = wiphy; + cfg->pub = drvr; + cfg->pm_state = INFF_CFG80211_PM_STATE_RESUMED; + cfg->num_softap = 0; + cfg->mchan_conf = INFF_MCHAN_CONF_DEFAULT; + init_vif_event(&cfg->vif_event); + INIT_LIST_HEAD(&cfg->vif_list); + + vif = inff_alloc_vif(cfg, NL80211_IFTYPE_STATION); + if (IS_ERR(vif)) + goto wiphy_out; + + ifp = netdev_priv(ndev); + vif->ifp = ifp; + vif->wdev.netdev = ndev; + ndev->ieee80211_ptr = &vif->wdev; + SET_NETDEV_DEV(ndev, wiphy_dev(cfg->wiphy)); + + err = wl_init_priv(cfg); + if (err) { + iphy_err(drvr, "Failed to init iwm_priv (%d)\n", err); + inff_free_vif(vif); + goto wiphy_out; + } + ifp->vif = vif; + + /* determine d11 io type before wiphy setup */ + err = inff_fil_cmd_int_get(ifp, INFF_C_GET_VERSION, &io_type); + if (err) { + iphy_err(drvr, "Failed to get D11 version (%d)\n", err); + goto priv_out; + } + cfg->d11inf.io_type = (u8)io_type; + inff_d11_attach(&cfg->d11inf); + + /* regulatory notifier below needs access to cfg so + * assign it now. + */ + drvr->config = cfg; + + err = inff_setup_wiphy(wiphy, ifp); + if (err < 0) + goto priv_out; + + inff_dbg(INFO, "Registering custom regulatory\n"); + wiphy->reg_notifier = inff_cfg80211_reg_notifier; + wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG; + wiphy->regulatory_flags |= REGULATORY_COUNTRY_IE_IGNORE; + wiphy_apply_custom_regulatory(wiphy, &inff_regdom); + + /* firmware defaults to 40MHz disabled in 2G band. We signal + * cfg80211 here that we do and have it decide we can enable + * it. But first check if device does support 2G operation. + */ + if (wiphy->bands[NL80211_BAND_2GHZ]) { + cap = &wiphy->bands[NL80211_BAND_2GHZ]->ht_cap.cap; + *cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; + } +#ifdef CONFIG_PM + if (inff_feat_is_enabled(ifp, INFF_FEAT_WOWL_GTK)) + ops->set_rekey_data = inff_cfg80211_set_rekey_data; +#endif + /* if the firmware has GTKO cap, + * user space can use NL80211_CMD_SET_REKEY_OFFLOAD command to pass gtk data. + */ + if (inff_feat_is_enabled(ifp, INFF_FEAT_GTKO)) + ops->set_rekey_data = inff_cfg80211_set_rekey_data; + + if (inff_feat_is_enabled(ifp, INFF_FEAT_DUMP_OBSS)) + ops->dump_survey = inff_cfg80211_dump_survey; + else if (inff_feat_is_enabled(ifp, INFF_FEAT_SURVEY_DUMP)) + ops->dump_survey = inff_cfg80211_dump_survey_2; + else + ops->dump_survey = NULL; + + err = inff_setup_wiphybands(cfg); + if (err) { + iphy_err(drvr, "Setting wiphy bands failed (%d)\n", err); + goto wiphy_unreg_out; + } + + err = wiphy_register(wiphy); + if (err < 0) { + iphy_err(drvr, "Could not register wiphy device (%d)\n", err); + goto priv_out; + } + + /* If cfg80211 didn't disable 40MHz HT CAP in wiphy_register(), + * setup 40MHz in 2GHz band and enable OBSS scanning. + */ + if (cap && (*cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40)) { + err = inff_enable_bw40_2g(cfg); + if (!err) + err = inff_fil_iovar_int_set(ifp, "obss_coex", + INFF_OBSS_COEX_AUTO); + else + *cap &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40; + } + + err = inff_fweh_activate_events(ifp); + if (err) { + iphy_err(drvr, "FWEH activation failed (%d)\n", err); + goto wiphy_unreg_out; + } + + err = inff_p2p_attach(cfg, p2pdev_forced); + if (err) { + iphy_err(drvr, "P2P initialisation failed (%d)\n", err); + goto wiphy_unreg_out; + } + err = inff_btcoex_attach(cfg); + if (err) { + iphy_err(drvr, "BT-coex initialisation failed (%d)\n", err); + goto detach_p2p; + } + err = inff_pno_attach(cfg); + if (err) { + iphy_err(drvr, "PNO initialisation failed (%d)\n", err); + goto detach_btcoex; + } + + err = inff_pmsr_attach(cfg); + if (err) { + iphy_err(drvr, "PMSR initialisation failed (%d)\n", err); + goto detach_pno; + } + + if (inff_feat_is_enabled(ifp, INFF_FEAT_TDLS)) { + err = inff_fil_iovar_int_set(ifp, "tdls_enable", 1); + if (err) { + inff_dbg(INFO, "TDLS not enabled (%d)\n", err); + wiphy->flags &= ~WIPHY_FLAG_SUPPORTS_TDLS; + } else { + inff_fweh_register(cfg->pub, INFF_E_TDLS_PEER_EVENT, + inff_notify_tdls_peer_event); + } + } + + err = inff_vndr_cmdstr_hashtbl_init(); + if (err) { + iphy_err(drvr, "vendor_cmd_hashtable initialisation failed (%d)\n", err); + goto detach_pmsr; + } + /* (re-) activate FWEH event handling */ + err = inff_fweh_activate_events(ifp); + if (err) { + iphy_err(drvr, "FWEH activation failed (%d)\n", err); + goto detach; + } + + /* Fill in some of the advertised nl80211 supported features */ + if (inff_feat_is_enabled(ifp, INFF_FEAT_SCAN_RANDOM_MAC)) { + wiphy->features |= NL80211_FEATURE_SCHED_SCAN_RANDOM_MAC_ADDR; +#ifdef CONFIG_PM + if (wiphy->wowlan && + wiphy->wowlan->flags & WIPHY_WOWLAN_NET_DETECT) + wiphy->features |= NL80211_FEATURE_ND_RANDOM_MAC_ADDR; +#endif + } + return cfg; + +detach: + inff_vndr_cmdstr_hashtbl_deinit(); +detach_pmsr: + inff_pmsr_detach(cfg); +detach_pno: + inff_pno_detach(cfg); +detach_btcoex: + inff_btcoex_detach(cfg); +detach_p2p: + inff_p2p_detach(&cfg->p2p); +wiphy_unreg_out: + wiphy_unregister(cfg->wiphy); +priv_out: + wl_deinit_priv(cfg); + inff_free_vif(vif); + ifp->vif = NULL; +wiphy_out: + inff_free_wiphy(wiphy); + kfree(cfg); + return NULL; +} + +void inff_cfg80211_detach(struct inff_cfg80211_info *cfg) +{ + if (!cfg) + return; + + inff_vndr_cmdstr_hashtbl_deinit(); + inff_pmsr_detach(cfg); + inff_pno_detach(cfg); + inff_btcoex_detach(cfg); + wiphy_unregister(cfg->wiphy); + wl_deinit_priv(cfg); + cancel_work_sync(&cfg->escan_timeout_work); + inff_free_wiphy(cfg->wiphy); + kfree(cfg); +} diff --git a/drivers/net/wireless/infineon/inffmac/cfg80211.h b/drivers/net/wireless/infineon/inffmac/cfg80211.h new file mode 100644 index 000000000000..549668b5d9bd --- /dev/null +++ b/drivers/net/wireless/infineon/inffmac/cfg80211.h @@ -0,0 +1,604 @@ +/* SPDX-License-Identifier: ISC */ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Copyright (c) 2025, Infineon Technologies AG, or an affiliate of Infineon Technologies AG. + * All rights reserved. + */ + +#ifndef INFF_CFG80211_H +#define INFF_CFG80211_H + +#include "core.h" +#include "fwil_types.h" +#include "p2p.h" +#include "pno.h" +#include "scan.h" +#include "interface.h" +#include "ie.h" +#include "security.h" +#include "chanspec.h" + +/* Max length of Interworking element */ +#define INFF_IW_IES_MAX_BUF_LEN 8 + +#define INFF_SCAN_IE_LEN_MAX 2048 + +#define WL_NUM_SCAN_MAX 10 +#define WL_TLV_INFO_MAX 1024 +#define WL_BSS_INFO_MAX 2048 +#define WL_ASSOC_INFO_MAX 512 /* assoc related fil max buf */ +#define WL_EXTRA_BUF_MAX 2048 +#define WL_ROAM_TRIGGER_LEVEL -75 +#define WL_ROAM_DELTA 20 + +/* WME Access Category Indices (ACIs) */ +#define AC_BE 0 /* Best Effort */ +#define AC_BK 1 /* Background */ +#define AC_VI 2 /* Video */ +#define AC_VO 3 /* Voice */ + +#define EDCF_AC_COUNT 4 +#define MAX_8021D_PRIO 8 + +#define EDCF_ACI_MASK 0x60 +#define EDCF_ACI_SHIFT 5 +#define EDCF_ACM_MASK 0x10 +#define EDCF_ECWMIN_MASK 0x0f +#define EDCF_ECWMAX_SHIFT 4 +#define EDCF_AIFSN_MASK 0x0f +#define EDCF_AIFSN_MAX 15 +#define EDCF_ECWMAX_MASK 0xf0 + +/* Keep INFF_ESCAN_BUF_SIZE below 64K (65536). Allocing over 64K can be + * problematic on some systems and should be avoided. + */ +#define INFF_ESCAN_BUF_SIZE 65000 +#define INFF_ESCAN_TIMER_INTERVAL_MS 10000 /* E-Scan timeout */ + +#define WL_ESCAN_ACTION_START 1 +#define WL_ESCAN_ACTION_CONTINUE 2 +#define WL_ESCAN_ACTION_ABORT 3 + +#define WL_AUTH_SHARED_KEY 1 /* d11 shared authentication */ +#define IE_MAX_LEN 512 + +/* IE TLV processing */ +#define TLV_LEN_OFF 1 /* length offset */ +#define TLV_HDR_LEN 2 /* header length */ +#define TLV_BODY_OFF 2 /* body offset */ +#define TLV_OUI_LEN 3 /* oui id length */ + +/* 802.11 Mgmt Packet flags */ +#define INFF_VNDR_IE_BEACON_FLAG 0x1 +#define INFF_VNDR_IE_PRBRSP_FLAG 0x2 +#define INFF_VNDR_IE_ASSOCRSP_FLAG 0x4 +#define INFF_VNDR_IE_AUTHRSP_FLAG 0x8 +#define INFF_VNDR_IE_PRBREQ_FLAG 0x10 +#define INFF_VNDR_IE_ASSOCREQ_FLAG 0x20 +/* vendor IE in IW advertisement protocol ID field */ +#define INFF_VNDR_IE_IWAPID_FLAG 0x40 +/* allow custom IE id */ +#define INFF_VNDR_IE_CUSTOM_FLAG 0x100 + +/* P2P Action Frames flags (spec ordered) */ +#define INFF_VNDR_IE_GONREQ_FLAG 0x001000 +#define INFF_VNDR_IE_GONRSP_FLAG 0x002000 +#define INFF_VNDR_IE_GONCFM_FLAG 0x004000 +#define INFF_VNDR_IE_INVREQ_FLAG 0x008000 +#define INFF_VNDR_IE_INVRSP_FLAG 0x010000 +#define INFF_VNDR_IE_DISREQ_FLAG 0x020000 +#define INFF_VNDR_IE_DISRSP_FLAG 0x040000 +#define INFF_VNDR_IE_PRDREQ_FLAG 0x080000 +#define INFF_VNDR_IE_PRDRSP_FLAG 0x100000 + +#define INFF_VNDR_IE_P2PAF_SHIFT 12 + +#define INFF_MAX_DEFAULT_KEYS 6 + +/* beacon loss timeout defaults */ +#define INFF_DEFAULT_BCN_TIMEOUT_ROAM_ON 2 +#define INFF_DEFAULT_BCN_TIMEOUT_ROAM_OFF 4 + +#define INFF_VIF_EVENT_TIMEOUT msecs_to_jiffies(1500) + +#define INFF_PM_WAIT_MAXRETRY 100 + +/* cfg80211 wowlan definitions */ +#define WL_WOWLAN_MAX_PATTERNS 8 +#define WL_WOWLAN_MIN_PATTERN_LEN 1 +#define WL_WOWLAN_MAX_PATTERN_LEN 255 +#define WL_WOWLAN_PKT_FILTER_ID_FIRST 201 +#define WL_WOWLAN_PKT_FILTER_ID_LAST (WL_WOWLAN_PKT_FILTER_ID_FIRST + \ + WL_WOWLAN_MAX_PATTERNS - 1) + +#define WL_RSPEC_ENCODE_HE 0x03000000 /* HE MCS and Nss is stored in RSPEC_RATE_MASK */ +#define WL_RSPEC_HE_NSS_UNSPECIFIED 0xF +#define WL_RSPEC_HE_NSS_SHIFT 4 /* HE Nss value shift */ +#define WL_RSPEC_HE_GI_MASK 0x00000C00 /* HE GI indices */ +#define WL_RSPEC_HE_GI_SHIFT 10 +#define HE_GI_TO_RSPEC(gi) (((gi) << WL_RSPEC_HE_GI_SHIFT) & WL_RSPEC_HE_GI_MASK) + +/** + * enum inff_scan_status - scan engine status + * + * @INFF_SCAN_STATUS_BUSY: scanning in progress on dongle. + * @INFF_SCAN_STATUS_ABORT: scan being aborted on dongle. + * @INFF_SCAN_STATUS_SUPPRESS: scanning is suppressed in driver. + */ +enum inff_scan_status { + INFF_SCAN_STATUS_BUSY, + INFF_SCAN_STATUS_ABORT, + INFF_SCAN_STATUS_SUPPRESS, +}; + +/* dongle configuration */ +struct inff_cfg80211_conf { + u32 frag_threshold; + u32 rts_threshold; + u32 retry_short; + u32 retry_long; +}; + +/* security information with currently associated ap */ +struct inff_cfg80211_security { + u32 wpa_versions; + u32 auth_type; + u32 cipher_pairwise; + u32 cipher_group; +}; + +enum inff_profile_fwsup { + INFF_PROFILE_FWSUP_NONE, + INFF_PROFILE_FWSUP_PSK, + INFF_PROFILE_FWSUP_1X, + INFF_PROFILE_FWSUP_SAE, + INFF_PROFILE_FWSUP_ROAM +}; + +/** + * enum inff_profile_fwauth - firmware authenticator profile + * + * @INFF_PROFILE_FWAUTH_NONE: no firmware authenticator + * @INFF_PROFILE_FWAUTH_PSK: authenticator for WPA/WPA2-PSK + * @INFF_PROFILE_FWAUTH_SAE: authenticator for SAE + */ +enum inff_profile_fwauth { + INFF_PROFILE_FWAUTH_NONE, + INFF_PROFILE_FWAUTH_PSK, + INFF_PROFILE_FWAUTH_SAE +}; + +/** + * struct inff_cfg80211_profile - profile information. + * + * @bssid: bssid of joined/joining ibss. + * @sec: security information. + * @key: key information + */ +struct inff_cfg80211_profile { + u8 bssid[ETH_ALEN]; + struct inff_cfg80211_security sec; + struct inff_wsec_key key[INFF_MAX_DEFAULT_KEYS]; + enum inff_profile_fwsup use_fwsup; + u16 use_fwauth; + bool is_ft; + bool is_okc; +}; + +/** + * enum inff_vif_status - bit indices for vif status. + * + * @INFF_VIF_STATUS_READY: ready for operation. + * @INFF_VIF_STATUS_CONNECTING: connect/join in progress. + * @INFF_VIF_STATUS_CONNECTED: connected/joined successfully. + * @INFF_VIF_STATUS_DISCONNECTING: disconnect/disable in progress. + * @INFF_VIF_STATUS_AP_CREATED: AP operation started. + * @INFF_VIF_STATUS_EAP_SUCCUSS: EAPOL handshake successful. + * @INFF_VIF_STATUS_ASSOC_SUCCESS: successful SET_SSID received. + */ +enum inff_vif_status { + INFF_VIF_STATUS_READY, + INFF_VIF_STATUS_CONNECTING, + INFF_VIF_STATUS_CONNECTED, + INFF_VIF_STATUS_DISCONNECTING, + INFF_VIF_STATUS_AP_CREATED, + INFF_VIF_STATUS_EAP_SUCCESS, + INFF_VIF_STATUS_ASSOC_SUCCESS, +}; + +enum inff_cfg80211_pm_state { + INFF_CFG80211_PM_STATE_RESUMED, + INFF_CFG80211_PM_STATE_RESUMING, + INFF_CFG80211_PM_STATE_SUSPENDED, + INFF_CFG80211_PM_STATE_SUSPENDING, +}; + +/** + * enum inff_mgmt_tx_status - mgmt frame tx status + * + * @INFF_MGMT_TX_ACK: mgmt frame acked + * @INFF_MGMT_TX_NOACK: mgmt frame not acked + * @INFF_MGMT_TX_OFF_CHAN_COMPLETED: off-channel complete + * @INFF_MGMT_TX_SEND_FRAME: mgmt frame tx is in progress + */ +enum inff_mgmt_tx_status { + INFF_MGMT_TX_ACK, + INFF_MGMT_TX_NOACK, + INFF_MGMT_TX_OFF_CHAN_COMPLETED, + INFF_MGMT_TX_SEND_FRAME +}; + +/** + * struct vif_saved_ie - holds saved IEs for a virtual interface. + * + * @probe_req_ie: IE info for probe request. + * @probe_res_ie: IE info for probe response. + * @beacon_ie: IE info for beacon frame. + * @assoc_res_ie: IE info for association response frame. + * @probe_req_ie_len: IE info length for probe request. + * @probe_res_ie_len: IE info length for probe response. + * @beacon_ie_len: IE info length for beacon frame. + * @assoc_res_ie_len: IE info length for association response frame. + */ +struct vif_saved_ie { + u8 probe_req_ie[IE_MAX_LEN]; + u8 probe_res_ie[IE_MAX_LEN]; + u8 beacon_ie[IE_MAX_LEN]; + u8 assoc_req_ie[IE_MAX_LEN]; + u8 assoc_res_ie[IE_MAX_LEN]; + u32 probe_req_ie_len; + u32 probe_res_ie_len; + u32 beacon_ie_len; + u32 assoc_req_ie_len; + u32 assoc_res_ie_len; +}; + +/** + * struct inff_cfg80211_vif - virtual interface specific information. + * + * @ifp: lower layer interface pointer + * @wdev: wireless device. + * @profile: profile information. + * @sme_state: SME state using enum inff_vif_status bits. + * @list: linked list. + * @mgmt_rx_reg: registered rx mgmt frame types. + * @cqm_rssi_low: Lower RSSI limit for CQM monitoring + * @cqm_rssi_high: Upper RSSI limit for CQM monitoring + * @cqm_rssi_last: Last RSSI reading for CQM monitoring + */ +struct inff_cfg80211_vif { + struct inff_if *ifp; + struct wireless_dev wdev; + struct inff_cfg80211_profile profile; + unsigned long sme_state; + struct vif_saved_ie saved_ie; + struct list_head list; + struct completion mgmt_tx; + unsigned long mgmt_tx_status; + u32 mgmt_tx_id; + u16 mgmt_rx_reg; + int is_11d; + s32 cqm_rssi_low; + s32 cqm_rssi_high; + s32 cqm_rssi_last; +}; + +/* association inform */ +struct inff_cfg80211_connect_info { + u8 *req_ie; + s32 req_ie_len; + u8 *resp_ie; + s32 resp_ie_len; +}; + +/* assoc ie length */ +struct inff_cfg80211_assoc_ielen_le { + __le32 req_len; + __le32 resp_len; + __le32 flags; +}; + +struct inff_cfg80211_edcf_acparam { + u8 ACI; + u8 ECW; + u16 TXOP; /* stored in network order (ls octet first) */ +}; + +/* dongle escan state */ +enum wl_escan_state { + WL_ESCAN_STATE_IDLE, + WL_ESCAN_STATE_SCANNING +}; + +struct cqm_rssi_info { + bool enable; + s32 rssi_threshold; +}; + +/** + * struct inff_cfg80211_vif_event - virtual interface event information. + * + * @vif_wq: waitqueue awaiting interface event from firmware. + * @vif_event_lock: protects other members in this structure. + * @vif_complete: completion for net attach. + * @action: either add, change, or delete. + * @vif: virtual interface object related to the event. + */ +struct inff_cfg80211_vif_event { + wait_queue_head_t vif_wq; + spinlock_t vif_event_lock; /* protects other members in this structure */ + u8 action; + struct inff_cfg80211_vif *vif; +}; + +/** + * struct inff_cfg80211_wowl - wowl related information. + * + * @active: set on suspend, cleared on resume. + * @pre_pmmode: firmware PM mode at entering suspend. + * @nd: net dectect data. + * @nd_info: helper struct to pass to cfg80211. + * @nd_data_wait: wait queue to sync net detect data. + * @nd_data_completed: completion for net detect data. + * @nd_enabled: net detect enabled. + */ +struct inff_cfg80211_wowl { + bool active; + u32 pre_pmmode; + struct cfg80211_wowlan_nd_match *nd; + struct cfg80211_wowlan_nd_info *nd_info; + wait_queue_head_t nd_data_wait; + bool nd_data_completed; + bool nd_enabled; +}; + +struct network_blob { + char ssid[IEEE80211_MAX_SSID_LEN]; + u8 ssid_len; + int key_mgmt; + char psk[WSEC_MAX_PASSWORD_LEN]; + char sae_password[WSEC_MAX_PASSWORD_LEN]; + u8 proto; + u8 pairwise_cipher; + u8 frequency; +}; + +struct drv_config_pfn_params { + u8 pfn_config; + u8 count; + struct network_blob *network_blob_data; +}; + +/** + * struct inff_cfg80211_info - dongle private data of cfg80211 interface + * + * @wiphy: wiphy object for cfg80211 interface. + * @ops: pointer to copy of ops as registered with wiphy object. + * @conf: dongle configuration. + * @p2p: peer-to-peer specific information. + * @btcoex: Bluetooth coexistence information. + * @scan_request: cfg80211 scan request object. + * @usr_sync: mainly for dongle up/down synchronization. + * @bss_list: bss_list holding scanned ap information. + * @bss_info: bss information for cfg80211 layer. + * @conn_info: association info. + * @pmk_list: wpa2 pmk list. + * @scan_status: scan activity on the dongle. + * @pub: common driver information. + * @channel: current channel. + * @int_escan_map: bucket map for which internal e-scan is done. + * @ibss_starter: indicates this sta is ibss starter. + * @pwr_save: indicate whether dongle to support power save mode. + * @dongle_up: indicate whether dongle up or not. + * @roam_on: on/off switch for dongle self-roaming. + * @scan_tried: indicates if first scan attempted. + * @dcmd_buf: dcmd buffer. + * @extra_buf: mainly to grab assoc information. + * @debugfsdir: debugfs folder for this device. + * @escan_info: escan information. + * @escan_timeout: Timer for catch scan timeout. + * @escan_timeout_work: scan timeout worker. + * @vif_list: linked list of vif instances. + * @vif_cnt: number of vif instances. + * @vif_event: vif event signalling. + * @wowl: wowl related information. + * @pno: information of pno module. + * @wlan_sense: WLAN Sensing specific information. + */ +struct inff_cfg80211_info { + struct wiphy *wiphy; + struct inff_cfg80211_conf *conf; + struct inff_p2p_info p2p; + struct inff_btcoex_info *btcoex; + struct cfg80211_scan_request *scan_request; + struct mutex usr_sync; /* mainly for dongle up/down synchronization */ + struct wl_cfg80211_bss_info *bss_info; + struct inff_cfg80211_connect_info conn_info; + struct inff_pmk_list_le pmk_list; + unsigned long scan_status; + struct inff_pub *pub; + u32 channel; + u32 int_escan_map; + bool ibss_starter; + bool pwr_save; + bool dongle_up; + bool scan_tried; + u8 *dcmd_buf; + u8 *extra_buf; + struct dentry *debugfsdir; + struct escan_info escan_info; + struct timer_list escan_timeout; + struct work_struct escan_timeout_work; + struct cqm_rssi_info cqm_info; + struct list_head vif_list; + struct inff_cfg80211_vif_event vif_event; + struct completion vif_disabled; + struct inff_d11inf d11inf; + struct inff_assoclist_le assoclist; + struct inff_cfg80211_wowl wowl; + struct inff_pno_info *pno; + u8 ac_priority[MAX_8021D_PRIO]; + u8 pm_state; + u8 num_softap; + u8 pfn_enable; + u8 pfn_connection; + struct drv_config_pfn_params pfn_data; + struct inff_pmsr_info *pmsr_info; + u8 mchan_conf; +}; + +static inline enum nl80211_band inff_d11_chan_band_to_nl80211(u8 band) +{ + if (band == INFF_CHAN_BAND_2G) + return NL80211_BAND_2GHZ; + else if (band == INFF_CHAN_BAND_5G) + return NL80211_BAND_5GHZ; + else + return NL80211_BAND_6GHZ; +} + +static inline struct wiphy *cfg_to_wiphy(struct inff_cfg80211_info *cfg) +{ + return cfg->wiphy; +} + +static inline struct inff_cfg80211_info *wiphy_to_cfg(struct wiphy *w) +{ + struct inff_pub *drvr = wiphy_priv(w); + + return drvr->config; +} + +static inline struct inff_cfg80211_info *wdev_to_cfg(struct wireless_dev *wd) +{ + return wiphy_to_cfg(wd->wiphy); +} + +static inline struct inff_cfg80211_vif *wdev_to_vif(struct wireless_dev *wdev) +{ + return container_of(wdev, struct inff_cfg80211_vif, wdev); +} + +static inline +struct net_device *cfg_to_ndev(struct inff_cfg80211_info *cfg) +{ + return inff_get_ifp(cfg->pub, 0)->ndev; +} + +static inline struct inff_cfg80211_info *ndev_to_cfg(struct net_device *ndev) +{ + return wdev_to_cfg(ndev->ieee80211_ptr); +} + +static inline struct inff_cfg80211_profile *ndev_to_prof(struct net_device *nd) +{ + struct inff_if *ifp = netdev_priv(nd); + + return &ifp->vif->profile; +} + +static inline struct inff_cfg80211_vif *ndev_to_vif(struct net_device *ndev) +{ + struct inff_if *ifp = netdev_priv(ndev); + + return ifp->vif; +} + +static inline struct +inff_cfg80211_connect_info *cfg_to_conn(struct inff_cfg80211_info *cfg) +{ + return &cfg->conn_info; +} + +static inline void inff_init_prof(struct inff_cfg80211_profile *prof) +{ + memset(prof, 0, sizeof(*prof)); +} + +u16 chandef_to_chanspec(struct inff_d11inf *d11inf, struct cfg80211_chan_def *ch); +u8 nl80211_band_to_fwil(enum nl80211_band band); + +struct inff_cfg80211_info *inff_cfg80211_attach(struct inff_pub *drvr, + struct cfg80211_ops *ops, + bool p2pdev_forced); +void inff_cfg80211_detach(struct inff_cfg80211_info *cfg); +s32 inff_cfg80211_up(struct net_device *ndev); +s32 inff_cfg80211_down(struct net_device *ndev); +struct cfg80211_ops *inff_cfg80211_get_ops(struct inff_mp_device *settings); + +u16 channel_to_chanspec(struct inff_d11inf *d11inf, + struct ieee80211_channel *ch); +bool inff_get_vif_state_any(struct inff_cfg80211_info *cfg, + unsigned long state); +void inff_cfg80211_arm_vif_event(struct inff_cfg80211_info *cfg, + struct inff_cfg80211_vif *vif); +bool inff_cfg80211_vif_event_armed(struct inff_cfg80211_info *cfg); +int inff_cfg80211_wait_vif_event(struct inff_cfg80211_info *cfg, + u8 action, ulong timeout); +void inff_set_mpc(struct inff_if *ndev, int mpc); +void inff_cfg80211_free_netdev(struct net_device *ndev); +void inff_cfg80211_update_proto_addr_mode(struct wireless_dev *wdev); +int inff_vndr_cmdstr_hashtbl_init(void); +void inff_vndr_cmdstr_hashtbl_deinit(void); +s32 +inff_set_channel(struct inff_cfg80211_info *cfg, struct ieee80211_channel *chan); +int inff_cfg80211_get_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, + unsigned int link_id, + struct cfg80211_chan_def *chandef); + +s32 +inff_compare_update_same_bss(struct inff_cfg80211_info *cfg, + struct inff_bss_info_le *bss, + struct inff_bss_info_le *bss_info_le); + +s32 inff_inform_bss(struct inff_cfg80211_info *cfg); + +struct inff_pno_net_info_le * +inff_get_netinfo_array(struct inff_pno_scanresults_le *pfn_v1); + +s32 inff_mchan_config(struct inff_cfg80211_info *cfg); + +bool inff_is_linkup(struct inff_cfg80211_vif *vif, + const struct inff_event_msg *e); + +s32 inff_inform_ibss(struct inff_cfg80211_info *cfg, + struct net_device *ndev, const u8 *bssid); + +s32 +inff_bss_roaming_done(struct inff_cfg80211_info *cfg, + struct net_device *ndev, + const struct inff_event_msg *e); + +s32 +inff_bss_connect_done(struct inff_cfg80211_info *cfg, + struct net_device *ndev, const struct inff_event_msg *e, + bool completed); + +bool inff_is_linkdown(struct inff_cfg80211_vif *vif, + const struct inff_event_msg *e); + +void inff_link_down(struct inff_cfg80211_vif *vif, u16 reason, + bool locally_generated); + +bool inff_is_nonetwork(struct inff_cfg80211_info *cfg, + const struct inff_event_msg *e); + +s32 inff_inform_single_bss(struct inff_cfg80211_info *cfg, + struct inff_bss_info_le *bi); + +void inff_wifi_prioritize_acparams(const + struct inff_cfg80211_edcf_acparam *acp, u8 *priority); + +static __always_inline void inff_delay(u32 ms) +{ + if (ms < 1000 / HZ) { + cond_resched(); + mdelay(ms); + } else { + msleep(ms); + } +} + +#endif /* INFF_CFG80211_H */ -- 2.25.1