From: Kalle Valo <quic_kvalo@xxxxxxxxxxx> (Patches split into one patch per file for easier review, but the final commit will be one big patch. See the cover letter for more info.) Signed-off-by: Kalle Valo <quic_kvalo@xxxxxxxxxxx> --- drivers/net/wireless/ath/ath12k/dp.c | 1580 ++++++++++++++++++++++++++++++++++ 1 file changed, 1580 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/dp.c b/drivers/net/wireless/ath/ath12k/dp.c new file mode 100644 index 000000000000..eb0261500ab0 --- /dev/null +++ b/drivers/net/wireless/ath/ath12k/dp.c @@ -0,0 +1,1580 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear +/* + * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include <crypto/hash.h> +#include "core.h" +#include "dp_tx.h" +#include "hal_tx.h" +#include "hif.h" +#include "debug.h" +#include "dp_rx.h" +#include "peer.h" +#include "dp_mon.h" + +static void ath12k_dp_htt_htc_tx_complete(struct ath12k_base *ab, + struct sk_buff *skb) +{ + dev_kfree_skb_any(skb); +} + +void ath12k_dp_peer_cleanup(struct ath12k *ar, int vdev_id, const u8 *addr) +{ + struct ath12k_base *ab = ar->ab; + struct ath12k_peer *peer; + + /* TODO: Any other peer specific DP cleanup */ + + spin_lock_bh(&ab->base_lock); + peer = ath12k_peer_find(ab, vdev_id, addr); + if (!peer) { + ath12k_warn(ab, "failed to lookup peer %pM on vdev %d\n", + addr, vdev_id); + spin_unlock_bh(&ab->base_lock); + return; + } + + ath12k_dp_rx_peer_tid_cleanup(ar, peer); + crypto_free_shash(peer->tfm_mmic); + spin_unlock_bh(&ab->base_lock); +} + +int ath12k_dp_peer_setup(struct ath12k *ar, int vdev_id, const u8 *addr) +{ + struct ath12k_base *ab = ar->ab; + struct ath12k_peer *peer; + u32 reo_dest; + int ret = 0, tid; + + /* NOTE: reo_dest ring id starts from 1 unlike mac_id which starts from 0 */ + reo_dest = ar->dp.mac_id + 1; + ret = ath12k_wmi_set_peer_param(ar, addr, vdev_id, + WMI_PEER_SET_DEFAULT_ROUTING, + DP_RX_HASH_ENABLE | (reo_dest << 1)); + + if (ret) { + ath12k_warn(ab, "failed to set default routing %d peer :%pM vdev_id :%d\n", + ret, addr, vdev_id); + return ret; + } + + for (tid = 0; tid <= IEEE80211_NUM_TIDS; tid++) { + ret = ath12k_dp_rx_peer_tid_setup(ar, addr, vdev_id, tid, 1, 0, + HAL_PN_TYPE_NONE); + if (ret) { + ath12k_warn(ab, "failed to setup rxd tid queue for tid %d: %d\n", + tid, ret); + goto peer_clean; + } + } + + ret = ath12k_dp_rx_peer_frag_setup(ar, addr, vdev_id); + if (ret) { + ath12k_warn(ab, "failed to setup rx defrag context\n"); + goto peer_clean; + } + + /* TODO: Setup other peer specific resource used in data path */ + + return 0; + +peer_clean: + spin_lock_bh(&ab->base_lock); + + peer = ath12k_peer_find(ab, vdev_id, addr); + if (!peer) { + ath12k_warn(ab, "failed to find the peer to del rx tid\n"); + spin_unlock_bh(&ab->base_lock); + return -ENOENT; + } + + for (; tid >= 0; tid--) + ath12k_dp_rx_peer_tid_delete(ar, peer, tid); + + spin_unlock_bh(&ab->base_lock); + + return ret; +} + +void ath12k_dp_srng_cleanup(struct ath12k_base *ab, struct dp_srng *ring) +{ + if (!ring->vaddr_unaligned) + return; + + dma_free_coherent(ab->dev, ring->size, ring->vaddr_unaligned, + ring->paddr_unaligned); + + ring->vaddr_unaligned = NULL; +} + +static int ath12k_dp_srng_find_ring_in_mask(int ring_num, const u8 *grp_mask) +{ + int ext_group_num; + u8 mask = 1 << ring_num; + + for (ext_group_num = 0; ext_group_num < ATH12K_EXT_IRQ_GRP_NUM_MAX; + ext_group_num++) { + if (mask & grp_mask[ext_group_num]) + return ext_group_num; + } + + return -ENOENT; +} + +static int ath12k_dp_srng_calculate_msi_group(struct ath12k_base *ab, + enum hal_ring_type type, int ring_num) +{ + const u8 *grp_mask; + + switch (type) { + case HAL_WBM2SW_RELEASE: + if (ring_num == HAL_WBM2SW_REL_ERR_RING_NUM) { + grp_mask = &ab->hw_params->ring_mask->rx_wbm_rel[0]; + ring_num = 0; + } else { + grp_mask = &ab->hw_params->ring_mask->tx[0]; + } + break; + case HAL_REO_EXCEPTION: + grp_mask = &ab->hw_params->ring_mask->rx_err[0]; + break; + case HAL_REO_DST: + grp_mask = &ab->hw_params->ring_mask->rx[0]; + break; + case HAL_REO_STATUS: + grp_mask = &ab->hw_params->ring_mask->reo_status[0]; + break; + case HAL_RXDMA_MONITOR_STATUS: + case HAL_RXDMA_MONITOR_DST: + grp_mask = &ab->hw_params->ring_mask->rx_mon_dest[0]; + break; + case HAL_TX_MONITOR_DST: + grp_mask = &ab->hw_params->ring_mask->tx_mon_dest[0]; + break; + case HAL_RXDMA_BUF: + grp_mask = &ab->hw_params->ring_mask->host2rxdma[0]; + break; + case HAL_RXDMA_MONITOR_BUF: + case HAL_TCL_DATA: + case HAL_TCL_CMD: + case HAL_REO_CMD: + case HAL_SW2WBM_RELEASE: + case HAL_WBM_IDLE_LINK: + case HAL_TCL_STATUS: + case HAL_REO_REINJECT: + case HAL_CE_SRC: + case HAL_CE_DST: + case HAL_CE_DST_STATUS: + default: + return -ENOENT; + } + + return ath12k_dp_srng_find_ring_in_mask(ring_num, grp_mask); +} + +static void ath12k_dp_srng_msi_setup(struct ath12k_base *ab, + struct hal_srng_params *ring_params, + enum hal_ring_type type, int ring_num) +{ + int msi_group_number, msi_data_count; + u32 msi_data_start, msi_irq_start, addr_lo, addr_hi; + int ret; + + ret = ath12k_hif_get_user_msi_vector(ab, "DP", + &msi_data_count, &msi_data_start, + &msi_irq_start); + if (ret) + return; + + msi_group_number = ath12k_dp_srng_calculate_msi_group(ab, type, + ring_num); + if (msi_group_number < 0) { + ath12k_dbg(ab, ATH12K_DBG_PCI, + "ring not part of an ext_group; ring_type: %d,ring_num %d", + type, ring_num); + ring_params->msi_addr = 0; + ring_params->msi_data = 0; + return; + } + + if (msi_group_number > msi_data_count) { + ath12k_dbg(ab, ATH12K_DBG_PCI, + "multiple msi_groups share one msi, msi_group_num %d", + msi_group_number); + } + + ath12k_hif_get_msi_address(ab, &addr_lo, &addr_hi); + + ring_params->msi_addr = addr_lo; + ring_params->msi_addr |= (dma_addr_t)(((uint64_t)addr_hi) << 32); + ring_params->msi_data = (msi_group_number % msi_data_count) + + msi_data_start; + ring_params->flags |= HAL_SRNG_FLAGS_MSI_INTR; +} + +int ath12k_dp_srng_setup(struct ath12k_base *ab, struct dp_srng *ring, + enum hal_ring_type type, int ring_num, + int mac_id, int num_entries) +{ + struct hal_srng_params params = { 0 }; + int entry_sz = ath12k_hal_srng_get_entrysize(ab, type); + int max_entries = ath12k_hal_srng_get_max_entries(ab, type); + int ret; + + if (max_entries < 0 || entry_sz < 0) + return -EINVAL; + + if (num_entries > max_entries) + num_entries = max_entries; + + ring->size = (num_entries * entry_sz) + HAL_RING_BASE_ALIGN - 1; + ring->vaddr_unaligned = dma_alloc_coherent(ab->dev, ring->size, + &ring->paddr_unaligned, + GFP_KERNEL); + if (!ring->vaddr_unaligned) + return -ENOMEM; + + ring->vaddr = PTR_ALIGN(ring->vaddr_unaligned, HAL_RING_BASE_ALIGN); + ring->paddr = ring->paddr_unaligned + ((unsigned long)ring->vaddr - + (unsigned long)ring->vaddr_unaligned); + + params.ring_base_vaddr = ring->vaddr; + params.ring_base_paddr = ring->paddr; + params.num_entries = num_entries; + ath12k_dp_srng_msi_setup(ab, ¶ms, type, ring_num + mac_id); + + switch (type) { + case HAL_REO_DST: + params.intr_batch_cntr_thres_entries = + HAL_SRNG_INT_BATCH_THRESHOLD_RX; + params.intr_timer_thres_us = HAL_SRNG_INT_TIMER_THRESHOLD_RX; + break; + case HAL_RXDMA_BUF: + case HAL_RXDMA_MONITOR_BUF: + case HAL_RXDMA_MONITOR_STATUS: + params.low_threshold = num_entries >> 3; + params.flags |= HAL_SRNG_FLAGS_LOW_THRESH_INTR_EN; + params.intr_batch_cntr_thres_entries = 0; + params.intr_timer_thres_us = HAL_SRNG_INT_TIMER_THRESHOLD_RX; + break; + case HAL_TX_MONITOR_DST: + params.low_threshold = DP_TX_MONITOR_BUF_SIZE_MAX >> 3; + params.flags |= HAL_SRNG_FLAGS_LOW_THRESH_INTR_EN; + params.intr_batch_cntr_thres_entries = 0; + params.intr_timer_thres_us = HAL_SRNG_INT_TIMER_THRESHOLD_RX; + break; + case HAL_WBM2SW_RELEASE: + if (ab->hw_params->hw_ops->dp_srng_is_tx_comp_ring(ring_num)) { + params.intr_batch_cntr_thres_entries = + HAL_SRNG_INT_BATCH_THRESHOLD_TX; + params.intr_timer_thres_us = + HAL_SRNG_INT_TIMER_THRESHOLD_TX; + break; + } + /* follow through when ring_num != HAL_WBM2SW_REL_ERR_RING_NUM */ + fallthrough; + case HAL_REO_EXCEPTION: + case HAL_REO_REINJECT: + case HAL_REO_CMD: + case HAL_REO_STATUS: + case HAL_TCL_DATA: + case HAL_TCL_CMD: + case HAL_TCL_STATUS: + case HAL_WBM_IDLE_LINK: + case HAL_SW2WBM_RELEASE: + case HAL_RXDMA_DST: + case HAL_RXDMA_MONITOR_DST: + case HAL_RXDMA_MONITOR_DESC: + params.intr_batch_cntr_thres_entries = + HAL_SRNG_INT_BATCH_THRESHOLD_OTHER; + params.intr_timer_thres_us = HAL_SRNG_INT_TIMER_THRESHOLD_OTHER; + break; + case HAL_RXDMA_DIR_BUF: + break; + default: + ath12k_warn(ab, "Not a valid ring type in dp :%d\n", type); + return -EINVAL; + } + + ret = ath12k_hal_srng_setup(ab, type, ring_num, mac_id, ¶ms); + if (ret < 0) { + ath12k_warn(ab, "failed to setup srng: %d ring_id %d\n", + ret, ring_num); + return ret; + } + + ring->ring_id = ret; + + return 0; +} + +static +u32 ath12k_dp_tx_get_vdev_bank_config(struct ath12k_base *ab, struct ath12k_vif *arvif) +{ + u32 bank_config = 0; + + /* Only valid for raw frames with HW crypto enabled. + * With SW crypto, mac80211 sets key per packet + */ + if (arvif->tx_encap_type == HAL_TCL_ENCAP_TYPE_RAW && + test_bit(ATH12K_FLAG_HW_CRYPTO_DISABLED, &ab->dev_flags)) + bank_config |= + u32_encode_bits(ath12k_dp_tx_get_encrypt_type(arvif->key_cipher), + HAL_TX_BANK_CONFIG_ENCRYPT_TYPE); + + bank_config |= u32_encode_bits(arvif->tx_encap_type, + HAL_TX_BANK_CONFIG_ENCAP_TYPE); + bank_config |= u32_encode_bits(0, HAL_TX_BANK_CONFIG_SRC_BUFFER_SWAP) | + u32_encode_bits(0, HAL_TX_BANK_CONFIG_LINK_META_SWAP) | + u32_encode_bits(0, HAL_TX_BANK_CONFIG_EPD); + + /* only valid if idx_lookup_override is not set in tcl_data_cmd */ + bank_config |= u32_encode_bits(0, HAL_TX_BANK_CONFIG_INDEX_LOOKUP_EN); + + bank_config |= u32_encode_bits(arvif->hal_addr_search_flags & HAL_TX_ADDRX_EN, + HAL_TX_BANK_CONFIG_ADDRX_EN) | + u32_encode_bits(!!(arvif->hal_addr_search_flags & + HAL_TX_ADDRY_EN), + HAL_TX_BANK_CONFIG_ADDRY_EN); + + bank_config |= u32_encode_bits(ieee80211_vif_is_mesh(arvif->vif) ? 3 : 0, + HAL_TX_BANK_CONFIG_MESH_EN) | + u32_encode_bits(arvif->vdev_id_check_en, + HAL_TX_BANK_CONFIG_VDEV_ID_CHECK_EN); + + bank_config |= u32_encode_bits(0, HAL_TX_BANK_CONFIG_DSCP_TIP_MAP_ID); + + return bank_config; +} + +static int ath12k_dp_tx_get_bank_profile(struct ath12k_base *ab, struct ath12k_vif *arvif, + struct ath12k_dp *dp) +{ + int bank_id = DP_INVALID_BANK_ID; + int i; + u32 bank_config; + bool configure_register = false; + + /* convert vdev params into hal_tx_bank_config */ + bank_config = ath12k_dp_tx_get_vdev_bank_config(ab, arvif); + + spin_lock_bh(&dp->tx_bank_lock); + /* TODO: implement using idr kernel framework*/ + for (i = 0; i < dp->num_bank_profiles; i++) { + if (dp->bank_profiles[i].is_configured && + (dp->bank_profiles[i].bank_config ^ bank_config) == 0) { + bank_id = i; + goto inc_ref_and_return; + } + if (!dp->bank_profiles[i].is_configured || + !dp->bank_profiles[i].num_users) { + bank_id = i; + goto configure_and_return; + } + } + + if (bank_id == DP_INVALID_BANK_ID) { + spin_unlock_bh(&dp->tx_bank_lock); + ath12k_err(ab, "unable to find TX bank!"); + return bank_id; + } + +configure_and_return: + dp->bank_profiles[bank_id].is_configured = true; + dp->bank_profiles[bank_id].bank_config = bank_config; + configure_register = true; +inc_ref_and_return: + dp->bank_profiles[bank_id].num_users++; + spin_unlock_bh(&dp->tx_bank_lock); + + if (configure_register) + ath12k_hal_tx_configure_bank_register(ab, bank_config, bank_id); + + ath12k_dbg(ab, ATH12K_DBG_DP_HTT, "dp_htt tcl bank_id %d input 0x%x match 0x%x num_users %u", + bank_id, bank_config, dp->bank_profiles[bank_id].bank_config, + dp->bank_profiles[bank_id].num_users); + + return bank_id; +} + +void ath12k_dp_tx_put_bank_profile(struct ath12k_dp *dp, u8 bank_id) +{ + spin_lock_bh(&dp->tx_bank_lock); + dp->bank_profiles[bank_id].num_users--; + spin_unlock_bh(&dp->tx_bank_lock); +} + +static void ath12k_dp_deinit_bank_profiles(struct ath12k_base *ab) +{ + struct ath12k_dp *dp = &ab->dp; + + kfree(dp->bank_profiles); + dp->bank_profiles = NULL; +} + +static int ath12k_dp_init_bank_profiles(struct ath12k_base *ab) +{ + struct ath12k_dp *dp = &ab->dp; + u32 num_tcl_banks = ab->hw_params->num_tcl_banks; + int i; + + dp->num_bank_profiles = num_tcl_banks; + dp->bank_profiles = kmalloc_array(num_tcl_banks, + sizeof(struct ath12k_dp_tx_bank_profile), + GFP_KERNEL); + if (!dp->bank_profiles) + return -ENOMEM; + + spin_lock_init(&dp->tx_bank_lock); + + for (i = 0; i < num_tcl_banks; i++) { + dp->bank_profiles[i].is_configured = false; + dp->bank_profiles[i].num_users = 0; + } + + return 0; +} + +static void ath12k_dp_srng_common_cleanup(struct ath12k_base *ab) +{ + struct ath12k_dp *dp = &ab->dp; + int i; + + ath12k_dp_srng_cleanup(ab, &dp->reo_status_ring); + ath12k_dp_srng_cleanup(ab, &dp->reo_cmd_ring); + ath12k_dp_srng_cleanup(ab, &dp->reo_except_ring); + ath12k_dp_srng_cleanup(ab, &dp->rx_rel_ring); + ath12k_dp_srng_cleanup(ab, &dp->reo_reinject_ring); + for (i = 0; i < ab->hw_params->max_tx_ring; i++) { + ath12k_dp_srng_cleanup(ab, &dp->tx_ring[i].tcl_comp_ring); + ath12k_dp_srng_cleanup(ab, &dp->tx_ring[i].tcl_data_ring); + } + ath12k_dp_srng_cleanup(ab, &dp->tcl_status_ring); + ath12k_dp_srng_cleanup(ab, &dp->tcl_cmd_ring); + ath12k_dp_srng_cleanup(ab, &dp->wbm_desc_rel_ring); +} + +static int ath12k_dp_srng_common_setup(struct ath12k_base *ab) +{ + struct ath12k_dp *dp = &ab->dp; + const struct ath12k_hal_tcl_to_wbm_rbm_map *map; + struct hal_srng *srng; + int i, ret, tx_comp_ring_num; + u32 ring_hash_map; + + ret = ath12k_dp_srng_setup(ab, &dp->wbm_desc_rel_ring, + HAL_SW2WBM_RELEASE, 0, 0, + DP_WBM_RELEASE_RING_SIZE); + if (ret) { + ath12k_warn(ab, "failed to set up wbm2sw_release ring :%d\n", + ret); + goto err; + } + + ret = ath12k_dp_srng_setup(ab, &dp->tcl_cmd_ring, HAL_TCL_CMD, 0, 0, + DP_TCL_CMD_RING_SIZE); + if (ret) { + ath12k_warn(ab, "failed to set up tcl_cmd ring :%d\n", ret); + goto err; + } + + ret = ath12k_dp_srng_setup(ab, &dp->tcl_status_ring, HAL_TCL_STATUS, + 0, 0, DP_TCL_STATUS_RING_SIZE); + if (ret) { + ath12k_warn(ab, "failed to set up tcl_status ring :%d\n", ret); + goto err; + } + + for (i = 0; i < ab->hw_params->max_tx_ring; i++) { + map = ab->hw_params->hal_ops->tcl_to_wbm_rbm_map; + tx_comp_ring_num = map[i].wbm_ring_num; + + ret = ath12k_dp_srng_setup(ab, &dp->tx_ring[i].tcl_data_ring, + HAL_TCL_DATA, i, 0, + DP_TCL_DATA_RING_SIZE); + if (ret) { + ath12k_warn(ab, "failed to set up tcl_data ring (%d) :%d\n", + i, ret); + goto err; + } + + ret = ath12k_dp_srng_setup(ab, &dp->tx_ring[i].tcl_comp_ring, + HAL_WBM2SW_RELEASE, tx_comp_ring_num, 0, + DP_TX_COMP_RING_SIZE); + if (ret) { + ath12k_warn(ab, "failed to set up tcl_comp ring (%d) :%d\n", + tx_comp_ring_num, ret); + goto err; + } + } + + ret = ath12k_dp_srng_setup(ab, &dp->reo_reinject_ring, HAL_REO_REINJECT, + 0, 0, DP_REO_REINJECT_RING_SIZE); + if (ret) { + ath12k_warn(ab, "failed to set up reo_reinject ring :%d\n", + ret); + goto err; + } + + ret = ath12k_dp_srng_setup(ab, &dp->rx_rel_ring, HAL_WBM2SW_RELEASE, + HAL_WBM2SW_REL_ERR_RING_NUM, 0, + DP_RX_RELEASE_RING_SIZE); + if (ret) { + ath12k_warn(ab, "failed to set up rx_rel ring :%d\n", ret); + goto err; + } + + ret = ath12k_dp_srng_setup(ab, &dp->reo_except_ring, HAL_REO_EXCEPTION, + 0, 0, DP_REO_EXCEPTION_RING_SIZE); + if (ret) { + ath12k_warn(ab, "failed to set up reo_exception ring :%d\n", + ret); + goto err; + } + + ret = ath12k_dp_srng_setup(ab, &dp->reo_cmd_ring, HAL_REO_CMD, + 0, 0, DP_REO_CMD_RING_SIZE); + if (ret) { + ath12k_warn(ab, "failed to set up reo_cmd ring :%d\n", ret); + goto err; + } + + srng = &ab->hal.srng_list[dp->reo_cmd_ring.ring_id]; + ath12k_hal_reo_init_cmd_ring(ab, srng); + + ret = ath12k_dp_srng_setup(ab, &dp->reo_status_ring, HAL_REO_STATUS, + 0, 0, DP_REO_STATUS_RING_SIZE); + if (ret) { + ath12k_warn(ab, "failed to set up reo_status ring :%d\n", ret); + goto err; + } + + /* When hash based routing of rx packet is enabled, 32 entries to map + * the hash values to the ring will be configured. Each hash entry uses + * four bits to map to a particular ring. The ring mapping will be + * 0:TCL, 1:SW1, 2:SW2, 3:SW3, 4:SW4, 5:Release, 6:FW and 7:SW5 + * 8:SW6, 9:SW7, 10:SW8, 11:Not used. + */ + ring_hash_map = HAL_HASH_ROUTING_RING_SW1 | + HAL_HASH_ROUTING_RING_SW2 << 4 | + HAL_HASH_ROUTING_RING_SW3 << 8 | + HAL_HASH_ROUTING_RING_SW4 << 12 | + HAL_HASH_ROUTING_RING_SW1 << 16 | + HAL_HASH_ROUTING_RING_SW2 << 20 | + HAL_HASH_ROUTING_RING_SW3 << 24 | + HAL_HASH_ROUTING_RING_SW4 << 28; + + ath12k_hal_reo_hw_setup(ab, ring_hash_map); + + return 0; + +err: + ath12k_dp_srng_common_cleanup(ab); + + return ret; +} + +static void ath12k_dp_scatter_idle_link_desc_cleanup(struct ath12k_base *ab) +{ + struct ath12k_dp *dp = &ab->dp; + struct hal_wbm_idle_scatter_list *slist = dp->scatter_list; + int i; + + for (i = 0; i < DP_IDLE_SCATTER_BUFS_MAX; i++) { + if (!slist[i].vaddr) + continue; + + dma_free_coherent(ab->dev, HAL_WBM_IDLE_SCATTER_BUF_SIZE_MAX, + slist[i].vaddr, slist[i].paddr); + slist[i].vaddr = NULL; + } +} + +static int ath12k_dp_scatter_idle_link_desc_setup(struct ath12k_base *ab, + int size, + u32 n_link_desc_bank, + u32 n_link_desc, + u32 last_bank_sz) +{ + struct ath12k_dp *dp = &ab->dp; + struct dp_link_desc_bank *link_desc_banks = dp->link_desc_banks; + struct hal_wbm_idle_scatter_list *slist = dp->scatter_list; + u32 n_entries_per_buf; + int num_scatter_buf, scatter_idx; + struct hal_wbm_link_desc *scatter_buf; + int align_bytes, n_entries; + dma_addr_t paddr; + int rem_entries; + int i; + int ret = 0; + u32 end_offset, cookie; + + n_entries_per_buf = HAL_WBM_IDLE_SCATTER_BUF_SIZE / + ath12k_hal_srng_get_entrysize(ab, HAL_WBM_IDLE_LINK); + num_scatter_buf = DIV_ROUND_UP(size, HAL_WBM_IDLE_SCATTER_BUF_SIZE); + + if (num_scatter_buf > DP_IDLE_SCATTER_BUFS_MAX) + return -EINVAL; + + for (i = 0; i < num_scatter_buf; i++) { + slist[i].vaddr = dma_alloc_coherent(ab->dev, + HAL_WBM_IDLE_SCATTER_BUF_SIZE_MAX, + &slist[i].paddr, GFP_KERNEL); + if (!slist[i].vaddr) { + ret = -ENOMEM; + goto err; + } + } + + scatter_idx = 0; + scatter_buf = slist[scatter_idx].vaddr; + rem_entries = n_entries_per_buf; + + for (i = 0; i < n_link_desc_bank; i++) { + align_bytes = link_desc_banks[i].vaddr - + link_desc_banks[i].vaddr_unaligned; + n_entries = (DP_LINK_DESC_ALLOC_SIZE_THRESH - align_bytes) / + HAL_LINK_DESC_SIZE; + paddr = link_desc_banks[i].paddr; + while (n_entries) { + cookie = DP_LINK_DESC_COOKIE_SET(n_entries, i); + ath12k_hal_set_link_desc_addr(scatter_buf, cookie, paddr); + n_entries--; + paddr += HAL_LINK_DESC_SIZE; + if (rem_entries) { + rem_entries--; + scatter_buf++; + continue; + } + + rem_entries = n_entries_per_buf; + scatter_idx++; + scatter_buf = slist[scatter_idx].vaddr; + } + } + + end_offset = (scatter_buf - slist[scatter_idx].vaddr) * + sizeof(struct hal_wbm_link_desc); + ath12k_hal_setup_link_idle_list(ab, slist, num_scatter_buf, + n_link_desc, end_offset); + + return 0; + +err: + ath12k_dp_scatter_idle_link_desc_cleanup(ab); + + return ret; +} + +static void +ath12k_dp_link_desc_bank_free(struct ath12k_base *ab, + struct dp_link_desc_bank *link_desc_banks) +{ + int i; + + for (i = 0; i < DP_LINK_DESC_BANKS_MAX; i++) { + if (link_desc_banks[i].vaddr_unaligned) { + dma_free_coherent(ab->dev, + link_desc_banks[i].size, + link_desc_banks[i].vaddr_unaligned, + link_desc_banks[i].paddr_unaligned); + link_desc_banks[i].vaddr_unaligned = NULL; + } + } +} + +static int ath12k_dp_link_desc_bank_alloc(struct ath12k_base *ab, + struct dp_link_desc_bank *desc_bank, + int n_link_desc_bank, + int last_bank_sz) +{ + struct ath12k_dp *dp = &ab->dp; + int i; + int ret = 0; + int desc_sz = DP_LINK_DESC_ALLOC_SIZE_THRESH; + + for (i = 0; i < n_link_desc_bank; i++) { + if (i == (n_link_desc_bank - 1) && last_bank_sz) + desc_sz = last_bank_sz; + + desc_bank[i].vaddr_unaligned = + dma_alloc_coherent(ab->dev, desc_sz, + &desc_bank[i].paddr_unaligned, + GFP_KERNEL); + if (!desc_bank[i].vaddr_unaligned) { + ret = -ENOMEM; + goto err; + } + + desc_bank[i].vaddr = PTR_ALIGN(desc_bank[i].vaddr_unaligned, + HAL_LINK_DESC_ALIGN); + desc_bank[i].paddr = desc_bank[i].paddr_unaligned + + ((unsigned long)desc_bank[i].vaddr - + (unsigned long)desc_bank[i].vaddr_unaligned); + desc_bank[i].size = desc_sz; + } + + return 0; + +err: + ath12k_dp_link_desc_bank_free(ab, dp->link_desc_banks); + + return ret; +} + +void ath12k_dp_link_desc_cleanup(struct ath12k_base *ab, + struct dp_link_desc_bank *desc_bank, + u32 ring_type, struct dp_srng *ring) +{ + ath12k_dp_link_desc_bank_free(ab, desc_bank); + + if (ring_type != HAL_RXDMA_MONITOR_DESC) { + ath12k_dp_srng_cleanup(ab, ring); + ath12k_dp_scatter_idle_link_desc_cleanup(ab); + } +} + +static int ath12k_wbm_idle_ring_setup(struct ath12k_base *ab, u32 *n_link_desc) +{ + struct ath12k_dp *dp = &ab->dp; + u32 n_mpdu_link_desc, n_mpdu_queue_desc; + u32 n_tx_msdu_link_desc, n_rx_msdu_link_desc; + int ret = 0; + + n_mpdu_link_desc = (DP_NUM_TIDS_MAX * DP_AVG_MPDUS_PER_TID_MAX) / + HAL_NUM_MPDUS_PER_LINK_DESC; + + n_mpdu_queue_desc = n_mpdu_link_desc / + HAL_NUM_MPDU_LINKS_PER_QUEUE_DESC; + + n_tx_msdu_link_desc = (DP_NUM_TIDS_MAX * DP_AVG_FLOWS_PER_TID * + DP_AVG_MSDUS_PER_FLOW) / + HAL_NUM_TX_MSDUS_PER_LINK_DESC; + + n_rx_msdu_link_desc = (DP_NUM_TIDS_MAX * DP_AVG_MPDUS_PER_TID_MAX * + DP_AVG_MSDUS_PER_MPDU) / + HAL_NUM_RX_MSDUS_PER_LINK_DESC; + + *n_link_desc = n_mpdu_link_desc + n_mpdu_queue_desc + + n_tx_msdu_link_desc + n_rx_msdu_link_desc; + + if (*n_link_desc & (*n_link_desc - 1)) + *n_link_desc = 1 << fls(*n_link_desc); + + ret = ath12k_dp_srng_setup(ab, &dp->wbm_idle_ring, + HAL_WBM_IDLE_LINK, 0, 0, *n_link_desc); + if (ret) { + ath12k_warn(ab, "failed to setup wbm_idle_ring: %d\n", ret); + return ret; + } + return ret; +} + +int ath12k_dp_link_desc_setup(struct ath12k_base *ab, + struct dp_link_desc_bank *link_desc_banks, + u32 ring_type, struct hal_srng *srng, + u32 n_link_desc) +{ + u32 tot_mem_sz; + u32 n_link_desc_bank, last_bank_sz; + u32 entry_sz, align_bytes, n_entries; + struct hal_wbm_link_desc *desc; + u32 paddr; + int i, ret; + u32 cookie; + + tot_mem_sz = n_link_desc * HAL_LINK_DESC_SIZE; + tot_mem_sz += HAL_LINK_DESC_ALIGN; + + if (tot_mem_sz <= DP_LINK_DESC_ALLOC_SIZE_THRESH) { + n_link_desc_bank = 1; + last_bank_sz = tot_mem_sz; + } else { + n_link_desc_bank = tot_mem_sz / + (DP_LINK_DESC_ALLOC_SIZE_THRESH - + HAL_LINK_DESC_ALIGN); + last_bank_sz = tot_mem_sz % + (DP_LINK_DESC_ALLOC_SIZE_THRESH - + HAL_LINK_DESC_ALIGN); + + if (last_bank_sz) + n_link_desc_bank += 1; + } + + if (n_link_desc_bank > DP_LINK_DESC_BANKS_MAX) + return -EINVAL; + + ret = ath12k_dp_link_desc_bank_alloc(ab, link_desc_banks, + n_link_desc_bank, last_bank_sz); + if (ret) + return ret; + + /* Setup link desc idle list for HW internal usage */ + entry_sz = ath12k_hal_srng_get_entrysize(ab, ring_type); + tot_mem_sz = entry_sz * n_link_desc; + + /* Setup scatter desc list when the total memory requirement is more */ + if (tot_mem_sz > DP_LINK_DESC_ALLOC_SIZE_THRESH && + ring_type != HAL_RXDMA_MONITOR_DESC) { + ret = ath12k_dp_scatter_idle_link_desc_setup(ab, tot_mem_sz, + n_link_desc_bank, + n_link_desc, + last_bank_sz); + if (ret) { + ath12k_warn(ab, "failed to setup scatting idle list descriptor :%d\n", + ret); + goto fail_desc_bank_free; + } + + return 0; + } + + spin_lock_bh(&srng->lock); + + ath12k_hal_srng_access_begin(ab, srng); + + for (i = 0; i < n_link_desc_bank; i++) { + align_bytes = link_desc_banks[i].vaddr - + link_desc_banks[i].vaddr_unaligned; + n_entries = (link_desc_banks[i].size - align_bytes) / + HAL_LINK_DESC_SIZE; + paddr = link_desc_banks[i].paddr; + while (n_entries && + (desc = ath12k_hal_srng_src_get_next_entry(ab, srng))) { + cookie = DP_LINK_DESC_COOKIE_SET(n_entries, i); + ath12k_hal_set_link_desc_addr(desc, + cookie, paddr); + n_entries--; + paddr += HAL_LINK_DESC_SIZE; + } + } + + ath12k_hal_srng_access_end(ab, srng); + + spin_unlock_bh(&srng->lock); + + return 0; + +fail_desc_bank_free: + ath12k_dp_link_desc_bank_free(ab, link_desc_banks); + + return ret; +} + +int ath12k_dp_service_srng(struct ath12k_base *ab, + struct ath12k_ext_irq_grp *irq_grp, + int budget) +{ + struct napi_struct *napi = &irq_grp->napi; + int grp_id = irq_grp->grp_id; + int work_done = 0; + int i = 0, j; + int tot_work_done = 0; + enum dp_monitor_mode monitor_mode; + u8 ring_mask; + + while (i < ab->hw_params->max_tx_ring) { + if (ab->hw_params->ring_mask->tx[grp_id] & + BIT(ab->hw_params->hal_ops->tcl_to_wbm_rbm_map[i].wbm_ring_num)) + ath12k_dp_tx_completion_handler(ab, i); + i++; + } + + if (ab->hw_params->ring_mask->rx_err[grp_id]) { + work_done = ath12k_dp_rx_process_err(ab, napi, budget); + budget -= work_done; + tot_work_done += work_done; + if (budget <= 0) + goto done; + } + + if (ab->hw_params->ring_mask->rx_wbm_rel[grp_id]) { + work_done = ath12k_dp_rx_process_wbm_err(ab, + napi, + budget); + budget -= work_done; + tot_work_done += work_done; + + if (budget <= 0) + goto done; + } + + if (ab->hw_params->ring_mask->rx[grp_id]) { + i = fls(ab->hw_params->ring_mask->rx[grp_id]) - 1; + work_done = ath12k_dp_rx_process(ab, i, napi, + budget); + budget -= work_done; + tot_work_done += work_done; + if (budget <= 0) + goto done; + } + + if (ab->hw_params->ring_mask->rx_mon_dest[grp_id]) { + monitor_mode = ATH12K_DP_RX_MONITOR_MODE; + ring_mask = ab->hw_params->ring_mask->rx_mon_dest[grp_id]; + for (i = 0; i < ab->num_radios; i++) { + for (j = 0; j < ab->hw_params->num_rxmda_per_pdev; j++) { + int id = i * ab->hw_params->num_rxmda_per_pdev + j; + + if (ring_mask & BIT(id)) { + work_done = + ath12k_dp_mon_process_ring(ab, id, napi, budget, + monitor_mode); + budget -= work_done; + tot_work_done += work_done; + + if (budget <= 0) + goto done; + } + } + } + } + + if (ab->hw_params->ring_mask->tx_mon_dest[grp_id]) { + monitor_mode = ATH12K_DP_TX_MONITOR_MODE; + ring_mask = ab->hw_params->ring_mask->tx_mon_dest[grp_id]; + for (i = 0; i < ab->num_radios; i++) { + for (j = 0; j < ab->hw_params->num_rxmda_per_pdev; j++) { + int id = i * ab->hw_params->num_rxmda_per_pdev + j; + + if (ring_mask & BIT(id)) { + work_done = + ath12k_dp_mon_process_ring(ab, id, napi, budget, + monitor_mode); + budget -= work_done; + tot_work_done += work_done; + + if (budget <= 0) + goto done; + } + } + } + } + + if (ab->hw_params->ring_mask->reo_status[grp_id]) + ath12k_dp_rx_process_reo_status(ab); + + if (ab->hw_params->ring_mask->host2rxdma[grp_id]) { + struct ath12k_dp *dp = &ab->dp; + struct dp_rxdma_ring *rx_ring = &dp->rx_refill_buf_ring; + + ath12k_dp_rx_bufs_replenish(ab, 0, rx_ring, 0, + ab->hw_params->hal_params->rx_buf_rbm, + true); + } + + /* TODO: Implement handler for other interrupts */ + +done: + return tot_work_done; +} + +void ath12k_dp_pdev_free(struct ath12k_base *ab) +{ + int i; + + del_timer_sync(&ab->mon_reap_timer); + + for (i = 0; i < ab->num_radios; i++) + ath12k_dp_rx_pdev_free(ab, i); +} + +void ath12k_dp_pdev_pre_alloc(struct ath12k_base *ab) +{ + struct ath12k *ar; + struct ath12k_pdev_dp *dp; + int i; + + for (i = 0; i < ab->num_radios; i++) { + ar = ab->pdevs[i].ar; + dp = &ar->dp; + dp->mac_id = i; + atomic_set(&dp->num_tx_pending, 0); + init_waitqueue_head(&dp->tx_empty_waitq); + + /* TODO: Add any RXDMA setup required per pdev */ + } +} + +static void ath12k_dp_service_mon_ring(struct timer_list *t) +{ + struct ath12k_base *ab = from_timer(ab, t, mon_reap_timer); + int i; + + for (i = 0; i < ab->hw_params->num_rxmda_per_pdev; i++) + ath12k_dp_mon_process_ring(ab, i, NULL, DP_MON_SERVICE_BUDGET, + ATH12K_DP_RX_MONITOR_MODE); + + mod_timer(&ab->mon_reap_timer, jiffies + + msecs_to_jiffies(ATH12K_MON_TIMER_INTERVAL)); +} + +static void ath12k_dp_mon_reap_timer_init(struct ath12k_base *ab) +{ + if (ab->hw_params->rxdma1_enable) + return; + + timer_setup(&ab->mon_reap_timer, ath12k_dp_service_mon_ring, 0); +} + +int ath12k_dp_pdev_alloc(struct ath12k_base *ab) +{ + struct ath12k *ar; + int ret; + int i; + + ret = ath12k_dp_rx_htt_setup(ab); + if (ret) + goto out; + + ath12k_dp_mon_reap_timer_init(ab); + + /* TODO: Per-pdev rx ring unlike tx ring which is mapped to different AC's */ + for (i = 0; i < ab->num_radios; i++) { + ar = ab->pdevs[i].ar; + ret = ath12k_dp_rx_pdev_alloc(ab, i); + if (ret) { + ath12k_warn(ab, "failed to allocate pdev rx for pdev_id :%d\n", + i); + goto err; + } + ret = ath12k_dp_rx_pdev_mon_attach(ar); + if (ret) { + ath12k_warn(ab, "failed to initialize mon pdev %d\n", i); + goto err; + } + } + + return 0; +err: + ath12k_dp_pdev_free(ab); +out: + return ret; +} + +int ath12k_dp_htt_connect(struct ath12k_dp *dp) +{ + struct ath12k_htc_svc_conn_req conn_req = {0}; + struct ath12k_htc_svc_conn_resp conn_resp = {0}; + int status; + + conn_req.ep_ops.ep_tx_complete = ath12k_dp_htt_htc_tx_complete; + conn_req.ep_ops.ep_rx_complete = ath12k_dp_htt_htc_t2h_msg_handler; + + /* connect to control service */ + conn_req.service_id = ATH12K_HTC_SVC_ID_HTT_DATA_MSG; + + status = ath12k_htc_connect_service(&dp->ab->htc, &conn_req, + &conn_resp); + + if (status) + return status; + + dp->eid = conn_resp.eid; + + return 0; +} + +static void ath12k_dp_update_vdev_search(struct ath12k_vif *arvif) +{ + switch (arvif->vdev_type) { + case WMI_VDEV_TYPE_STA: + /* TODO: Verify the search type and flags since ast hash + * is not part of peer mapv3 + */ + arvif->hal_addr_search_flags = HAL_TX_ADDRY_EN; + arvif->search_type = HAL_TX_ADDR_SEARCH_DEFAULT; + break; + case WMI_VDEV_TYPE_AP: + case WMI_VDEV_TYPE_IBSS: + arvif->hal_addr_search_flags = HAL_TX_ADDRX_EN; + arvif->search_type = HAL_TX_ADDR_SEARCH_DEFAULT; + break; + case WMI_VDEV_TYPE_MONITOR: + default: + return; + } +} + +void ath12k_dp_vdev_tx_attach(struct ath12k *ar, struct ath12k_vif *arvif) +{ + struct ath12k_base *ab = ar->ab; + + arvif->tcl_metadata |= u32_encode_bits(1, HTT_TCL_META_DATA_TYPE) | + u32_encode_bits(arvif->vdev_id, + HTT_TCL_META_DATA_VDEV_ID) | + u32_encode_bits(ar->pdev->pdev_id, + HTT_TCL_META_DATA_PDEV_ID); + + /* set HTT extension valid bit to 0 by default */ + arvif->tcl_metadata &= ~HTT_TCL_META_DATA_VALID_HTT; + + ath12k_dp_update_vdev_search(arvif); + arvif->vdev_id_check_en = true; + arvif->bank_id = ath12k_dp_tx_get_bank_profile(ab, arvif, &ab->dp); + + /* TODO: error path for bank id failure */ + if (arvif->bank_id == DP_INVALID_BANK_ID) { + ath12k_err(ar->ab, "Failed to initialize DP TX Banks"); + return; + } +} + +static void ath12k_dp_cc_cleanup(struct ath12k_base *ab) +{ + struct ath12k_rx_desc_info *desc_info, *tmp; + struct ath12k_tx_desc_info *tx_desc_info, *tmp1; + struct ath12k_dp *dp = &ab->dp; + struct sk_buff *skb; + int i; + + if (!dp->spt_info) + return; + + /* RX Descriptor cleanup */ + spin_lock_bh(&dp->rx_desc_lock); + + list_for_each_entry_safe(desc_info, tmp, &dp->rx_desc_used_list, list) { + list_del(&desc_info->list); + skb = desc_info->skb; + + if (!skb) + continue; + + dma_unmap_single(ab->dev, ATH12K_SKB_RXCB(skb)->paddr, + skb->len + skb_tailroom(skb), DMA_FROM_DEVICE); + dev_kfree_skb_any(skb); + } + + spin_unlock_bh(&dp->rx_desc_lock); + + /* TX Descriptor cleanup */ + for (i = 0; i < ATH12K_HW_MAX_QUEUES; i++) { + spin_lock_bh(&dp->tx_desc_lock[i]); + + list_for_each_entry_safe(tx_desc_info, tmp1, &dp->tx_desc_used_list[i], + list) { + list_del(&tx_desc_info->list); + skb = tx_desc_info->skb; + + if (!skb) + continue; + + dma_unmap_single(ab->dev, ATH12K_SKB_CB(skb)->paddr, + skb->len, DMA_TO_DEVICE); + dev_kfree_skb_any(skb); + } + + spin_unlock_bh(&dp->tx_desc_lock[i]); + } + + /* unmap SPT pages */ + for (i = 0; i < dp->num_spt_pages; i++) { + if (!dp->spt_info[i].vaddr) + continue; + + dma_free_coherent(ab->dev, ATH12K_PAGE_SIZE, + dp->spt_info[i].vaddr, dp->spt_info[i].paddr); + dp->spt_info[i].vaddr = NULL; + } + + kfree(dp->spt_info); +} + +static void ath12k_dp_reoq_lut_cleanup(struct ath12k_base *ab) +{ + struct ath12k_dp *dp = &ab->dp; + + if (!ab->hw_params->reoq_lut_support) + return; + + if (!dp->reoq_lut.vaddr) + return; + + dma_free_coherent(ab->dev, DP_REOQ_LUT_SIZE, + dp->reoq_lut.vaddr, dp->reoq_lut.paddr); + dp->reoq_lut.vaddr = NULL; + + ath12k_hif_write32(ab, + HAL_SEQ_WCSS_UMAC_REO_REG + HAL_REO1_QDESC_LUT_BASE0(ab), 0); +} + +void ath12k_dp_free(struct ath12k_base *ab) +{ + struct ath12k_dp *dp = &ab->dp; + int i; + + ath12k_dp_link_desc_cleanup(ab, dp->link_desc_banks, + HAL_WBM_IDLE_LINK, &dp->wbm_idle_ring); + + ath12k_dp_cc_cleanup(ab); + ath12k_dp_reoq_lut_cleanup(ab); + ath12k_dp_deinit_bank_profiles(ab); + ath12k_dp_srng_common_cleanup(ab); + + ath12k_dp_rx_reo_cmd_list_cleanup(ab); + + for (i = 0; i < ab->hw_params->max_tx_ring; i++) + kfree(dp->tx_ring[i].tx_status); + + ath12k_dp_rx_free(ab); + /* Deinit any SOC level resource */ +} + +void ath12k_dp_cc_config(struct ath12k_base *ab) +{ + u32 cmem_base = ab->qmi.dev_mem[ATH12K_QMI_DEVMEM_CMEM_INDEX].start; + u32 reo_base = HAL_SEQ_WCSS_UMAC_REO_REG; + u32 wbm_base = HAL_SEQ_WCSS_UMAC_WBM_REG; + u32 val = 0; + + ath12k_hif_write32(ab, reo_base + HAL_REO1_SW_COOKIE_CFG0(ab), cmem_base); + + val |= u32_encode_bits(ATH12K_CMEM_ADDR_MSB, + HAL_REO1_SW_COOKIE_CFG_CMEM_BASE_ADDR_MSB) | + u32_encode_bits(ATH12K_CC_PPT_MSB, + HAL_REO1_SW_COOKIE_CFG_COOKIE_PPT_MSB) | + u32_encode_bits(ATH12K_CC_SPT_MSB, + HAL_REO1_SW_COOKIE_CFG_COOKIE_SPT_MSB) | + u32_encode_bits(1, HAL_REO1_SW_COOKIE_CFG_ALIGN) | + u32_encode_bits(1, HAL_REO1_SW_COOKIE_CFG_ENABLE) | + u32_encode_bits(1, HAL_REO1_SW_COOKIE_CFG_GLOBAL_ENABLE); + + ath12k_hif_write32(ab, reo_base + HAL_REO1_SW_COOKIE_CFG1(ab), val); + + /* Enable HW CC for WBM */ + ath12k_hif_write32(ab, wbm_base + HAL_WBM_SW_COOKIE_CFG0, cmem_base); + + val = u32_encode_bits(ATH12K_CMEM_ADDR_MSB, + HAL_WBM_SW_COOKIE_CFG_CMEM_BASE_ADDR_MSB) | + u32_encode_bits(ATH12K_CC_PPT_MSB, + HAL_WBM_SW_COOKIE_CFG_COOKIE_PPT_MSB) | + u32_encode_bits(ATH12K_CC_SPT_MSB, + HAL_WBM_SW_COOKIE_CFG_COOKIE_SPT_MSB) | + u32_encode_bits(1, HAL_WBM_SW_COOKIE_CFG_ALIGN); + + ath12k_hif_write32(ab, wbm_base + HAL_WBM_SW_COOKIE_CFG1, val); + + /* Enable conversion complete indication */ + val = ath12k_hif_read32(ab, wbm_base + HAL_WBM_SW_COOKIE_CFG2); + val |= u32_encode_bits(1, HAL_WBM_SW_COOKIE_CFG_RELEASE_PATH_EN) | + u32_encode_bits(1, HAL_WBM_SW_COOKIE_CFG_ERR_PATH_EN) | + u32_encode_bits(1, HAL_WBM_SW_COOKIE_CFG_CONV_IND_EN); + + ath12k_hif_write32(ab, wbm_base + HAL_WBM_SW_COOKIE_CFG2, val); + + /* Enable Cookie conversion for WBM2SW Rings */ + val = ath12k_hif_read32(ab, wbm_base + HAL_WBM_SW_COOKIE_CONVERT_CFG); + val |= u32_encode_bits(1, HAL_WBM_SW_COOKIE_CONV_CFG_GLOBAL_EN) | + ab->hw_params->hal_params->wbm2sw_cc_enable; + + ath12k_hif_write32(ab, wbm_base + HAL_WBM_SW_COOKIE_CONVERT_CFG, val); +} + +static u32 ath12k_dp_cc_cookie_gen(u16 ppt_idx, u16 spt_idx) +{ + return (u32)ppt_idx << ATH12K_CC_PPT_SHIFT | spt_idx; +} + +static inline void *ath12k_dp_cc_get_desc_addr_ptr(struct ath12k_base *ab, + u16 ppt_idx, u16 spt_idx) +{ + struct ath12k_dp *dp = &ab->dp; + + return dp->spt_info[ppt_idx].vaddr + spt_idx; +} + +struct ath12k_rx_desc_info *ath12k_dp_get_rx_desc(struct ath12k_base *ab, + u32 cookie) +{ + struct ath12k_rx_desc_info **desc_addr_ptr; + u16 ppt_idx, spt_idx; + + ppt_idx = u32_get_bits(cookie, ATH12K_DP_CC_COOKIE_PPT); + spt_idx = u32_get_bits(cookie, ATH12k_DP_CC_COOKIE_SPT); + + if (ppt_idx > ATH12K_NUM_RX_SPT_PAGES || + spt_idx > ATH12K_MAX_SPT_ENTRIES) + return NULL; + + desc_addr_ptr = ath12k_dp_cc_get_desc_addr_ptr(ab, ppt_idx, spt_idx); + + return *desc_addr_ptr; +} + +struct ath12k_tx_desc_info *ath12k_dp_get_tx_desc(struct ath12k_base *ab, + u32 cookie) +{ + struct ath12k_tx_desc_info **desc_addr_ptr; + u16 ppt_idx, spt_idx; + + ppt_idx = u32_get_bits(cookie, ATH12K_DP_CC_COOKIE_PPT); + spt_idx = u32_get_bits(cookie, ATH12k_DP_CC_COOKIE_SPT); + + if (ppt_idx < ATH12K_NUM_RX_SPT_PAGES || + ppt_idx > ab->dp.num_spt_pages || + spt_idx > ATH12K_MAX_SPT_ENTRIES) + return NULL; + + desc_addr_ptr = ath12k_dp_cc_get_desc_addr_ptr(ab, ppt_idx, spt_idx); + + return *desc_addr_ptr; +} + +static int ath12k_dp_cc_desc_init(struct ath12k_base *ab) +{ + struct ath12k_dp *dp = &ab->dp; + struct ath12k_rx_desc_info *rx_descs, **rx_desc_addr; + struct ath12k_tx_desc_info *tx_descs, **tx_desc_addr; + u32 i, j, pool_id, tx_spt_page; + u32 ppt_idx; + + spin_lock_bh(&dp->rx_desc_lock); + + /* First ATH12K_NUM_RX_SPT_PAGES of allocated SPT pages are used for RX */ + for (i = 0; i < ATH12K_NUM_RX_SPT_PAGES; i++) { + rx_descs = kcalloc(ATH12K_MAX_SPT_ENTRIES, sizeof(*rx_descs), + GFP_ATOMIC); + + if (!rx_descs) { + spin_unlock_bh(&dp->rx_desc_lock); + return -ENOMEM; + } + + for (j = 0; j < ATH12K_MAX_SPT_ENTRIES; j++) { + rx_descs[j].cookie = ath12k_dp_cc_cookie_gen(i, j); + rx_descs[j].magic = ATH12K_DP_RX_DESC_MAGIC; + list_add_tail(&rx_descs[j].list, &dp->rx_desc_free_list); + + /* Update descriptor VA in SPT */ + rx_desc_addr = ath12k_dp_cc_get_desc_addr_ptr(ab, i, j); + *rx_desc_addr = &rx_descs[j]; + } + } + + spin_unlock_bh(&dp->rx_desc_lock); + + for (pool_id = 0; pool_id < ATH12K_HW_MAX_QUEUES; pool_id++) { + spin_lock_bh(&dp->tx_desc_lock[pool_id]); + for (i = 0; i < ATH12K_TX_SPT_PAGES_PER_POOL; i++) { + tx_descs = kcalloc(ATH12K_MAX_SPT_ENTRIES, sizeof(*tx_descs), + GFP_ATOMIC); + + if (!tx_descs) { + spin_unlock_bh(&dp->tx_desc_lock[pool_id]); + /* Caller takes care of TX pending and RX desc cleanup */ + return -ENOMEM; + } + + for (j = 0; j < ATH12K_MAX_SPT_ENTRIES; j++) { + tx_spt_page = i + pool_id * ATH12K_TX_SPT_PAGES_PER_POOL; + ppt_idx = ATH12K_NUM_RX_SPT_PAGES + tx_spt_page; + tx_descs[j].desc_id = ath12k_dp_cc_cookie_gen(ppt_idx, j); + tx_descs[j].pool_id = pool_id; + list_add_tail(&tx_descs[j].list, + &dp->tx_desc_free_list[pool_id]); + + /* Update descriptor VA in SPT */ + tx_desc_addr = + ath12k_dp_cc_get_desc_addr_ptr(ab, ppt_idx, j); + *tx_desc_addr = &tx_descs[j]; + } + } + spin_unlock_bh(&dp->tx_desc_lock[pool_id]); + } + return 0; +} + +static int ath12k_dp_cc_init(struct ath12k_base *ab) +{ + struct ath12k_dp *dp = &ab->dp; + int i, ret = 0; + u32 cmem_base; + + INIT_LIST_HEAD(&dp->rx_desc_free_list); + INIT_LIST_HEAD(&dp->rx_desc_used_list); + spin_lock_init(&dp->rx_desc_lock); + + for (i = 0; i < ATH12K_HW_MAX_QUEUES; i++) { + INIT_LIST_HEAD(&dp->tx_desc_free_list[i]); + INIT_LIST_HEAD(&dp->tx_desc_used_list[i]); + spin_lock_init(&dp->tx_desc_lock[i]); + } + + dp->num_spt_pages = ATH12K_NUM_SPT_PAGES; + if (dp->num_spt_pages > ATH12K_MAX_PPT_ENTRIES) + dp->num_spt_pages = ATH12K_MAX_PPT_ENTRIES; + + dp->spt_info = kcalloc(dp->num_spt_pages, sizeof(struct ath12k_spt_info), + GFP_KERNEL); + + if (!dp->spt_info) { + ath12k_warn(ab, "SPT page allocation failure"); + return -ENOMEM; + } + + cmem_base = ab->qmi.dev_mem[ATH12K_QMI_DEVMEM_CMEM_INDEX].start; + + for (i = 0; i < dp->num_spt_pages; i++) { + dp->spt_info[i].vaddr = dma_alloc_coherent(ab->dev, + ATH12K_PAGE_SIZE, + &dp->spt_info[i].paddr, + GFP_KERNEL); + + if (!dp->spt_info[i].vaddr) { + ret = -ENOMEM; + goto free; + } + + if (dp->spt_info[i].paddr & ATH12K_SPT_4K_ALIGN_CHECK) { + ath12k_warn(ab, "SPT allocated memoty is not 4K aligned"); + ret = -EINVAL; + goto free; + } + + /* Write to PPT in CMEM */ + ath12k_hif_write32(ab, cmem_base + ATH12K_PPT_ADDR_OFFSET(i), + dp->spt_info[i].paddr >> ATH12K_SPT_4K_ALIGN_OFFSET); + } + + ret = ath12k_dp_cc_desc_init(ab); + if (ret) { + ath12k_warn(ab, "HW CC desc init failed %d", ret); + goto free; + } + + return 0; +free: + ath12k_dp_cc_cleanup(ab); + return ret; +} + +static int ath12k_dp_reoq_lut_setup(struct ath12k_base *ab) +{ + struct ath12k_dp *dp = &ab->dp; + + if (!ab->hw_params->reoq_lut_support) + return 0; + + dp->reoq_lut.vaddr = dma_alloc_coherent(ab->dev, + DP_REOQ_LUT_SIZE, + &dp->reoq_lut.paddr, + GFP_KERNEL); + + if (!dp->reoq_lut.vaddr) { + ath12k_warn(ab, "failed to allocate memory for reoq table"); + return -ENOMEM; + } + + memset(dp->reoq_lut.vaddr, 0, DP_REOQ_LUT_SIZE); + + ath12k_hif_write32(ab, HAL_SEQ_WCSS_UMAC_REO_REG + HAL_REO1_QDESC_LUT_BASE0(ab), + dp->reoq_lut.paddr); + return 0; +} + +int ath12k_dp_alloc(struct ath12k_base *ab) +{ + struct ath12k_dp *dp = &ab->dp; + struct hal_srng *srng = NULL; + size_t size = 0; + u32 n_link_desc = 0; + int ret; + int i; + + dp->ab = ab; + + INIT_LIST_HEAD(&dp->reo_cmd_list); + INIT_LIST_HEAD(&dp->reo_cmd_cache_flush_list); + spin_lock_init(&dp->reo_cmd_lock); + + dp->reo_cmd_cache_flush_count = 0; + + ret = ath12k_wbm_idle_ring_setup(ab, &n_link_desc); + if (ret) { + ath12k_warn(ab, "failed to setup wbm_idle_ring: %d\n", ret); + return ret; + } + + srng = &ab->hal.srng_list[dp->wbm_idle_ring.ring_id]; + + ret = ath12k_dp_link_desc_setup(ab, dp->link_desc_banks, + HAL_WBM_IDLE_LINK, srng, n_link_desc); + if (ret) { + ath12k_warn(ab, "failed to setup link desc: %d\n", ret); + return ret; + } + + ret = ath12k_dp_cc_init(ab); + + if (ret) { + ath12k_warn(ab, "failed to setup cookie converter %d\n", ret); + goto fail_link_desc_cleanup; + } + ret = ath12k_dp_init_bank_profiles(ab); + if (ret) { + ath12k_warn(ab, "failed to setup bank profiles %d\n", ret); + goto fail_hw_cc_cleanup; + } + + ret = ath12k_dp_srng_common_setup(ab); + if (ret) + goto fail_dp_bank_profiles_cleanup; + + size = sizeof(struct hal_wbm_release_ring_tx) * DP_TX_COMP_RING_SIZE; + + ret = ath12k_dp_reoq_lut_setup(ab); + if (ret) { + ath12k_warn(ab, "failed to setup reoq table %d\n", ret); + goto fail_cmn_srng_cleanup; + } + + for (i = 0; i < ab->hw_params->max_tx_ring; i++) { + dp->tx_ring[i].tcl_data_ring_id = i; + + dp->tx_ring[i].tx_status_head = 0; + dp->tx_ring[i].tx_status_tail = DP_TX_COMP_RING_SIZE - 1; + dp->tx_ring[i].tx_status = kmalloc(size, GFP_KERNEL); + if (!dp->tx_ring[i].tx_status) { + ret = -ENOMEM; + /* FIXME: The allocated tx status is not freed + * properly here + */ + goto fail_cmn_reoq_cleanup; + } + } + + for (i = 0; i < HAL_DSCP_TID_MAP_TBL_NUM_ENTRIES_MAX; i++) + ath12k_hal_tx_set_dscp_tid_map(ab, i); + + ret = ath12k_dp_rx_alloc(ab); + if (ret) + goto fail_dp_rx_free; + + /* Init any SOC level resource for DP */ + + return 0; + +fail_dp_rx_free: + ath12k_dp_rx_free(ab); + +fail_cmn_reoq_cleanup: + ath12k_dp_reoq_lut_cleanup(ab); + +fail_cmn_srng_cleanup: + ath12k_dp_srng_common_cleanup(ab); + +fail_dp_bank_profiles_cleanup: + ath12k_dp_deinit_bank_profiles(ab); + +fail_hw_cc_cleanup: + ath12k_dp_cc_cleanup(ab); + +fail_link_desc_cleanup: + ath12k_dp_link_desc_cleanup(ab, dp->link_desc_banks, + HAL_WBM_IDLE_LINK, &dp->wbm_idle_ring); + + return ret; +}