new file mode 100644
@@ -0,0 +1,581 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * This file contains asynchronous fw routine.
+ *
+ * Copyright (C) [2022-2025] Renesas Electronics Corporation and/or its affiliates.
+ */
+
+#include "core.h"
+#include "cfg80211.h"
+#include "dev.h"
+#include "params.h"
+#include "dbg.h"
+#include "indi.h"
+
+#define RA6W_INDI_THREAD_NAME "ra6w_indi_thread"
+
+static int ra6w_indi_freq_to_idx(const struct ra6w_cfg80211_priv *priv, u16 freq, u16 *idx_res)
+{
+ const struct ieee80211_supported_band *sband;
+ u16 ch;
+ u16 band;
+ u16 idx = 0;
+ int ret = -ENOENT;
+
+ for (band = NL80211_BAND_2GHZ; band < NUM_NL80211_BANDS; band++) {
+ sband = priv->wiphy->bands[band];
+ if (!sband)
+ continue;
+
+ for (ch = 0; ch < sband->n_channels; ch++, idx++) {
+ if (sband->channels[ch].center_freq != freq)
+ continue;
+
+ *idx_res = idx;
+ ret = 0;
+
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static void ra6w_indi_channel_survey(struct ra6w_cfg80211_priv *priv, const void *data)
+{
+ struct ra6w_cfg80211_survey_info *survey = NULL;
+ const struct ra6w_cmd_sc_survey_info *ind = data;
+ u16 idx = 0;
+ int ret;
+
+ ret = ra6w_indi_freq_to_idx(priv, le16_to_cpu(ind->freq), &idx);
+ if (ret)
+ return;
+
+ survey = &priv->survey_table[idx];
+
+ survey->chan_dwell_ms = le32_to_cpu(ind->chan_dwell_ms);
+ survey->filled |= SURVEY_INFO_TIME;
+ survey->chan_busy_ms = le32_to_cpu(ind->chan_busy_ms);
+ survey->filled |= SURVEY_INFO_TIME_BUSY;
+
+ if (ind->chan_noise_dbm) {
+ survey->chan_noise_dbm = ind->chan_noise_dbm;
+ survey->filled |= SURVEY_INFO_NOISE_DBM;
+ }
+}
+
+static void ra6w_indi_scan_result(struct ra6w_cfg80211_priv *priv, void *data)
+{
+ struct cfg80211_bss *bss = NULL;
+ struct ieee80211_channel *chan = NULL;
+ struct ra6w_cmd_sc_result_ind *ind = data;
+
+ chan = ieee80211_get_channel(priv->wiphy, le16_to_cpu(ind->center_freq));
+ if (!chan)
+ return;
+
+ /*
+ * Since we are using CFG80211_SIGNAL_TYPE_MBM signal_type,
+ * we have to multiply rssi by 100
+ */
+ bss = cfg80211_inform_bss_frame(priv->wiphy, chan,
+ (struct ieee80211_mgmt *)ind->payload,
+ le16_to_cpu(ind->length), (s32)ind->rssi * 100,
+ GFP_ATOMIC);
+ if (!bss)
+ return;
+
+ cfg80211_put_bss(priv->wiphy, bss);
+}
+
+static void ra6w_indi_scan_complete(struct ra6w_cfg80211_priv *priv)
+{
+ struct cfg80211_scan_info info = {
+ .aborted = false,
+ };
+
+ if (!priv->scan_request)
+ return;
+
+ cfg80211_scan_done(priv->scan_request, &info);
+ priv->scan_request = NULL;
+}
+
+static void ra6w_indi_sm_connect(struct ra6w_cfg80211_priv *priv, void *data)
+{
+ struct ra6w_cfg80211_vif *vif = NULL;
+ struct net_device *ndev = NULL;
+ struct ra6w_cmd_sm_connect_ind *ind = data;
+ const u8 *req_ie;
+ const u8 *rsp_ie;
+
+ vif = ra6w_cfg80211_vif_get(priv, ind->vif_idx);
+ if (!vif || !vif->up)
+ return;
+
+ ndev = vif->ndev;
+ if (!ndev)
+ return;
+
+ if (ind->conn_status == 0) {
+ struct ra6w_cfg80211_sta *sta = ra6w_cfg80211_sta_get(priv, ind->ap_idx);
+ struct ieee80211_channel *chan;
+ struct cfg80211_chan_def chandef;
+
+ sta->valid = true;
+ sta->sta_idx = ind->ap_idx;
+ sta->ch_idx = ind->ch_idx;
+ sta->vif_idx = ind->vif_idx;
+ sta->qos = ind->flag_qos;
+ sta->aid = le16_to_cpu(ind->assoc_id);
+ sta->band = ind->oper_chan.ch_band;
+ sta->width = ind->oper_chan.ch_bw;
+ sta->center_freq = le16_to_cpu(ind->oper_chan.freq_prim20);
+ sta->center_freq1 = le16_to_cpu(ind->oper_chan.freq_cen1);
+ sta->center_freq2 = le16_to_cpu(ind->oper_chan.freq_cen2);
+ vif->sta.ap = sta;
+ vif->generation++;
+ chan = ieee80211_get_channel(priv->wiphy, le16_to_cpu(ind->oper_chan.freq_prim20));
+ cfg80211_chandef_create(&chandef, chan, NL80211_CHAN_NO_HT);
+ if (!ra6w_params_ht_supported())
+ chandef.width = NL80211_CHAN_WIDTH_20_NOHT;
+ else
+ chandef.width = chnl2bw[ind->oper_chan.ch_bw];
+ chandef.center_freq1 = le16_to_cpu(ind->oper_chan.freq_cen1);
+ chandef.center_freq2 = le16_to_cpu(ind->oper_chan.freq_cen2);
+ ra6w_cfg80211_chaninfo_set(vif, ind->ch_idx, &chandef);
+ ether_addr_copy(sta->mac_addr, (u8 *)&ind->bssid.addr);
+ }
+
+ req_ie = (const u8 *)ind->assoc_ie_buf;
+ rsp_ie = req_ie + le16_to_cpu(ind->assoc_req_ie_len);
+
+ cfg80211_connect_result(ndev, (const u8 *)ind->bssid.addr,
+ req_ie, le16_to_cpu(ind->assoc_req_ie_len),
+ rsp_ie, le16_to_cpu(ind->assoc_rsp_ie_len),
+ le16_to_cpu(ind->conn_status), GFP_ATOMIC);
+}
+
+static const char *ra6w_indi_reason_to_str(int reason_code)
+{
+ switch (reason_code) {
+ case RA6W_INDI_DIS_RSN_BEACON_MISS: return "BEACON_MISS";
+ case RA6W_INDI_DIS_RSN_PS_TX_MAX_ERR: return "PS_TX_MAX_ERR";
+ case RA6W_INDI_DIS_RSN_CHAN_SWITCH_FAIL: return "CHAN_SWITCH_FAIL";
+ default: return "UNKNOWN";
+ }
+}
+
+static void ra6w_indi_sm_disconnect(struct ra6w_cfg80211_priv *priv, const void *data)
+{
+ struct ra6w_cfg80211_vif *vif = NULL;
+ const struct ra6w_cmd_sm_disconnect_ind *ind = data;
+ struct net_device *ndev = NULL;
+
+ vif = ra6w_cfg80211_vif_get(priv, ind->vif_idx);
+ if (!vif || !vif->up)
+ return;
+
+ ndev = vif->ndev;
+
+ if (!ind->reassoc) {
+ u16 reason_code = le16_to_cpu(ind->reason_code);
+ bool locally_generated = false;
+
+ if (reason_code > RA6W_INDI_DIS_RSN_MIN &&
+ reason_code < RA6W_INDI_DIS_RSN_MAX) {
+ ra6w_info("[%s] disconnect reason is %s\n",
+ __func__, ra6w_indi_reason_to_str(reason_code));
+ reason_code = WLAN_REASON_UNSPECIFIED;
+ locally_generated = true;
+ }
+
+ ra6w_stats_deinit(&vif->stats);
+
+ cfg80211_disconnected(ndev, reason_code, NULL, 0, locally_generated, GFP_ATOMIC);
+ }
+
+ netif_carrier_off(ndev);
+
+ if (vif->sta.ap)
+ ra6w_cfg80211_sta_free(vif, vif->sta.ap->sta_idx);
+
+ vif->generation++;
+
+ ra6w_cfg80211_chaninfo_unset(vif);
+}
+
+static void ra6w_indi_me_mic_failure(struct ra6w_cfg80211_priv *priv, void *data)
+{
+ struct ra6w_cfg80211_vif *vif = NULL;
+ struct net_device *ndev = NULL;
+ struct ra6w_cmd_me_mic_failure_ind *ind = data;
+
+ vif = ra6w_cfg80211_vif_get(priv, ind->vif_idx);
+ if (!vif || !vif->up)
+ return;
+
+ ndev = vif->ndev;
+ if (!ndev)
+ return;
+
+ cfg80211_michael_mic_failure(ndev, (u8 *)&ind->mac_addr,
+ (ind->group ? NL80211_KEYTYPE_GROUP :
+ NL80211_KEYTYPE_PAIRWISE),
+ ind->keyid, (u8 *)&ind->tsc, GFP_ATOMIC);
+}
+
+static void ra6w_indi_twt_setup(struct ra6w_cfg80211_priv *priv, const void *data)
+{
+ struct ra6w_cfg80211_sta *sta = NULL;
+ const struct ra6w_cfg80211_twt_setup_ind *ind = data;
+
+ sta = ra6w_cfg80211_sta_get(priv, ind->sta_idx);
+ if (!sta)
+ return;
+
+ memcpy(&sta->twt_ind, ind, sizeof(*ind));
+}
+
+static void ra6w_indi_rssi_status(struct ra6w_cfg80211_priv *priv, const void *data)
+{
+ struct ra6w_cfg80211_vif *vif = NULL;
+ struct net_device *ndev = NULL;
+ const struct ra6w_cmd_rssi_status_ind *ind = data;
+
+ vif = ra6w_cfg80211_vif_get(priv, ind->vif_idx);
+ if (!vif || !vif->up)
+ return;
+
+ ndev = vif->ndev;
+ if (!ndev)
+ return;
+
+ cfg80211_cqm_rssi_notify(ndev, (ind->rssi_status ? NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW :
+ NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH),
+ ind->rssi, GFP_ATOMIC);
+}
+
+static void ra6w_indi_sm_ext_auth_req(struct ra6w_cfg80211_priv *priv, void *data)
+{
+ struct ra6w_cfg80211_vif *vif = NULL;
+ struct cfg80211_external_auth_params params;
+ struct ra6w_cmd_sm_ext_auth_req_ind *ind = data;
+
+ vif = ra6w_cfg80211_vif_get(priv, ind->vif_idx);
+ if (!vif || !vif->up)
+ return;
+
+ params.action = NL80211_EXTERNAL_AUTH_START;
+ ether_addr_copy(params.bssid, (u8 *)&ind->bssid.addr);
+ params.ssid.ssid_len = ind->ssid.ssid_len;
+ memcpy(params.ssid.ssid, ind->ssid.ssid,
+ min_t(size_t, ind->ssid.ssid_len, sizeof(params.ssid.ssid)));
+ params.key_mgmt_suite = le32_to_cpu(ind->key_mgmt_suite);
+
+ if (vif->type != NL80211_IFTYPE_STATION ||
+ cfg80211_external_auth_request(vif->ndev, ¶ms, GFP_ATOMIC)) {
+ struct ra6w_ctrl *ctrl = &priv->core->ctrl;
+
+ wiphy_err(priv->wiphy, "Failed to start external auth on vif %d", ind->vif_idx);
+ ra6w_ctrl_sm_ext_auth_req_rsp(ctrl, ind->vif_idx, WLAN_STATUS_UNSPECIFIED_FAILURE);
+
+ return;
+ }
+
+ vif->sta.flags |= RA6W_CMD_STA_AUTH_EXT_BIT;
+}
+
+static void ra6w_indi_rx_chan_switch(struct ra6w_cfg80211_priv *priv, const void *data)
+{
+ struct ra6w_cfg80211_vif *vif = NULL;
+ const struct ra6w_cmd_channel_switch_ind *ind = data;
+
+ if (ind->roc_tdls) {
+ u8 vif_idx = ind->vif_idx;
+
+ list_for_each_entry(vif, &priv->vifs, list) {
+ if (vif->vif_idx == vif_idx)
+ vif->roc_tdls = true;
+ }
+ } else if (ind->roc_req) {
+ struct ra6w_cfg80211_remain_on_channel *roc = priv->roc;
+
+ if (!roc)
+ return;
+
+ vif = roc->vif;
+ if (!vif)
+ return;
+
+ if (!roc->internal)
+ cfg80211_ready_on_channel(&vif->wdev, (u64)(roc), roc->chan,
+ roc->duration, GFP_ATOMIC);
+
+ roc->on_chan = true;
+ }
+
+ priv->chaninfo_index = ind->chan_index;
+}
+
+static void ra6w_indi_ap_probe_client(struct ra6w_cfg80211_priv *priv, const void *data)
+{
+ struct ra6w_cfg80211_vif *vif = NULL;
+ struct ra6w_cfg80211_sta *sta = NULL;
+ const struct ra6w_cmd_ap_probe_client_ind *ind = data;
+
+ vif = ra6w_cfg80211_vif_get(priv, ind->vif_idx);
+ if (!vif || !vif->up)
+ return;
+
+ sta = ra6w_cfg80211_sta_get(priv, ind->sta_idx);
+ if (!sta)
+ return;
+
+ sta->stats.last_acttive_time = jiffies;
+ cfg80211_probe_status(vif->ndev, sta->mac_addr, (u64)le32_to_cpu(ind->probe_id),
+ ind->client_present, 0, false, GFP_ATOMIC);
+}
+
+static void ra6w_indi_roc_exp(struct ra6w_cfg80211_priv *priv)
+{
+ struct ra6w_cfg80211_remain_on_channel *roc = NULL;
+ struct ra6w_cfg80211_vif *vif = NULL;
+
+ roc = priv->roc;
+ if (!roc)
+ return;
+
+ vif = roc->vif;
+ if (!vif)
+ return;
+
+ if (!roc->internal && roc->on_chan)
+ cfg80211_remain_on_channel_expired(&vif->wdev, (u64)(roc), roc->chan, GFP_ATOMIC);
+
+ kfree(roc);
+ priv->roc = NULL;
+}
+
+static void ra6w_indi_pktloss_notify(struct ra6w_cfg80211_priv *priv, const void *data)
+{
+ struct ra6w_cfg80211_vif *vif = NULL;
+ const struct ra6w_cmd_pktloss_ind *ind = data;
+
+ vif = ra6w_cfg80211_vif_get(priv, ind->vif_idx);
+ if (!vif || !vif->up)
+ return;
+
+ cfg80211_cqm_pktloss_notify(vif->ndev, (const u8 *)ind->mac_addr.addr,
+ le32_to_cpu(ind->num_packets), GFP_ATOMIC);
+}
+
+static void ra6w_indi_csa_counter(struct ra6w_cfg80211_priv *priv, const void *data)
+{
+ struct ra6w_cfg80211_vif *vif = NULL;
+ const struct ra6w_cmd_csa_counter_ind *ind = data;
+
+ vif = ra6w_cfg80211_vif_get(priv, ind->vif_idx);
+ if (!vif || !vif->up)
+ return;
+
+ if (!vif->ap.csa) {
+ netdev_err(vif->ndev, "CSA counter update but no active CSA");
+ return;
+ }
+
+ vif->ap.csa->count = ind->csa_count;
+}
+
+static void ra6w_indi_csa_finish(struct ra6w_cfg80211_priv *priv, const void *data)
+{
+ struct ra6w_cfg80211_vif *vif = NULL;
+ const struct ra6w_cmd_csa_finish_ind *ind = data;
+
+ vif = ra6w_cfg80211_vif_get(priv, ind->vif_idx);
+ if (!vif || !vif->up)
+ return;
+
+ if (vif->type == NL80211_IFTYPE_AP ||
+ vif->type == NL80211_IFTYPE_P2P_GO) {
+ if (!vif->ap.csa) {
+ netdev_err(vif->ndev, "CSA finish indication but no active CSA");
+ return;
+ }
+
+ vif->ap.csa->status = ind->status;
+ vif->ap.csa->ch_idx = ind->chan_idx;
+ schedule_work(&vif->ap.csa->work);
+
+ return;
+ }
+
+ if (ind->status == 0) {
+ ra6w_cfg80211_chaninfo_unset(vif);
+ ra6w_cfg80211_chaninfo_set(vif, ind->chan_idx, NULL);
+ }
+}
+
+static void ra6w_indi_rx(struct ra6w_indi *indi, struct sk_buff *skb)
+{
+ struct ra6w_core *core = container_of(indi, struct ra6w_core, indi);
+ struct ra6w_cfg80211_priv *priv = core->priv;
+ struct ra6w_indi_buf *indi_data = (struct ra6w_indi_buf *)skb->data;
+
+ if (!priv || !indi_data || indi_data->data_len == 0)
+ return;
+
+ switch (indi_data->cmd) {
+ case RA6W_CMD_ME_TKIP_MIC_FAILURE_IND:
+ ra6w_indi_me_mic_failure(priv, indi_data->data);
+ break;
+ case RA6W_CMD_SC_RESULT_IND:
+ ra6w_indi_scan_result(priv, indi_data->data);
+ break;
+ case RA6W_CMD_SC_COMPLETE_IND:
+ ra6w_indi_scan_complete(priv);
+ break;
+ case RA6W_CMD_SC_CHANNEL_SURVEY_IND:
+ ra6w_indi_channel_survey(priv, indi_data->data);
+ break;
+ case RA6W_CMD_SM_CONNECT_IND:
+ ra6w_indi_sm_connect(priv, indi_data->data);
+ break;
+ case RA6W_CMD_SM_DISCONNECT_IND:
+ ra6w_indi_sm_disconnect(priv, indi_data->data);
+ break;
+ case RA6W_CMD_SM_EXTERNAL_AUTH_REQUIRED_IND:
+ ra6w_indi_sm_ext_auth_req(priv, indi_data->data);
+ break;
+ case RA6W_CMD_TWT_SETUP_IND:
+ ra6w_indi_twt_setup(priv, indi_data->data);
+ break;
+ case RA6W_CMD_AM_PROBE_CLIENT_IND:
+ ra6w_indi_ap_probe_client(priv, indi_data->data);
+ break;
+ case RA6W_CMD_MM_CHANNEL_SWITCH_IND:
+ ra6w_indi_rx_chan_switch(priv, indi_data->data);
+ break;
+ case RA6W_CMD_MM_CHANNEL_PRE_SWITCH_IND:
+ break;
+ case RA6W_CMD_MM_REMAIN_ON_CHANNEL_EXP_IND:
+ ra6w_indi_roc_exp(priv);
+ break;
+ case RA6W_CMD_MM_PS_CHANGE_IND:
+ break;
+ case RA6W_CMD_MM_TRAFFIC_REQ_IND:
+ break;
+ case RA6W_CMD_MM_RSSI_STATUS_IND:
+ ra6w_indi_rssi_status(priv, indi_data->data);
+ break;
+ case RA6W_CMD_MM_CSA_COUNTER_IND:
+ ra6w_indi_csa_counter(priv, indi_data->data);
+ break;
+ case RA6W_CMD_MM_CSA_FINISH_IND:
+ ra6w_indi_csa_finish(priv, indi_data->data);
+ break;
+ case RA6W_CMD_MM_CSA_TRAFFIC_IND:
+ break;
+ case RA6W_CMD_MM_PACKET_LOSS_IND:
+ ra6w_indi_pktloss_notify(priv, indi_data->data);
+ break;
+ case RA6W_CMD_DBG_ERROR_IND:
+ ra6w_recovery_event_post(&core->recovery);
+ break;
+ default:
+ ra6w_err("[%s] unknown indi cmd[%d]\n", __func__, indi_data->cmd);
+ break;
+ }
+}
+
+static void ra6w_indi_worker(struct ra6w_indi *indi)
+{
+ struct sk_buff *skb = NULL;
+
+ while (!kthread_should_stop() &&
+ (skb = ra6w_q_pop(&indi->q))) {
+ ra6w_indi_rx(indi, skb);
+ dev_kfree_skb(skb);
+ }
+}
+
+static int indi_thread_handler(void *arg)
+{
+ struct ra6w_indi *indi = arg;
+ int event = 0;
+
+ do {
+ event = ra6w_q_wait(&indi->event, RA6W_INDI_EVENT_MASK);
+ if (event & BIT(RA6W_INDI_EVENT))
+ ra6w_indi_worker(indi);
+
+ if (event & BIT(RA6W_INDI_EVENT_RESET))
+ break;
+
+ atomic_set(&indi->event.condition, 0);
+ } while (!kthread_should_stop());
+
+ return 0;
+}
+
+static int _ra6w_indi_init(struct ra6w_indi *indi, size_t indi_buf_num)
+{
+ int ret;
+
+ if (indi_buf_num == 0) {
+ ra6w_err("[%s] indication queue size must be greater then zero\n", __func__);
+ return -EINVAL;
+ }
+
+ ret = ra6w_q_init(&indi->q, indi_buf_num, sizeof(struct ra6w_indi_buf *));
+ if (ret)
+ return ret;
+
+ atomic_set(&indi->event.condition, 0);
+ init_waitqueue_head(&indi->event.wait_queue);
+
+ indi->task = kthread_run(indi_thread_handler, indi, RA6W_INDI_THREAD_NAME);
+ if (!indi->task) {
+ ra6w_err("[%s] kthread_run %s failed\n", __func__, RA6W_INDI_THREAD_NAME);
+ goto indi_buf_free;
+ }
+
+ return 0;
+
+indi_buf_free:
+ ra6w_q_deinit(&indi->q);
+
+ return ret;
+}
+
+int ra6w_indi_init(struct ra6w_indi *indi)
+{
+ return _ra6w_indi_init(indi, RA6W_INDI_BUF_Q_MAX);
+}
+
+void ra6w_indi_deinit(struct ra6w_indi *indi)
+{
+ if (indi->task) {
+ atomic_set(&indi->event.condition, BIT(RA6W_INDI_EVENT_RESET));
+ kthread_stop(indi->task);
+ }
+
+ ra6w_q_deinit(&indi->q);
+}
+
+int ra6w_indi_event_post(struct ra6w_indi *indi, struct sk_buff *skb)
+{
+ struct ra6w_core *core = container_of(indi, struct ra6w_core, indi);
+ struct ra6w_indi_buf *indi_data = (struct ra6w_indi_buf *)skb->data;
+ int ret;
+
+ if (indi_data->ext_len == RA6W_INDI_EXT_LEN)
+ ra6w_status_set(&core->status, indi_data->ext_hdr.status);
+
+ ret = ra6w_q_push(&indi->q, skb);
+ if (!ret || !ra6w_q_empty(&indi->q))
+ ra6w_q_event_set(&indi->event, BIT(RA6W_INDI_EVENT));
+
+ return ret;
+}