new file mode 100644
@@ -0,0 +1,2519 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * This file contains communication with cfg80211 module.
+ *
+ * Copyright (C) [2022-2025] Renesas Electronics Corporation and/or its affiliates.
+ */
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/rtnetlink.h>
+#include <linux/if_ether.h>
+#include <linux/inetdevice.h>
+#include <net/cfg80211.h>
+#include <net/ip.h>
+
+#include "dbg.h"
+#include "core.h"
+#include "ctrl.h"
+#include "cfg80211.h"
+#include "dev.h"
+#include "dbgfs.h"
+#include "testmode.h"
+#include "params.h"
+
+#define RATE(_bitrate, _hw_rate, _flags) { \
+ .bitrate = (_bitrate), \
+ .flags = (_flags), \
+ .hw_value = (_hw_rate), \
+}
+
+#define CHAN(_band, _channel, _freq) { \
+ .band = (_band), \
+ .hw_value = (_channel), \
+ .center_freq = (_freq), \
+ .flags = 0, \
+ .max_antenna_gain = 0, \
+ .max_power = RA6W_CFG80211_CH_MAX_POWER, \
+}
+
+static const u8 ra6w_ac2hwq[IEEE80211_NUM_ACS] = {
+ [NL80211_TXQ_Q_VO] = RA6W_CMD_AC_VO,
+ [NL80211_TXQ_Q_VI] = RA6W_CMD_AC_VI,
+ [NL80211_TXQ_Q_BE] = RA6W_CMD_AC_BE,
+ [NL80211_TXQ_Q_BK] = RA6W_CMD_AC_BK
+};
+
+static struct ieee80211_iface_limit ra6w_if_limits[] = {
+ {
+ .max = RA6W_CFG80211_VIF_MAX,
+ .types = BIT(NL80211_IFTYPE_AP) | BIT(NL80211_IFTYPE_STATION)
+ }
+};
+
+static const struct ieee80211_iface_combination ra6w_if_comb[] = {
+ {
+ .limits = ra6w_if_limits,
+ .n_limits = ARRAY_SIZE(ra6w_if_limits),
+ .num_different_channels = RA6W_CFG80211_CHANINFO_MAX,
+ .max_interfaces = RA6W_CFG80211_VIF_MAX,
+ },
+};
+
+static struct ieee80211_txrx_stypes ra6w_macm_stypes[NUM_NL80211_IFTYPES] = {
+ [NL80211_IFTYPE_STATION] = {
+ .tx = 0xffff,
+ .rx = BIT(IEEE80211_STYPE_ACTION >> 4) |
+ BIT(IEEE80211_STYPE_PROBE_REQ >> 4) |
+ BIT(IEEE80211_STYPE_AUTH >> 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)
+ },
+};
+
+static u32 ra6w_cipher_suites[] = {
+ WLAN_CIPHER_SUITE_WEP40,
+ WLAN_CIPHER_SUITE_WEP104,
+ WLAN_CIPHER_SUITE_TKIP,
+ WLAN_CIPHER_SUITE_CCMP,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+};
+
+static struct ieee80211_rate ra6w_rate_table[] = {
+ RATE(10, 0x00, 0),
+ RATE(20, 0x01, IEEE80211_RATE_SHORT_PREAMBLE),
+ RATE(55, 0x02, IEEE80211_RATE_SHORT_PREAMBLE),
+ RATE(110, 0x03, IEEE80211_RATE_SHORT_PREAMBLE),
+ RATE(60, 0x04, 0),
+ RATE(90, 0x05, 0),
+ RATE(120, 0x06, 0),
+ RATE(180, 0x07, 0),
+ RATE(240, 0x08, 0),
+ RATE(360, 0x09, 0),
+ RATE(480, 0x0A, 0),
+ RATE(540, 0x0B, 0),
+};
+
+static struct ieee80211_channel ra6w_chans_2g[] = {
+ CHAN(NL80211_BAND_2GHZ, 1, 2412),
+ CHAN(NL80211_BAND_2GHZ, 2, 2417),
+ CHAN(NL80211_BAND_2GHZ, 3, 2422),
+ CHAN(NL80211_BAND_2GHZ, 4, 2427),
+ CHAN(NL80211_BAND_2GHZ, 5, 2432),
+ CHAN(NL80211_BAND_2GHZ, 6, 2437),
+ CHAN(NL80211_BAND_2GHZ, 7, 2442),
+ CHAN(NL80211_BAND_2GHZ, 8, 2447),
+ CHAN(NL80211_BAND_2GHZ, 9, 2452),
+ CHAN(NL80211_BAND_2GHZ, 10, 2457),
+ CHAN(NL80211_BAND_2GHZ, 11, 2462),
+ CHAN(NL80211_BAND_2GHZ, 12, 2467),
+ CHAN(NL80211_BAND_2GHZ, 13, 2472),
+ CHAN(NL80211_BAND_2GHZ, 14, 2484),
+};
+
+static struct ieee80211_channel ra6w_chans_5g[] = {
+ CHAN(NL80211_BAND_5GHZ, 36, 5180),
+ CHAN(NL80211_BAND_5GHZ, 40, 5200),
+ CHAN(NL80211_BAND_5GHZ, 44, 5220),
+ CHAN(NL80211_BAND_5GHZ, 48, 5240),
+ CHAN(NL80211_BAND_5GHZ, 52, 5260),
+ CHAN(NL80211_BAND_5GHZ, 56, 5280),
+ CHAN(NL80211_BAND_5GHZ, 60, 5300),
+ CHAN(NL80211_BAND_5GHZ, 64, 5320),
+ CHAN(NL80211_BAND_5GHZ, 100, 5500),
+ CHAN(NL80211_BAND_5GHZ, 104, 5520),
+ CHAN(NL80211_BAND_5GHZ, 108, 5540),
+ CHAN(NL80211_BAND_5GHZ, 112, 5560),
+ CHAN(NL80211_BAND_5GHZ, 116, 5580),
+ CHAN(NL80211_BAND_5GHZ, 120, 5600),
+ CHAN(NL80211_BAND_5GHZ, 124, 5620),
+ CHAN(NL80211_BAND_5GHZ, 128, 5640),
+ CHAN(NL80211_BAND_5GHZ, 132, 5660),
+ CHAN(NL80211_BAND_5GHZ, 136, 5680),
+ CHAN(NL80211_BAND_5GHZ, 140, 5700),
+ CHAN(NL80211_BAND_5GHZ, 144, 5720),
+ CHAN(NL80211_BAND_5GHZ, 149, 5745),
+ CHAN(NL80211_BAND_5GHZ, 153, 5765),
+ CHAN(NL80211_BAND_5GHZ, 157, 5785),
+ CHAN(NL80211_BAND_5GHZ, 161, 5805),
+ CHAN(NL80211_BAND_5GHZ, 165, 5825),
+ CHAN(NL80211_BAND_5GHZ, 169, 5845),
+ CHAN(NL80211_BAND_5GHZ, 173, 5865),
+};
+
+static struct ieee80211_sband_iftype_data ra6w_cap_he_2g = {
+ .types_mask = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP),
+};
+
+static struct ieee80211_sband_iftype_data ra6w_cap_he_5g = {
+ .types_mask = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP),
+};
+
+static struct ieee80211_supported_band ra6w_band_2g = {
+ .channels = ra6w_chans_2g,
+ .n_channels = ARRAY_SIZE(ra6w_chans_2g),
+ .bitrates = ra6w_rate_table,
+ .n_bitrates = ARRAY_SIZE(ra6w_rate_table),
+ .n_iftype_data = 1,
+ .band = NL80211_BAND_2GHZ,
+};
+
+static struct ieee80211_supported_band ra6w_band_5g = {
+ .channels = ra6w_chans_5g,
+ .n_channels = ARRAY_SIZE(ra6w_chans_5g),
+ .bitrates = &ra6w_rate_table[4],
+ .n_bitrates = ARRAY_SIZE(ra6w_rate_table) - 4,
+ .n_iftype_data = 1,
+ .band = NL80211_BAND_5GHZ,
+};
+
+static const struct ra6w_cfg80211_legrate ra6w_legacy_rate_table[] = {
+ [0] = { .idx = 0, .rate = 10 },
+ [1] = { .idx = 1, .rate = 20 },
+ [2] = { .idx = 2, .rate = 55 },
+ [3] = { .idx = 3, .rate = 110 },
+ [4] = { .idx = -1, .rate = 0 },
+ [5] = { .idx = -1, .rate = 0 },
+ [6] = { .idx = -1, .rate = 0 },
+ [7] = { .idx = -1, .rate = 0 },
+ [8] = { .idx = 10, .rate = 480 },
+ [9] = { .idx = 8, .rate = 240 },
+ [10] = { .idx = 6, .rate = 120 },
+ [11] = { .idx = 4, .rate = 60 },
+ [12] = { .idx = 11, .rate = 540 },
+ [13] = { .idx = 9, .rate = 360 },
+ [14] = { .idx = 7, .rate = 180 },
+ [15] = { .idx = 5, .rate = 90 },
+};
+
+static const int ra6w_mcs_map_to_rate[] = {
+ [IEEE80211_VHT_MCS_SUPPORT_0_7] = 65,
+ [IEEE80211_VHT_MCS_SUPPORT_0_8] = 78,
+ [IEEE80211_VHT_MCS_SUPPORT_0_9] = 78,
+};
+
+static void ra6w_cfg80211_dev_init(struct net_device *ndev)
+{
+ ra6w_dev_init(ndev);
+}
+
+static void ra6w_cfg80211_reg_notifier(struct wiphy *wiphy, struct regulatory_request *req)
+{
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+
+ ra6w_dbg("[%s] initiator=%d, alpha=%c%c\n", __func__,
+ req->initiator, req->alpha2[0], req->alpha2[1]);
+
+ memcpy(priv->phy_config.country_code, req->alpha2, sizeof(req->alpha2));
+
+ ra6w_ctrl_chan_config(&priv->core->ctrl, wiphy);
+}
+
+u8 *ra6w_cfg80211_create_beacon(struct ra6w_cfg80211_beacon_info *bcn,
+ struct cfg80211_beacon_data *new)
+{
+ u8 *buf = NULL;
+ u8 *pos = NULL;
+
+ if (new->head) {
+ u8 *head = kzalloc(new->head_len, GFP_KERNEL);
+
+ if (!head)
+ return NULL;
+
+ kfree(bcn->head);
+
+ bcn->head = head;
+ bcn->head_len = new->head_len;
+ memcpy(bcn->head, new->head, new->head_len);
+ }
+
+ if (new->tail) {
+ u8 *tail = kzalloc(new->tail_len, GFP_KERNEL);
+
+ if (!tail)
+ return NULL;
+
+ kfree(bcn->tail);
+
+ bcn->tail = tail;
+ bcn->tail_len = new->tail_len;
+ memcpy(bcn->tail, new->tail, new->tail_len);
+ }
+
+ if (!bcn->head)
+ return NULL;
+
+ bcn->tim_len = 6;
+ bcn->len = bcn->head_len + bcn->tail_len + bcn->ies_len + bcn->tim_len;
+
+ buf = kzalloc(bcn->len, GFP_KERNEL);
+ if (!buf)
+ return NULL;
+
+ pos = buf;
+ memcpy(pos, bcn->head, bcn->head_len);
+ pos += bcn->head_len;
+ *pos++ = WLAN_EID_TIM;
+ *pos++ = 4;
+ *pos++ = 0;
+ *pos++ = bcn->dtim_period;
+ *pos++ = 0;
+ *pos++ = 0;
+ if (bcn->tail) {
+ memcpy(pos, bcn->tail, bcn->tail_len);
+ pos += bcn->tail_len;
+ }
+
+ if (bcn->ies)
+ memcpy(pos, bcn->ies, bcn->ies_len);
+
+ return buf;
+}
+
+static void ra6w_cfg80211_remove_beacon(struct ra6w_cfg80211_beacon_info *bcn)
+{
+ kfree(bcn->head);
+ bcn->head = NULL;
+ bcn->head_len = 0;
+
+ kfree(bcn->tail);
+ bcn->tail = NULL;
+ bcn->tail_len = 0;
+
+ kfree(bcn->ies);
+ bcn->ies = NULL;
+ bcn->ies_len = 0;
+ bcn->tim_len = 0;
+ bcn->dtim_period = 0;
+ bcn->len = 0;
+}
+
+static void ra6w_cfg80211_csa_remove(struct ra6w_cfg80211_vif *vif)
+{
+ struct ra6w_cfg80211_csa_info *csa = vif->ap.csa;
+
+ if (!csa)
+ return;
+
+ ra6w_cfg80211_remove_beacon(&csa->bcn);
+ kfree(csa);
+ vif->ap.csa = NULL;
+}
+
+struct ra6w_cfg80211_chan_info *ra6w_cfg80211_chaninfo_get(struct ra6w_cfg80211_vif *vif)
+{
+ struct ra6w_cfg80211_priv *priv;
+
+ if (!vif)
+ return NULL;
+
+ priv = vif->priv;
+ if (!priv)
+ return NULL;
+
+ if (vif->ch_idx >= RA6W_CFG80211_CHANINFO_MAX ||
+ !priv->chaninfo_table[vif->ch_idx].chan_def.chan)
+ return NULL;
+
+ return &priv->chaninfo_table[vif->ch_idx];
+}
+
+static void ra6w_cfg80211_csa_work(struct work_struct *ws)
+{
+ struct ra6w_cfg80211_csa_info *csa = container_of(ws, struct ra6w_cfg80211_csa_info,
+ work);
+ struct ra6w_cfg80211_vif *vif = csa->vif;
+ struct ra6w_cfg80211_priv *priv = vif->priv;
+
+ if (!priv)
+ goto out;
+
+ if (csa->status) {
+ cfg80211_stop_iface(priv->wiphy, &vif->wdev, GFP_KERNEL);
+ goto out;
+ }
+
+ ra6w_ctrl_change_beacon_req(&priv->core->ctrl, vif->vif_idx,
+ csa->buf,
+ csa->bcn.len, csa->bcn.head_len,
+ csa->bcn.tim_len, NULL);
+
+ wiphy_lock(priv->wiphy);
+ ra6w_cfg80211_chaninfo_unset(vif);
+ ra6w_cfg80211_chaninfo_set(vif, csa->ch_idx, &csa->chandef);
+ cfg80211_ch_switch_notify(vif->ndev, &csa->chandef, 0);
+ wiphy_unlock(priv->wiphy);
+
+out:
+ ra6w_cfg80211_csa_remove(vif);
+}
+
+static int ra6w_cfg80211_station_info_fill(struct ra6w_cfg80211_sta *sta,
+ struct station_info *sinfo,
+ struct ra6w_cfg80211_vif *vif)
+{
+ const struct ra6w_cfg80211_sta_stats *stats = &sta->stats;
+ u16 format_mod = 0;
+
+ sinfo->generation = vif->generation;
+ sinfo->inactive_time = jiffies_to_msecs(jiffies - stats->last_acttive_time);
+ sinfo->filled = BIT(NL80211_STA_INFO_INACTIVE_TIME);
+ sinfo->rx_bytes = sta->stats.rx_bytes;
+ sinfo->filled |= BIT(NL80211_STA_INFO_RX_BYTES64);
+ sinfo->rx_packets = sta->stats.rx_packets;
+ sinfo->filled |= BIT(NL80211_STA_INFO_RX_PACKETS);
+ sinfo->tx_bytes = sta->stats.tx_bytes;
+ sinfo->filled |= BIT(NL80211_STA_INFO_TX_BYTES64);
+ sinfo->tx_packets = sta->stats.tx_packets;
+ sinfo->filled |= BIT(NL80211_STA_INFO_TX_PACKETS);
+ sinfo->tx_failed = sta->stats.tx_failed;
+ sinfo->filled |= BIT(NL80211_STA_INFO_TX_FAILED);
+ sinfo->signal = stats->last_rx_data_ext.rssi1;
+ sinfo->filled |= BIT(NL80211_STA_INFO_SIGNAL);
+
+ switch (stats->last_rx_data_ext.ch_bw) {
+ case 0:
+ sinfo->rxrate.bw = RATE_INFO_BW_20;
+ break;
+ case 1:
+ sinfo->rxrate.bw = RATE_INFO_BW_40;
+ break;
+ case 2:
+ sinfo->rxrate.bw = RATE_INFO_BW_80;
+ break;
+ case 3:
+ sinfo->rxrate.bw = RATE_INFO_BW_160;
+ break;
+ default:
+ sinfo->rxrate.bw = RATE_INFO_BW_HE_RU;
+ break;
+ }
+
+ if (stats->last_rx_data_ext.pre_type)
+ format_mod = stats->last_rx_data_ext.format_mod + 1;
+ else
+ format_mod = stats->last_rx_data_ext.format_mod;
+
+ if (stats->last_stats.format_mod > 1 && format_mod < 2)
+ format_mod = stats->last_stats.format_mod;
+
+ switch (format_mod) {
+ case RA6W_CFG80211_FORMATMOD_NON_HT:
+ case RA6W_CFG80211_FORMATMOD_NON_HT_DUP_OFDM:
+ sinfo->rxrate.flags = 0;
+ sinfo->rxrate.legacy =
+ ra6w_legacy_rate_table[stats->last_rx_data_ext.leg_rate].rate;
+ break;
+ case RA6W_CFG80211_FORMATMOD_HT_MF:
+ case RA6W_CFG80211_FORMATMOD_HT_GF:
+ sinfo->rxrate.flags = RATE_INFO_FLAGS_MCS;
+ if (stats->last_stats.ht.short_gi)
+ sinfo->rxrate.flags |= RATE_INFO_FLAGS_SHORT_GI;
+ sinfo->rxrate.mcs = stats->last_stats.ht.mcs;
+ break;
+ case RA6W_CFG80211_FORMATMOD_VHT:
+ sinfo->rxrate.flags = RATE_INFO_FLAGS_VHT_MCS;
+ if (stats->last_stats.vht.short_gi)
+ sinfo->rxrate.flags |= RATE_INFO_FLAGS_SHORT_GI;
+ sinfo->rxrate.mcs = stats->last_stats.vht.mcs;
+ sinfo->rxrate.nss = stats->last_stats.vht.nss + 1;
+ break;
+ case RA6W_CFG80211_FORMATMOD_HE_MU:
+ sinfo->rxrate.he_ru_alloc = stats->last_stats.he.ru_size;
+ fallthrough;
+ case RA6W_CFG80211_FORMATMOD_HE_SU:
+ case RA6W_CFG80211_FORMATMOD_HE_ER:
+ case RA6W_CFG80211_FORMATMOD_HE_TB:
+ sinfo->rxrate.flags = RATE_INFO_FLAGS_HE_MCS;
+ sinfo->rxrate.mcs = stats->last_stats.he.mcs;
+ sinfo->rxrate.nss = stats->last_stats.he.nss;
+ sinfo->rxrate.he_gi = stats->last_stats.he.gi_type;
+ sinfo->rxrate.he_dcm = stats->last_stats.he.dcm;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ sinfo->filled |= BIT(NL80211_STA_INFO_RX_BITRATE);
+
+ return 0;
+}
+
+struct ra6w_cfg80211_sta *ra6w_cfg80211_sta_get(struct ra6w_cfg80211_priv *priv, u8 sta_idx)
+{
+ if (!priv)
+ return NULL;
+
+ if (sta_idx >= RA6W_CFG80211_STA_TABLE_MAX)
+ return NULL;
+
+ return &priv->sta_table[sta_idx];
+}
+
+void ra6w_cfg80211_sta_free(struct ra6w_cfg80211_vif *vif, u8 sta_idx)
+{
+ struct ra6w_cfg80211_priv *priv = vif->priv;
+ struct ra6w_cfg80211_sta *sta = NULL;
+
+ if (!priv)
+ return;
+
+ sta = ra6w_cfg80211_sta_get(priv, sta_idx);
+ if (!sta)
+ return;
+
+ switch (vif->type) {
+ case NL80211_IFTYPE_AP:
+ case NL80211_IFTYPE_P2P_GO:
+ list_del(&sta->list);
+ break;
+ case NL80211_IFTYPE_STATION:
+ case NL80211_IFTYPE_P2P_CLIENT:
+ vif->sta.ap = NULL;
+ default:
+ break;
+ }
+
+ sta->valid = false;
+ sta->aid = RA6W_CFG80211_STA_IDX_INVALID;
+ sta->sta_idx = RA6W_CFG80211_STA_IDX_INVALID;
+ sta->vif_idx = RA6W_CFG80211_VIF_IDX_INVALID;
+ memset(sta->mac_addr, 0, ETH_ALEN);
+}
+
+static struct ra6w_cfg80211_sta *ra6w_cfg80211_find_sta(struct ra6w_cfg80211_priv *priv,
+ const u8 *mac_addr)
+{
+ struct ra6w_cfg80211_sta *sta = NULL;
+ u8 i;
+
+ for (i = 0; i < RA6W_CFG80211_STA_MAX; i++) {
+ sta = ra6w_cfg80211_sta_get(priv, i);
+ if (!sta)
+ continue;
+
+ if (sta->valid && ether_addr_equal(sta->mac_addr, mac_addr))
+ return sta;
+ }
+
+ return NULL;
+}
+
+static int _ra6w_cfg80211_del_station(struct ra6w_cfg80211_vif *vif, const u8 *mac)
+{
+ struct ra6w_cfg80211_priv *priv = vif->priv;
+ struct ra6w_ctrl *ctrl = &priv->core->ctrl;
+ struct ra6w_cfg80211_sta *sta = NULL;
+ struct ra6w_cfg80211_sta *tmp = NULL;
+ int ret = 0;
+ bool all_sta = false;
+
+ if (!mac || is_broadcast_ether_addr(mac))
+ all_sta = true;
+
+ list_for_each_entry_safe(sta, tmp, &vif->ap.sta_list, list) {
+ if (all_sta || (mac && ether_addr_equal(sta->mac_addr, mac))) {
+ u8 sta_idx = sta->sta_idx;
+
+ ra6w_cfg80211_sta_free(vif, sta_idx);
+
+ ret = ra6w_ctrl_del_station_req(ctrl, sta_idx, false);
+ if (ret)
+ return ret;
+
+ vif->generation++;
+
+ if (!all_sta)
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int ra6w_cfg80211_del_station(struct wiphy *wiphy, struct net_device *ndev,
+ struct station_del_parameters *params)
+{
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+ const u8 *mac = NULL;
+
+ if (!vif->up)
+ return -EIO;
+
+ if (params)
+ mac = params->mac;
+
+ return _ra6w_cfg80211_del_station(vif, mac);
+}
+
+static int ra6w_cfg80211_vif_assign(struct ra6w_cfg80211_priv *priv, enum nl80211_iftype type,
+ u8 *vif_idx, u8 *addr_idx)
+{
+ struct ra6w_cmd_add_if_rsp rsp = { 0 };
+ int ret;
+ bool p2p = false;
+ unsigned long n;
+
+ switch (type) {
+ case NL80211_IFTYPE_P2P_CLIENT:
+ case NL80211_IFTYPE_P2P_GO:
+ p2p = true;
+ break;
+ default:
+ break;
+ }
+
+ n = find_first_zero_bit(priv->addr_map, RA6W_CFG80211_VIF_MAX);
+ if (n >= RA6W_CFG80211_VIF_MAX)
+ return -EIO;
+
+ ret = ra6w_ctrl_if_add(&priv->core->ctrl, priv->addresses[n].addr, (u8)type, p2p, &rsp);
+ if (ret)
+ return ret;
+
+ *addr_idx = (u8)n;
+ *vif_idx = rsp.vif_idx;
+
+ return 0;
+}
+
+static void ra6w_cfg80211_vif_unassign(struct ra6w_cfg80211_priv *priv, u8 vif_idx, u8 addr_idx)
+{
+ struct ra6w_cfg80211_vif *vif = ra6w_cfg80211_vif_get(priv, vif_idx);
+
+ if (!vif)
+ return;
+
+ list_del(&vif->list);
+ priv->vif_table[vif_idx] = NULL;
+ clear_bit(addr_idx, priv->addr_map);
+ clear_bit(vif_idx, priv->vif_map);
+
+ ra6w_ctrl_if_remove(&priv->core->ctrl, vif_idx);
+}
+
+static int ra6w_cfg80211_vif_type_allowed(struct ra6w_cfg80211_priv *priv,
+ enum nl80211_iftype type)
+{
+ struct ra6w_cfg80211_vif *vif = NULL;
+ bool mon_presents = false;
+ bool data_presents = false;
+
+ switch (type) {
+ case NL80211_IFTYPE_MONITOR:
+ mon_presents = true;
+ break;
+ case NL80211_IFTYPE_STATION:
+ case NL80211_IFTYPE_P2P_CLIENT:
+ case NL80211_IFTYPE_P2P_GO:
+ case NL80211_IFTYPE_P2P_DEVICE:
+ case NL80211_IFTYPE_AP:
+ data_presents = true;
+ break;
+ case NL80211_IFTYPE_ADHOC:
+ case NL80211_IFTYPE_OCB:
+ case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_AP_VLAN:
+ case NL80211_IFTYPE_MESH_POINT:
+ case NL80211_IFTYPE_UNSPECIFIED:
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ list_for_each_entry(vif, &priv->vifs, list) {
+ if (vif->type == NL80211_IFTYPE_MONITOR) {
+ mon_presents = true;
+ continue;
+ }
+
+ data_presents = true;
+ }
+
+ if (mon_presents && data_presents) {
+ wiphy_err(priv->wiphy,
+ "Can't enable monitor and data interfaces simultaneosly\n");
+ return -EPERM;
+ }
+
+ return 0;
+}
+
+static struct net_device *ra6w_cfg80211_alloc_ndev(struct wiphy *wiphy,
+ const char *name,
+ unsigned char name_assign_type,
+ u8 addr_idx,
+ enum nl80211_iftype type,
+ struct vif_params *params)
+{
+ struct ra6w_cfg80211_vif *vif;
+ struct net_device *ndev;
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct sockaddr addr = { 0 };
+ int ret;
+
+ ndev = alloc_netdev_mqs(sizeof(*vif), name, name_assign_type,
+ ra6w_cfg80211_dev_init,
+ RA6W_CFG80211_NDEV_TXQ,
+ RA6W_CFG80211_NDEV_RXQ);
+ if (!ndev)
+ return ERR_PTR(-ENOMEM);
+
+ vif = netdev_priv(ndev);
+ ndev->ieee80211_ptr = &vif->wdev;
+ ndev->ieee80211_ptr->iftype = type;
+ SET_NETDEV_DEV(ndev, wiphy_dev(wiphy));
+ ether_addr_copy(addr.sa_data, priv->addresses[addr_idx].addr);
+ ret = eth_mac_addr(ndev, &addr);
+ if (ret)
+ return ERR_PTR(ret);
+
+ if (params)
+ ndev->ieee80211_ptr->use_4addr = params->use_4addr;
+
+ return ndev;
+}
+
+static struct wireless_dev *ra6w_cfg80211_add_iface(struct wiphy *wiphy,
+ const char *name,
+ unsigned char name_assign_type,
+ enum nl80211_iftype type,
+ struct vif_params *params)
+{
+ int ret = -EFAULT;
+ struct net_device *ndev = NULL;
+ struct ra6w_cfg80211_vif *vif = NULL;
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ u8 vif_idx = 0;
+ u8 addr_idx = 0;
+
+ ret = ra6w_cfg80211_vif_type_allowed(priv, type);
+ if (ret)
+ return ERR_PTR(ret);
+
+ ret = ra6w_cfg80211_vif_assign(priv, type, &vif_idx, &addr_idx);
+ if (ret)
+ return ERR_PTR(ret);
+
+ ndev = ra6w_cfg80211_alloc_ndev(wiphy, name, name_assign_type, addr_idx, type, params);
+ if (IS_ERR(ndev))
+ return ERR_PTR(-ENOMEM);
+
+ vif = netdev_priv(ndev);
+ vif->wdev.wiphy = priv->wiphy;
+ vif->priv = priv;
+ vif->ndev = ndev;
+ vif->wdev.netdev = ndev;
+ vif->wdev.iftype = type;
+ vif->up = false;
+ vif->ch_idx = RA6W_CFG80211_CH_IDX_INVALID;
+ vif->generation = 0;
+ vif->vif_idx = vif_idx;
+ set_bit(vif_idx, priv->vif_map);
+ vif->addr_idx = addr_idx;
+ set_bit(addr_idx, priv->addr_map);
+ vif->type = type;
+
+ switch (type) {
+ case NL80211_IFTYPE_AP:
+ case NL80211_IFTYPE_P2P_GO:
+ INIT_LIST_HEAD(&vif->ap.sta_list);
+ memset(&vif->ap.bcn, 0, sizeof(vif->ap.bcn));
+ vif->ap.ap_isolate = 0;
+ break;
+ case NL80211_IFTYPE_MONITOR:
+ ndev->type = ARPHRD_IEEE80211_RADIOTAP;
+ ra6w_dev_set_monitor_ops(ndev);
+ break;
+ case NL80211_IFTYPE_ADHOC:
+ case NL80211_IFTYPE_OCB:
+ case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_AP_VLAN:
+ case NL80211_IFTYPE_MESH_POINT:
+ ret = -EOPNOTSUPP;
+ goto vif_unset;
+ case NL80211_IFTYPE_UNSPECIFIED:
+ ret = -EINVAL;
+ goto vif_unset;
+ default:
+ break;
+ }
+
+ if (params)
+ vif->use_4addr = params->use_4addr;
+
+ priv->vif_table[vif_idx] = vif;
+ list_add_tail(&vif->list, &priv->vifs);
+ set_bit(vif_idx, priv->vif_map);
+
+ ret = cfg80211_register_netdevice(ndev);
+ if (ret) {
+ ra6w_err("[%s] %s not registered: %d\n", __func__, ndev->name, ret);
+ goto vif_unset;
+ }
+
+ return &vif->wdev;
+
+vif_unset:
+ ra6w_cfg80211_vif_unassign(priv, vif_idx, addr_idx);
+ free_netdev(ndev);
+
+ return ERR_PTR(ret);
+}
+
+static void ra6w_cfg80211_vif_cleanup(struct ra6w_cfg80211_vif *vif)
+{
+ struct net_device *ndev = vif->ndev;
+
+ if (ndev->reg_state == NETREG_REGISTERED)
+ unregister_netdevice(ndev);
+
+ ndev->ieee80211_ptr = NULL;
+ vif->ndev = NULL;
+}
+
+static int ra6w_cfg80211_del_iface(struct wiphy *wiphy, struct wireless_dev *wdev)
+{
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_cfg80211_vif *vif = netdev_priv(wdev->netdev);
+
+ ra6w_cfg80211_vif_cleanup(vif);
+ ra6w_cfg80211_vif_unassign(priv, vif->vif_idx, vif->addr_idx);
+
+ return 0;
+}
+
+static int ra6w_cfg80211_change_iface(struct wiphy *wiphy, struct net_device *ndev,
+ enum nl80211_iftype type, struct vif_params *params)
+{
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+ int ret;
+ u8 vif_idx;
+ u8 addr_idx;
+
+ if (vif->up)
+ return -EBUSY;
+
+ ret = ra6w_cfg80211_vif_type_allowed(priv, type);
+ if (ret)
+ return ret;
+
+ ra6w_cfg80211_vif_unassign(priv, vif->vif_idx, vif->addr_idx);
+
+ ret = ra6w_cfg80211_vif_assign(priv, type, &vif_idx, &addr_idx);
+ if (ret)
+ return ret;
+
+ vif->type = type;
+ vif->vif_idx = vif_idx;
+ vif->addr_idx = addr_idx;
+ set_bit(addr_idx, priv->addr_map);
+
+ priv->vif_table[vif->vif_idx] = vif;
+ list_add_tail(&vif->list, &priv->vifs);
+ set_bit(vif_idx, priv->vif_map);
+
+ ndev->type = ARPHRD_ETHER;
+ ra6w_dev_set_ops(ndev);
+
+ switch (type) {
+ case NL80211_IFTYPE_STATION:
+ case NL80211_IFTYPE_P2P_CLIENT:
+ vif->sta.flags = 0;
+ vif->sta.ap = NULL;
+ vif->sta.tdls_sta = NULL;
+ break;
+ case NL80211_IFTYPE_MESH_POINT:
+ return -EOPNOTSUPP;
+ case NL80211_IFTYPE_AP:
+ case NL80211_IFTYPE_P2P_GO:
+ INIT_LIST_HEAD(&vif->ap.sta_list);
+ memset(&vif->ap.bcn, 0, sizeof(vif->ap.bcn));
+ vif->ap.ap_isolate = 0;
+ break;
+ case NL80211_IFTYPE_MONITOR:
+ ndev->type = ARPHRD_IEEE80211_RADIOTAP;
+ ra6w_dev_set_monitor_ops(ndev);
+ break;
+ default:
+ break;
+ }
+
+ vif->generation = 0;
+ vif->wdev.iftype = type;
+
+ if (params->use_4addr != -1)
+ vif->use_4addr = params->use_4addr;
+
+ return 0;
+}
+
+static int ra6w_cfg80211_scan(struct wiphy *wiphy, struct cfg80211_scan_request *req)
+{
+ int ret = 0;
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_cfg80211_vif *vif = container_of(req->wdev, struct ra6w_cfg80211_vif, wdev);
+
+ if (!vif->up)
+ return -EIO;
+
+ if (vif->type == NL80211_IFTYPE_AP)
+ return -EOPNOTSUPP;
+
+ if (priv->scan_request)
+ return -EAGAIN;
+
+ ret = ra6w_ctrl_scan_start(&priv->core->ctrl, req);
+ if (ret)
+ return ret;
+
+ priv->scan_request = req;
+
+ return 0;
+}
+
+static void ra6w_cfg80211_scan_abort(struct wiphy *wiphy, struct wireless_dev *wdev)
+{
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_cfg80211_vif *vif = container_of(wdev, struct ra6w_cfg80211_vif, wdev);
+
+ if (!priv->scan_request)
+ return;
+
+ ra6w_ctrl_scan_cancel(&priv->core->ctrl, vif);
+}
+
+void ra6w_cfg80211_scan_done(struct ra6w_cfg80211_priv *priv)
+{
+ static struct cfg80211_scan_info scan_req = {
+ .aborted = true,
+ };
+
+ cfg80211_scan_done(priv->scan_request, &scan_req);
+ priv->scan_request = NULL;
+}
+
+static int ra6w_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *ndev, u16 reason_code)
+{
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+
+ if (!vif->up)
+ return -EIO;
+
+ ra6w_stats_deinit(&vif->stats);
+
+ return ra6w_ctrl_disconnect_req(&priv->core->ctrl, vif, reason_code);
+}
+
+struct ra6w_cfg80211_vif *ra6w_cfg80211_vif_get(struct ra6w_cfg80211_priv *priv, u8 vif_idx)
+{
+ if (!priv)
+ return NULL;
+
+ if (!test_bit(vif_idx, priv->vif_map))
+ return NULL;
+
+ return priv->vif_table[vif_idx];
+}
+
+static int ra6w_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev,
+ int link_id, u8 key_index, bool pairwise,
+ const u8 *mac_addr, struct key_params *params)
+{
+ int ret = 0;
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+ struct ra6w_cfg80211_sta *sta = NULL;
+ struct ra6w_cfg80211_key *key = NULL;
+ struct ra6w_cmd_key_add_rsp rsp = { 0 };
+ u8 cipher_type = 0;
+ u8 *tk;
+
+ if (key_index >= RA6W_CFG80211_KEYS_MAX) {
+ ra6w_err("Invalid key index\n");
+ return -EINVAL;
+ }
+
+ if (!vif->up)
+ return -EIO;
+
+ if (mac_addr) {
+ sta = ra6w_cfg80211_find_sta(priv, mac_addr);
+ if (!sta) {
+ ra6w_err("No STA found with MAC address\n");
+ return -EINVAL;
+ }
+ key = &sta->key;
+ } else {
+ key = &vif->keys[key_index];
+ }
+
+ tk = kzalloc(params->key_len, GFP_KERNEL);
+ if (!tk)
+ return -ENOMEM;
+
+ memcpy(tk, params->key, params->key_len);
+
+ switch (params->cipher) {
+ case WLAN_CIPHER_SUITE_WEP40:
+ cipher_type = RA6W_CIPHER_WEP40;
+ break;
+ case WLAN_CIPHER_SUITE_WEP104:
+ cipher_type = RA6W_CIPHER_WEP104;
+ break;
+ case WLAN_CIPHER_SUITE_TKIP:
+ cipher_type = RA6W_CIPHER_TKIP;
+ break;
+ case WLAN_CIPHER_SUITE_CCMP:
+ cipher_type = RA6W_CIPHER_CCMP;
+ break;
+ case WLAN_CIPHER_SUITE_AES_CMAC:
+ cipher_type = RA6W_CIPHER_AES_CMAC_128;
+ break;
+ case WLAN_CIPHER_SUITE_SMS4: {
+ u8 tmp;
+ int i = 0;
+
+ cipher_type = RA6W_CIPHER_SMS4;
+
+ for (i = 0; i < RA6W_CFG80211_WPI_SUBKEY_LEN / 2; i++) {
+ tmp = tk[i];
+ tk[i] = tk[RA6W_CFG80211_WPI_SUBKEY_LEN - 1 - i];
+ tk[RA6W_CFG80211_WPI_SUBKEY_LEN - 1 - i] = tmp;
+ }
+
+ for (i = 0; i < RA6W_CFG80211_WPI_SUBKEY_LEN / 2; i++) {
+ tmp = tk[i + RA6W_CFG80211_WPI_SUBKEY_LEN];
+ tk[i + RA6W_CFG80211_WPI_SUBKEY_LEN] =
+ tk[RA6W_CFG80211_WPI_KEY_LEN - 1 - i];
+ tk[RA6W_CFG80211_WPI_KEY_LEN - 1 - i] = tmp;
+ }
+ break;
+ }
+ case WLAN_CIPHER_SUITE_GCMP:
+ cipher_type = RA6W_CIPHER_GCMP_128;
+ break;
+ case WLAN_CIPHER_SUITE_GCMP_256:
+ cipher_type = RA6W_CIPHER_GCMP_256;
+ break;
+ case WLAN_CIPHER_SUITE_CCMP_256:
+ cipher_type = RA6W_CIPHER_CCMP_256;
+ break;
+ default:
+ ra6w_err("[%s] Unsupported cipher %d\n", __func__, params->cipher);
+ ret = -EINVAL;
+ goto free;
+ }
+
+ key->cipher_type = cipher_type;
+ key->pairwise = pairwise;
+ key->sta_idx = sta ? sta->sta_idx : RA6W_CFG80211_STA_IDX_INVALID;
+ key->vif_idx = vif->vif_idx;
+
+ ret = ra6w_ctrl_add_key_req(&priv->core->ctrl, key, tk, params->key_len, key_index, &rsp);
+ if (ret)
+ goto free;
+
+ key->key_index = rsp.hw_key_index;
+ memcpy(key->key, tk, params->key_len);
+ key->key_len = params->key_len;
+ memcpy(key->seq, tk, params->seq_len);
+ key->seq_len = params->seq_len;
+
+free:
+ kfree(tk);
+
+ return ret;
+}
+
+static int ra6w_cfg80211_get_key(struct wiphy *wiphy, struct net_device *ndev, int link_id,
+ u8 key_index, bool pairwise, const u8 *mac_addr, void *cookie,
+ void (*callback)(void *cookie, struct key_params *))
+{
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+ struct ra6w_cfg80211_key *key = NULL;
+ struct key_params params;
+
+ if (!vif->up)
+ return -EIO;
+
+ if (key_index > RA6W_CFG80211_KEYS_MAX) {
+ ra6w_err("[%s] key index %d out of bounds\n", __func__, key_index);
+ return -ENOENT;
+ }
+
+ key = &vif->keys[key_index];
+ memset(¶ms, 0, sizeof(params));
+ params.cipher = key->cipher;
+ params.key_len = key->key_len;
+ params.seq_len = key->seq_len;
+ params.seq = key->seq;
+ params.key = key->key;
+
+ callback(cookie, ¶ms);
+
+ return key->key_len ? 0 : -ENOENT;
+}
+
+static int ra6w_cfg80211_del_key(struct wiphy *wiphy, struct net_device *ndev, int link_id,
+ u8 key_index, bool pairwise, const u8 *mac_addr)
+{
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+ struct ra6w_cfg80211_sta *sta = NULL;
+ struct ra6w_cfg80211_key *key = NULL;
+
+ if (!vif->up)
+ return -EIO;
+
+ if (key_index >= RA6W_CFG80211_KEYS_MAX) {
+ ra6w_err("[%s] key index %d out of bounds\n", __func__, key_index);
+ return -EINVAL;
+ }
+
+ if (mac_addr) {
+ sta = ra6w_cfg80211_find_sta(priv, mac_addr);
+ if (!sta)
+ return -EINVAL;
+
+ key = &sta->key;
+ } else {
+ key = &vif->keys[key_index];
+ }
+
+ return ra6w_ctrl_del_key_req(&priv->core->ctrl, key->key_index);
+}
+
+static int ra6w_cfg80211_set_default_key(struct wiphy *wiphy, struct net_device *ndev,
+ int link_id, u8 key_index, bool unicast, bool multicast)
+{
+ int ret = 0;
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+
+ if (!vif->up)
+ return -EIO;
+
+ return ret;
+}
+
+static int ra6w_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev,
+ struct cfg80211_connect_params *sme)
+{
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+ struct ra6w_cmd_sm_connect_rsp rsp = { 0 };
+ int ret;
+
+ if (!vif->up)
+ return -EIO;
+
+ if ((sme->crypto.cipher_group == WLAN_CIPHER_SUITE_WEP40 ||
+ sme->crypto.cipher_group == WLAN_CIPHER_SUITE_WEP104) &&
+ (sme->key && sme->key_len > 0)) {
+ struct key_params key_params;
+
+ key_params.key = sme->key;
+ key_params.seq = NULL;
+ key_params.key_len = sme->key_len;
+ key_params.seq_len = 0;
+ key_params.cipher = sme->crypto.cipher_group;
+ ra6w_cfg80211_add_key(wiphy, ndev, -1, sme->key_idx, false, NULL, &key_params);
+ } else if ((sme->auth_type == NL80211_AUTHTYPE_SAE) &&
+ !(sme->flags & CONNECT_REQ_EXTERNAL_AUTH_SUPPORT)) {
+ netdev_err(ndev, "Doesn't support SAE without external authentication\n");
+ return -EINVAL;
+ }
+
+ ret = ra6w_ctrl_connect(&priv->core->ctrl, ndev, sme, &rsp);
+ if (ret == 0)
+ netif_carrier_on(ndev);
+
+ return ret;
+}
+
+static int ra6w_cfg80211_add_station(struct wiphy *wiphy, struct net_device *ndev, const u8 *mac,
+ struct station_parameters *params)
+{
+ int ret = 0;
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+ struct ra6w_cmd_sta_add_rsp rsp = { 0 };
+ struct ra6w_cfg80211_sta *sta = NULL;
+
+ if (params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER))
+ return 0;
+
+ ret = ra6w_ctrl_add_station_req(&priv->core->ctrl, params, mac, vif->vif_idx, &rsp);
+ if (ret)
+ return ret;
+
+ sta = ra6w_cfg80211_sta_get(priv, rsp.sta_idx);
+ if (!sta)
+ return -EINVAL;
+
+ sta->aid = params->aid;
+ sta->sta_idx = rsp.sta_idx;
+ sta->ch_idx = vif->ch_idx;
+ sta->vif_idx = vif->vif_idx;
+ sta->qos = (params->sta_flags_set & BIT(NL80211_STA_FLAG_WME)) != 0;
+ sta->ht = params->link_sta_params.ht_capa ? 1 : 0;
+ sta->vht = params->link_sta_params.vht_capa ? 1 : 0;
+ sta->he = params->link_sta_params.he_capa ? 1 : 0;
+ sta->listen_interval = params->listen_interval;
+ ether_addr_copy(sta->mac_addr, mac);
+ list_add_tail(&sta->list, &vif->ap.sta_list);
+ vif->generation++;
+ sta->valid = true;
+
+ return 0;
+}
+
+static int ra6w_cfg80211_change_station(struct wiphy *wiphy, struct net_device *ndev,
+ const u8 *mac, struct station_parameters *params)
+{
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_cfg80211_sta *sta = NULL;
+ bool authorized;
+
+ if (is_zero_ether_addr(mac))
+ return 0;
+
+ if (!(params->sta_flags_mask & BIT(NL80211_STA_FLAG_AUTHORIZED)))
+ return 0;
+
+ sta = ra6w_cfg80211_find_sta(priv, mac);
+ if (!sta)
+ return -EINVAL;
+
+ authorized = params->sta_flags_set & BIT(NL80211_STA_FLAG_AUTHORIZED);
+
+ return ra6w_ctrl_port_control_req(&priv->core->ctrl, authorized, sta->sta_idx);
+}
+
+static int ra6w_cfg80211_start_ap(struct wiphy *wiphy, struct net_device *ndev,
+ struct cfg80211_ap_settings *settings)
+{
+ int ret = 0;
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+ struct ra6w_cfg80211_sta *sta = NULL;
+ struct ra6w_cmd_ap_start_rsp rsp = { 0 };
+ struct ra6w_cfg80211_chan_info *chan_info = NULL;
+
+ ret = ra6w_ctrl_ap_start_req(&priv->core->ctrl, vif, settings, &rsp);
+ if (ret) {
+ ra6w_err("Failed to start AP (%d)\n", ret);
+ return ret;
+ }
+
+ vif->ap.bcmc_index = rsp.bcmc_idx;
+ vif->ap.ap_isolate = 0;
+ sta = ra6w_cfg80211_sta_get(priv, rsp.bcmc_idx);
+ if (!sta)
+ return -EINVAL;
+
+ sta->valid = true;
+ sta->aid = 0;
+ sta->sta_idx = rsp.bcmc_idx;
+ sta->ch_idx = rsp.ch_idx;
+ sta->vif_idx = vif->vif_idx;
+ sta->qos = false;
+ sta->listen_interval = 5;
+ sta->ht = 0;
+ sta->vht = 0;
+ sta->he = 0;
+
+ ra6w_cfg80211_chaninfo_set(vif, rsp.ch_idx, &settings->chandef);
+
+ netif_carrier_on(ndev);
+
+ chan_info = &priv->chaninfo_table[vif->ch_idx];
+ ra6w_info("AP started: freq1 %u bcmc_idx %d\n",
+ chan_info->chan_def.center_freq1, vif->ap.bcmc_index);
+
+ return 0;
+}
+
+static int ra6w_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *ndev,
+ unsigned int link_id)
+{
+ int ret = 0;
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+
+ netif_carrier_off(ndev);
+ _ra6w_cfg80211_del_station(vif, NULL);
+ ret = ra6w_ctrl_ap_stop_req(&priv->core->ctrl, vif);
+ ra6w_cfg80211_chaninfo_unset(vif);
+
+ return ret;
+}
+
+static int ra6w_cfg80211_change_beacon(struct wiphy *wiphy, struct net_device *ndev,
+ struct cfg80211_ap_update *info)
+{
+ int ret = 0;
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+ struct ra6w_cfg80211_beacon_info *bcn = NULL;
+ u8 *bcn_buf;
+
+ bcn = &vif->ap.bcn;
+
+ bcn_buf = ra6w_cfg80211_create_beacon(bcn, &info->beacon);
+ if (!bcn_buf)
+ return -ENOMEM;
+
+ ret = ra6w_ctrl_change_beacon_req(&priv->core->ctrl, vif->vif_idx,
+ bcn_buf, bcn->len, bcn->head_len, bcn->tim_len, NULL);
+
+ kfree(bcn_buf);
+
+ return ret;
+}
+
+static
+int ra6w_cfg80211_set_monitor_channel(struct wiphy *wiphy,
+ struct net_device *ndev,
+ struct cfg80211_chan_def *chandef)
+{
+ int ret = 0;
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_cfg80211_vif *vif = NULL;
+ struct ra6w_cmd_mon_mode_rsp rsp = { 0 };
+ const struct ra6w_cfg80211_chan_info *chan_info = NULL;
+ struct cfg80211_chan_def chandef_mon;
+
+ if (priv->mon_vif_idx == RA6W_CFG80211_VIF_IDX_INVALID)
+ return -EINVAL;
+
+ vif = priv->vif_table[priv->mon_vif_idx];
+
+ chan_info = ra6w_cfg80211_chaninfo_get(vif);
+ if (chan_info && chandef && cfg80211_chandef_identical(&chan_info->chan_def, chandef))
+ return 0;
+
+ ret = ra6w_ctrl_monitor_mode_req(&priv->core->ctrl, chandef, &rsp);
+ if (ret)
+ return -EIO;
+
+ ra6w_cfg80211_chaninfo_unset(vif);
+
+ if (rsp.chan_index == RA6W_CFG80211_CH_IDX_INVALID)
+ return 0;
+
+ if (priv->vif_started > 1) {
+ ra6w_cfg80211_chaninfo_set(vif, rsp.chan_index, NULL);
+ return -EBUSY;
+ }
+
+ memset(&chandef_mon, 0, sizeof(chandef_mon));
+ chandef_mon.chan = ieee80211_get_channel(wiphy, le16_to_cpu(rsp.chan.freq_prim20));
+ chandef_mon.center_freq1 = le16_to_cpu(rsp.chan.freq_cen1);
+ chandef_mon.center_freq2 = le16_to_cpu(rsp.chan.freq_cen2);
+ chandef_mon.width = chnl2bw[rsp.chan.ch_bw];
+ ra6w_cfg80211_chaninfo_set(vif, rsp.chan_index, &chandef_mon);
+
+ return 0;
+}
+
+static int ra6w_cfg80211_probe_client(struct wiphy *wiphy, struct net_device *ndev,
+ const u8 *peer, u64 *cookie)
+{
+ int ret = 0;
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+ struct ra6w_cfg80211_sta *sta = NULL;
+ struct ra6w_cmd_probe_client_rsp rsp = { 0 };
+
+ if (vif->type != NL80211_IFTYPE_AP &&
+ vif->type != NL80211_IFTYPE_P2P_GO)
+ return -EINVAL;
+
+ list_for_each_entry(sta, &vif->ap.sta_list, list) {
+ if (sta->valid && ether_addr_equal(sta->mac_addr, peer))
+ break;
+ }
+
+ if (!sta)
+ return -EINVAL;
+
+ ret = ra6w_ctrl_probe_client_req(&priv->core->ctrl, vif->vif_idx, sta->sta_idx, &rsp);
+ if (ret)
+ return ret;
+
+ *cookie = (u64)le32_to_cpu(rsp.probe_id);
+
+ return 0;
+}
+
+static int ra6w_cfg80211_set_txq_params(struct wiphy *wiphy, struct net_device *ndev,
+ struct ieee80211_txq_params *params)
+{
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+ u32 param;
+ u8 ac;
+ u8 aifs;
+ u8 cwmin;
+ u8 cwmax;
+
+ ac = ra6w_ac2hwq[params->ac];
+ aifs = params->aifs;
+ cwmin = (u8)fls(params->cwmin);
+ cwmax = (u8)fls(params->cwmax);
+
+ param = (u32)(aifs << 0);
+ param |= (u32)(cwmin << 4);
+ param |= (u32)(cwmax << 8);
+ param |= (u32)(params->txop << 12);
+
+ return ra6w_ctrl_edca_req(&priv->core->ctrl, ac, param, false, vif->vif_idx);
+}
+
+static int ra6w_cfg80211_set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev,
+ enum nl80211_tx_power_setting type, int mbm)
+{
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_cfg80211_vif *vif = NULL;
+ struct ra6w_cfg80211_vif *tmp = NULL;
+ s8 tx_pwr = 0;
+
+ switch (type) {
+ case NL80211_TX_POWER_AUTOMATIC:
+ tx_pwr = 0x7F;
+ break;
+ case NL80211_TX_POWER_LIMITED:
+ case NL80211_TX_POWER_FIXED:
+ tx_pwr = (s8)MBM_TO_DBM(mbm);
+ break;
+ default:
+ ra6w_err("[%s] Unsupported type %d\n", __func__, type);
+ return -EINVAL;
+ }
+
+ if (wdev) {
+ vif = container_of(wdev, struct ra6w_cfg80211_vif, wdev);
+ if (!vif->up)
+ return -EIO;
+
+ goto req;
+ }
+
+ list_for_each_entry(tmp, &priv->vifs, list) {
+ if (!tmp->up)
+ continue;
+
+ vif = tmp;
+ break;
+ }
+
+req:
+ if (!vif)
+ return -EIO;
+
+ return ra6w_ctrl_set_tx_power_req(&priv->core->ctrl, vif->vif_idx, tx_pwr);
+}
+
+static int ra6w_cfg80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *ndev,
+ bool enabled, int timeout)
+{
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+ u8 ps_mode;
+
+ if (!vif->up)
+ return -EIO;
+
+ if (!ra6w_params_ps_supported())
+ return -EOPNOTSUPP;
+
+ ps_mode = enabled ? RA6W_CFG80211_PS_MODE_ON_DYN : RA6W_CFG80211_PS_MODE_OFF;
+
+ return ra6w_ctrl_set_power_mgmt_req(&priv->core->ctrl, ps_mode);
+}
+
+static int ra6w_cfg80211_get_station(struct wiphy *wiphy, struct net_device *ndev,
+ const u8 *mac, struct station_info *sinfo)
+{
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+ struct ra6w_cfg80211_sta *sta = NULL;
+
+ if (!vif->up)
+ return -EIO;
+
+ if (vif->type == NL80211_IFTYPE_MONITOR)
+ return -EINVAL;
+
+ sta = ra6w_cfg80211_find_sta(vif->priv, mac);
+ if (!sta)
+ return -ENOENT;
+
+ return ra6w_cfg80211_station_info_fill(sta, sinfo, vif);
+}
+
+static int ra6w_cfg80211_dump_station(struct wiphy *wiphy, struct net_device *ndev, int idx,
+ u8 *mac, struct station_info *sinfo)
+{
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+ struct ra6w_cfg80211_sta *sta = NULL;
+ struct ra6w_cfg80211_sta *tmp = NULL;
+ int i = 0;
+
+ if (!vif)
+ return -ENOENT;
+
+ if (vif->type == NL80211_IFTYPE_MONITOR)
+ return -EINVAL;
+
+ if ((vif->type == NL80211_IFTYPE_STATION ||
+ vif->type == NL80211_IFTYPE_P2P_CLIENT) &&
+ !idx && vif->sta.ap && vif->sta.ap->valid) {
+ sta = vif->sta.ap;
+ goto fill;
+ }
+
+ list_for_each_entry(tmp, &vif->ap.sta_list, list) {
+ if (i == idx) {
+ sta = tmp;
+ break;
+ }
+
+ i++;
+ }
+
+fill:
+ if (!sta)
+ return -ENOENT;
+
+ ether_addr_copy(mac, (const u8 *)&sta->mac_addr);
+
+ return ra6w_cfg80211_station_info_fill(sta, sinfo, vif);
+}
+
+static int ra6w_cfg80211_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev,
+ struct ieee80211_channel *chan,
+ unsigned int duration, u64 *cookie)
+{
+ int ret = 0;
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_cfg80211_vif *vif = netdev_priv(wdev->netdev);
+ struct ra6w_cfg80211_remain_on_channel *roc = NULL;
+
+ if (!priv || !vif)
+ return -EIO;
+
+ if (priv->roc)
+ return -EBUSY;
+
+ roc = kzalloc(sizeof(*roc), GFP_KERNEL);
+ if (!roc)
+ return -ENOMEM;
+
+ roc->vif = vif;
+ roc->chan = chan;
+ roc->duration = duration;
+ roc->internal = false;
+ roc->on_chan = false;
+ roc->tx_cnt = 0;
+ memset(roc->tx_cookie, 0, sizeof(roc->tx_cookie));
+
+ priv->roc = roc;
+ ret = ra6w_ctrl_remain_on_channel_req(&priv->core->ctrl, vif, chan, duration);
+ if (ret) {
+ kfree(roc);
+ priv->roc = NULL;
+
+ return ret;
+ }
+
+ if (cookie)
+ *cookie = (u64)roc;
+
+ return ret;
+}
+
+static int ra6w_cfg80211_cancel_remain_on_channel(struct wiphy *wiphy,
+ struct wireless_dev *wdev, u64 cookie)
+{
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+
+ if (!priv->roc)
+ return 0;
+
+ if (cookie != (u64)priv->roc)
+ return -EINVAL;
+
+ return ra6w_ctrl_cancel_remain_on_channel_req(&priv->core->ctrl);
+}
+
+static int ra6w_cfg80211_channel_switch(struct wiphy *wiphy, struct net_device *ndev,
+ struct cfg80211_csa_settings *params)
+{
+ int ret = 0;
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+ struct ra6w_cfg80211_beacon_info *bcn;
+ struct ra6w_cfg80211_beacon_info *bcn_after;
+ struct ra6w_cfg80211_csa_info *csa;
+ u16 csa_oft[RA6W_CMD_BCN_MAX_CSA_CPT];
+ u8 *bcn_buf;
+ const u8 *bcn_after_buf;
+ int i;
+
+ if (vif->ap.csa)
+ return -EBUSY;
+
+ if (params->n_counter_offsets_beacon > RA6W_CMD_BCN_MAX_CSA_CPT)
+ return -EINVAL;
+
+ bcn = &vif->ap.bcn;
+ bcn_buf = ra6w_cfg80211_create_beacon(bcn, ¶ms->beacon_csa);
+ if (!bcn_buf)
+ return -ENOMEM;
+
+ memset(csa_oft, 0, sizeof(csa_oft));
+ for (i = 0; i < params->n_counter_offsets_beacon; i++)
+ csa_oft[i] = params->counter_offsets_beacon[i] + bcn->head_len + bcn->tim_len;
+
+ if (params->count == 0) {
+ params->count = 2;
+ for (i = 0; i < params->n_counter_offsets_beacon; i++)
+ bcn_buf[csa_oft[i]] = 2;
+ }
+
+ csa = kzalloc(sizeof(*csa), GFP_KERNEL);
+ if (!csa) {
+ ret = -ENOMEM;
+ goto free_bcn_buf;
+ }
+
+ bcn_after = &csa->bcn;
+ bcn_after_buf = ra6w_cfg80211_create_beacon(bcn_after, ¶ms->beacon_after);
+ if (!bcn_after_buf) {
+ ret = -ENOMEM;
+ goto free_csa;
+ }
+
+ vif->ap.csa = csa;
+ csa->vif = vif;
+ csa->chandef = params->chandef;
+ memcpy(csa->buf, bcn_after_buf, bcn_after->len);
+ kfree(bcn_after_buf);
+
+ /* Send new Beacon. FW will extract channel and count from the beacon */
+ ret = ra6w_ctrl_change_beacon_req(&priv->core->ctrl, vif->vif_idx, bcn_buf, bcn->len,
+ bcn->head_len, bcn->tim_len, csa_oft);
+ if (ret)
+ goto free_csa;
+
+ INIT_WORK(&csa->work, ra6w_cfg80211_csa_work);
+ cfg80211_ch_switch_started_notify(ndev, &csa->chandef, params->link_id,
+ params->count, params->block_tx);
+
+ kfree(bcn_buf);
+
+ return ret;
+
+free_csa:
+ ra6w_cfg80211_csa_remove(vif);
+
+free_bcn_buf:
+ kfree(bcn_buf);
+
+ return ret;
+}
+
+static int ra6w_cfg80211_dump_survey(struct wiphy *wiphy, struct net_device *ndev, int idx,
+ struct survey_info *info)
+{
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ const struct ra6w_cfg80211_survey_info *survey_info = NULL;
+ struct ieee80211_supported_band *sband;
+
+ if (idx >= ARRAY_SIZE(priv->survey_table))
+ return -ENONET;
+
+ survey_info = &priv->survey_table[idx];
+
+ sband = wiphy->bands[NL80211_BAND_2GHZ];
+ if (!sband)
+ return -ENOENT;
+
+ if (idx >= sband->n_channels) {
+ idx -= sband->n_channels;
+ sband = NULL;
+ }
+
+ if (!sband) {
+ sband = wiphy->bands[NL80211_BAND_5GHZ];
+
+ if (!sband || idx >= sband->n_channels)
+ return -ENOENT;
+ }
+
+ info->channel = &sband->channels[idx];
+ info->filled = survey_info->filled;
+
+ if (info->filled) {
+ info->time = (u64)survey_info->chan_dwell_ms;
+ info->time_busy = (u64)survey_info->chan_busy_ms;
+ info->noise = survey_info->chan_noise_dbm;
+ }
+
+ return 0;
+}
+
+static int ra6w_cfg80211_get_channel(struct wiphy *wiphy, struct wireless_dev *wdev,
+ unsigned int link_id,
+ struct cfg80211_chan_def *chandef)
+{
+ struct ra6w_cfg80211_vif *vif = container_of(wdev, struct ra6w_cfg80211_vif, wdev);
+ const struct ra6w_cfg80211_chan_info *chan_info = NULL;
+
+ if (!vif->up)
+ return -ENODATA;
+
+ chan_info = ra6w_cfg80211_chaninfo_get(vif);
+ if (!chan_info)
+ return -ENODATA;
+
+ *chandef = chan_info->chan_def;
+
+ return 0;
+}
+
+static int ra6w_cfg80211_mgmt_tx_cancel_wait(struct wiphy *wiphy, struct wireless_dev *wdev,
+ u64 cookie)
+{
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ int n_tx_cookie = 0;
+ int i;
+
+ if (!priv->roc || !priv->roc->tx_cnt)
+ return 0;
+
+ for (i = 0; i < RA6W_CFG80211_ROC_TX; i++) {
+ n_tx_cookie++;
+
+ if (priv->roc->tx_cookie[i] == cookie) {
+ priv->roc->tx_cookie[i] = 0;
+ priv->roc->tx_cnt--;
+ break;
+ }
+ }
+
+ if (i == RA6W_CFG80211_ROC_TX) {
+ if (n_tx_cookie != priv->roc->tx_cnt)
+ priv->roc->tx_cnt--;
+ else
+ return 0;
+ }
+
+ if (!priv->roc->internal || priv->roc->tx_cnt > 0)
+ return 0;
+
+ return ra6w_cfg80211_cancel_remain_on_channel(wiphy, wdev, (u64)priv->roc);
+}
+
+static int ra6w_cfg80211_set_cqm_rssi_config(struct wiphy *wiphy, struct net_device *ndev,
+ s32 rssi_thold, u32 rssi_hyst)
+{
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_ctrl *ctrl = &priv->core->ctrl;
+ const struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+
+ return ra6w_ctrl_cqm_rssi_config_req(ctrl, vif->vif_idx, rssi_thold, rssi_hyst);
+}
+
+static int ra6w_cfg80211_change_bss(struct wiphy *wiphy, struct net_device *ndev,
+ struct bss_parameters *params)
+{
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_ctrl *ctrl = &priv->core->ctrl;
+ struct ra6w_cmd_ap_isolate_rsp rsp;
+
+ if (params->ap_isolate < 0)
+ return -EINVAL;
+
+ vif->ap.ap_isolate = params->ap_isolate;
+
+ return ra6w_ctrl_set_ap_isolate_req(ctrl, vif->ap.ap_isolate, &rsp);
+}
+
+static void ra6w_cfg80211_ht_set(struct ieee80211_sta_ht_cap *ht_cap)
+{
+ int i;
+ int nss;
+
+ ht_cap->ht_supported = ra6w_params_ht_supported();
+ if (!ht_cap->ht_supported)
+ return;
+
+ if (ra6w_params_stbc_enabled())
+ ht_cap->cap |= 1 << IEEE80211_HT_CAP_RX_STBC_SHIFT;
+
+ if (ra6w_params_ldpc_enabled())
+ ht_cap->cap |= IEEE80211_HT_CAP_LDPC_CODING;
+
+ nss = ra6w_params_nss();
+ ht_cap->mcs.rx_highest = cpu_to_le16(65 * nss);
+
+ if (nss > 1)
+ ht_cap->cap |= IEEE80211_HT_CAP_TX_STBC;
+
+ ht_cap->ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K;
+ ht_cap->ampdu_density = IEEE80211_HT_MPDU_DENSITY_16;
+
+ if (ra6w_params_rx_amsdu_size())
+ ht_cap->cap |= IEEE80211_HT_CAP_MAX_AMSDU;
+
+ ht_cap->mcs.rx_mask[0] = 0xff;
+ ht_cap->mcs.rx_highest = cpu_to_le16(65);
+ ht_cap->mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED;
+
+ if (ra6w_params_use_sgi()) {
+ ht_cap->cap |= IEEE80211_HT_CAP_SGI_20;
+ ht_cap->mcs.rx_highest = cpu_to_le16(72 * nss);
+ }
+
+ for (i = 0; i < nss; i++)
+ ht_cap->mcs.rx_mask[i] = 0xFF;
+}
+
+static void ra6w_cfg80211_vht_set(struct ieee80211_sta_vht_cap *vht_cap)
+{
+ u8 nss;
+ u32 mcs_map;
+ u32 mcs_map_max_2ss_rx = IEEE80211_VHT_MCS_SUPPORT_0_9;
+ int mcs_map_max_2ss_tx = IEEE80211_VHT_MCS_SUPPORT_0_9;
+ u32 i;
+
+ vht_cap->vht_supported = ra6w_params_vht_supported();
+ if (!vht_cap->vht_supported)
+ return;
+
+ vht_cap->cap = 7 << IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT;
+
+ if (ra6w_params_stbc_enabled())
+ vht_cap->cap |= IEEE80211_VHT_CAP_RXSTBC_1;
+
+ if (ra6w_params_ldpc_enabled())
+ vht_cap->cap |= IEEE80211_VHT_CAP_RXLDPC;
+
+ if (ra6w_params_bfmee_enabled()) {
+ vht_cap->cap |= IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE;
+ vht_cap->cap |= 3 << IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT;
+ }
+
+ nss = ra6w_params_nss();
+ if (nss > 1)
+ vht_cap->cap |= IEEE80211_VHT_CAP_TXSTBC;
+
+ vht_cap->cap |= ra6w_params_rx_amsdu_size();
+
+ if (ra6w_params_bfmer_enabled()) {
+ vht_cap->cap |= IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE;
+ vht_cap->cap |= (nss - 1) << IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_SHIFT;
+ }
+
+ if (ra6w_params_mu_mimo_rx_enabled())
+ vht_cap->cap |= IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE;
+
+ if (ra6w_params_mu_mimo_tx_enabled())
+ vht_cap->cap |= IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE;
+
+ mcs_map = ra6w_params_mcs_map_range();
+ vht_cap->vht_mcs.rx_mcs_map = cpu_to_le16(0);
+
+ for (i = 0; i < nss; i++) {
+ vht_cap->vht_mcs.rx_mcs_map |= cpu_to_le16(mcs_map << (i * 2));
+ vht_cap->vht_mcs.rx_highest = cpu_to_le16(ra6w_mcs_map_to_rate[mcs_map] * nss);
+ mcs_map = min_t(int, mcs_map, mcs_map_max_2ss_rx);
+ }
+
+ for (; i < 8; i++)
+ vht_cap->vht_mcs.rx_mcs_map |=
+ cpu_to_le16(IEEE80211_VHT_MCS_NOT_SUPPORTED << (i * 2));
+
+ mcs_map = ra6w_params_mcs_map_range();
+ vht_cap->vht_mcs.tx_mcs_map = cpu_to_le16(0);
+
+ for (i = 0; i < nss; i++) {
+ vht_cap->vht_mcs.tx_mcs_map |= cpu_to_le16(mcs_map << (i * 2));
+ vht_cap->vht_mcs.tx_highest = cpu_to_le16(ra6w_mcs_map_to_rate[mcs_map] * nss);
+ mcs_map = min_t(int, mcs_map, mcs_map_max_2ss_tx);
+ }
+
+ for (; i < 8; i++)
+ vht_cap->vht_mcs.tx_mcs_map |=
+ cpu_to_le16(IEEE80211_VHT_MCS_NOT_SUPPORTED << (i * 2));
+
+ vht_cap->cap &= ~IEEE80211_VHT_CAP_SHORT_GI_80;
+}
+
+static void ra6w_cfg80211_threshold_set(struct ieee80211_sta_he_cap *he_cap)
+{
+ const u8 PPE_THRES_INFO_OFT = 7;
+ const u8 PPE_THRES_INFO_BIT_LEN = 6;
+ struct ppe_thres_info_tag {
+ u8 ppet16 : 3;
+ u8 ppet8 : 3;
+ } __packed;
+
+ struct ppe_thres_field_tag {
+ u8 nsts : 3;
+ u8 ru_idx_bmp : 4;
+ };
+
+ struct ppe_thres_field_tag *ppe_thres_field;
+ struct ppe_thres_info_tag ppe_thres_info = {
+ .ppet16 = 0, // BPSK
+ .ppet8 = 7 // None
+ };
+
+ const u8 *ppe_thres_info_ptr = (u8 *)&ppe_thres_info;
+ u16 *ppe_thres_ptr = (u16 *)he_cap->ppe_thres;
+ u8 i;
+ u8 offset;
+ u8 nss;
+
+ ppe_thres_field = (struct ppe_thres_field_tag *)he_cap->ppe_thres;
+
+ ppe_thres_field->ru_idx_bmp = 1;
+
+ nss = ra6w_params_nss();
+ ppe_thres_field->nsts = nss - 1;
+ for (i = 0; i < nss; i++) {
+ offset = i * PPE_THRES_INFO_BIT_LEN + PPE_THRES_INFO_OFT;
+ ppe_thres_ptr = (u16 *)&he_cap->ppe_thres[offset / 8];
+ *ppe_thres_ptr |= *ppe_thres_info_ptr << (offset % 8);
+ }
+}
+
+static void ra6w_cfg80211_he_set(struct ra6w_cfg80211_priv *priv,
+ struct ieee80211_sband_iftype_data *ifdata)
+{
+ int i;
+ u8 nss;
+ struct ieee80211_sta_he_cap *he_cap = &ifdata->he_cap;
+ int mcs_map;
+ int mcs_map_max_2ss = IEEE80211_HE_MCS_SUPPORT_0_11;
+ u8 dcm_max_ru = IEEE80211_HE_PHY_CAP8_DCM_MAX_RU_242;
+ u32 phy_vers = le32_to_cpu(priv->core->sinfo.fw_ver.phy_version);
+ __le16 unsup_for_ss = 0;
+
+ he_cap->has_he = ra6w_params_he_supported();
+ if (!he_cap->has_he)
+ return;
+
+ if (ra6w_params_twt_enabled()) {
+ priv->ext_capa[9] = WLAN_EXT_CAPA10_TWT_REQUESTER_SUPPORT;
+ he_cap->he_cap_elem.mac_cap_info[0] |= IEEE80211_HE_MAC_CAP0_TWT_REQ;
+ }
+
+ he_cap->he_cap_elem.mac_cap_info[2] |= IEEE80211_HE_MAC_CAP2_ALL_ACK;
+
+ ra6w_cfg80211_threshold_set(he_cap);
+
+ if (ra6w_params_ldpc_enabled())
+ he_cap->he_cap_elem.phy_cap_info[1] |= IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD;
+
+ he_cap->he_cap_elem.phy_cap_info[1] |=
+ IEEE80211_HE_PHY_CAP1_HE_LTF_AND_GI_FOR_HE_PPDUS_0_8US |
+ IEEE80211_HE_PHY_CAP1_MIDAMBLE_RX_TX_MAX_NSTS;
+ he_cap->he_cap_elem.phy_cap_info[2] |= IEEE80211_HE_PHY_CAP2_MIDAMBLE_RX_TX_MAX_NSTS |
+ IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US |
+ IEEE80211_HE_PHY_CAP2_DOPPLER_RX;
+
+ if (ra6w_params_stbc_enabled())
+ he_cap->he_cap_elem.phy_cap_info[2] |= IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ;
+
+ he_cap->he_cap_elem.phy_cap_info[3] |= IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_16_QAM |
+ IEEE80211_HE_PHY_CAP3_RX_PARTIAL_BW_SU_IN_20MHZ_MU;
+
+ nss = ra6w_params_nss();
+ if (nss > 0)
+ he_cap->he_cap_elem.phy_cap_info[3] |= IEEE80211_HE_PHY_CAP3_DCM_MAX_RX_NSS_2;
+ else
+ he_cap->he_cap_elem.phy_cap_info[3] |= IEEE80211_HE_PHY_CAP3_DCM_MAX_RX_NSS_1;
+
+ if (ra6w_params_bfmee_enabled()) {
+ he_cap->he_cap_elem.phy_cap_info[4] |= IEEE80211_HE_PHY_CAP4_SU_BEAMFORMEE;
+ he_cap->he_cap_elem.phy_cap_info[4] |=
+ IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_4;
+ }
+
+ he_cap->he_cap_elem.phy_cap_info[5] |= IEEE80211_HE_PHY_CAP5_NG16_SU_FEEDBACK |
+ IEEE80211_HE_PHY_CAP5_NG16_MU_FEEDBACK;
+ he_cap->he_cap_elem.phy_cap_info[6] |=
+ IEEE80211_HE_PHY_CAP6_CODEBOOK_SIZE_42_SU |
+ IEEE80211_HE_PHY_CAP6_CODEBOOK_SIZE_75_MU |
+ IEEE80211_HE_PHY_CAP6_TRIG_SU_BEAMFORMING_FB |
+ IEEE80211_HE_PHY_CAP6_TRIG_MU_BEAMFORMING_PARTIAL_BW_FB |
+ IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT |
+ IEEE80211_HE_PHY_CAP6_PARTIAL_BANDWIDTH_DL_MUMIMO;
+ he_cap->he_cap_elem.phy_cap_info[7] |=
+ IEEE80211_HE_PHY_CAP7_HE_SU_MU_PPDU_4XLTF_AND_08_US_GI;
+ he_cap->he_cap_elem.phy_cap_info[8] |= IEEE80211_HE_PHY_CAP8_20MHZ_IN_40MHZ_HE_PPDU_IN_2G |
+ dcm_max_ru;
+ he_cap->he_cap_elem.phy_cap_info[9] |=
+ IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_COMP_SIGB |
+ IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_NON_COMP_SIGB |
+ IEEE80211_HE_PHY_CAP9_NOMINAL_PKT_PADDING_16US;
+
+ /* Starting from version v31 more HE_ER_SU modulations is supported */
+ if (RA6W_PHY_VERSION(phy_vers) > 30) {
+ he_cap->he_cap_elem.phy_cap_info[6] |= IEEE80211_HE_PHY_CAP6_PARTIAL_BW_EXT_RANGE;
+ he_cap->he_cap_elem.phy_cap_info[8] |=
+ IEEE80211_HE_PHY_CAP8_HE_ER_SU_1XLTF_AND_08_US_GI |
+ IEEE80211_HE_PHY_CAP8_HE_ER_SU_PPDU_4XLTF_AND_08_US_GI;
+ }
+
+ mcs_map = ra6w_params_he_mcs_map_range();
+ memset(&he_cap->he_mcs_nss_supp, 0, sizeof(he_cap->he_mcs_nss_supp));
+ for (i = 0; i < nss; i++) {
+ unsup_for_ss = cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << (i * 2));
+ he_cap->he_mcs_nss_supp.rx_mcs_80 |= cpu_to_le16(mcs_map << (i * 2));
+ he_cap->he_mcs_nss_supp.rx_mcs_160 |= unsup_for_ss;
+ he_cap->he_mcs_nss_supp.rx_mcs_80p80 |= unsup_for_ss;
+ mcs_map = min_t(int, ra6w_params_he_mcs_map_range(), mcs_map_max_2ss);
+ }
+
+ for (; i < 8; i++) {
+ unsup_for_ss = cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << (i * 2));
+ he_cap->he_mcs_nss_supp.rx_mcs_80 |= unsup_for_ss;
+ he_cap->he_mcs_nss_supp.rx_mcs_160 |= unsup_for_ss;
+ he_cap->he_mcs_nss_supp.rx_mcs_80p80 |= unsup_for_ss;
+ }
+
+ mcs_map = ra6w_params_he_mcs_map_range();
+ for (i = 0; i < nss; i++) {
+ unsup_for_ss = cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << (i * 2));
+ he_cap->he_mcs_nss_supp.tx_mcs_80 |= cpu_to_le16(mcs_map << (i * 2));
+ he_cap->he_mcs_nss_supp.tx_mcs_160 |= unsup_for_ss;
+ he_cap->he_mcs_nss_supp.tx_mcs_80p80 |= unsup_for_ss;
+ mcs_map = min_t(int, ra6w_params_he_mcs_map_range(), mcs_map_max_2ss);
+ }
+
+ for (; i < 8; i++) {
+ unsup_for_ss = cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << (i * 2));
+ he_cap->he_mcs_nss_supp.tx_mcs_80 |= unsup_for_ss;
+ he_cap->he_mcs_nss_supp.tx_mcs_160 |= unsup_for_ss;
+ he_cap->he_mcs_nss_supp.tx_mcs_80p80 |= unsup_for_ss;
+ }
+}
+
+static int ra6w_cfg80211_set_default_wiphy(struct ra6w_cfg80211_priv *priv, struct wiphy *wiphy)
+{
+ const u8 *mac_addr = priv->core->sinfo.default_mac;
+ int i;
+
+ ether_addr_copy(wiphy->perm_addr, mac_addr);
+ for (i = 0; i < RA6W_CFG80211_VIF_MAX; i++) {
+ ether_addr_copy(priv->addresses[i].addr, mac_addr);
+ priv->addresses[i].addr[5] ^= i;
+ }
+
+ wiphy->addresses = priv->addresses;
+ wiphy->n_addresses = RA6W_CFG80211_VIF_MAX;
+
+ bitmap_zero(priv->addr_map, RA6W_CFG80211_VIF_MAX);
+
+ wiphy->mgmt_stypes = ra6w_macm_stypes;
+
+ ra6w_cfg80211_ht_set(&ra6w_band_2g.ht_cap);
+ if (ra6w_params_he_supported()) {
+ ra6w_cfg80211_he_set(priv, &ra6w_cap_he_2g);
+ ra6w_band_2g.iftype_data = &ra6w_cap_he_2g;
+ }
+
+ wiphy->bands[NL80211_BAND_2GHZ] = &ra6w_band_2g;
+
+ ra6w_cfg80211_ht_set(&ra6w_band_5g.ht_cap);
+ ra6w_cfg80211_vht_set(&ra6w_band_5g.vht_cap);
+ if (ra6w_params_he_supported()) {
+ ra6w_cfg80211_he_set(priv, &ra6w_cap_he_5g);
+ ra6w_band_5g.iftype_data = &ra6w_cap_he_5g;
+ }
+
+ wiphy->bands[NL80211_BAND_5GHZ] = &ra6w_band_5g;
+
+ wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
+ BIT(NL80211_IFTYPE_AP) |
+ BIT(NL80211_IFTYPE_P2P_CLIENT) |
+ BIT(NL80211_IFTYPE_P2P_GO) |
+ BIT(NL80211_IFTYPE_MONITOR);
+ wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL |
+ WIPHY_FLAG_HAS_CHANNEL_SWITCH |
+ WIPHY_FLAG_4ADDR_STATION |
+ WIPHY_FLAG_4ADDR_AP |
+ WIPHY_FLAG_REPORTS_OBSS |
+ WIPHY_FLAG_OFFCHAN_TX;
+
+ wiphy->max_scan_ssids = RA6W_CMD_SCAN_SSID_MAX;
+ wiphy->max_scan_ie_len = RA6W_CMD_SCAN_MAX_IE_LEN;
+
+ wiphy->support_mbssid = 1;
+
+ wiphy->max_num_csa_counters = RA6W_CMD_BCN_MAX_CSA_CPT;
+ wiphy->max_remain_on_channel_duration = 5000;
+
+ wiphy->features |= NL80211_FEATURE_NEED_OBSS_SCAN |
+ NL80211_FEATURE_SK_TX_STATUS |
+ NL80211_FEATURE_VIF_TXPOWER |
+ NL80211_FEATURE_ACTIVE_MONITOR |
+ NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE |
+ NL80211_FEATURE_SAE;
+
+ wiphy->iface_combinations = ra6w_if_comb;
+ wiphy->n_iface_combinations = ARRAY_SIZE(ra6w_if_comb) - 1;
+
+ if (ra6w_params_regd_mode_is_auto())
+ wiphy->reg_notifier = ra6w_cfg80211_reg_notifier;
+ else
+ wiphy->regulatory_flags |= REGULATORY_WIPHY_SELF_MANAGED;
+
+ wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM;
+
+ wiphy->extended_capabilities = priv->ext_capa;
+ wiphy->extended_capabilities_mask = priv->ext_capa;
+ wiphy->extended_capabilities_len = ARRAY_SIZE(priv->ext_capa);
+
+ wiphy->cipher_suites = ra6w_cipher_suites;
+ wiphy->n_cipher_suites =
+ ARRAY_SIZE(ra6w_cipher_suites) - RA6W_CFG80211_RESERVED_CIPHER_NUM;
+
+ if (ra6w_params_wapi_supported())
+ ra6w_cipher_suites[wiphy->n_cipher_suites++] = WLAN_CIPHER_SUITE_SMS4;
+
+ if (ra6w_params_mfp_supported())
+ ra6w_cipher_suites[wiphy->n_cipher_suites++] = WLAN_CIPHER_SUITE_AES_CMAC;
+
+ if (ra6w_params_ccmp256_supported())
+ ra6w_cipher_suites[wiphy->n_cipher_suites++] = WLAN_CIPHER_SUITE_CCMP_256;
+
+ if (ra6w_params_gcmp_supported()) {
+ ra6w_cipher_suites[wiphy->n_cipher_suites++] = WLAN_CIPHER_SUITE_GCMP;
+ ra6w_cipher_suites[wiphy->n_cipher_suites++] = WLAN_CIPHER_SUITE_GCMP_256;
+ }
+
+ if (ra6w_params_tdls_supported()) {
+ wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS;
+ wiphy->features |= NL80211_FEATURE_TDLS_CHANNEL_SWITCH;
+ wiphy->flags |= WIPHY_FLAG_TDLS_EXTERNAL_SETUP;
+ }
+
+ if (ra6w_params_ap_uapsd_enabled())
+ wiphy->flags |= WIPHY_FLAG_AP_UAPSD;
+
+ if (ra6w_params_ps_supported())
+ wiphy->flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT;
+
+ strscpy(wiphy->fw_version, priv->core->sinfo.fw_version, sizeof(wiphy->fw_version));
+
+ return 0;
+}
+
+static void ra6w_cfg80211_vif_del_all(struct ra6w_cfg80211_priv *priv)
+{
+ struct ra6w_cfg80211_vif *vif = NULL;
+ struct ra6w_cfg80211_vif *tmp_vif = NULL;
+
+ rtnl_lock();
+
+ list_for_each_entry_safe(vif, tmp_vif, &priv->vifs, list) {
+ ra6w_cfg80211_vif_cleanup(vif);
+ ra6w_cfg80211_vif_unassign(priv, vif->vif_idx, vif->addr_idx);
+ }
+
+ rtnl_unlock();
+}
+
+void ra6w_cfg80211_chaninfo_set(struct ra6w_cfg80211_vif *vif, u8 ch_idx,
+ const struct cfg80211_chan_def *chandef)
+{
+ struct ra6w_cfg80211_chan_info *chan_info = NULL;
+ struct ra6w_cfg80211_priv *priv = vif->priv;
+
+ if (ch_idx >= RA6W_CFG80211_CHANINFO_MAX) {
+ ra6w_err("Invalid channel idx %d\n", ch_idx);
+ return;
+ }
+
+ vif->ch_idx = ch_idx;
+ chan_info = &priv->chaninfo_table[ch_idx];
+
+ /* For now chandef is NULL for STATION interface */
+ if (chandef && !chan_info->chan_def.chan) {
+ chan_info->chan_def = *chandef;
+ chan_info->count++;
+ }
+}
+
+void ra6w_cfg80211_chaninfo_unset(struct ra6w_cfg80211_vif *vif)
+{
+ struct ra6w_cfg80211_chan_info *chan_info = NULL;
+ struct ra6w_cfg80211_priv *priv = vif->priv;
+
+ if (vif->ch_idx == RA6W_CFG80211_CH_IDX_INVALID)
+ return;
+
+ chan_info = &priv->chaninfo_table[vif->ch_idx];
+
+ if (chan_info->count == 0)
+ ra6w_dbg("Chan info ref count is already 0\n");
+ else
+ chan_info->count--;
+
+ if (chan_info->count == 0)
+ chan_info->chan_def.chan = NULL;
+
+ vif->ch_idx = RA6W_CFG80211_CH_IDX_INVALID;
+}
+
+static
+struct ra6w_cfg80211_sta *ra6w_cfg80211_get_sta_from_fc(struct ra6w_cfg80211_priv *priv,
+ struct ra6w_cfg80211_vif *vif,
+ const u8 *addr, __le16 fc, bool ap)
+{
+ bool bufferable;
+ struct ra6w_cfg80211_sta *sta = NULL;
+
+ if (!ap) {
+ if (vif->sta.ap && vif->sta.ap->valid &&
+ ether_addr_equal(vif->sta.ap->mac_addr, addr))
+ return vif->sta.ap;
+
+ return NULL;
+ }
+
+ bufferable = ieee80211_is_deauth(fc) ||
+ ieee80211_is_disassoc(fc) ||
+ ieee80211_is_action(fc);
+
+ if (!bufferable)
+ return NULL;
+
+ if (is_broadcast_ether_addr(addr) || is_multicast_ether_addr(addr)) {
+ sta = ra6w_cfg80211_sta_get(priv, vif->ap.bcmc_index);
+ if (!sta || !sta->valid)
+ return NULL;
+
+ return sta;
+ }
+
+ list_for_each_entry(sta, &vif->ap.sta_list, list) {
+ if (sta->valid && ether_addr_equal(sta->mac_addr, addr))
+ return sta;
+ }
+
+ return NULL;
+}
+
+static int ra6w_cfg80211_offchan_proc(struct wiphy *wiphy, struct wireless_dev *wdev,
+ struct ra6w_cfg80211_priv *priv,
+ const struct ra6w_cfg80211_vif *vif,
+ struct cfg80211_mgmt_tx_params *params)
+{
+ struct ieee80211_channel *chan = params->chan;
+ int ret;
+ unsigned int duration = 30;
+
+ if (!chan)
+ return -EINVAL;
+
+ /* Offchannel transmission, need to start a RoC */
+ if (priv->roc) {
+ /* Test if current RoC can be re-used */
+ if (priv->roc->vif != vif || priv->roc->chan->center_freq != chan->center_freq)
+ return -EINVAL;
+
+ return 0;
+ }
+
+ /* Start a new ROC procedure */
+ if (params->wait)
+ duration = params->wait;
+
+ ret = ra6w_cfg80211_remain_on_channel(wiphy, wdev, chan, duration, NULL);
+ if (ret)
+ return ret;
+
+ /* Internal RoC, no need to inform user space about it */
+ priv->roc->internal = true;
+
+ return 0;
+}
+
+static int ra6w_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
+ struct cfg80211_mgmt_tx_params *params, u64 *cookie)
+{
+ int ret = 0;
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_cfg80211_vif *vif = container_of(wdev, struct ra6w_cfg80211_vif, wdev);
+ struct ra6w_cfg80211_sta *sta = NULL;
+ struct ra6w_tx *tx = &priv->core->tx;
+ const struct ieee80211_mgmt *mgmt = (const struct ieee80211_mgmt *)params->buf;
+ bool ap = false;
+ bool offchan = false;
+ const u8 *da;
+ __le16 fc;
+
+ switch (vif->type) {
+ case NL80211_IFTYPE_AP:
+ case NL80211_IFTYPE_P2P_GO:
+ ap = true;
+
+ if (!(ieee80211_is_assoc_resp(mgmt->frame_control) ||
+ ieee80211_is_auth(mgmt->frame_control)))
+ break;
+
+ cfg80211_mgmt_tx_status(wdev, *cookie, params->buf, params->len, true, GFP_ATOMIC);
+
+ break;
+ default:
+ break;
+ }
+
+ da = mgmt->da;
+ fc = mgmt->frame_control;
+ sta = ra6w_cfg80211_get_sta_from_fc(priv, vif, da, fc, ap);
+
+ if (params->offchan) {
+ const struct ra6w_cfg80211_chan_info *chan_info = ra6w_cfg80211_chaninfo_get(vif);
+
+ if (!chan_info)
+ return -EINVAL;
+
+ offchan = chan_info->chan_def.chan->center_freq == params->chan->center_freq;
+
+ ret = ra6w_cfg80211_offchan_proc(wiphy, wdev, priv, vif, params);
+ if (ret)
+ return ret;
+ }
+
+ ret = ra6w_tx_mgmt(tx, vif, sta, params, cookie);
+ if (offchan) {
+ if (priv->roc->tx_cnt < RA6W_CFG80211_ROC_TX)
+ priv->roc->tx_cookie[priv->roc->tx_cnt] = *cookie;
+ else
+ ra6w_warn("[%s] %d frames sent within the same Roc (> RA6W_NET_ROC_TX)",
+ __func__, priv->roc->tx_cnt + 1);
+ priv->roc->tx_cnt++;
+ }
+
+ return ret;
+}
+
+static int ra6w_cfg80211_external_auth(struct wiphy *wiphy, struct net_device *ndev,
+ struct cfg80211_external_auth_params *params)
+{
+ struct ra6w_cfg80211_priv *priv = wiphy_priv(wiphy);
+ struct ra6w_ctrl *ctrl = &priv->core->ctrl;
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+
+ if (!(vif->sta.flags & RA6W_CMD_STA_AUTH_EXT_BIT))
+ return -EINVAL;
+
+ vif->sta.flags &= ~RA6W_CMD_STA_AUTH_EXT_BIT;
+
+ return ra6w_ctrl_sm_ext_auth_req_rsp(ctrl, vif->vif_idx, params->status);
+}
+
+static struct cfg80211_ops ra6w_cfg80211_ops = {
+ .add_virtual_intf = ra6w_cfg80211_add_iface,
+ .del_virtual_intf = ra6w_cfg80211_del_iface,
+ .change_virtual_intf = ra6w_cfg80211_change_iface,
+ .scan = ra6w_cfg80211_scan,
+ .abort_scan = ra6w_cfg80211_scan_abort,
+ .connect = ra6w_cfg80211_connect,
+ .disconnect = ra6w_cfg80211_disconnect,
+ .add_key = ra6w_cfg80211_add_key,
+ .get_key = ra6w_cfg80211_get_key,
+ .del_key = ra6w_cfg80211_del_key,
+ .set_default_key = ra6w_cfg80211_set_default_key,
+ .add_station = ra6w_cfg80211_add_station,
+ .del_station = ra6w_cfg80211_del_station,
+ .change_station = ra6w_cfg80211_change_station,
+ .mgmt_tx = ra6w_cfg80211_mgmt_tx,
+ .mgmt_tx_cancel_wait = ra6w_cfg80211_mgmt_tx_cancel_wait,
+ .start_ap = ra6w_cfg80211_start_ap,
+ .change_beacon = ra6w_cfg80211_change_beacon,
+ .stop_ap = ra6w_cfg80211_stop_ap,
+ .set_monitor_channel = ra6w_cfg80211_set_monitor_channel,
+ .probe_client = ra6w_cfg80211_probe_client,
+ .set_txq_params = ra6w_cfg80211_set_txq_params,
+ .set_tx_power = ra6w_cfg80211_set_tx_power,
+ .set_power_mgmt = ra6w_cfg80211_set_power_mgmt,
+ .get_station = ra6w_cfg80211_get_station,
+ .dump_station = ra6w_cfg80211_dump_station,
+ .remain_on_channel = ra6w_cfg80211_remain_on_channel,
+ .cancel_remain_on_channel = ra6w_cfg80211_cancel_remain_on_channel,
+ .channel_switch = ra6w_cfg80211_channel_switch,
+ .dump_survey = ra6w_cfg80211_dump_survey,
+ .get_channel = ra6w_cfg80211_get_channel,
+ .set_cqm_rssi_config = ra6w_cfg80211_set_cqm_rssi_config,
+ .change_bss = ra6w_cfg80211_change_bss,
+ CFG80211_TESTMODE_CMD(ra6w_testmode_cmd)
+ .external_auth = ra6w_cfg80211_external_auth,
+};
+
+static int ra6w_cfg80211_register(struct ra6w_core *core, struct device *dev)
+{
+ int ret;
+ struct wiphy *wiphy = NULL;
+ struct ra6w_cfg80211_priv *priv = NULL;
+
+ wiphy = wiphy_new(&ra6w_cfg80211_ops, sizeof(*priv));
+ if (!wiphy) {
+ ra6w_err("Failed to create new wiphy\n");
+ return -ENOENT;
+ }
+
+ priv = wiphy_priv(wiphy);
+ priv->core = core;
+
+ set_wiphy_dev(wiphy, dev);
+ priv->wiphy = wiphy;
+ core->priv = priv;
+
+ priv->vif_started = 0;
+ priv->mon_vif_idx = RA6W_CFG80211_VIF_IDX_INVALID;
+
+ bitmap_zero(priv->vif_map, RA6W_CFG80211_VIF_MAX);
+
+ INIT_LIST_HEAD(&priv->vifs);
+
+ priv->roc = NULL;
+
+ priv->ext_capa[0] = WLAN_EXT_CAPA1_EXT_CHANNEL_SWITCHING;
+ priv->ext_capa[2] = WLAN_EXT_CAPA3_MULTI_BSSID_SUPPORT;
+ priv->ext_capa[7] = WLAN_EXT_CAPA8_OPMODE_NOTIF |
+ WLAN_EXT_CAPA8_MAX_MSDU_IN_AMSDU_LSB;
+ priv->ext_capa[8] = WLAN_EXT_CAPA9_MAX_MSDU_IN_AMSDU_MSB;
+
+ ret = ra6w_cfg80211_set_default_wiphy(priv, wiphy);
+ if (ret)
+ goto free_wiphy;
+
+ ret = ra6w_ctrl_me_config(&priv->core->ctrl, wiphy);
+ if (ret)
+ goto free_wiphy;
+
+ ret = wiphy_register(wiphy);
+ if (ret) {
+ ra6w_err("register wiphy device failed: %d\n", ret);
+ goto free_wiphy;
+ }
+
+ ret = ra6w_params_regd_set_self(wiphy);
+ if (ret)
+ goto free_wiphy;
+
+ ra6w_dbgfs_register(priv);
+
+ ret = ra6w_ctrl_dev_start(&priv->core->ctrl, &priv->phy_config);
+ if (ret)
+ goto free_wiphy;
+
+ return 0;
+
+free_wiphy:
+ wiphy_free(wiphy);
+ priv->wiphy = NULL;
+ core->priv = NULL;
+
+ return ret;
+}
+
+void ra6w_cfg80211_deinit(struct ra6w_core *core)
+{
+ struct ra6w_cfg80211_priv *priv = NULL;
+ struct wiphy *wiphy = NULL;
+
+ if (!core)
+ return;
+
+ priv = core->priv;
+ if (!priv)
+ return;
+
+ ra6w_cfg80211_vif_del_all(priv);
+
+ ra6w_dbgfs_deregister(priv);
+
+ wiphy = priv->wiphy;
+ if (wiphy) {
+ wiphy_unregister(wiphy);
+ wiphy_free(wiphy);
+ }
+
+ priv->wiphy = NULL;
+ core->priv = NULL;
+}
+
+int ra6w_cfg80211_init(struct ra6w_core *core, struct device *dev)
+{
+ int ret;
+ struct wireless_dev *wdev = NULL;
+
+ ret = ra6w_cfg80211_register(core, dev);
+ if (ret)
+ return ret;
+
+ rtnl_lock();
+ wdev = ra6w_cfg80211_add_iface(core->priv->wiphy, "wlan%d", NET_NAME_UNKNOWN,
+ NL80211_IFTYPE_STATION, NULL);
+ rtnl_unlock();
+ if (!wdev) {
+ ra6w_info("Failed to instantiate a network device\n");
+ ret = -ENOENT;
+ goto cfg80211_deinit;
+ }
+
+ ra6w_info("New interface created %s\n", wdev->netdev->name);
+
+ return 0;
+
+cfg80211_deinit:
+ ra6w_cfg80211_deinit(core);
+
+ return ret;
+}