From 0fa55ca418c8afd6da242407a184c23548c553dc Mon Sep 17 00:00:00 2001 From: Sriram R Date: Fri, 5 Jun 2020 12:21:15 +0530 Subject: [PATCH 2/3] ath11k: Add nss driver interface This patch adds interface support for accessing nss driver with support for initialization, teardown, vap up/down, peer create/delete, tx/rx. NSS Stats addition is not part of this version. Signed-off-by: Sriram R --- drivers/net/wireless/ath/ath11k/Kconfig | 9 + drivers/net/wireless/ath/ath11k/Makefile | 1 + drivers/net/wireless/ath/ath11k/nss.c | 1762 ++++++++++++++++++++++++++++++ drivers/net/wireless/ath/ath11k/nss.h | 217 ++++ 4 files changed, 1989 insertions(+) create mode 100644 drivers/net/wireless/ath/ath11k/nss.c create mode 100644 drivers/net/wireless/ath/ath11k/nss.h --- a/drivers/net/wireless/ath/ath11k/Kconfig +++ b/drivers/net/wireless/ath/ath11k/Kconfig @@ -11,6 +11,15 @@ config ATH11K If you choose to build a module, it'll be called ath11k. +config ATH11K_NSS_SUPPORT + bool "QCA ath11k nss support" + depends on ATH11K + default y + ---help--- + Enables NSS offload support for ATH11K driver + + If unsure, say Y to enable NSS offload support. + config ATH11K_AHB tristate "Atheros ath11k AHB support" depends on m --- a/drivers/net/wireless/ath/ath11k/Makefile +++ b/drivers/net/wireless/ath/ath11k/Makefile @@ -27,6 +27,7 @@ ath11k-$(CONFIG_THERMAL) += thermal.o ath11k-$(CONFIG_WANT_DEV_COREDUMP) += coredump.o ath11k-$(CPTCFG_ATH11K_SPECTRAL) += spectral.o ath11k-$(CPTCFG_ATH11K_PKTLOG) += pktlog.o +ath11k-$(CPTCFG_ATH11K_NSS_SUPPORT) += nss.o obj-$(CPTCFG_ATH11K_AHB) += ath11k_ahb.o ath11k_ahb-y += ahb.o --- /dev/null +++ b/drivers/net/wireless/ath/ath11k/nss.c @@ -0,0 +1,2388 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear +/* + * Copyright (c) 2020 The Linux Foundation. All rights reserved. + */ + +#include "mac.h" +#include "nss.h" +#include "core.h" +#include "peer.h" +#include "dp_rx.h" +#include "dp_tx.h" +#include "hif.h" +#include "../../../../../net/mac80211/sta_info.h" + +/*-----------------------------ATH11K-NSS Helpers--------------------------*/ + +static enum ath11k_nss_opmode +ath11k_nss_get_vdev_opmode(struct ath11k_vif *arvif) +{ + switch (arvif->vdev_type) { + case WMI_VDEV_TYPE_AP: + return ATH11K_NSS_OPMODE_AP; + case WMI_VDEV_TYPE_STA: + return ATH11K_NSS_OPMODE_STA; + default: + ath11k_warn(arvif->ar->ab, "unsupported nss vdev type %d\n", + arvif->vdev_type); + } + + return ATH11K_NSS_OPMODE_UNKNOWN; +} + +static void ath11k_nss_wifili_stats_sync(struct ath11k_base *ab, + struct nss_wifili_stats_sync_msg *wlsoc_stats) +{ + struct nss_wifili_device_stats *devstats = &wlsoc_stats->stats; + struct ath11k_soc_dp_stats *soc_stats = &ab->soc_stats; + int i; + + spin_lock_bh(&ab->base_lock); + + soc_stats->err_ring_pkts += devstats->rxwbm_stats.err_src_rxdma; + soc_stats->invalid_rbm += devstats->rxwbm_stats.invalid_buf_mgr; + + for (i = 0; i < HAL_REO_ENTR_RING_RXDMA_ECODE_MAX; i++) + soc_stats->rxdma_error[i] += devstats->rxwbm_stats.err_dma_codes[i]; + + for (i = 0; i < HAL_REO_DEST_RING_ERROR_CODE_MAX; i++) + soc_stats->reo_error[i] += devstats->rxwbm_stats.err_reo_codes[i]; + + for (i = 0; i < DP_REO_DST_RING_MAX; i++) + soc_stats->hal_reo_error[i] += devstats->rxreo_stats[i].ring_error; + + for (i = 0; i < DP_TCL_NUM_RING_MAX; i++) + soc_stats->tx_err.desc_na[i] += devstats->tcl_stats[i].tcl_ring_full; + + + for (i = 0; i < NSS_WIFILI_MAX_TCL_DATA_RINGS_MSG; i++) + atomic_add(devstats->txcomp_stats[i].invalid_bufsrc + + devstats->txcomp_stats[i].invalid_cookie + + devstats->tx_sw_pool_stats[i].desc_alloc_fail + + devstats->tx_ext_sw_pool_stats[i].desc_alloc_fail, + &soc_stats->tx_err.misc_fail); + + for (i = 0; i < DP_TCL_NUM_RING_MAX; i++) + atomic_add(devstats->tx_data_stats[i].tx_send_fail_cnt, + &soc_stats->tx_err.misc_fail); + + spin_unlock_bh(&ab->base_lock); +} + +static void ath11k_nss_get_peer_stats(struct ath11k_base *ab, struct nss_wifili_peer_stats *stats) +{ + struct ath11k_peer *peer; + struct nss_wifili_peer_ctrl_stats *pstats = NULL; + int i, j; + u64 tx_packets, tx_bytes, tx_dropped = 0; + u64 rx_packets, rx_bytes, rx_dropped; + + if (!ab->nss.stats_enabled) + return; + + for (i = 0; i < stats->npeers; i++) { + pstats = &stats->wpcs[i]; + + rcu_read_lock(); + spin_lock_bh(&ab->base_lock); + + peer = ath11k_peer_find_by_id(ab, pstats->peer_id); + if (!peer || !peer->sta) { + ath11k_dbg(ab, ATH11K_DBG_NSS, "nss wifili: unable to find peer %d\n", pstats->peer_id); + spin_unlock_bh(&ab->base_lock); + rcu_read_unlock(); + continue; + } + + if (!peer->nss.nss_stats) { + spin_unlock_bh(&ab->base_lock); + rcu_read_unlock(); + return; + } + + if (pstats->tx.tx_success_cnt) + peer->nss.nss_stats->last_ack = jiffies; + + if (pstats->rx.rx_recvd) { + peer->nss.nss_stats->last_rx = jiffies; + } + + tx_packets = pstats->tx.tx_mcast_cnt + + pstats->tx.tx_ucast_cnt + + pstats->tx.tx_bcast_cnt; + peer->nss.nss_stats->tx_packets += tx_packets; + tx_bytes = pstats->tx.tx_mcast_bytes + + pstats->tx.tx_ucast_bytes + + pstats->tx.tx_bcast_bytes; + peer->nss.nss_stats->tx_bytes += tx_bytes; + peer->nss.nss_stats->tx_retries += pstats->tx.retries; + + for (j = 0; j < NSS_WIFILI_TQM_RR_MAX; j++) + tx_dropped += pstats->tx.dropped.drop_stats[j]; + + peer->nss.nss_stats->tx_failed += tx_dropped; + + ATH11K_NSS_TXRX_NETDEV_STATS(tx, peer->vif, tx_bytes, tx_packets); + + rx_packets = pstats->rx.rx_recvd; + peer->nss.nss_stats->rx_packets += rx_packets; + rx_bytes = pstats->rx.rx_recvd_bytes; + peer->nss.nss_stats->rx_bytes += rx_bytes; + rx_dropped = pstats->rx.err.mic_err + + pstats->rx.err.decrypt_err; + peer->nss.nss_stats->rx_dropped += rx_dropped; + + ATH11K_NSS_TXRX_NETDEV_STATS(rx, peer->vif, rx_bytes, rx_packets); + + spin_unlock_bh(&ab->base_lock); + rcu_read_unlock(); + } +} + +void ath11k_nss_ext_rx_stats(struct ath11k_base *ab, struct htt_rx_ring_tlv_filter *tlv_filter) +{ + if (ab->nss.enabled) + tlv_filter->rx_filter |= HTT_RX_FILTER_TLV_FLAGS_PPDU_END_USER_STATS; +} + +static u32 ath11k_nss_cipher_type(struct ath11k_base *ab, u32 cipher) +{ + switch (cipher) { + case WLAN_CIPHER_SUITE_CCMP: + return PEER_SEC_TYPE_AES_CCMP; + case WLAN_CIPHER_SUITE_TKIP: + return PEER_SEC_TYPE_TKIP; + case WLAN_CIPHER_SUITE_CCMP_256: + return PEER_SEC_TYPE_AES_CCMP_256; + case WLAN_CIPHER_SUITE_GCMP: + return PEER_SEC_TYPE_AES_GCMP; + case WLAN_CIPHER_SUITE_GCMP_256: + return PEER_SEC_TYPE_AES_GCMP_256; + default: + ath11k_warn(ab, "unknown cipher type %d\n", cipher); + return PEER_SEC_TYPE_NONE; + } +} + +static void ath11k_nss_tx_encap_nwifi(struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (void *)skb->data; + u8 *qos_ctl; + + if (!ieee80211_is_data_qos(hdr->frame_control)) + return; + + qos_ctl = ieee80211_get_qos_ctl(hdr); + memmove(skb->data + IEEE80211_QOS_CTL_LEN, + skb->data, (void *)qos_ctl - (void *)skb->data); + skb_pull(skb, IEEE80211_QOS_CTL_LEN); + + hdr = (void *)skb->data; + hdr->frame_control &= ~__cpu_to_le16(IEEE80211_STYPE_QOS_DATA); +} + +static void ath11k_nss_tx_encap_raw(struct sk_buff *skb) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_hdr *hdr = (void *)skb->data; + u32 cipher; + + if (!ieee80211_has_protected(hdr->frame_control) || !info->control.hw_key) + return; + + /* Include length for MIC */ + skb_put(skb, IEEE80211_CCMP_MIC_LEN); + + /* Include length for ICV if TKIP is used */ + cipher = info->control.hw_key->cipher; + if (cipher == WLAN_CIPHER_SUITE_TKIP) + skb_put(skb, IEEE80211_TKIP_ICV_LEN); +} + +static void ath11k_nss_peer_mem_free(struct ath11k_base *ab, u32 peer_id) +{ + struct ath11k_peer *peer; + + spin_lock_bh(&ab->base_lock); + + peer = ath11k_peer_find_by_id(ab, peer_id); + if (!peer) { + spin_unlock_bh(&ab->base_lock); + ath11k_warn(ab, "ath11k_nss: unable to free peer mem, peer_id:%d\n", + peer_id); + return; + } + + dma_unmap_single(ab->dev, peer->nss.paddr, + WIFILI_NSS_PEER_BYTE_SIZE, DMA_FROM_DEVICE); + + kfree(peer->nss.vaddr); + if (peer->nss.nss_stats) { + kfree(peer->nss.nss_stats); + peer->nss.nss_stats = NULL; + } + + complete(&peer->nss.complete); + spin_unlock_bh(&ab->base_lock); + + ath11k_dbg(ab, ATH11K_DBG_NSS, "nss peer %d mem freed\n", peer_id); +} + +/*-----------------------------Events/Callbacks------------------------------*/ + +void ath11k_nss_wifili_event_receive(struct ath11k_base *ab, struct nss_wifili_msg *msg) +{ + u32 msg_type = msg->cm.type; + enum nss_cmn_response response = msg->cm.response; + u32 error = msg->cm.error; + u32 peer_id; + struct nss_wifili_peer_stats *peer_stats; + + if (!ab) + return; + + ath11k_dbg(ab, ATH11K_DBG_NSS, "nss wifili event received %d response %d error %d\n", + msg_type, response, error); + + switch (msg_type) { + case NSS_WIFILI_INIT_MSG: + case NSS_WIFILI_PDEV_INIT_MSG: + case NSS_WIFILI_START_MSG: + case NSS_WIFILI_SOC_RESET_MSG: + case NSS_WIFILI_STOP_MSG: + case NSS_WIFILI_PDEV_DEINIT_MSG: + ab->nss.response = response; + complete(&ab->nss.complete); + break; + + case NSS_WIFILI_PEER_CREATE_MSG: + if (response != NSS_CMN_RESPONSE_EMSG) + break; + + peer_id = (&msg->msg.peermsg)->peer_id; + + /* free peer memory allocated during peer create due to failure */ + ath11k_nss_peer_mem_free(ab, peer_id); + break; + case NSS_WIFILI_PEER_DELETE_MSG: + peer_id = (&msg->msg.peermsg)->peer_id; + + if (response == NSS_CMN_RESPONSE_EMSG) + ath11k_warn(ab, "ath11k_nss: peer delete failed%d\n", + peer_id); + + /* free peer memory allocated during peer create irrespective of + * delete status + */ + ath11k_nss_peer_mem_free(ab, peer_id); + break; + case NSS_WIFILI_PEER_SECURITY_TYPE_MSG: + if (response == NSS_CMN_RESPONSE_EMSG) + ath11k_warn(ab, "peer securty config failed\n"); + + break; + case NSS_WIFILI_PEER_UPDATE_AUTH_FLAG: + if (response == NSS_CMN_RESPONSE_EMSG) + ath11k_warn(ab, "peer authorize config failed\n"); + + break; + case NSS_WIFILI_STATS_MSG: + if (response == NSS_CMN_RESPONSE_EMSG) { + ath11k_warn(ab, "soc_dp_stats failed to get updated\n"); + break; + } + ath11k_nss_wifili_stats_sync(ab, &msg->msg.wlsoc_stats); + break; + case NSS_WIFILI_PEER_STATS_MSG: + peer_stats = &msg->msg.peer_stats.stats; + if (response == NSS_CMN_RESPONSE_EMSG) { + ath11k_warn(ab, "peer stats msg failed with error = %u\n", error); + break; + } + ath11k_nss_get_peer_stats(ab, peer_stats); + break; + case NSS_WIFILI_TID_REOQ_SETUP_MSG: + /* TODO setup tidq */ + break; + default: + ath11k_dbg(ab, ATH11K_DBG_NSS, "unhandled event %d\n", msg_type); + break; + } +} + +void ath11k_nss_process_mic_error(struct ath11k_base *ab, struct sk_buff *skb) +{ + struct ath11k_vif *arvif; + struct ath11k_peer *peer = NULL; + struct hal_rx_desc *desc = (struct hal_rx_desc *)skb->data; + struct wireless_dev *wdev; + u16 peer_id; + u8 peer_addr[ETH_ALEN]; + u8 ucast_keyidx, mcast_keyidx; + bool is_mcbc; + + if (!ath11k_dp_rx_h_msdu_end_first_msdu(ab, desc)) + goto fail; + + is_mcbc = ath11k_dp_rx_h_attn_is_mcbc(ab, desc); + peer_id = ath11k_dp_rx_h_mpdu_start_peer_id(ab, desc); + + spin_lock_bh(&ab->base_lock); + peer = ath11k_peer_find_by_id(ab, peer_id); + if (!peer) { + ath11k_info(ab, "ath11k_nss:peer not found"); + spin_unlock_bh(&ab->base_lock); + goto fail; + } + + if (!peer->vif) { + ath11k_warn(ab, "ath11k_nss:vif not found"); + spin_unlock_bh(&ab->base_lock); + goto fail; + } + + ether_addr_copy(peer_addr, peer->addr); + mcast_keyidx = peer->mcast_keyidx; + ucast_keyidx = peer->ucast_keyidx; + arvif = ath11k_vif_to_arvif(peer->vif); + spin_unlock_bh(&ab->base_lock); + + if (!arvif->is_started) { + ath11k_warn(ab, "ath11k_nss:arvif not started"); + goto fail; + } + + wdev = ieee80211_vif_to_wdev_relaxed(arvif->vif); + if (!wdev) { + ath11k_warn(ab, "ath11k_nss: wdev is null\n"); + goto fail; + } + + if (!wdev->netdev) { + ath11k_warn(ab, "ath11k_nss: netdev is null\n"); + goto fail; + } + + cfg80211_michael_mic_failure(wdev->netdev, peer_addr, + is_mcbc ? NL80211_KEYTYPE_GROUP : + NL80211_KEYTYPE_PAIRWISE, + is_mcbc ? mcast_keyidx : ucast_keyidx, + NULL, GFP_ATOMIC); + kfree(skb); + return; + +fail: + kfree(skb); + ath11k_warn(ab, "ath11k_nss: Failed to handle mic error\n"); + return; +} + +static void +ath11k_nss_wifili_ext_callback_fn(struct ath11k_base *ab, struct sk_buff *skb, + __attribute__((unused)) struct napi_struct *napi) +{ + struct nss_wifili_soc_per_packet_metadata *wepm; + + wepm = (struct nss_wifili_soc_per_packet_metadata *)(skb->head + + NSS_WIFILI_SOC_PER_PACKET_METADATA_OFFSET); + + switch (wepm->pkt_type) { + case NSS_WIFILI_SOC_EXT_DATA_PKT_MIC_ERROR: + ath11k_nss_process_mic_error(ab, skb); + break; + default: + kfree(skb); + break; + } +} + +void ath11k_nss_vdev_cfg_cb(void *app_data, struct nss_cmn_msg *msg) +{ + struct ath11k_vif *arvif = (struct ath11k_vif *)app_data; + + if (!arvif) + return; + + ath11k_dbg(arvif->ar->ab, ATH11K_DBG_NSS, "vdev cfg msg callback received msg:%d rsp:%d\n", + msg->type, msg->response); + + complete(&arvif->nss.complete); +} + +static void ath11k_nss_vdev_event_receive(void *dev, struct nss_cmn_msg *vdev_msg) +{ + /*TODO*/ +} + +static void +ath11k_nss_vdev_special_data_receive(struct net_device *dev, struct sk_buff *skb, + __attribute__((unused)) struct napi_struct *napi) +{ + /* TODO */ +} + +/* TODO: move to mac80211 after cleanups/refactoring required after feature completion */ +static int ath11k_nss_deliver_rx(struct ieee80211_vif *vif, struct sk_buff *skb, + bool eth, int data_offs, struct napi_struct *napi) +{ + struct sk_buff_head subframe_list; + struct ieee80211_hdr *hdr; + struct sk_buff *subframe; + struct net_device *dev; + int hdr_len; + u8 *qc; + + dev = skb->dev; + + if (eth) + goto deliver_msdu; + + hdr = (struct ieee80211_hdr *)skb->data; + hdr_len = ieee80211_hdrlen(hdr->frame_control); + + if (ieee80211_is_data_qos(hdr->frame_control)) { + qc = ieee80211_get_qos_ctl(hdr); + if (*qc & IEEE80211_QOS_CTL_A_MSDU_PRESENT) + goto deliver_amsdu; + } + + if (ieee80211_data_to_8023_exthdr(skb, NULL, vif->addr, vif->type, + data_offs - hdr_len, false)) { + dev_kfree_skb_any(skb); + return -EINVAL; + } + +deliver_msdu: + skb->protocol = eth_type_trans(skb, dev); + napi_gro_receive(napi, skb); + return 0; + +deliver_amsdu: + /* Move to the start of the first subframe */ + skb_pull(skb, data_offs); + + __skb_queue_head_init(&subframe_list); + + /* create list containing all the subframes */ + ieee80211_amsdu_to_8023s(skb, &subframe_list, NULL, + vif->type, 0, NULL, NULL); + + /* This shouldn't happen, indicating error during defragmentation */ + if (skb_queue_empty(&subframe_list)) + return -EINVAL; + + while (!skb_queue_empty(&subframe_list)) { + subframe = __skb_dequeue(&subframe_list); + subframe->protocol = eth_type_trans(subframe, dev); + napi_gro_receive(napi, subframe); + } + + return 0; +} + +static int ath11k_nss_undecap_raw(struct ath11k_vif *arvif, struct sk_buff *skb, + int *data_offset) +{ + struct ath11k_base *ab = arvif->ar->ab; + struct ath11k *ar = arvif->ar; + enum hal_encrypt_type enctype; + struct ath11k_peer *peer = NULL; + struct ieee80211_hdr *hdr; + int hdr_len; + + hdr = (struct ieee80211_hdr *)skb->data; + hdr_len = ieee80211_hdrlen(hdr->frame_control); + + *data_offset = hdr_len; + + /* FCS is included in the raw mode skb, we can trim it, fcs error + * packets are not expected to be received in this path + */ + skb_trim(skb, skb->len - FCS_LEN); + + spin_lock_bh(&ab->base_lock); + + peer = ath11k_peer_find_by_addr(ab, hdr->addr2); + if (!peer) { + ath11k_warn(ab, "peer not found for raw/nwifi undecap, drop this packet\n"); + spin_unlock_bh(&ab->base_lock); + return -EINVAL; + } + enctype = peer->sec_type; + + spin_unlock_bh(&ab->base_lock); + + *data_offset += ath11k_dp_rx_crypto_param_len(ar, enctype); + + /* Strip ICV, MIC, MMIC */ + skb_trim(skb, skb->len - + ath11k_dp_rx_crypto_mic_len(ar, enctype)); + + skb_trim(skb, skb->len - + ath11k_dp_rx_crypto_icv_len(ar, enctype)); + + if (enctype == HAL_ENCRYPT_TYPE_TKIP_MIC) + skb_trim(skb, skb->len - IEEE80211_CCMP_MIC_LEN); + + return 0; +} + +static int ath11k_nss_undecap_nwifi(struct ath11k_vif *arvif, struct sk_buff *skb, + int *data_offset) +{ + struct ieee80211_hdr *hdr; + int hdr_len; + + hdr = (struct ieee80211_hdr *)skb->data; + hdr_len = ieee80211_hdrlen(hdr->frame_control); + + *data_offset = hdr_len; + + /* We dont receive the IV from nss host on slow path + * hence we can return only the header length as offset. + **/ + return 0; +} + +static void +ath11k_nss_vdev_data_receive(struct net_device *dev, struct sk_buff *skb, + __attribute__((unused)) struct napi_struct *napi) +{ + enum ath11k_hw_txrx_mode decap_type; + struct wireless_dev *wdev; + struct ieee80211_vif *vif; + struct ath11k_vif *arvif; + struct ath11k_base *ab; + bool eth_decap = false; + int data_offs = 0; + int ret; + + if (!dev) { + dev_kfree_skb_any(skb); + return; + } + + wdev = dev->ieee80211_ptr; + if (!wdev) { + dev_kfree_skb_any(skb); + return; + } + + vif = wdev_to_ieee80211_vif(wdev); + if (!vif) { + dev_kfree_skb_any(skb); + return; + } + + arvif = (struct ath11k_vif *)vif->drv_priv; + if (!arvif) { + dev_kfree_skb_any(skb); + return; + } + + ab = arvif->ar->ab; + + skb->dev = dev; + + /* log the original skb received from nss */ + ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "", "dp rx msdu from nss: ", + skb->data, skb->len); + + decap_type = arvif->nss.decap; + + switch (decap_type) { + case ATH11K_HW_TXRX_RAW: + ret = ath11k_nss_undecap_raw(arvif, skb, &data_offs); + break; + case ATH11K_HW_TXRX_NATIVE_WIFI: + ret = ath11k_nss_undecap_nwifi(arvif, skb, &data_offs); + break; + case ATH11K_HW_TXRX_ETHERNET: + /* no changes required for ethernet decap */ + ret = 0; + eth_decap = true; + break; + default: + ret = -EINVAL; + break; + } + + if (ret) { + ath11k_warn(ab, "error in nss rx undecap, type %d err %d\n", decap_type, + ret); + dev_kfree_skb_any(skb); + return; + } + + ath11k_nss_deliver_rx(arvif->vif, skb, eth_decap, data_offs, napi); +} + +int ath11k_nss_tx(struct ath11k_vif *arvif, struct sk_buff *skb) +{ + struct ath11k *ar = arvif->ar; + nss_tx_status_t status; + int encap_type = ath11k_dp_tx_get_encap_type(arvif, skb); + struct ath11k_soc_dp_stats *soc_stats = &ar->ab->soc_stats; + + if(encap_type != arvif->nss.encap) { + ath11k_warn(ar->ab, "encap mismatch in nss tx skb encap type %d" \ + "vif encap type %d\n", encap_type, arvif->nss.encap); + goto drop; + } + + if (encap_type == HAL_TCL_ENCAP_TYPE_ETHERNET) + goto send; + + if (encap_type == HAL_TCL_ENCAP_TYPE_RAW) + ath11k_nss_tx_encap_raw(skb); + else + ath11k_nss_tx_encap_nwifi(skb); + +send: + ath11k_dbg_dump(ar->ab, ATH11K_DBG_DP_TX, "", "nss tx msdu: ", + skb->data, skb->len); + + status = nss_wifi_vdev_tx_buf(arvif->ar->nss.ctx, skb, arvif->nss.if_num); + + if (status != NSS_TX_SUCCESS) { + ath11k_dbg(ar->ab, (ATH11K_DBG_NSS | ATH11K_DBG_DP_TX), + "nss tx failure: %d\n", status); + atomic_inc(&soc_stats->tx_err.nss_tx_fail); + } + + return status; +drop: + atomic_inc(&soc_stats->tx_err.misc_fail); + WARN_ON_ONCE(1); + return -EINVAL; +} + +int ath11k_nss_vdev_set_cmd(struct ath11k_vif *arvif, int cmd, int val) +{ + struct nss_wifi_vdev_msg *vdev_msg = NULL; + struct nss_wifi_vdev_cmd_msg *vdev_cmd; + struct ath11k *ar = arvif->ar; + nss_tx_status_t status; + + if (!ar->ab->nss.enabled) + return 0; + + /* Monitor interface is not offloaded to nss */ + if (arvif->vdev_type == WMI_VDEV_TYPE_MONITOR) + return 0; + + vdev_msg = kzalloc(sizeof(*vdev_msg), GFP_ATOMIC); + if (!vdev_msg) + return -ENOMEM; + + /* TODO: Convert to function for conversion in case of many + * such commands + */ + if (cmd == NSS_WIFI_VDEV_SECURITY_TYPE_CMD) + val = ath11k_nss_cipher_type(ar->ab, val); + + if (cmd == NSS_WIFI_VDEV_ENCAP_TYPE_CMD) + arvif->nss.encap = val; + else if (cmd == NSS_WIFI_VDEV_DECAP_TYPE_CMD) + arvif->nss.decap = val; + + vdev_cmd = &vdev_msg->msg.vdev_cmd; + vdev_cmd->cmd = cmd; + vdev_cmd->value = val; + + nss_wifi_vdev_msg_init(vdev_msg, arvif->nss.if_num, + NSS_WIFI_VDEV_INTERFACE_CMD_MSG, + sizeof(struct nss_wifi_vdev_cmd_msg), + NULL, NULL); + + status = nss_wifi_vdev_tx_msg(ar->nss.ctx, vdev_msg); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ar->ab, "nss vdev set cmd failure cmd:%d val:%d", + cmd, val); + goto free; + } + + ath11k_dbg(ar->ab, ATH11K_DBG_NSS, "nss vdev set cmd success cmd:%d val:%d\n", + cmd, val); +free: + kfree(vdev_msg); + return status; +} + +static int ath11k_nss_vdev_configure(struct ath11k_vif *arvif) +{ + struct ath11k *ar = arvif->ar; + struct nss_wifi_vdev_msg *vdev_msg; + struct nss_wifi_vdev_config_msg *vdev_cfg; + nss_tx_status_t status; + int ret; + + vdev_msg = kzalloc(sizeof(struct nss_wifi_vdev_msg), GFP_ATOMIC); + if (!vdev_msg) + return -ENOMEM; + + vdev_cfg = &vdev_msg->msg.vdev_config; + + vdev_cfg->radio_ifnum = ar->nss.if_num; + vdev_cfg->vdev_id = arvif->vdev_id; + + vdev_cfg->opmode = ath11k_nss_get_vdev_opmode(arvif); + + ether_addr_copy(vdev_cfg->mac_addr, arvif->vif->addr); + + reinit_completion(&arvif->nss.complete); + + nss_wifi_vdev_msg_init(vdev_msg, arvif->nss.if_num, + NSS_WIFI_VDEV_INTERFACE_CONFIGURE_MSG, + sizeof(struct nss_wifi_vdev_config_msg), + (nss_wifi_vdev_msg_callback_t *)ath11k_nss_vdev_cfg_cb, + arvif); + + status = nss_wifi_vdev_tx_msg(ar->nss.ctx, vdev_msg); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ar->ab, "failed to configure nss vdev nss_err:%d\n", + status); + ret = -EINVAL; + goto free; + } + + ret = wait_for_completion_timeout(&arvif->nss.complete, + msecs_to_jiffies(ATH11K_NSS_MSG_TIMEOUT_MS)); + if (!ret) { + ath11k_warn(ar->ab, "timeout in receiving nss vdev cfg response\n"); + ret = -ETIMEDOUT; + goto free; + } + + ret = 0; +free: + kfree(vdev_msg); + + return ret; +} + +static void ath11k_nss_vdev_unregister(struct ath11k_vif *arvif) +{ + struct ath11k_base *ab = arvif->ar->ab; + + switch (arvif->vif->type) { + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_STATION: + nss_unregister_wifi_vdev_if(arvif->nss.if_num); + ath11k_dbg(ab, ATH11K_DBG_NSS, "unregistered nss vdev %d \n", + arvif->nss.if_num); + break; + default: + ath11k_warn(ab, "unsupported interface type %d for nss vdev unregister\n", + arvif->vif->type); + return; + } +} + +static int ath11k_nss_vdev_register(struct ath11k_vif *arvif, + struct net_device *netdev) +{ + struct ath11k *ar = arvif->ar; + struct ath11k_base *ab = ar->ab; + nss_tx_status_t status; + u32 features = 0; + + switch (arvif->vif->type) { + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_STATION: + status = nss_register_wifi_vdev_if(ar->nss.ctx, + arvif->nss.if_num, + ath11k_nss_vdev_data_receive, + ath11k_nss_vdev_special_data_receive, + ath11k_nss_vdev_event_receive, + netdev, features); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ab, "failed to register nss vdev if_num %d nss_err:%d\n", + arvif->nss.if_num, status); + return -EINVAL; + } + + ath11k_dbg(ab, ATH11K_DBG_NSS, "registered nss vdev if_num %d\n", + arvif->nss.if_num); + + break; + default: + ath11k_warn(ab, "unsupported interface type %d for nss vdev register\n", + arvif->vif->type); + return -ENOTSUPP; + } + + return 0; +} + +void ath11k_nss_vdev_free(struct ath11k_vif *arvif) +{ + struct ath11k_base *ab = arvif->ar->ab; + nss_tx_status_t status; + + switch (arvif->vif->type) { + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_STATION: + status = nss_dynamic_interface_dealloc_node( + arvif->nss.if_num, + NSS_DYNAMIC_INTERFACE_TYPE_VAP); + if (status != NSS_TX_SUCCESS) + ath11k_warn(ab, "failed to free nss vdev nss_err:%d\n", + status); + else + ath11k_dbg(ab, ATH11K_DBG_NSS, + "nss vdev interface deallocated\n"); + + return; + default: + ath11k_warn(ab, "unsupported interface type %d for nss vdev dealloc\n", + arvif->vif->type); + return; + } +} + +static int ath11k_nss_vdev_alloc(struct ath11k_vif *arvif) +{ + struct ath11k_base *ab = arvif->ar->ab; + enum nss_dynamic_interface_type if_type; + int if_num; + + /* Initialize completion for verifying NSS message response */ + init_completion(&arvif->nss.complete); + + switch (arvif->vif->type) { + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_STATION: + if_type = NSS_DYNAMIC_INTERFACE_TYPE_VAP; + /* allocate interface context with NSS driver for the new vdev */ + if_num = nss_dynamic_interface_alloc_node(if_type); + if (if_num < 0) { + ath11k_warn(ab, "failed to allocate nss vdev\n"); + return -EINVAL; + } + + arvif->nss.if_num = if_num; + + ath11k_dbg(ab, ATH11K_DBG_NSS, "allocated nss vdev if_num %d\n", + arvif->nss.if_num); + + break; + default: + ath11k_warn(ab, "unsupported interface type %d for nss vdev alloc\n", + arvif->vif->type); + return -ENOTSUPP; + } + + return 0; +} + +int ath11k_nss_vdev_create(struct ath11k_vif *arvif) +{ + struct ath11k *ar = arvif->ar; + struct ath11k_base *ab = ar->ab; + struct wireless_dev *wdev; + int ret; + + if (!ab->nss.enabled) + return 0; + + /* Monitor interface is not offloaded to nss */ + if (arvif->vdev_type == WMI_VDEV_TYPE_MONITOR) + return 0; + + if (arvif->nss.created) + return 0; + + wdev = ieee80211_vif_to_wdev_relaxed(arvif->vif); + if (!wdev) { + ath11k_warn(ab, "ath11k_nss: wdev is null\n"); + return -EINVAL; + } + + if (!wdev->netdev) { + ath11k_warn(ab, "ath11k_nss: netdev is null\n"); + return -EINVAL; + } + + ret = ath11k_nss_vdev_alloc(arvif); + if (ret) + return ret; + + ret = ath11k_nss_vdev_register(arvif, wdev->netdev); + if (ret) + goto free_vdev; + + switch (arvif->vif->type) { + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_STATION: + ret = ath11k_nss_vdev_configure(arvif); + if (ret) + goto unregister_vdev; + + break; + default: + ret = -ENOTSUPP; + goto unregister_vdev; + } + + arvif->nss.created = true; + + ath11k_dbg(ab, ATH11K_DBG_NSS, + "nss vdev interface created ctx %pK, ifnum %d\n", + ar->nss.ctx, arvif->nss.if_num); + + return ret; + +unregister_vdev: + ath11k_nss_vdev_unregister(arvif); +free_vdev: + ath11k_nss_vdev_free(arvif); + + return ret; +} + +void ath11k_nss_vdev_delete(struct ath11k_vif *arvif) +{ + if (!arvif->ar->ab->nss.enabled) + return; + + /* Monitor interface is not offloaded to nss */ + if (arvif->vdev_type == WMI_VDEV_TYPE_MONITOR) + return; + + if (!arvif->nss.created) + return; + + ath11k_nss_vdev_unregister(arvif); + + ath11k_nss_vdev_free(arvif); + + arvif->nss.created = false; +} + +int ath11k_nss_vdev_up(struct ath11k_vif *arvif) +{ + struct nss_wifi_vdev_msg *vdev_msg = NULL; + struct nss_wifi_vdev_enable_msg *vdev_en; + struct ath11k *ar = arvif->ar; + nss_tx_status_t status; + int ret = 0; + + if (!ar->ab->nss.enabled) + return 0; + + /* Monitor interface is not offloaded to nss */ + if (arvif->vdev_type == WMI_VDEV_TYPE_MONITOR) + return 0; + + vdev_msg = kzalloc(sizeof(struct nss_wifi_vdev_msg), GFP_ATOMIC); + if (!vdev_msg) + return -ENOMEM; + + vdev_en = &vdev_msg->msg.vdev_enable; + + ether_addr_copy(vdev_en->mac_addr, arvif->vif->addr); + + nss_wifi_vdev_msg_init(vdev_msg, arvif->nss.if_num, + NSS_WIFI_VDEV_INTERFACE_UP_MSG, + sizeof(struct nss_wifi_vdev_enable_msg), + NULL, NULL); + + status = nss_wifi_vdev_tx_msg(ar->nss.ctx, vdev_msg); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ar->ab, "nss vdev up tx msg error %d\n", status); + ret = -EINVAL; + goto free; + } + + ath11k_dbg(ar->ab, ATH11K_DBG_NSS, "nss vdev up tx msg success\n"); +free: + kfree(vdev_msg); + return ret; +} + +int ath11k_nss_vdev_down(struct ath11k_vif *arvif) +{ + struct nss_wifi_vdev_msg *vdev_msg = NULL; + struct ath11k *ar = arvif->ar; + nss_tx_status_t status; + int ret = 0; + + if (!ar->ab->nss.enabled) + return 0; + + /* Monitor interface is not offloaded to nss */ + if (arvif->vdev_type == WMI_VDEV_TYPE_MONITOR) + return 0; + + vdev_msg = kzalloc(sizeof(struct nss_wifi_vdev_msg), GFP_ATOMIC); + if (!vdev_msg) + return -ENOMEM; + + nss_wifi_vdev_msg_init(vdev_msg, arvif->nss.if_num, + NSS_WIFI_VDEV_INTERFACE_DOWN_MSG, + sizeof(struct nss_wifi_vdev_disable_msg), + NULL, NULL); + + status = nss_wifi_vdev_tx_msg(ar->nss.ctx, vdev_msg); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ar->ab, "nss vdev down tx msg error %d\n", status); + ret = -EINVAL; + goto free; + } + + ath11k_dbg(ar->ab, ATH11K_DBG_NSS, "nss vdev down tx msg success\n"); +free: + kfree(vdev_msg); + return ret; +} + +/*----------------------------Peer Setup/Config -----------------------------*/ + +int ath11k_nss_set_peer_sec_type(struct ath11k *ar, + struct ath11k_peer *peer, + struct ieee80211_key_conf *key_conf) +{ + struct nss_wifili_peer_security_type_msg *sec_msg; + nss_wifili_msg_callback_t msg_cb; + struct nss_wifili_msg *wlmsg; + nss_tx_status_t status; + u8 *mic_key; + + if (!ar->ab->nss.enabled) + return 0; + + wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC); + if (!wlmsg) + return -ENOMEM; + + sec_msg = &wlmsg->msg.securitymsg; + sec_msg->peer_id = peer->peer_id; + + /* 0 -unicast , 1 - mcast/unicast */ + sec_msg->pkt_type = !(key_conf->flags & IEEE80211_KEY_FLAG_PAIRWISE); + + sec_msg->security_type = ath11k_nss_cipher_type(ar->ab, + key_conf->cipher); + + if (sec_msg->security_type == PEER_SEC_TYPE_TKIP) { + mic_key = &key_conf->key[NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY]; + memcpy(&sec_msg->mic_key[0], mic_key, NSS_WIFILI_MIC_KEY_LEN); + } + + msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive; + + nss_cmn_msg_init(&wlmsg->cm, ar->ab->nss.if_num, + NSS_WIFILI_PEER_SECURITY_TYPE_MSG, + sizeof(struct nss_wifili_peer_security_type_msg), + msg_cb, NULL); + + status = nss_wifili_tx_msg(ar->nss.ctx, wlmsg); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ar->ab, "nss peer %d security cfg fail %d\n", + peer->peer_id, status); + goto free; + } + + ath11k_dbg(ar->ab, ATH11K_DBG_NSS, "nss peer id %d security cfg complete\n", + peer->peer_id); +free: + kfree(wlmsg); + return status; +} + +int ath11k_nss_set_peer_authorize(struct ath11k *ar, u16 peer_id) +{ + struct nss_wifili_peer_update_auth_flag *auth_msg; + nss_wifili_msg_callback_t msg_cb; + struct nss_wifili_msg *wlmsg; + nss_tx_status_t status; + + if (!ar->ab->nss.enabled) + return 0; + + wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC); + if (!wlmsg) + return -ENOMEM; + + auth_msg = &wlmsg->msg.peer_auth; + auth_msg->peer_id = peer_id; + auth_msg->auth_flag = 1; + + msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive; + + nss_cmn_msg_init(&wlmsg->cm, ar->ab->nss.if_num, + NSS_WIFILI_PEER_UPDATE_AUTH_FLAG, + sizeof(struct nss_wifili_peer_update_auth_flag), + msg_cb, NULL); + + status = nss_wifili_tx_msg(ar->nss.ctx, wlmsg); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ar->ab, "nss peer %d auth cfg fail %d\n", + peer_id, status); + goto free; + } + + ath11k_dbg(ar->ab, ATH11K_DBG_NSS, "nss peer id %d auth cfg complete\n", + peer_id); +free: + kfree(wlmsg); + return status; +} + +void ath11k_nss_update_sta_stats(struct station_info *sinfo, + struct ieee80211_sta *sta, + struct ath11k_sta *arsta) +{ + struct sta_info *stainfo; + struct ath11k_peer *peer; + int tid_idx; + struct ath11k *ar = arsta->arvif->ar; + struct ath11k_base *ab = ar->ab; + + if (!ab->nss.enabled) + return; + + spin_lock_bh(&ab->base_lock); + peer = ath11k_peer_find_by_addr(arsta->arvif->ar->ab, sta->addr); + if (!peer) { + ath11k_dbg(ab, ATH11K_DBG_NSS, "unable to find peer %pM\n", + sta->addr); + goto exit; + } + + if (!peer->nss.nss_stats) + goto exit; + + stainfo = container_of(sta, struct sta_info, sta); + if (peer->nss.nss_stats->last_rx && + time_after((unsigned long)peer->nss.nss_stats->last_rx, stainfo->rx_stats.last_rx)) + stainfo->rx_stats.last_rx = peer->nss.nss_stats->last_rx; + + if (peer->nss.nss_stats->last_ack && + time_after((unsigned long)peer->nss.nss_stats->last_ack, stainfo->status_stats.last_ack)) + stainfo->status_stats.last_ack = peer->nss.nss_stats->last_ack; + + stainfo->rx_stats.dropped += peer->nss.nss_stats->rx_dropped - + peer->nss.nss_stats->last_rxdrop; + peer->nss.nss_stats->last_rxdrop = peer->nss.nss_stats->rx_dropped; + + sinfo->tx_packets = 0; + /* Add only ac-0 count as mgmt packets uses WME_AC_BE */ + sinfo->tx_packets += stainfo->tx_stats.packets[WME_AC_BE]; + sinfo->tx_packets += peer->nss.nss_stats->tx_packets; + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_PACKETS); + sinfo->tx_bytes = 0; + + /* Add only ac-0 count as mgmt packets uses WME_AC_BE */ + sinfo->tx_bytes += stainfo->tx_stats.bytes[WME_AC_BE]; + sinfo->tx_bytes += peer->nss.nss_stats->tx_bytes; + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BYTES); + + sinfo->tx_failed = stainfo->status_stats.retry_failed + + peer->nss.nss_stats->tx_failed; + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_FAILED); + + sinfo->tx_retries = stainfo->status_stats.retry_count + + peer->nss.nss_stats->tx_retries; + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_RETRIES); + + sinfo->rx_packets = stainfo->rx_stats.packets + + peer->nss.nss_stats->rx_packets; + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_PACKETS); + + sinfo->rx_bytes = stainfo->rx_stats.bytes + + peer->nss.nss_stats->rx_bytes; + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_BYTES); + + if (peer->nss.nss_stats->rxrate.legacy || peer->nss.nss_stats->rxrate.nss) { + if (peer->nss.nss_stats->rxrate.legacy) { + sinfo->rxrate.legacy = peer->nss.nss_stats->rxrate.legacy; + } else { + sinfo->rxrate.mcs = peer->nss.nss_stats->rxrate.mcs; + sinfo->rxrate.nss = peer->nss.nss_stats->rxrate.nss; + sinfo->rxrate.bw = peer->nss.nss_stats->rxrate.bw; + sinfo->rxrate.he_gi = peer->nss.nss_stats->rxrate.he_gi; + sinfo->rxrate.he_dcm = peer->nss.nss_stats->rxrate.he_dcm; + sinfo->rxrate.he_ru_alloc = peer->nss.nss_stats->rxrate.he_ru_alloc; + } + sinfo->rxrate.flags = peer->nss.nss_stats->rxrate.flags; + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_BITRATE); + } + +exit: + spin_unlock_bh(&ab->base_lock); +} + +void ath11k_nss_update_sta_rxrate(struct hal_rx_mon_ppdu_info *ppdu_info, + struct ath11k_peer *peer) +{ + struct ath11k_sta *arsta = (struct ath11k_sta *)peer->sta->drv_priv; + u16 ath11k_hal_rx_legacy_rates[] = + { 10, 20, 55, 60, 90, 110, 120, 180, 240, 360, 480, 540 }; + u16 rate = 0; + u16 num_msdu; + struct ath11k *ar = arsta->arvif->ar; + struct ath11k_base *ab = ar->ab; + + if (!ab->nss.enabled) + return; + + if (!peer->nss.nss_stats) + return; + + if ((ppdu_info->preamble_type == WMI_RATE_PREAMBLE_CCK || + ppdu_info->preamble_type == WMI_RATE_PREAMBLE_OFDM) && + (ppdu_info->rate < ATH11K_LEGACY_NUM)) { + rate = ath11k_hal_rx_legacy_rates[ppdu_info->rate]; + } + + memset(&peer->nss.nss_stats->rxrate, 0, sizeof(peer->nss.nss_stats->rxrate)); + + switch (ppdu_info->preamble_type) { + case WMI_RATE_PREAMBLE_OFDM: + peer->nss.nss_stats->rxrate.legacy = rate; + break; + case WMI_RATE_PREAMBLE_CCK: + peer->nss.nss_stats->rxrate.legacy = rate; + break; + case WMI_RATE_PREAMBLE_HT: + if (ppdu_info->mcs > ATH11K_HT_MCS_MAX) { + ath11k_dbg(ar->ab, ATH11K_DBG_NSS, + "Received invalid mcs in HT mode %d\n", + ppdu_info->mcs); + return; + } + peer->nss.nss_stats->rxrate.mcs = ppdu_info->mcs + 8 * (ppdu_info->nss - 1); + peer->nss.nss_stats->rxrate.flags = RATE_INFO_FLAGS_MCS; + if (ppdu_info->gi) + peer->nss.nss_stats->rxrate.flags |= RATE_INFO_FLAGS_SHORT_GI; + break; + case WMI_RATE_PREAMBLE_VHT: + if (ppdu_info->mcs > ATH11K_VHT_MCS_MAX) { + ath11k_dbg(ar->ab, ATH11K_DBG_NSS, + "Received invalid mcs in VHT mode %d\n", + ppdu_info->mcs); + return; + } + peer->nss.nss_stats->rxrate.mcs = ppdu_info->mcs; + peer->nss.nss_stats->rxrate.flags = RATE_INFO_FLAGS_VHT_MCS; + if (ppdu_info->gi) + peer->nss.nss_stats->rxrate.flags |= RATE_INFO_FLAGS_SHORT_GI; + break; + case WMI_RATE_PREAMBLE_HE: + if (ppdu_info->mcs > ATH11K_HE_MCS_MAX) { + ath11k_dbg(ar->ab, ATH11K_DBG_NSS, + "Received invalid mcs in HE mode %d\n", + ppdu_info->mcs); + return; + } + peer->nss.nss_stats->rxrate.mcs = ppdu_info->mcs; + peer->nss.nss_stats->rxrate.flags = RATE_INFO_FLAGS_HE_MCS; + peer->nss.nss_stats->rxrate.he_dcm = ppdu_info->dcm; + peer->nss.nss_stats->rxrate.he_gi = ath11k_he_gi_to_nl80211_he_gi(ppdu_info->gi); + peer->nss.nss_stats->rxrate.he_ru_alloc = ppdu_info->ru_alloc; + break; + } + + peer->nss.nss_stats->rxrate.nss = ppdu_info->nss; + peer->nss.nss_stats->rxrate.bw = ath11k_mac_bw_to_mac80211_bw(ppdu_info->bw); + + num_msdu = ppdu_info->tcp_msdu_count + ppdu_info->tcp_ack_msdu_count + + ppdu_info->udp_msdu_count + ppdu_info->other_msdu_count; +} + +int ath11k_nss_peer_delete(struct ath11k_base *ab, u8 *addr) +{ + struct nss_wifili_peer_msg *peer_msg; + struct nss_wifili_msg *wlmsg = NULL; + struct ath11k_peer *peer; + nss_wifili_msg_callback_t msg_cb; + nss_tx_status_t status; + int ret; + + if (!ab->nss.enabled) + return 0; + + spin_lock_bh(&ab->base_lock); + + peer = ath11k_peer_find_by_addr(ab, addr); + if (!peer) { + ath11k_warn(ab, "peer not found for nss peer delete\n"); + spin_unlock_bh(&ab->base_lock); + return -EINVAL; + } + + if (!peer->nss.vaddr) { + ath11k_warn(ab, "peer already deleted or peer create failed %pM\n", + addr); + spin_unlock_bh(&ab->base_lock); + return -EINVAL; + } + + wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC); + if (!wlmsg) { + ath11k_warn(ab, "nss send peer delete msg alloc failure\n"); + ret = -ENOMEM; + goto free_peer; + } + + peer_msg = &wlmsg->msg.peermsg; + + peer_msg->vdev_id = peer->vdev_id; + peer_msg->peer_id = peer->peer_id; + + msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive; + + nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num, + NSS_WIFILI_PEER_DELETE_MSG, + sizeof(struct nss_wifili_peer_msg), + msg_cb, NULL); + + reinit_completion(&peer->nss.complete); + + status = nss_wifili_tx_msg(ab->nss.ctx, wlmsg); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ab, "nss send peer (%pM) delete msg tx error %d\n", + addr, status); + ret = -EINVAL; + kfree(wlmsg); + goto free_peer; + } else { + ath11k_dbg(ab, ATH11K_DBG_NSS, "nss peer delete message success : peer_id %d\n", + peer->peer_id); + ret = 0; + } + + spin_unlock_bh(&ab->base_lock); + + kfree(wlmsg); + + /* No need to return failure or free up here, since the msg was tx succesfully + * the peer delete response would be received from NSS which will free up + * the allocated memory + */ + ret = wait_for_completion_timeout(&peer->nss.complete, + msecs_to_jiffies(ATH11K_NSS_MSG_TIMEOUT_MS)); + if (!ret) + ath11k_warn(ab, "timeout while waiting for nss peer delete msg response\n"); + + return 0; + +free_peer: + dma_unmap_single(ab->dev, peer->nss.paddr, + WIFILI_NSS_PEER_BYTE_SIZE, DMA_FROM_DEVICE); + kfree(peer->nss.vaddr); + if (peer->nss.nss_stats) { + kfree(peer->nss.nss_stats); + peer->nss.nss_stats = NULL; + } + spin_unlock_bh(&ab->base_lock); + return ret; +} + +int ath11k_nss_peer_create(struct ath11k_base *ab, struct ath11k_peer *peer) +{ + struct nss_wifili_peer_msg *peer_msg; + struct nss_wifili_msg *wlmsg = NULL; + nss_wifili_msg_callback_t msg_cb; + nss_tx_status_t status; + int ret; + + if (!ab->nss.enabled) + return -ENOTSUPP; + + wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC); + if (!wlmsg) + return -ENOMEM; + + peer_msg = &wlmsg->msg.peermsg; + + peer_msg->vdev_id = peer->vdev_id; + peer_msg->peer_id = peer->peer_id; + peer_msg->hw_ast_idx = peer->hw_peer_id; + peer_msg->tx_ast_hash = peer->ast_hash; + ether_addr_copy(peer_msg->peer_mac_addr, peer->addr); + + peer->nss.vaddr = kzalloc(WIFILI_NSS_PEER_BYTE_SIZE, GFP_ATOMIC); + + /* Initialize completion for verifying Peer NSS message response */ + init_completion(&peer->nss.complete); + + if (!peer->nss.vaddr) { + ath11k_warn(ab, "failed to allocate memory for nss peer info\n"); + kfree(wlmsg); + return -ENOMEM; + } + + peer->nss.paddr = dma_map_single(ab->dev, peer->nss.vaddr, + WIFILI_NSS_PEER_BYTE_SIZE, DMA_TO_DEVICE); + + ret = dma_mapping_error(ab->dev, peer->nss.paddr); + if (ret) { + ath11k_warn(ab, "error during nss peer info memalloc\n"); + kfree(peer->nss.vaddr); + ret = -ENOMEM; + goto msg_free; + } + + peer_msg->nss_peer_mem = peer->nss.paddr; + peer_msg->psta_vdev_id = peer->vdev_id; + + msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive; + + nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num, + NSS_WIFILI_PEER_CREATE_MSG, + sizeof(struct nss_wifili_peer_msg), + msg_cb, NULL); + + status = nss_wifili_tx_msg(ab->nss.ctx, wlmsg); + if (status != NSS_TX_SUCCESS) { + ret = -EINVAL; + ath11k_warn(ab, "nss send peer (%pM) create msg tx error\n", + peer->addr); + goto peer_mem_free; + } + + ret = 0; + ath11k_dbg(ab, ATH11K_DBG_NSS, + "nss peer_create msg success mac:%pM vdev:%d peer_id:%d hw_ast_idx:%d ast_hash:%d\n", + peer_msg->peer_mac_addr, peer_msg->vdev_id, peer_msg->peer_id, + peer_msg->hw_ast_idx, peer_msg->tx_ast_hash); + + peer->nss.nss_stats = kzalloc(sizeof(*peer->nss.nss_stats), GFP_ATOMIC); + if (!peer->nss.nss_stats) { + ret = -ENOMEM; + ath11k_warn(ab, "Unable to create nss stats memory\n"); + goto peer_mem_free; + } + + goto msg_free; + +peer_mem_free: + dma_unmap_single(ab->dev, peer->nss.paddr, + WIFILI_NSS_PEER_BYTE_SIZE, DMA_FROM_DEVICE); + kfree(peer->nss.vaddr); +msg_free: + kfree(wlmsg); + return ret; +} + +/*-------------------------------INIT/DEINIT---------------------------------*/ + +static int ath11k_nss_radio_buf_cfg(struct ath11k *ar, int range, int buf_sz) +{ + struct nss_wifili_radio_buf_cfg_msg *buf_cfg; + struct nss_wifili_radio_cfg_msg *radio_buf_cfg_msg; + struct nss_wifili_msg *wlmsg = NULL; + nss_tx_status_t status; + int ret = 0; + + wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC); + if (!wlmsg) + return -ENOMEM; + + radio_buf_cfg_msg = &wlmsg->msg.radiocfgmsg; + + radio_buf_cfg_msg->radio_if_num = ar->nss.if_num; + buf_cfg = &wlmsg->msg.radiocfgmsg.radiomsg.radiobufcfgmsg; + buf_cfg->range = range; + buf_cfg->buf_cnt = buf_sz; + + nss_cmn_msg_init(&wlmsg->cm, ar->ab->nss.if_num, + NSS_WIFILI_RADIO_BUF_CFG, + sizeof(struct nss_wifili_radio_buf_cfg_msg), + NULL, NULL); + + status = nss_wifili_tx_msg(ar->nss.ctx, wlmsg); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ar->ab, "nss radio buf cfg send failed %d\n", status); + ret = -EINVAL; + } else { + ath11k_dbg(ar->ab, ATH11K_DBG_NSS, + "nss radio cfg message range:%d buf_sz:%d if_num:%d ctx:%p\n", + range, buf_sz, ar->nss.if_num, ar->nss.ctx); + } + + kfree(wlmsg); + return ret; +} + +static void ath11k_nss_fill_srng_info(struct ath11k_base *ab, int ring_id, + struct nss_wifili_hal_srng_info *hsi) +{ + struct ath11k_hal *hal = &ab->hal; + struct hal_srng *srng; + u32 offset; + int i; + + if (ring_id < 0) { + ath11k_warn(ab, "Invalid ring id used for nss init\n"); + WARN_ON(1); + return; + } + + srng = &hal->srng_list[ring_id]; + + hsi->ring_id = srng->ring_id; + hsi->ring_dir = srng->ring_dir; + hsi->ring_base_paddr = srng->ring_base_paddr; + hsi->entry_size = srng->entry_size; + hsi->num_entries = srng->num_entries; + hsi->flags = srng->flags; + + ath11k_dbg(ab, ATH11K_DBG_NSS, + "Ring info to send to nss - ring_id:%d ring_dir:%d ring_paddr:%d entry_size:%d num_entries:%d flags:%d\n", + hsi->ring_id, hsi->ring_dir, hsi->ring_base_paddr, + hsi->entry_size, hsi->num_entries, hsi->flags); + + for (i = 0; i < HAL_SRNG_NUM_REG_GRP; i++) { + offset = srng->hwreg_base[i]; + + /* For PCI based devices, get the umac ring base address offset + * based on window register configuration. + */ + if (!(srng->flags & HAL_SRNG_FLAGS_LMAC_RING)) + offset = ath11k_hif_get_window_offset(ab, srng->hwreg_base[i]); + + hsi->hwreg_base[i] = (u32)ab->mem_pa + offset; + + ath11k_dbg(ab, ATH11K_DBG_NSS, "SRNG Register %d address %d\n", + i, hsi->hwreg_base[i]); + } +} + +static void ath11k_nss_tx_desc_mem_free(struct ath11k_base *ab) +{ + int i; + + for (i = 0; i < ATH11K_NSS_MAX_NUMBER_OF_PAGE; i++) { + if (!ab->nss.tx_desc_paddr[i]) + continue; + + dma_free_coherent(ab->dev, + ab->nss.tx_desc_size[i], + ab->nss.tx_desc_vaddr[i], + ab->nss.tx_desc_paddr[i]); + ab->nss.tx_desc_vaddr[i] = NULL; + } + + ath11k_dbg(ab, ATH11K_DBG_NSS, "allocated tx desc mem freed\n"); +} + +static int ath11k_nss_tx_desc_mem_alloc(struct ath11k_base *ab, u32 required_size, u32 *page_idx) +{ + int i, alloc_size; + int curr_page_idx; + + ath11k_dbg(ab, ATH11K_DBG_NSS, "tx desc mem alloc size: %d\n", required_size); + + curr_page_idx = *page_idx; + + for (i = 0, alloc_size = 0; i < required_size; i += alloc_size) { + alloc_size = required_size - i; + + if (alloc_size > WIFILI_NSS_MAX_MEM_PAGE_SIZE) + alloc_size = WIFILI_NSS_MAX_MEM_PAGE_SIZE; + + ab->nss.tx_desc_vaddr[curr_page_idx] = + dma_alloc_coherent(ab->dev, alloc_size, + &ab->nss.tx_desc_paddr[curr_page_idx], + GFP_KERNEL); + + if (!ab->nss.tx_desc_vaddr[curr_page_idx]) + return -ENOMEM; + + ab->nss.tx_desc_size[curr_page_idx] = alloc_size; + curr_page_idx++; + + ath11k_dbg(ab, ATH11K_DBG_NSS, + "curr page %d, allocated %d, total allocated %d\n", + curr_page_idx, alloc_size, i + alloc_size); + + if (curr_page_idx == ATH11K_NSS_MAX_NUMBER_OF_PAGE) { + ath11k_warn(ab, "max page number reached while tx desc mem allocation\n"); + return -EINVAL; + } + } + *page_idx = curr_page_idx; + return 0; +} + +static int ath11k_nss_fill_tx_desc_info(struct ath11k_base *ab, + struct nss_wifili_init_msg *wim) +{ + struct nss_wifili_tx_desc_addtnl_mem_msg *dam; + u32 required_size, required_size_ext; + struct nss_wifili_tx_desc_init_msg *dim; + u32 tx_desc_limit_0 = 0; + u32 tx_desc_limit_1 = 0; + u32 tx_desc_limit_2 = 0; + u32 dam_page_idx = 0; + int page_idx = 0; + int i; + + wim->tx_sw_internode_queue_size = ATH11K_WIFIILI_MAX_TX_PROCESSQ; + + dim = &wim->wtdim; + dam = &wim->wtdam; + + dim->num_pool = ab->num_radios; + dim->num_tx_device_limit = ATH11K_WIFILI_MAX_TX_DESC; + + //TODO Revisit below calc based on platform/mem cfg + switch (dim->num_pool) { + case 1: + tx_desc_limit_0 = ATH11K_WIFILI_DBDC_NUM_TX_DESC; + break; + case 2: + tx_desc_limit_0 = ATH11K_WIFILI_DBDC_NUM_TX_DESC; + tx_desc_limit_1 = ATH11K_WIFILI_DBDC_NUM_TX_DESC; + break; + case 3: + tx_desc_limit_0 = ATH11K_WIFILI_DBTC_NUM_TX_DESC; + tx_desc_limit_1 = ATH11K_WIFILI_DBTC_NUM_TX_DESC; + tx_desc_limit_2 = ATH11K_WIFILI_DBTC_NUM_TX_DESC; + break; + default: + ath11k_warn(ab, "unexpected num radios during tx desc alloc\n"); + return -EINVAL; + } + + dim->num_tx_desc = tx_desc_limit_0; + dim->num_tx_desc_ext = tx_desc_limit_0; + dim->num_tx_desc_2 = tx_desc_limit_1; + dim->num_tx_desc_ext_2 = tx_desc_limit_1; + dim->num_tx_desc_3 = tx_desc_limit_2; + dim->num_tx_desc_ext_3 = tx_desc_limit_2; + + required_size = (dim->num_tx_desc + dim->num_tx_desc_2 + + dim->num_tx_desc_3 + + dim->num_pool) * WIFILI_NSS_TX_DESC_SIZE; + + required_size_ext = (dim->num_tx_desc_ext + dim->num_tx_desc_ext_2 + + dim->num_tx_desc_ext_3 + + dim->num_pool) * WIFILI_NSS_TX_EXT_DESC_SIZE; + + if (ath11k_nss_tx_desc_mem_alloc(ab, required_size, &page_idx)) { + ath11k_warn(ab, "memory allocation for tx desc of size %d failed\n", + required_size); + return -ENOMEM; + } + + /* Fill the page number from where extension tx descriptor is available */ + dim->ext_desc_page_num = page_idx; + + if (ath11k_nss_tx_desc_mem_alloc(ab, required_size_ext, &page_idx)) { + ath11k_warn(ab, "memory allocation for extension tx desc of size %d failed\n", + required_size_ext); + return -ENOMEM; + } + + for (i = 0; i < page_idx; i++) { + if (i < NSS_WIFILI_MAX_NUMBER_OF_PAGE_MSG) { + dim->memory_addr[i] = (u32)ab->nss.tx_desc_paddr[i]; + dim->memory_size[i] = (u32)ab->nss.tx_desc_size[i]; + dim->num_memaddr++; + } else { + dam_page_idx = i - NSS_WIFILI_MAX_NUMBER_OF_PAGE_MSG; + dam->addtnl_memory_addr[dam_page_idx] = (u32)ab->nss.tx_desc_paddr[i]; + dam->addtnl_memory_size[dam_page_idx] = (u32)ab->nss.tx_desc_size[i]; + dam->num_addtnl_addr++; + } + } + + if (i > NSS_WIFILI_MAX_NUMBER_OF_PAGE_MSG) + wim->flags |= WIFILI_ADDTL_MEM_SEG_SET; + + return 0; +} + +static int ath11k_nss_get_target_type(struct ath11k_base *ab) +{ + switch (ab->hw_rev) { + case ATH11K_HW_IPQ8074: + return ATH11K_WIFILI_TARGET_TYPE_QCA8074V2; + case ATH11K_HW_IPQ6018_HW10: + return ATH11K_WIFILI_TARGET_TYPE_QCA6018; + case ATH11K_HW_QCN9074_HW10: + return ATH11K_WIFILI_TARGET_TYPE_QCN9074; + case ATH11K_HW_IPQ5018: + return ATH11K_WIFILI_TARGET_TYPE_QCA5018; + default: + ath11k_warn(ab, "NSS Offload not supported for this HW\n"); + return ATH11K_WIFILI_TARGET_TYPE_UNKNOWN; + } +} + +static int ath11k_nss_get_interface_type(struct ath11k_base *ab) +{ + switch (ab->hw_rev) { + case ATH11K_HW_IPQ8074: + case ATH11K_HW_IPQ6018_HW10: + case ATH11K_HW_IPQ5018: + return NSS_WIFILI_INTERNAL_INTERFACE; + case ATH11K_HW_QCN9074_HW10: + return nss_get_available_wifili_external_if(); + default: + /* This can't happen since we validated target type earlier */ + WARN_ON(1); + return NSS_MAX_NET_INTERFACES; + } +} + +static int ath11k_nss_get_dynamic_interface_type(struct ath11k_base *ab) +{ + switch (ab->nss.if_num) { + case NSS_WIFILI_INTERNAL_INTERFACE: + return NSS_DYNAMIC_INTERFACE_TYPE_WIFILI_INTERNAL; + case NSS_WIFILI_EXTERNAL_INTERFACE0: + return NSS_DYNAMIC_INTERFACE_TYPE_WIFILI_EXTERNAL0; + case NSS_WIFILI_EXTERNAL_INTERFACE1: + return NSS_DYNAMIC_INTERFACE_TYPE_WIFILI_EXTERNAL1; + default: + ath11k_warn(ab, "NSS Offload invalid interface\n"); + return NSS_DYNAMIC_INTERFACE_TYPE_NONE; + } +} + +static int ath11k_nss_init(struct ath11k_base *ab) +{ + struct nss_wifili_init_msg *wim = NULL; + struct nss_wifili_msg *wlmsg = NULL; + struct nss_ctx_instance *nss_contex; + nss_wifili_msg_callback_t msg_cb; + u32 target_type; + u32 features = 0; + nss_tx_status_t status; + struct ath11k_dp *dp; + int i, ret; + + dp = &ab->dp; + + target_type = ath11k_nss_get_target_type(ab); + + if (target_type == ATH11K_WIFILI_TARGET_TYPE_UNKNOWN) + return -ENOTSUPP; + + wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC); + if (!wlmsg) + return -ENOMEM; + + wim = &wlmsg->msg.init; + + wim->target_type = target_type; + + /* fill rx parameters to initialize rx context */ + wim->wrip.tlv_size = sizeof(struct hal_rx_desc); + wim->wrip.rx_buf_len = DP_RX_BUFFER_SIZE; + + /* fill hal srng message */ + wim->hssm.dev_base_addr = (u32)ab->mem_pa; + wim->hssm.shadow_rdptr_mem_addr = (u32)ab->hal.rdp.paddr; + wim->hssm.shadow_wrptr_mem_addr = (u32)ab->hal.wrp.paddr; + + /* fill TCL data/completion ring info */ + wim->num_tcl_data_rings = DP_TCL_NUM_RING_MAX; + for (i = 0; i < DP_TCL_NUM_RING_MAX; i++) { + ath11k_nss_fill_srng_info(ab, dp->tx_ring[i].tcl_data_ring.ring_id, + &wim->tcl_ring_info[i]); + ath11k_nss_fill_srng_info(ab, dp->tx_ring[i].tcl_comp_ring.ring_id, + &wim->tx_comp_ring[i]); + } + + /* allocate tx desc memory for NSS and fill corresponding info */ + ret = ath11k_nss_fill_tx_desc_info(ab, wim); + if (ret) + goto free; + + /* fill reo dest ring info */ + wim->num_reo_dest_rings = DP_REO_DST_RING_MAX; + for (i = 0; i < DP_REO_DST_RING_MAX; i++) { + ath11k_nss_fill_srng_info(ab, dp->reo_dst_ring[i].ring_id, + &wim->reo_dest_ring[i]); + } + + /* fill reo reinject ring info */ + ath11k_nss_fill_srng_info(ab, dp->reo_reinject_ring.ring_id, + &wim->reo_reinject_ring); + + /* fill reo release ring info */ + ath11k_nss_fill_srng_info(ab, dp->rx_rel_ring.ring_id, + &wim->rx_rel_ring); + + /* fill reo exception ring info */ + ath11k_nss_fill_srng_info(ab, dp->reo_except_ring.ring_id, + &wim->reo_exception_ring); + + ab->nss.if_num = ath11k_nss_get_interface_type(ab); + + ath11k_info(ab, "nss init soc nss if_num %d userpd_id %d\n", ab->nss.if_num, ab->userpd_id); + + if (ab->nss.if_num >= NSS_MAX_NET_INTERFACES) { + ath11k_warn(ab, "NSS invalid interface\n"); + goto free; + } + + /* register callbacks for events and exceptions with nss */ + nss_contex = nss_register_wifili_if(ab->nss.if_num, NULL, + (nss_wifili_callback_t)ath11k_nss_wifili_ext_callback_fn, + (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive, + (struct net_device *)ab, features); + + if (!nss_contex) { + ath11k_warn(ab, "nss wifili register failure\n"); + goto free; + } + + if (nss_cmn_get_state(nss_contex) != NSS_STATE_INITIALIZED) { + ath11k_warn(ab, "nss state in default init state\n"); + goto free; + } + + /* The registered soc context is stored in ab, and will be used for + * all soc related messages with nss + */ + ab->nss.ctx = nss_contex; + + msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive; + + /* Initialize the common part of the wlmsg */ + nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num, + NSS_WIFILI_INIT_MSG, + sizeof(struct nss_wifili_init_msg), + msg_cb, NULL); + + reinit_completion(&ab->nss.complete); + + /* Note: response is contention free during init sequence */ + ab->nss.response = ATH11K_NSS_MSG_ACK; + + status = nss_wifili_tx_msg(nss_contex, wlmsg); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ab, "failure to send nss init msg\n"); + goto unregister; + } + + ret = wait_for_completion_timeout(&ab->nss.complete, + msecs_to_jiffies(ATH11K_NSS_MSG_TIMEOUT_MS)); + if (!ret) { + ath11k_warn(ab, "timeout while waiting for nss init msg response\n"); + goto unregister; + } + + /* Check if the response is success from the callback */ + if (ab->nss.response != ATH11K_NSS_MSG_ACK) + goto unregister; + + kfree(wlmsg); + + ath11k_dbg(ab, ATH11K_DBG_NSS, "NSS Init Message TX Success %p %d\n", + ab->nss.ctx, ab->nss.if_num); + return 0; + +unregister: + nss_unregister_wifili_if(ab->nss.if_num); +free: + ath11k_nss_tx_desc_mem_free(ab); + kfree(wlmsg); + return -EINVAL; +} + +static int ath11k_nss_stats_cfg(struct ath11k *ar, int nss_msg, int enable) +{ + struct nss_wifili_msg *wlmsg = NULL; + struct nss_wifili_stats_cfg_msg *stats_cfg; + nss_wifili_msg_callback_t msg_cb; + nss_tx_status_t status; + struct ath11k_base *ab = ar->ab; + int ret = 0; + + wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC); + if (!wlmsg) + return -ENOMEM; + + stats_cfg = &wlmsg->msg.scm; + stats_cfg->cfg = enable; + + msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive; + + nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num, + nss_msg, + sizeof(struct nss_wifili_stats_cfg_msg), + msg_cb, NULL); + + status = nss_wifili_tx_msg(ar->nss.ctx, wlmsg); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ab, "nss stats cfg %d msg tx failure\n", nss_msg); + ret = -EINVAL; + goto free; + } + + ath11k_dbg(ab, ATH11K_DBG_NSS, "nss stats %d enable %d\n", nss_msg, enable); + +free: + kfree(wlmsg); + return ret; +} + +static void ath11k_nss_sojourn_stats_disable(struct ath11k *ar) +{ + ath11k_nss_stats_cfg(ar, NSS_WIFILI_STATS_V2_CFG_MSG, + ATH11K_NSS_STATS_DISABLE); +} + +void ath11k_nss_peer_stats_disable(struct ath11k *ar) +{ + ath11k_nss_stats_cfg(ar, NSS_WIFILI_STATS_CFG_MSG, + ATH11K_NSS_STATS_DISABLE); +} + +void ath11k_nss_peer_stats_enable(struct ath11k *ar) +{ + ath11k_nss_stats_cfg(ar, NSS_WIFILI_STATS_CFG_MSG, + ATH11K_NSS_STATS_ENABLE); +} + +int ath11k_nss_pdev_init(struct ath11k_base *ab, int radio_id) +{ + struct ath11k *ar = ab->pdevs[radio_id].ar; + struct nss_wifili_pdev_init_msg *pdevmsg; + struct nss_wifili_msg *wlmsg = NULL; + nss_wifili_msg_callback_t msg_cb; + nss_tx_status_t status; + int radio_if_num = -1; + int refill_ring_id; + int features = 0; + int dyn_if_type; + int ret, i; + + dyn_if_type = ath11k_nss_get_dynamic_interface_type(ab); + + /* Allocate a node for dynamic interface */ + radio_if_num = nss_dynamic_interface_alloc_node(dyn_if_type); + + if (radio_if_num < 0) + return -EINVAL; + + /* The ifnum and registered radio context is stored in ar and used + * for messages related to vdev/radio + */ + ar->nss.if_num = radio_if_num; + + /* No callbacks are registered for radio specific events/data */ + ar->nss.ctx = nss_register_wifili_radio_if((u32)radio_if_num, NULL, + NULL, NULL, (struct net_device *)ar, + features); + + if (!ar->nss.ctx) { + ath11k_warn(ab, "failure during nss pdev register\n"); + ret = -EINVAL; + goto dealloc; + } + + ath11k_dbg(ab, ATH11K_DBG_NSS, "nss pdev init - id:%d init ctxt:%p ifnum:%d\n", + ar->pdev->pdev_id, ar->nss.ctx, ar->nss.if_num); + + wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC); + if (!wlmsg) { + ret = -ENOMEM; + goto unregister; + } + + pdevmsg = &wlmsg->msg.pdevmsg; + + pdevmsg->radio_id = radio_id; + pdevmsg->lmac_id = ar->lmac_id; + pdevmsg->target_pdev_id = ar->pdev->pdev_id; + pdevmsg->num_rx_swdesc = WIFILI_RX_DESC_POOL_WEIGHT * DP_RXDMA_BUF_RING_SIZE; + + /* Store rxdma ring info to the message */ + refill_ring_id = ar->dp.rx_refill_buf_ring.refill_buf_ring.ring_id; + ath11k_nss_fill_srng_info(ab, refill_ring_id, &pdevmsg->rxdma_ring); + + msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive; + + nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num, + NSS_WIFILI_PDEV_INIT_MSG, + sizeof(struct nss_wifili_pdev_init_msg), + msg_cb, NULL); + + reinit_completion(&ab->nss.complete); + + /* Note: response is contention free during init sequence */ + ab->nss.response = ATH11K_NSS_MSG_ACK; + + status = nss_wifili_tx_msg(ar->nss.ctx, wlmsg); + + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ab, "nss send pdev msg tx error : %d\n", status); + ret = -EINVAL; + goto free; + } + + ret = wait_for_completion_timeout(&ab->nss.complete, + msecs_to_jiffies(ATH11K_NSS_MSG_TIMEOUT_MS)); + if (!ret) { + ath11k_warn(ab, "timeout while waiting for pdev init msg response\n"); + ret = -ETIMEDOUT; + goto free; + } + + /* Check if the response is success from the callback */ + if (ab->nss.response != ATH11K_NSS_MSG_ACK) { + ret = -EINVAL; + goto free; + } + + kfree(wlmsg); + + /* Disable nss sojourn stats by default */ + ath11k_nss_sojourn_stats_disable(ar); + /* Enable nss wifili peer stats by default */ + ath11k_nss_peer_stats_enable(ar); + + /*TODO CFG Tx buffer limit as per no clients range per radio + * this needs to be based on target/mem cfg + * similar to tx desc cfg at soc init per radio + */ + + for (i = 0; i < ATH11K_NSS_RADIO_TX_LIMIT_RANGE; i++) + ath11k_nss_radio_buf_cfg(ar, i, ATH11K_NSS_RADIO_TX_LIMIT_RANGE3); + + return 0; + +free: + kfree(wlmsg); +unregister: + nss_unregister_wifili_radio_if(ar->nss.if_num); +dealloc: + nss_dynamic_interface_dealloc_node(ar->nss.if_num, dyn_if_type); + return ret; +} + +/* TODO : Check if start, reset and stop messages can be done using single function as + * body is similar, having it now for clarity */ + +int ath11k_nss_start(struct ath11k_base *ab) +{ + struct nss_wifili_msg *wlmsg = NULL; + nss_wifili_msg_callback_t msg_cb; + nss_tx_status_t status; + int ret; + + wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC); + if (!wlmsg) + return -ENOMEM; + + msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive; + + /* Empty message for NSS Start message */ + nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num, + NSS_WIFILI_START_MSG, + 0, + msg_cb, NULL); + + reinit_completion(&ab->nss.complete); + + /* Note: response is contention free during init sequence */ + ab->nss.response = ATH11K_NSS_MSG_ACK; + + status = nss_wifili_tx_msg(ab->nss.ctx, wlmsg); + + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ab, "nss send start msg tx error %d\n", status); + ret = -EINVAL; + goto free; + } + + ret = wait_for_completion_timeout(&ab->nss.complete, + msecs_to_jiffies(ATH11K_NSS_MSG_TIMEOUT_MS)); + if (!ret) { + ath11k_warn(ab, "timeout while waiting for response for nss start msg\n"); + ret = -ETIMEDOUT; + goto free; + } + + /* Check if the response is success from the callback */ + if (ab->nss.response != ATH11K_NSS_MSG_ACK) { + ret = -EINVAL; + goto free; + } + + /* NSS Start success */ + ret = 0; + ath11k_dbg(ab, ATH11K_DBG_NSS, "nss start success\n"); + +free: + kfree(wlmsg); + return ret; +} + +static void ath11k_nss_reset(struct ath11k_base *ab) +{ + struct nss_wifili_msg *wlmsg = NULL; + nss_wifili_msg_callback_t msg_cb; + nss_tx_status_t status; + int ret; + + ath11k_dbg(ab, ATH11K_DBG_NSS, "NSS reset\n"); + wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC); + if (!wlmsg) { + ath11k_warn(ab, "mem allocation failure during nss reset\n"); + return; + } + + msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive; + + /* Empty message for NSS Reset message */ + nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num, + NSS_WIFILI_SOC_RESET_MSG, + 0, + msg_cb, NULL); + + reinit_completion(&ab->nss.complete); + + /* Note: response is contention free during deinit sequence */ + ab->nss.response = ATH11K_NSS_MSG_ACK; + + status = nss_wifili_tx_msg(ab->nss.ctx, wlmsg); + + /* Add a retry mechanism to reset nss until success */ + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ab, "nss send reset msg tx error %d\n", status); + goto free; + } + + ret = wait_for_completion_timeout(&ab->nss.complete, + msecs_to_jiffies(ATH11K_NSS_MSG_TIMEOUT_MS)); + if (!ret) { + ath11k_warn(ab, "timeout while waiting for response for nss reset msg\n"); + goto free; + } + + /* Check if the response is success from the callback */ + if (ab->nss.response != ATH11K_NSS_MSG_ACK) { + ath11k_warn(ab, "failure response during nss reset %d\n", ab->nss.response); + goto free; + } + + /* Unregister wifili interface */ + nss_unregister_wifili_if(ab->nss.if_num); + +free: + kfree(wlmsg); +} + +static int ath11k_nss_stop(struct ath11k_base *ab) +{ + struct nss_wifili_msg *wlmsg = NULL; + nss_wifili_msg_callback_t msg_cb; + nss_tx_status_t status; + int ret; + + ath11k_dbg(ab, ATH11K_DBG_NSS, "NSS stop\n"); + wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC); + if (!wlmsg) + return -ENOMEM; + + msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive; + + /* Empty message for Stop command */ + nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num, + NSS_WIFILI_STOP_MSG, + 0, + msg_cb, NULL); + + reinit_completion(&ab->nss.complete); + + /* Note: response is contention free during deinit sequence */ + ab->nss.response = ATH11K_NSS_MSG_ACK; + + status = nss_wifili_tx_msg(ab->nss.ctx, wlmsg); + + /* Add a retry mechanism to stop nss until success */ + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ab, "nss send stop msg tx error %d\n", status); + ret = -EINVAL; + goto free; + } + + ret = wait_for_completion_timeout(&ab->nss.complete, + msecs_to_jiffies(ATH11K_NSS_MSG_TIMEOUT_MS)); + if (!ret) { + ath11k_warn(ab, "timeout while waiting for response for nss stop msg\n"); + ret = -ETIMEDOUT; + goto free; + } + + /* Check if the response is success from the callback */ + if (ab->nss.response != ATH11K_NSS_MSG_ACK) { + ret = -EINVAL; + goto free; + } + + /* NSS Stop success */ + ret = 0; +free: + kfree(wlmsg); + return ret; +} + +int ath11k_nss_pdev_deinit(struct ath11k_base *ab, int radio_id) +{ + struct ath11k *ar = ab->pdevs[radio_id].ar; + struct nss_wifili_pdev_deinit_msg *deinit; + struct nss_wifili_msg *wlmsg = NULL; + nss_wifili_msg_callback_t msg_cb; + nss_tx_status_t status; + int dyn_if_type; + int ret; + + ath11k_dbg(ab, ATH11K_DBG_NSS, "NSS pdev %d deinit\n", radio_id); + dyn_if_type = ath11k_nss_get_dynamic_interface_type(ab); + + /* Disable NSS wifili peer stats before teardown */ + if (ab->nss.stats_enabled) + ath11k_nss_peer_stats_disable(ar); + + wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC); + if (!wlmsg) + return -ENOMEM; + + deinit = &wlmsg->msg.pdevdeinit; + deinit->ifnum = radio_id; + + msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive; + + nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num, + NSS_WIFILI_PDEV_DEINIT_MSG, + sizeof(struct nss_wifili_pdev_deinit_msg), + msg_cb, NULL); + + reinit_completion(&ab->nss.complete); + + /* Note: response is contention free during deinit sequence */ + ab->nss.response = ATH11K_NSS_MSG_ACK; + + status = nss_wifili_tx_msg(ar->nss.ctx, wlmsg); + if (status != NSS_TX_SUCCESS) { + ath11k_warn(ab, "nss send pdev deinit msg tx error %d\n", status); + ret = -EINVAL; + goto free; + } + + ret = wait_for_completion_timeout(&ab->nss.complete, + msecs_to_jiffies(ATH11K_NSS_MSG_TIMEOUT_MS)); + if (!ret) { + ath11k_warn(ab, "timeout while waiting for pdev deinit msg response\n"); + ret = -ETIMEDOUT; + goto free; + } + + /* Check if the response is success from the callback */ + if (ab->nss.response != ATH11K_NSS_MSG_ACK) { + ret = -EINVAL; + goto free; + } + + /* pdev deinit msg success, dealloc, deregister and return */ + ret = 0; + + nss_dynamic_interface_dealloc_node(ar->nss.if_num, dyn_if_type); + nss_unregister_wifili_radio_if(ar->nss.if_num); +free: + kfree(wlmsg); + return ret; +} + +int ath11k_nss_teardown(struct ath11k_base *ab) +{ + int i, ret; + + if (!ab->nss.enabled) + return 0; + + ath11k_nss_stop(ab); + + for (i = 0; i < ab->num_radios ; i++) { + ret = ath11k_nss_pdev_deinit(ab, i); + if (ret) + ath11k_warn(ab, "failure during pdev%d deinit\n", i); + } + + ath11k_nss_reset(ab); + ath11k_nss_tx_desc_mem_free(ab); + ath11k_dbg(ab, ATH11K_DBG_NSS, "NSS Teardown Complete\n"); + + return 0; +} + +int ath11k_nss_setup(struct ath11k_base *ab) +{ + int i; + int ret = 0; + u32 target_type; + + if (!ab->nss.enabled) + return 0; + + target_type = ath11k_nss_get_target_type(ab); + + if (target_type == ATH11K_WIFILI_TARGET_TYPE_UNKNOWN) + return -ENOTSUPP; + + /* Verify NSS support is enabled */ + if (nss_cmn_get_nss_enabled() == false) { + ath11k_warn(ab, "NSS offload support disabled, falling back to default mode\n"); + return -ENOTSUPP; + } + + /* Initialize completion for verifying NSS message response */ + init_completion(&ab->nss.complete); + + /* Setup common resources for NSS */ + ret = ath11k_nss_init(ab); + if (ret) { + ath11k_warn(ab, "NSS SOC Initialization Failed :%d\n", ret); + goto fail; + } + + /* Setup pdev related resources for NSS */ + for (i = 0; i < ab->num_radios; i++) { + ret = ath11k_nss_pdev_init(ab, i); + if (ret) { + ath11k_warn(ab, "NSS PDEV %d Initialization Failed :%d\n", i, ret); + goto pdev_deinit; + } + } + + /* Set the NSS statemachine to start */ + ret = ath11k_nss_start(ab); + if (ret) { + ath11k_warn(ab, "NSS Start Failed : %d\n", ret); + goto pdev_deinit; + } + + /* Default nexthop interface is set to ETH RX */ + ret = nss_wifi_vdev_base_set_next_hop(ab->nss.ctx, NSS_ETH_RX_INTERFACE); + if (ret != NSS_TX_SUCCESS) { + ath11k_warn(ab, "Failure to set default next hop : %d\n", ret); + goto stop; + } + + ath11k_dbg(ab, ATH11K_DBG_NSS, "NSS Setup Complete\n"); + return ret; + +stop: + ath11k_nss_stop(ab); + +pdev_deinit: + for (i -= 1; i >= 0; i--) + ath11k_nss_pdev_deinit(ab, i); + + ath11k_nss_reset(ab); + ath11k_nss_tx_desc_mem_free(ab); +fail: + return ret; +} --- /dev/null +++ b/drivers/net/wireless/ath/ath11k/nss.h @@ -0,0 +1,300 @@ +/* SPDX-License-Identifier: BSD-3-Clause-Clear */ +/* + * Copyright (c) 2020 The Linux Foundation. All rights reserved. + */ + +#ifndef ATH11K_NSS_H +#define ATH11K_NSS_H + +#include +#include + +struct ath11k; +struct ath11k_base; +struct ath11k_vif; +struct ath11k_peer; +struct ath11k_sta; +struct hal_rx_mon_ppdu_info; + +/* NSS DBG macro is not included as part of debug enum to avoid + * frequent changes during upgrade*/ +#define ATH11K_DBG_NSS 0x80000000 + +/* WIFILI Supported Target Types */ +#define ATH11K_WIFILI_TARGET_TYPE_UNKNOWN 0xFF +#define ATH11K_WIFILI_TARGET_TYPE_QCA8074 20 +#define ATH11K_WIFILI_TARGET_TYPE_QCA8074V2 24 +#define ATH11K_WIFILI_TARGET_TYPE_QCA6018 25 +#define ATH11K_WIFILI_TARGET_TYPE_QCN9074 26 +#define ATH11K_WIFILI_TARGET_TYPE_QCA5018 29 + +/* Max limit for NSS Queue */ +#define ATH11K_WIFIILI_MAX_TX_PROCESSQ 1024 + +/* Max TX Desc limit */ +#define ATH11K_WIFILI_MAX_TX_DESC 65536 + +/* TX Desc related info */ +/*TODO : Check this again during experiments for lowmem or + changes for platforms based on num radios supported */ +#define ATH11K_WIFILI_DBDC_NUM_TX_DESC (1024 * 8) +#define ATH11K_WIFILI_DBTC_NUM_TX_DESC (1024 * 8) + +// TODO Revisit these page size calc +#define WIFILI_NSS_TX_DESC_SIZE 20*4 +#define WIFILI_NSS_TX_EXT_DESC_SIZE 40*4 +/* Number of desc per page(12bit) should be<4096, page limit per 1024 byte is 80*3=240 */ +#define WIFILI_NSS_TX_DESC_PAGE_LIMIT 240 +#define WIFILI_NSS_MAX_MEM_PAGE_SIZE (WIFILI_NSS_TX_DESC_PAGE_LIMIT * 1024) +#define WIFILI_NSS_MAX_EXT_MEM_PAGE_SIZE (WIFILI_NSS_TX_DESC_PAGE_LIMIT * 1024) +#define WIFILI_RX_DESC_POOL_WEIGHT 3 + +/* Status of the NSS messages sent from driver */ +#define ATH11K_NSS_MSG_ACK 0 +/* Timeout for waiting for response from NSS on TX msg */ +#define ATH11K_NSS_MSG_TIMEOUT_MS 5000 + +/* Init Flags */ +#define WIFILI_NSS_CCE_DISABLED 0x1 +#define WIFILI_ADDTL_MEM_SEG_SET 0x000000002 + +/* ATH11K NSS PEER Info */ +/* Host memory allocated for peer info storage in nss */ +#define WIFILI_NSS_PEER_BYTE_SIZE NSS_WIFILI_PEER_SIZE + +/* ATH11K NSS Stats */ +#define ATH11K_NSS_STATS_ENABLE 1 +#define ATH11K_NSS_STATS_DISABLE 0 + +/* TX Buf cfg range */ +#define ATH11K_NSS_RADIO_TX_LIMIT_RANGE 4 + +/* TODO : Analysis based on platform */ +/* TX Limit till 64 clients */ +#define ATH11K_NSS_RADIO_TX_LIMIT_RANGE0 8192 +/* TX Limit till 128 clients */ +#define ATH11K_NSS_RADIO_TX_LIMIT_RANGE1 8192 +/* TX Limit till 256 clients */ +#define ATH11K_NSS_RADIO_TX_LIMIT_RANGE2 8192 +/* TX Limit > 256 clients */ +#define ATH11K_NSS_RADIO_TX_LIMIT_RANGE3 8192 + +#define ATH11K_NSS_MAX_NUMBER_OF_PAGE 96 + +#define NSS_TX_TID_MAX 8 + +#define ATH11K_NSS_TXRX_NETDEV_STATS(txrx, vif, len, pkt_count) \ +do { \ + struct wireless_dev *wdev = ieee80211_vif_to_wdev(vif); \ + struct pcpu_sw_netstats *tstats; \ + \ + if (!wdev) \ + break; \ + tstats = this_cpu_ptr(netdev_tstats(wdev->netdev)); \ + u64_stats_update_begin(&tstats->syncp); \ + tstats->txrx ## _packets += pkt_count; \ + tstats->txrx ## _bytes += len; \ + u64_stats_update_end(&tstats->syncp); \ +} while (0) + +enum ath11k_nss_opmode { + ATH11K_NSS_OPMODE_UNKNOWN, + ATH11K_NSS_OPMODE_AP, + ATH11K_NSS_OPMODE_IBSS, + ATH11K_NSS_OPMODE_STA, + ATH11K_NSS_OPMODE_MONITOR, +}; + +struct peer_stats { + u64 last_rx; + u64 last_ack; + u32 tx_packets; + u32 tx_bytes; + u32 tx_retries; + u32 tx_failed; + u32 rx_packets; + u32 rx_bytes; + u32 rx_dropped; + u32 last_rxdrop; + struct rate_info rxrate; +}; + +enum ath11k_nss_peer_sec_type { + PEER_SEC_TYPE_NONE, + PEER_SEC_TYPE_WEP128, + PEER_SEC_TYPE_WEP104, + PEER_SEC_TYPE_WEP40, + PEER_SEC_TYPE_TKIP, + PEER_SEC_TYPE_TKIP_NOMIC, + PEER_SEC_TYPE_AES_CCMP, + PEER_SEC_TYPE_WAPI, + PEER_SEC_TYPE_AES_CCMP_256, + PEER_SEC_TYPE_AES_GCMP, + PEER_SEC_TYPE_AES_GCMP_256, + PEER_SEC_TYPES_MAX +}; + +/* this holds the memory allocated for nss managed peer info */ +struct ath11k_nss_peer { + uint32_t *vaddr; + dma_addr_t paddr; + struct peer_stats *nss_stats; + struct completion complete; +}; + +/* Structure to hold the vif related info for nss offload support */ +struct arvif_nss { + /* dynamic ifnum allocated by nss driver for vif */ + int if_num; + /* Used for completion status for vdev config nss messages */ + struct completion complete; + /* Keep the copy of encap type for nss */ + int encap; + /* Keep the copy of decap type for nss */ + int decap; + bool created; +}; + +/* Structure to hold the pdev/radio related info for nss offload support */ +struct ath11k_nss { + /* dynamic ifnum allocated by nss driver for pdev */ + int if_num; + /* Radio/pdev Context obtained on pdev register */ + void* ctx; +}; + +/* Structure to hold the soc related info for nss offload support */ +struct ath11k_soc_nss { + /* turn on/off nss offload support in ath11k */ + bool enabled; + /* turn on/off nss stats support in ath11k */ + bool stats_enabled; +#ifdef CPTCFG_ATH11K_NSS_SUPPORT + /* soc nss ctx */ + void* ctx; + /* if_num to be used for soc related nss messages */ + int if_num; + /* Completion to nss message response */ + struct completion complete; + /* Response to nss messages are stored here on msg callback + * used only in contention free messages during init */ + int response; + /* Below is used for identifying allocated tx descriptors */ + dma_addr_t tx_desc_paddr[ATH11K_NSS_MAX_NUMBER_OF_PAGE]; + uint32_t * tx_desc_vaddr[ATH11K_NSS_MAX_NUMBER_OF_PAGE]; + uint32_t tx_desc_size[ATH11K_NSS_MAX_NUMBER_OF_PAGE]; +#endif +}; + +#ifdef CPTCFG_ATH11K_NSS_SUPPORT +int ath11k_nss_tx(struct ath11k_vif *arvif, struct sk_buff *skb); +int ath11k_nss_vdev_set_cmd(struct ath11k_vif *arvif, int cmd, int val); +int ath11k_nss_vdev_create(struct ath11k_vif *arvif); +void ath11k_nss_vdev_delete(struct ath11k_vif *arvif); +int ath11k_nss_vdev_up(struct ath11k_vif *arvif); +int ath11k_nss_vdev_down(struct ath11k_vif *arvif); +int ath11k_nss_peer_delete(struct ath11k_base *ab, u8 *addr); +int ath11k_nss_set_peer_authorize(struct ath11k *ar, u16 peer_id); +int ath11k_nss_peer_create(struct ath11k_base *ab, struct ath11k_peer *peer); +void ath11k_nss_peer_stats_enable(struct ath11k *ar); +void ath11k_nss_peer_stats_disable(struct ath11k *ar); +int ath11k_nss_set_peer_sec_type(struct ath11k *ar, struct ath11k_peer *peer, + struct ieee80211_key_conf *key_conf); +void ath11k_nss_update_sta_stats(struct station_info *sinfo, + struct ieee80211_sta *sta, + struct ath11k_sta *arsta); +void ath11k_nss_update_sta_rxrate(struct hal_rx_mon_ppdu_info *ppdu_info, + struct ath11k_peer *peer); +int ath11k_nss_setup(struct ath11k_base *ab); +int ath11k_nss_teardown(struct ath11k_base *ab); +void ath11k_nss_ext_rx_stats(struct ath11k_base *ab, struct htt_rx_ring_tlv_filter *tlv_filter); +#else +static inline int ath11k_nss_tx(struct ath11k_vif *arvif, struct sk_buff *skb) +{ + return 0; +} + +static inline int ath11k_nss_vdev_set_cmd(struct ath11k_vif *arvif, int cmd, int val) +{ + return 0; +} + +static inline int ath11k_nss_vdev_create(struct ath11k_vif *arvif) +{ + return 0; +} + +static inline int ath11k_nss_set_peer_authorize(struct ath11k *ar, u16 peer_id) +{ + return 0; +} + +static inline void ath11k_nss_vdev_delete(struct ath11k_vif *arvif) +{ +} + +static inline void ath11k_nss_update_sta_stats(struct station_info *sinfo, + struct ieee80211_sta *sta, + struct ath11k_sta *arsta) +{ + return; +} + +static void ath11k_nss_update_sta_rxrate(struct hal_rx_mon_ppdu_info *ppdu_info, + struct ath11k_peer *peer) +{ + return; +} + +static inline int ath11k_nss_vdev_up(struct ath11k_vif *arvif) +{ + return 0; +} + +static inline int ath11k_nss_vdev_down(struct ath11k_vif *arvif) +{ + return 0; +} + +static inline int ath11k_nss_peer_delete(struct ath11k_base *ab, u8 *addr) +{ + return 0; +} + +static inline int ath11k_nss_peer_create(struct ath11k_base *ab, struct ath11k_peer *peer) +{ + return 0; +} + +static inline void ath11k_nss_peer_stats_enable(struct ath11k *ar) +{ + return; +} + +static inline void ath11k_nss_peer_stats_disable(struct ath11k *ar) +{ + return; +} + +static inline int ath11k_nss_set_peer_sec_type(struct ath11k *ar, struct ath11k_peer *peer, + struct ieee80211_key_conf *key_conf) +{ + return 0; +} + +static inline int ath11k_nss_setup(struct ath11k_base *ab) +{ + return 0; +} + +static inline int ath11k_nss_teardown(struct ath11k_base *ab) +{ + return 0; +} + +void ath11k_nss_ext_rx_stats(struct ath11k_base *ab, struct htt_rx_ring_tlv_filter *tlv_filter) +{ + return; +} +#endif /* CPTCFG_ATH11K_NSS_SUPPORT */ +#endif --- a/drivers/net/wireless/ath/ath11k/hif.h +++ b/drivers/net/wireless/ath/ath11k/hif.h @@ -29,6 +29,7 @@ struct ath11k_hif_ops { void (*ce_irq_enable)(struct ath11k_base *ab); void (*ce_irq_disable)(struct ath11k_base *ab); void (*get_ce_msi_idx)(struct ath11k_base *ab, u32 ce_id, u32 *msi_idx); + u32 (*get_window_offset)(struct ath11k_base *ab, u32 offset); }; static inline void ath11k_hif_ce_irq_enable(struct ath11k_base *ab) @@ -126,6 +127,14 @@ static inline void ath11k_get_msi_addres ab->hif.ops->get_msi_address(ab, msi_addr_lo, msi_addr_hi); } +static inline u32 ath11k_hif_get_window_offset(struct ath11k_base *ab, u32 offset) +{ + if (ab->hif.ops->get_window_offset) + return ab->hif.ops->get_window_offset(ab, offset); + + return offset; +} + static inline void ath11k_get_ce_msi_idx(struct ath11k_base *ab, u32 ce_id, u32 *msi_data_idx) { --- a/drivers/net/wireless/ath/ath11k/pci.c +++ b/drivers/net/wireless/ath/ath11k/pci.c @@ -174,6 +174,20 @@ static inline u32 ath11k_pci_get_window_ return window_start; } +static inline u32 ath11k_pci_get_window_offset(struct ath11k_base *ab, + u32 offset) +{ + u32 window_start; + + if (ab->bus_params.static_window_map) { + window_start = ath11k_pci_get_window_start(ab, offset); + + if (window_start) + offset = window_start + (offset & WINDOW_RANGE_MASK); + } + return offset; +} + void ath11k_pci_write32(struct ath11k_base *ab, u32 offset, u32 value) { struct ath11k_pci *ab_pci = ath11k_pci_priv(ab); @@ -1180,6 +1194,7 @@ static const struct ath11k_hif_ops ath11 .map_service_to_pipe = ath11k_pci_map_service_to_pipe, .ce_irq_enable = ath11k_pci_hif_ce_irq_enable, .ce_irq_disable = ath11k_pci_hif_ce_irq_disable, + .get_window_offset = ath11k_pci_get_window_offset, .get_ce_msi_idx = ath11k_pci_get_ce_msi_idx, }; --- a/drivers/net/wireless/ath/ath11k/dp_rx.c +++ b/drivers/net/wireless/ath/ath11k/dp_rx.c @@ -955,6 +955,29 @@ unlock_exit: spin_unlock_bh(&ab->base_lock); } +/* Sends WMI config to filter packets to route packets to WBM release ring */ +int ath11k_dp_rx_pkt_type_filter(struct ath11k *ar, enum ath11k_routing_pkt_type pkt_type, u32 meta_data) +{ + struct ath11k_wmi_pkt_route_param param; + int ret; + + /* Routing Eapol packets to CCE is only allowed now */ + if (pkt_type != ATH11K_PKT_TYPE_EAP) + return -EINVAL; + + param.opcode = ATH11K_WMI_PKTROUTE_ADD; + param.meta_data = meta_data; + param.dst_ring = ATH11K_ROUTE_WBM_RELEASE; + param.dst_ring_handler = ATH11K_WMI_PKTROUTE_USE_CCE; + param.route_type_bmap = 1 << pkt_type; + + ret = ath11k_wmi_send_pdev_pkt_route(ar, ¶m); + if (ret) + ath11k_warn(ar->ab, "failed to configure pkt route %d", ret); + + return ret; +} + int ath11k_peer_rx_tid_setup(struct ath11k *ar, const u8 *peer_mac, int vdev_id, u8 tid, u32 ba_win_sz, u16 ssn, enum hal_pn_type pn_type) --- a/drivers/net/wireless/ath/ath11k/wmi.c +++ b/drivers/net/wireless/ath/ath11k/wmi.c @@ -1073,6 +1073,44 @@ int ath11k_wmi_send_pdev_set_regdomain(s return ret; } +int ath11k_wmi_send_pdev_pkt_route(struct ath11k *ar, struct ath11k_wmi_pkt_route_param *param) +{ + struct ath11k_pdev_wmi *wmi = ar->wmi; + struct wmi_pdev_pkt_route_cmd *cmd; + struct sk_buff *skb; + int ret; + + skb = ath11k_wmi_alloc_skb(wmi->wmi_ab, sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_pdev_pkt_route_cmd *)skb->data; + cmd->tlv_header = FIELD_PREP(WMI_TLV_TAG, + WMI_TAG_PDEV_UPDATE_PKT_ROUTING_CMD) | + FIELD_PREP(WMI_TLV_LEN, sizeof(*cmd) - TLV_HDR_SIZE); + + cmd->pdev_id = ar->pdev->pdev_id; + cmd->opcode = param->opcode; + cmd->route_type_bmap = param->route_type_bmap; + cmd->dst_ring = param->dst_ring; + cmd->meta_data = param->meta_data; + cmd->dst_ring_handler = param->dst_ring_handler; + + ath11k_dbg(ar->ab, ATH11K_DBG_WMI, + "WMI pdev pkt route opcode %d route_bmap %d dst_ring %d meta_datan %d dst_ringg_handler %d\n", + param->opcode, param->route_type_bmap, + param->dst_ring, param->meta_data, param->dst_ring_handler); + + ret = ath11k_wmi_cmd_send(wmi, skb, WMI_PDEV_UPDATE_PKT_ROUTING_CMDID); + if (ret) { + ath11k_warn(ar->ab, + "failed to send WMI_PDEV_UPDATE_PKT_ROUTING cmd\n"); + dev_kfree_skb(skb); + } + + return ret; +} + int ath11k_wmi_set_peer_param(struct ath11k *ar, const u8 *peer_addr, u32 vdev_id, u32 param_id, u32 param_val) { --- a/drivers/net/wireless/ath/ath11k/wmi.h +++ b/drivers/net/wireless/ath/ath11k/wmi.h @@ -2785,6 +2785,27 @@ struct pdev_set_regdomain_params { u32 pdev_id; }; + /* Defines various options for routing policy */ +enum wmi_pdev_dest_ring_handler_type { + ATH11K_WMI_PKTROUTE_USE_CCE = 0, + ATH11K_WMI_PKTROUTE_USE_ASPT = 1, + ATH11K_WMI_PKTROUTE_USE_FSE = 2, + ATH11K_WMI_PKTROUTE_USE_CCE2 = 3, +}; + +enum ath11k_wmi_pkt_route_opcode { + ATH11K_WMI_PKTROUTE_ADD, + ATH11K_WMI_PKTROUTE_DEL, +}; + +struct ath11k_wmi_pkt_route_param { + enum ath11k_wmi_pkt_route_opcode opcode; + u32 route_type_bmap; + u32 dst_ring_handler; + u32 dst_ring; + u32 meta_data; +}; + struct rx_reorder_queue_remove_params { u8 *peer_macaddr; u16 vdev_id; @@ -3037,6 +3058,16 @@ struct wmi_pdev_set_regdomain_cmd { u32 dfs_domain; } __packed; +struct wmi_pdev_pkt_route_cmd { + u32 tlv_header; + u32 pdev_id; + u32 opcode; + u32 route_type_bmap; + u32 dst_ring; + u32 meta_data; + u32 dst_ring_handler; +} __packed; + struct wmi_peer_set_param_cmd { u32 tlv_header; u32 vdev_id; @@ -5733,6 +5764,8 @@ int ath11k_wmi_send_peer_create_cmd(stru int ath11k_wmi_vdev_set_param_cmd(struct ath11k *ar, u32 vdev_id, u32 param_id, u32 param_value); +int ath11k_wmi_send_pdev_pkt_route(struct ath11k *ar, + struct ath11k_wmi_pkt_route_param *param); int ath11k_wmi_set_sta_ps_param(struct ath11k *ar, u32 vdev_id, u32 param, u32 param_value); int ath11k_wmi_force_fw_hang_cmd(struct ath11k *ar, u32 type, u32 delay_time_ms); --- a/drivers/net/wireless/ath/ath11k/dp_rx.h +++ b/drivers/net/wireless/ath/ath11k/dp_rx.h @@ -20,6 +20,34 @@ #define DP_RX_MPDU_ERR_MPDU_LEN BIT(6) #define DP_RX_MPDU_ERR_UNENCRYPTED_FRAME BIT(7) +/* different supported pkt types for routing */ +enum ath11k_routing_pkt_type { + ATH11K_PKT_TYPE_ARP_IPV4, + ATH11K_PKT_TYPE_NS_IPV6, + ATH11K_PKT_TYPE_IGMP_IPV4, + ATH11K_PKT_TYPE_MLD_IPV6, + ATH11K_PKT_TYPE_DHCP_IPV4, + ATH11K_PKT_TYPE_DHCP_IPV6, + ATH11K_PKT_TYPE_DNS_TCP_IPV4, + ATH11K_PKT_TYPE_DNS_TCP_IPV6, + ATH11K_PKT_TYPE_DNS_UDP_IPV4, + ATH11K_PKT_TYPE_DNS_UDP_IPV6, + ATH11K_PKT_TYPE_ICMP_IPV4, + ATH11K_PKT_TYPE_ICMP_IPV6, + ATH11K_PKT_TYPE_TCP_IPV4, + ATH11K_PKT_TYPE_TCP_IPV6, + ATH11K_PKT_TYPE_UDP_IPV4, + ATH11K_PKT_TYPE_UDP_IPV6, + ATH11K_PKT_TYPE_IPV4, + ATH11K_PKT_TYPE_IPV6, + ATH11K_PKT_TYPE_EAP, + ATH11K_PKT_TYPE_MAX +}; + +#define ATH11K_RX_PROTOCOL_TAG_START_OFFSET 128 +#define ATH11K_ROUTE_WBM_RELEASE 5 +#define ATH11K_ROUTE_EAP_METADATA (ATH11K_RX_PROTOCOL_TAG_START_OFFSET + ATH11K_PKT_TYPE_EAP) + enum dp_rx_decap_type { DP_RX_DECAP_TYPE_RAW, DP_RX_DECAP_TYPE_NATIVE_WIFI, @@ -74,6 +102,9 @@ void ath11k_peer_rx_tid_delete(struct at int ath11k_peer_rx_tid_setup(struct ath11k *ar, const u8 *peer_mac, int vdev_id, u8 tid, u32 ba_win_sz, u16 ssn, enum hal_pn_type pn_type); +int ath11k_dp_rx_pkt_type_filter(struct ath11k *ar, + enum ath11k_routing_pkt_type pkt_type, + u32 meta_data); void ath11k_dp_htt_htc_t2h_msg_handler(struct ath11k_base *ab, struct sk_buff *skb); int ath11k_dp_pdev_reo_setup(struct ath11k_base *ab); --- a/drivers/net/wireless/ath/ath11k/mac.c +++ b/drivers/net/wireless/ath/ath11k/mac.c @@ -5217,6 +5217,16 @@ static int ath11k_mac_op_start(struct ie } } + /* nss offload requires eapol packets to be routed to wbm release ring */ + if (ab->nss.enabled) { + ret = ath11k_dp_rx_pkt_type_filter(ar, ATH11K_PKT_TYPE_EAP, + ATH11K_ROUTE_EAP_METADATA); + if (ret) { + ath11k_err(ar->ab, "failed to configure EAP pkt route: %d\n", ret); + goto err; + } + } + __ath11k_set_antenna(ar, ar->cfg_tx_chainmask, ar->cfg_rx_chainmask); /* TODO: Do we need to enable ANI? */