162306a36Sopenharmony_ci// SPDX-License-Identifier: ISC 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 2005-2011 Atheros Communications Inc. 462306a36Sopenharmony_ci * Copyright (c) 2011-2017 Qualcomm Atheros, Inc. 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include "core.h" 862306a36Sopenharmony_ci#include "hif.h" 962306a36Sopenharmony_ci#include "debug.h" 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci/********/ 1262306a36Sopenharmony_ci/* Send */ 1362306a36Sopenharmony_ci/********/ 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_cistatic void ath10k_htc_control_tx_complete(struct ath10k *ar, 1662306a36Sopenharmony_ci struct sk_buff *skb) 1762306a36Sopenharmony_ci{ 1862306a36Sopenharmony_ci kfree_skb(skb); 1962306a36Sopenharmony_ci} 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistatic struct sk_buff *ath10k_htc_build_tx_ctrl_skb(void *ar) 2262306a36Sopenharmony_ci{ 2362306a36Sopenharmony_ci struct sk_buff *skb; 2462306a36Sopenharmony_ci struct ath10k_skb_cb *skb_cb; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci skb = dev_alloc_skb(ATH10K_HTC_CONTROL_BUFFER_SIZE); 2762306a36Sopenharmony_ci if (!skb) 2862306a36Sopenharmony_ci return NULL; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci skb_reserve(skb, 20); /* FIXME: why 20 bytes? */ 3162306a36Sopenharmony_ci WARN_ONCE((unsigned long)skb->data & 3, "unaligned skb"); 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci skb_cb = ATH10K_SKB_CB(skb); 3462306a36Sopenharmony_ci memset(skb_cb, 0, sizeof(*skb_cb)); 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_HTC, "%s: skb %pK\n", __func__, skb); 3762306a36Sopenharmony_ci return skb; 3862306a36Sopenharmony_ci} 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_cistatic inline void ath10k_htc_restore_tx_skb(struct ath10k_htc *htc, 4162306a36Sopenharmony_ci struct sk_buff *skb) 4262306a36Sopenharmony_ci{ 4362306a36Sopenharmony_ci struct ath10k_skb_cb *skb_cb = ATH10K_SKB_CB(skb); 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci if (htc->ar->bus_param.dev_type != ATH10K_DEV_TYPE_HL) 4662306a36Sopenharmony_ci dma_unmap_single(htc->ar->dev, skb_cb->paddr, skb->len, DMA_TO_DEVICE); 4762306a36Sopenharmony_ci skb_pull(skb, sizeof(struct ath10k_htc_hdr)); 4862306a36Sopenharmony_ci} 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_civoid ath10k_htc_notify_tx_completion(struct ath10k_htc_ep *ep, 5162306a36Sopenharmony_ci struct sk_buff *skb) 5262306a36Sopenharmony_ci{ 5362306a36Sopenharmony_ci struct ath10k *ar = ep->htc->ar; 5462306a36Sopenharmony_ci struct ath10k_htc_hdr *hdr; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_HTC, "%s: ep %d skb %pK\n", __func__, 5762306a36Sopenharmony_ci ep->eid, skb); 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci /* A corner case where the copy completion is reaching to host but still 6062306a36Sopenharmony_ci * copy engine is processing it due to which host unmaps corresponding 6162306a36Sopenharmony_ci * memory and causes SMMU fault, hence as workaround adding delay 6262306a36Sopenharmony_ci * the unmapping memory to avoid SMMU faults. 6362306a36Sopenharmony_ci */ 6462306a36Sopenharmony_ci if (ar->hw_params.delay_unmap_buffer && 6562306a36Sopenharmony_ci ep->ul_pipe_id == 3) 6662306a36Sopenharmony_ci mdelay(2); 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci hdr = (struct ath10k_htc_hdr *)skb->data; 6962306a36Sopenharmony_ci ath10k_htc_restore_tx_skb(ep->htc, skb); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci if (!ep->ep_ops.ep_tx_complete) { 7262306a36Sopenharmony_ci ath10k_warn(ar, "no tx handler for eid %d\n", ep->eid); 7362306a36Sopenharmony_ci dev_kfree_skb_any(skb); 7462306a36Sopenharmony_ci return; 7562306a36Sopenharmony_ci } 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci if (hdr->flags & ATH10K_HTC_FLAG_SEND_BUNDLE) { 7862306a36Sopenharmony_ci dev_kfree_skb_any(skb); 7962306a36Sopenharmony_ci return; 8062306a36Sopenharmony_ci } 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci ep->ep_ops.ep_tx_complete(ep->htc->ar, skb); 8362306a36Sopenharmony_ci} 8462306a36Sopenharmony_ciEXPORT_SYMBOL(ath10k_htc_notify_tx_completion); 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistatic void ath10k_htc_prepare_tx_skb(struct ath10k_htc_ep *ep, 8762306a36Sopenharmony_ci struct sk_buff *skb) 8862306a36Sopenharmony_ci{ 8962306a36Sopenharmony_ci struct ath10k_htc_hdr *hdr; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci hdr = (struct ath10k_htc_hdr *)skb->data; 9262306a36Sopenharmony_ci memset(hdr, 0, sizeof(struct ath10k_htc_hdr)); 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci hdr->eid = ep->eid; 9562306a36Sopenharmony_ci hdr->len = __cpu_to_le16(skb->len - sizeof(*hdr)); 9662306a36Sopenharmony_ci hdr->flags = 0; 9762306a36Sopenharmony_ci if (ep->tx_credit_flow_enabled && !ep->bundle_tx) 9862306a36Sopenharmony_ci hdr->flags |= ATH10K_HTC_FLAG_NEED_CREDIT_UPDATE; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci spin_lock_bh(&ep->htc->tx_lock); 10162306a36Sopenharmony_ci hdr->seq_no = ep->seq_no++; 10262306a36Sopenharmony_ci spin_unlock_bh(&ep->htc->tx_lock); 10362306a36Sopenharmony_ci} 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_cistatic int ath10k_htc_consume_credit(struct ath10k_htc_ep *ep, 10662306a36Sopenharmony_ci unsigned int len, 10762306a36Sopenharmony_ci bool consume) 10862306a36Sopenharmony_ci{ 10962306a36Sopenharmony_ci struct ath10k_htc *htc = ep->htc; 11062306a36Sopenharmony_ci struct ath10k *ar = htc->ar; 11162306a36Sopenharmony_ci enum ath10k_htc_ep_id eid = ep->eid; 11262306a36Sopenharmony_ci int credits, ret = 0; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci if (!ep->tx_credit_flow_enabled) 11562306a36Sopenharmony_ci return 0; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci credits = DIV_ROUND_UP(len, ep->tx_credit_size); 11862306a36Sopenharmony_ci spin_lock_bh(&htc->tx_lock); 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci if (ep->tx_credits < credits) { 12162306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_HTC, 12262306a36Sopenharmony_ci "htc insufficient credits ep %d required %d available %d consume %d\n", 12362306a36Sopenharmony_ci eid, credits, ep->tx_credits, consume); 12462306a36Sopenharmony_ci ret = -EAGAIN; 12562306a36Sopenharmony_ci goto unlock; 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci if (consume) { 12962306a36Sopenharmony_ci ep->tx_credits -= credits; 13062306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_HTC, 13162306a36Sopenharmony_ci "htc ep %d consumed %d credits total %d\n", 13262306a36Sopenharmony_ci eid, credits, ep->tx_credits); 13362306a36Sopenharmony_ci } 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ciunlock: 13662306a36Sopenharmony_ci spin_unlock_bh(&htc->tx_lock); 13762306a36Sopenharmony_ci return ret; 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cistatic void ath10k_htc_release_credit(struct ath10k_htc_ep *ep, unsigned int len) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci struct ath10k_htc *htc = ep->htc; 14362306a36Sopenharmony_ci struct ath10k *ar = htc->ar; 14462306a36Sopenharmony_ci enum ath10k_htc_ep_id eid = ep->eid; 14562306a36Sopenharmony_ci int credits; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci if (!ep->tx_credit_flow_enabled) 14862306a36Sopenharmony_ci return; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci credits = DIV_ROUND_UP(len, ep->tx_credit_size); 15162306a36Sopenharmony_ci spin_lock_bh(&htc->tx_lock); 15262306a36Sopenharmony_ci ep->tx_credits += credits; 15362306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_HTC, 15462306a36Sopenharmony_ci "htc ep %d reverted %d credits back total %d\n", 15562306a36Sopenharmony_ci eid, credits, ep->tx_credits); 15662306a36Sopenharmony_ci spin_unlock_bh(&htc->tx_lock); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci if (ep->ep_ops.ep_tx_credits) 15962306a36Sopenharmony_ci ep->ep_ops.ep_tx_credits(htc->ar); 16062306a36Sopenharmony_ci} 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ciint ath10k_htc_send(struct ath10k_htc *htc, 16362306a36Sopenharmony_ci enum ath10k_htc_ep_id eid, 16462306a36Sopenharmony_ci struct sk_buff *skb) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci struct ath10k *ar = htc->ar; 16762306a36Sopenharmony_ci struct ath10k_htc_ep *ep = &htc->endpoint[eid]; 16862306a36Sopenharmony_ci struct ath10k_skb_cb *skb_cb = ATH10K_SKB_CB(skb); 16962306a36Sopenharmony_ci struct ath10k_hif_sg_item sg_item; 17062306a36Sopenharmony_ci struct device *dev = htc->ar->dev; 17162306a36Sopenharmony_ci int ret; 17262306a36Sopenharmony_ci unsigned int skb_len; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci if (htc->ar->state == ATH10K_STATE_WEDGED) 17562306a36Sopenharmony_ci return -ECOMM; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci if (eid >= ATH10K_HTC_EP_COUNT) { 17862306a36Sopenharmony_ci ath10k_warn(ar, "Invalid endpoint id: %d\n", eid); 17962306a36Sopenharmony_ci return -ENOENT; 18062306a36Sopenharmony_ci } 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci skb_push(skb, sizeof(struct ath10k_htc_hdr)); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci skb_len = skb->len; 18562306a36Sopenharmony_ci ret = ath10k_htc_consume_credit(ep, skb_len, true); 18662306a36Sopenharmony_ci if (ret) 18762306a36Sopenharmony_ci goto err_pull; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci ath10k_htc_prepare_tx_skb(ep, skb); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci skb_cb->eid = eid; 19262306a36Sopenharmony_ci if (ar->bus_param.dev_type != ATH10K_DEV_TYPE_HL) { 19362306a36Sopenharmony_ci skb_cb->paddr = dma_map_single(dev, skb->data, skb->len, 19462306a36Sopenharmony_ci DMA_TO_DEVICE); 19562306a36Sopenharmony_ci ret = dma_mapping_error(dev, skb_cb->paddr); 19662306a36Sopenharmony_ci if (ret) { 19762306a36Sopenharmony_ci ret = -EIO; 19862306a36Sopenharmony_ci goto err_credits; 19962306a36Sopenharmony_ci } 20062306a36Sopenharmony_ci } 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci sg_item.transfer_id = ep->eid; 20362306a36Sopenharmony_ci sg_item.transfer_context = skb; 20462306a36Sopenharmony_ci sg_item.vaddr = skb->data; 20562306a36Sopenharmony_ci sg_item.paddr = skb_cb->paddr; 20662306a36Sopenharmony_ci sg_item.len = skb->len; 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci ret = ath10k_hif_tx_sg(htc->ar, ep->ul_pipe_id, &sg_item, 1); 20962306a36Sopenharmony_ci if (ret) 21062306a36Sopenharmony_ci goto err_unmap; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci return 0; 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_cierr_unmap: 21562306a36Sopenharmony_ci if (ar->bus_param.dev_type != ATH10K_DEV_TYPE_HL) 21662306a36Sopenharmony_ci dma_unmap_single(dev, skb_cb->paddr, skb->len, DMA_TO_DEVICE); 21762306a36Sopenharmony_cierr_credits: 21862306a36Sopenharmony_ci ath10k_htc_release_credit(ep, skb_len); 21962306a36Sopenharmony_cierr_pull: 22062306a36Sopenharmony_ci skb_pull(skb, sizeof(struct ath10k_htc_hdr)); 22162306a36Sopenharmony_ci return ret; 22262306a36Sopenharmony_ci} 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_civoid ath10k_htc_tx_completion_handler(struct ath10k *ar, struct sk_buff *skb) 22562306a36Sopenharmony_ci{ 22662306a36Sopenharmony_ci struct ath10k_htc *htc = &ar->htc; 22762306a36Sopenharmony_ci struct ath10k_skb_cb *skb_cb; 22862306a36Sopenharmony_ci struct ath10k_htc_ep *ep; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci if (WARN_ON_ONCE(!skb)) 23162306a36Sopenharmony_ci return; 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci skb_cb = ATH10K_SKB_CB(skb); 23462306a36Sopenharmony_ci ep = &htc->endpoint[skb_cb->eid]; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci ath10k_htc_notify_tx_completion(ep, skb); 23762306a36Sopenharmony_ci /* the skb now belongs to the completion handler */ 23862306a36Sopenharmony_ci} 23962306a36Sopenharmony_ciEXPORT_SYMBOL(ath10k_htc_tx_completion_handler); 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci/***********/ 24262306a36Sopenharmony_ci/* Receive */ 24362306a36Sopenharmony_ci/***********/ 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_cistatic void 24662306a36Sopenharmony_ciath10k_htc_process_credit_report(struct ath10k_htc *htc, 24762306a36Sopenharmony_ci const struct ath10k_htc_credit_report *report, 24862306a36Sopenharmony_ci int len, 24962306a36Sopenharmony_ci enum ath10k_htc_ep_id eid) 25062306a36Sopenharmony_ci{ 25162306a36Sopenharmony_ci struct ath10k *ar = htc->ar; 25262306a36Sopenharmony_ci struct ath10k_htc_ep *ep; 25362306a36Sopenharmony_ci int i, n_reports; 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci if (len % sizeof(*report)) 25662306a36Sopenharmony_ci ath10k_warn(ar, "Uneven credit report len %d", len); 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci n_reports = len / sizeof(*report); 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci spin_lock_bh(&htc->tx_lock); 26162306a36Sopenharmony_ci for (i = 0; i < n_reports; i++, report++) { 26262306a36Sopenharmony_ci if (report->eid >= ATH10K_HTC_EP_COUNT) 26362306a36Sopenharmony_ci break; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci ep = &htc->endpoint[report->eid]; 26662306a36Sopenharmony_ci ep->tx_credits += report->credits; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_HTC, "htc ep %d got %d credits (total %d)\n", 26962306a36Sopenharmony_ci report->eid, report->credits, ep->tx_credits); 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci if (ep->ep_ops.ep_tx_credits) { 27262306a36Sopenharmony_ci spin_unlock_bh(&htc->tx_lock); 27362306a36Sopenharmony_ci ep->ep_ops.ep_tx_credits(htc->ar); 27462306a36Sopenharmony_ci spin_lock_bh(&htc->tx_lock); 27562306a36Sopenharmony_ci } 27662306a36Sopenharmony_ci } 27762306a36Sopenharmony_ci spin_unlock_bh(&htc->tx_lock); 27862306a36Sopenharmony_ci} 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_cistatic int 28162306a36Sopenharmony_ciath10k_htc_process_lookahead(struct ath10k_htc *htc, 28262306a36Sopenharmony_ci const struct ath10k_htc_lookahead_report *report, 28362306a36Sopenharmony_ci int len, 28462306a36Sopenharmony_ci enum ath10k_htc_ep_id eid, 28562306a36Sopenharmony_ci void *next_lookaheads, 28662306a36Sopenharmony_ci int *next_lookaheads_len) 28762306a36Sopenharmony_ci{ 28862306a36Sopenharmony_ci struct ath10k *ar = htc->ar; 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci /* Invalid lookahead flags are actually transmitted by 29162306a36Sopenharmony_ci * the target in the HTC control message. 29262306a36Sopenharmony_ci * Since this will happen at every boot we silently ignore 29362306a36Sopenharmony_ci * the lookahead in this case 29462306a36Sopenharmony_ci */ 29562306a36Sopenharmony_ci if (report->pre_valid != ((~report->post_valid) & 0xFF)) 29662306a36Sopenharmony_ci return 0; 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci if (next_lookaheads && next_lookaheads_len) { 29962306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_HTC, 30062306a36Sopenharmony_ci "htc rx lookahead found pre_valid 0x%x post_valid 0x%x\n", 30162306a36Sopenharmony_ci report->pre_valid, report->post_valid); 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci /* look ahead bytes are valid, copy them over */ 30462306a36Sopenharmony_ci memcpy((u8 *)next_lookaheads, report->lookahead, 4); 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci *next_lookaheads_len = 1; 30762306a36Sopenharmony_ci } 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci return 0; 31062306a36Sopenharmony_ci} 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_cistatic int 31362306a36Sopenharmony_ciath10k_htc_process_lookahead_bundle(struct ath10k_htc *htc, 31462306a36Sopenharmony_ci const struct ath10k_htc_lookahead_bundle *report, 31562306a36Sopenharmony_ci int len, 31662306a36Sopenharmony_ci enum ath10k_htc_ep_id eid, 31762306a36Sopenharmony_ci void *next_lookaheads, 31862306a36Sopenharmony_ci int *next_lookaheads_len) 31962306a36Sopenharmony_ci{ 32062306a36Sopenharmony_ci struct ath10k *ar = htc->ar; 32162306a36Sopenharmony_ci int bundle_cnt = len / sizeof(*report); 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci if (!bundle_cnt || (bundle_cnt > htc->max_msgs_per_htc_bundle)) { 32462306a36Sopenharmony_ci ath10k_warn(ar, "Invalid lookahead bundle count: %d\n", 32562306a36Sopenharmony_ci bundle_cnt); 32662306a36Sopenharmony_ci return -EINVAL; 32762306a36Sopenharmony_ci } 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci if (next_lookaheads && next_lookaheads_len) { 33062306a36Sopenharmony_ci int i; 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci for (i = 0; i < bundle_cnt; i++) { 33362306a36Sopenharmony_ci memcpy(((u8 *)next_lookaheads) + 4 * i, 33462306a36Sopenharmony_ci report->lookahead, 4); 33562306a36Sopenharmony_ci report++; 33662306a36Sopenharmony_ci } 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci *next_lookaheads_len = bundle_cnt; 33962306a36Sopenharmony_ci } 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci return 0; 34262306a36Sopenharmony_ci} 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ciint ath10k_htc_process_trailer(struct ath10k_htc *htc, 34562306a36Sopenharmony_ci u8 *buffer, 34662306a36Sopenharmony_ci int length, 34762306a36Sopenharmony_ci enum ath10k_htc_ep_id src_eid, 34862306a36Sopenharmony_ci void *next_lookaheads, 34962306a36Sopenharmony_ci int *next_lookaheads_len) 35062306a36Sopenharmony_ci{ 35162306a36Sopenharmony_ci struct ath10k_htc_lookahead_bundle *bundle; 35262306a36Sopenharmony_ci struct ath10k *ar = htc->ar; 35362306a36Sopenharmony_ci int status = 0; 35462306a36Sopenharmony_ci struct ath10k_htc_record *record; 35562306a36Sopenharmony_ci u8 *orig_buffer; 35662306a36Sopenharmony_ci int orig_length; 35762306a36Sopenharmony_ci size_t len; 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci orig_buffer = buffer; 36062306a36Sopenharmony_ci orig_length = length; 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci while (length > 0) { 36362306a36Sopenharmony_ci record = (struct ath10k_htc_record *)buffer; 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci if (length < sizeof(record->hdr)) { 36662306a36Sopenharmony_ci status = -EINVAL; 36762306a36Sopenharmony_ci break; 36862306a36Sopenharmony_ci } 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_ci if (record->hdr.len > length) { 37162306a36Sopenharmony_ci /* no room left in buffer for record */ 37262306a36Sopenharmony_ci ath10k_warn(ar, "Invalid record length: %d\n", 37362306a36Sopenharmony_ci record->hdr.len); 37462306a36Sopenharmony_ci status = -EINVAL; 37562306a36Sopenharmony_ci break; 37662306a36Sopenharmony_ci } 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci switch (record->hdr.id) { 37962306a36Sopenharmony_ci case ATH10K_HTC_RECORD_CREDITS: 38062306a36Sopenharmony_ci len = sizeof(struct ath10k_htc_credit_report); 38162306a36Sopenharmony_ci if (record->hdr.len < len) { 38262306a36Sopenharmony_ci ath10k_warn(ar, "Credit report too long\n"); 38362306a36Sopenharmony_ci status = -EINVAL; 38462306a36Sopenharmony_ci break; 38562306a36Sopenharmony_ci } 38662306a36Sopenharmony_ci ath10k_htc_process_credit_report(htc, 38762306a36Sopenharmony_ci record->credit_report, 38862306a36Sopenharmony_ci record->hdr.len, 38962306a36Sopenharmony_ci src_eid); 39062306a36Sopenharmony_ci break; 39162306a36Sopenharmony_ci case ATH10K_HTC_RECORD_LOOKAHEAD: 39262306a36Sopenharmony_ci len = sizeof(struct ath10k_htc_lookahead_report); 39362306a36Sopenharmony_ci if (record->hdr.len < len) { 39462306a36Sopenharmony_ci ath10k_warn(ar, "Lookahead report too long\n"); 39562306a36Sopenharmony_ci status = -EINVAL; 39662306a36Sopenharmony_ci break; 39762306a36Sopenharmony_ci } 39862306a36Sopenharmony_ci status = ath10k_htc_process_lookahead(htc, 39962306a36Sopenharmony_ci record->lookahead_report, 40062306a36Sopenharmony_ci record->hdr.len, 40162306a36Sopenharmony_ci src_eid, 40262306a36Sopenharmony_ci next_lookaheads, 40362306a36Sopenharmony_ci next_lookaheads_len); 40462306a36Sopenharmony_ci break; 40562306a36Sopenharmony_ci case ATH10K_HTC_RECORD_LOOKAHEAD_BUNDLE: 40662306a36Sopenharmony_ci bundle = record->lookahead_bundle; 40762306a36Sopenharmony_ci status = ath10k_htc_process_lookahead_bundle(htc, 40862306a36Sopenharmony_ci bundle, 40962306a36Sopenharmony_ci record->hdr.len, 41062306a36Sopenharmony_ci src_eid, 41162306a36Sopenharmony_ci next_lookaheads, 41262306a36Sopenharmony_ci next_lookaheads_len); 41362306a36Sopenharmony_ci break; 41462306a36Sopenharmony_ci default: 41562306a36Sopenharmony_ci ath10k_warn(ar, "Unhandled record: id:%d length:%d\n", 41662306a36Sopenharmony_ci record->hdr.id, record->hdr.len); 41762306a36Sopenharmony_ci break; 41862306a36Sopenharmony_ci } 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_ci if (status) 42162306a36Sopenharmony_ci break; 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci /* multiple records may be present in a trailer */ 42462306a36Sopenharmony_ci buffer += sizeof(record->hdr) + record->hdr.len; 42562306a36Sopenharmony_ci length -= sizeof(record->hdr) + record->hdr.len; 42662306a36Sopenharmony_ci } 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci if (status) 42962306a36Sopenharmony_ci ath10k_dbg_dump(ar, ATH10K_DBG_HTC, "htc rx bad trailer", "", 43062306a36Sopenharmony_ci orig_buffer, orig_length); 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci return status; 43362306a36Sopenharmony_ci} 43462306a36Sopenharmony_ciEXPORT_SYMBOL(ath10k_htc_process_trailer); 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_civoid ath10k_htc_rx_completion_handler(struct ath10k *ar, struct sk_buff *skb) 43762306a36Sopenharmony_ci{ 43862306a36Sopenharmony_ci int status = 0; 43962306a36Sopenharmony_ci struct ath10k_htc *htc = &ar->htc; 44062306a36Sopenharmony_ci struct ath10k_htc_hdr *hdr; 44162306a36Sopenharmony_ci struct ath10k_htc_ep *ep; 44262306a36Sopenharmony_ci u16 payload_len; 44362306a36Sopenharmony_ci u32 trailer_len = 0; 44462306a36Sopenharmony_ci size_t min_len; 44562306a36Sopenharmony_ci u8 eid; 44662306a36Sopenharmony_ci bool trailer_present; 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci hdr = (struct ath10k_htc_hdr *)skb->data; 44962306a36Sopenharmony_ci skb_pull(skb, sizeof(*hdr)); 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci eid = hdr->eid; 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci if (eid >= ATH10K_HTC_EP_COUNT) { 45462306a36Sopenharmony_ci ath10k_warn(ar, "HTC Rx: invalid eid %d\n", eid); 45562306a36Sopenharmony_ci ath10k_dbg_dump(ar, ATH10K_DBG_HTC, "htc bad header", "", 45662306a36Sopenharmony_ci hdr, sizeof(*hdr)); 45762306a36Sopenharmony_ci goto out; 45862306a36Sopenharmony_ci } 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci ep = &htc->endpoint[eid]; 46162306a36Sopenharmony_ci if (ep->service_id == ATH10K_HTC_SVC_ID_UNUSED) { 46262306a36Sopenharmony_ci ath10k_warn(ar, "htc rx endpoint %d is not connected\n", eid); 46362306a36Sopenharmony_ci goto out; 46462306a36Sopenharmony_ci } 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_ci payload_len = __le16_to_cpu(hdr->len); 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci if (payload_len + sizeof(*hdr) > ATH10K_HTC_MAX_LEN) { 46962306a36Sopenharmony_ci ath10k_warn(ar, "HTC rx frame too long, len: %zu\n", 47062306a36Sopenharmony_ci payload_len + sizeof(*hdr)); 47162306a36Sopenharmony_ci ath10k_dbg_dump(ar, ATH10K_DBG_HTC, "htc bad rx pkt len", "", 47262306a36Sopenharmony_ci hdr, sizeof(*hdr)); 47362306a36Sopenharmony_ci goto out; 47462306a36Sopenharmony_ci } 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_ci if (skb->len < payload_len) { 47762306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_HTC, 47862306a36Sopenharmony_ci "HTC Rx: insufficient length, got %d, expected %d\n", 47962306a36Sopenharmony_ci skb->len, payload_len); 48062306a36Sopenharmony_ci ath10k_dbg_dump(ar, ATH10K_DBG_HTC, "htc bad rx pkt len", 48162306a36Sopenharmony_ci "", hdr, sizeof(*hdr)); 48262306a36Sopenharmony_ci goto out; 48362306a36Sopenharmony_ci } 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci /* get flags to check for trailer */ 48662306a36Sopenharmony_ci trailer_present = hdr->flags & ATH10K_HTC_FLAG_TRAILER_PRESENT; 48762306a36Sopenharmony_ci if (trailer_present) { 48862306a36Sopenharmony_ci u8 *trailer; 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ci trailer_len = hdr->trailer_len; 49162306a36Sopenharmony_ci min_len = sizeof(struct ath10k_ath10k_htc_record_hdr); 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci if ((trailer_len < min_len) || 49462306a36Sopenharmony_ci (trailer_len > payload_len)) { 49562306a36Sopenharmony_ci ath10k_warn(ar, "Invalid trailer length: %d\n", 49662306a36Sopenharmony_ci trailer_len); 49762306a36Sopenharmony_ci goto out; 49862306a36Sopenharmony_ci } 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_ci trailer = (u8 *)hdr; 50162306a36Sopenharmony_ci trailer += sizeof(*hdr); 50262306a36Sopenharmony_ci trailer += payload_len; 50362306a36Sopenharmony_ci trailer -= trailer_len; 50462306a36Sopenharmony_ci status = ath10k_htc_process_trailer(htc, trailer, 50562306a36Sopenharmony_ci trailer_len, hdr->eid, 50662306a36Sopenharmony_ci NULL, NULL); 50762306a36Sopenharmony_ci if (status) 50862306a36Sopenharmony_ci goto out; 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci skb_trim(skb, skb->len - trailer_len); 51162306a36Sopenharmony_ci } 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci if (((int)payload_len - (int)trailer_len) <= 0) 51462306a36Sopenharmony_ci /* zero length packet with trailer data, just drop these */ 51562306a36Sopenharmony_ci goto out; 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_HTC, "htc rx completion ep %d skb %pK\n", 51862306a36Sopenharmony_ci eid, skb); 51962306a36Sopenharmony_ci ep->ep_ops.ep_rx_complete(ar, skb); 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci /* skb is now owned by the rx completion handler */ 52262306a36Sopenharmony_ci skb = NULL; 52362306a36Sopenharmony_ciout: 52462306a36Sopenharmony_ci kfree_skb(skb); 52562306a36Sopenharmony_ci} 52662306a36Sopenharmony_ciEXPORT_SYMBOL(ath10k_htc_rx_completion_handler); 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_cistatic void ath10k_htc_control_rx_complete(struct ath10k *ar, 52962306a36Sopenharmony_ci struct sk_buff *skb) 53062306a36Sopenharmony_ci{ 53162306a36Sopenharmony_ci struct ath10k_htc *htc = &ar->htc; 53262306a36Sopenharmony_ci struct ath10k_htc_msg *msg = (struct ath10k_htc_msg *)skb->data; 53362306a36Sopenharmony_ci 53462306a36Sopenharmony_ci switch (__le16_to_cpu(msg->hdr.message_id)) { 53562306a36Sopenharmony_ci case ATH10K_HTC_MSG_READY_ID: 53662306a36Sopenharmony_ci case ATH10K_HTC_MSG_CONNECT_SERVICE_RESP_ID: 53762306a36Sopenharmony_ci /* handle HTC control message */ 53862306a36Sopenharmony_ci if (completion_done(&htc->ctl_resp)) { 53962306a36Sopenharmony_ci /* this is a fatal error, target should not be 54062306a36Sopenharmony_ci * sending unsolicited messages on the ep 0 54162306a36Sopenharmony_ci */ 54262306a36Sopenharmony_ci ath10k_warn(ar, "HTC rx ctrl still processing\n"); 54362306a36Sopenharmony_ci complete(&htc->ctl_resp); 54462306a36Sopenharmony_ci goto out; 54562306a36Sopenharmony_ci } 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci htc->control_resp_len = 54862306a36Sopenharmony_ci min_t(int, skb->len, 54962306a36Sopenharmony_ci ATH10K_HTC_MAX_CTRL_MSG_LEN); 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci memcpy(htc->control_resp_buffer, skb->data, 55262306a36Sopenharmony_ci htc->control_resp_len); 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_ci complete(&htc->ctl_resp); 55562306a36Sopenharmony_ci break; 55662306a36Sopenharmony_ci case ATH10K_HTC_MSG_SEND_SUSPEND_COMPLETE: 55762306a36Sopenharmony_ci htc->htc_ops.target_send_suspend_complete(ar); 55862306a36Sopenharmony_ci break; 55962306a36Sopenharmony_ci default: 56062306a36Sopenharmony_ci ath10k_warn(ar, "ignoring unsolicited htc ep0 event\n"); 56162306a36Sopenharmony_ci break; 56262306a36Sopenharmony_ci } 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ciout: 56562306a36Sopenharmony_ci kfree_skb(skb); 56662306a36Sopenharmony_ci} 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ci/***************/ 56962306a36Sopenharmony_ci/* Init/Deinit */ 57062306a36Sopenharmony_ci/***************/ 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_cistatic const char *htc_service_name(enum ath10k_htc_svc_id id) 57362306a36Sopenharmony_ci{ 57462306a36Sopenharmony_ci switch (id) { 57562306a36Sopenharmony_ci case ATH10K_HTC_SVC_ID_RESERVED: 57662306a36Sopenharmony_ci return "Reserved"; 57762306a36Sopenharmony_ci case ATH10K_HTC_SVC_ID_RSVD_CTRL: 57862306a36Sopenharmony_ci return "Control"; 57962306a36Sopenharmony_ci case ATH10K_HTC_SVC_ID_WMI_CONTROL: 58062306a36Sopenharmony_ci return "WMI"; 58162306a36Sopenharmony_ci case ATH10K_HTC_SVC_ID_WMI_DATA_BE: 58262306a36Sopenharmony_ci return "DATA BE"; 58362306a36Sopenharmony_ci case ATH10K_HTC_SVC_ID_WMI_DATA_BK: 58462306a36Sopenharmony_ci return "DATA BK"; 58562306a36Sopenharmony_ci case ATH10K_HTC_SVC_ID_WMI_DATA_VI: 58662306a36Sopenharmony_ci return "DATA VI"; 58762306a36Sopenharmony_ci case ATH10K_HTC_SVC_ID_WMI_DATA_VO: 58862306a36Sopenharmony_ci return "DATA VO"; 58962306a36Sopenharmony_ci case ATH10K_HTC_SVC_ID_NMI_CONTROL: 59062306a36Sopenharmony_ci return "NMI Control"; 59162306a36Sopenharmony_ci case ATH10K_HTC_SVC_ID_NMI_DATA: 59262306a36Sopenharmony_ci return "NMI Data"; 59362306a36Sopenharmony_ci case ATH10K_HTC_SVC_ID_HTT_DATA_MSG: 59462306a36Sopenharmony_ci return "HTT Data"; 59562306a36Sopenharmony_ci case ATH10K_HTC_SVC_ID_HTT_DATA2_MSG: 59662306a36Sopenharmony_ci return "HTT Data"; 59762306a36Sopenharmony_ci case ATH10K_HTC_SVC_ID_HTT_DATA3_MSG: 59862306a36Sopenharmony_ci return "HTT Data"; 59962306a36Sopenharmony_ci case ATH10K_HTC_SVC_ID_TEST_RAW_STREAMS: 60062306a36Sopenharmony_ci return "RAW"; 60162306a36Sopenharmony_ci case ATH10K_HTC_SVC_ID_HTT_LOG_MSG: 60262306a36Sopenharmony_ci return "PKTLOG"; 60362306a36Sopenharmony_ci } 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci return "Unknown"; 60662306a36Sopenharmony_ci} 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_cistatic void ath10k_htc_reset_endpoint_states(struct ath10k_htc *htc) 60962306a36Sopenharmony_ci{ 61062306a36Sopenharmony_ci struct ath10k_htc_ep *ep; 61162306a36Sopenharmony_ci int i; 61262306a36Sopenharmony_ci 61362306a36Sopenharmony_ci for (i = ATH10K_HTC_EP_0; i < ATH10K_HTC_EP_COUNT; i++) { 61462306a36Sopenharmony_ci ep = &htc->endpoint[i]; 61562306a36Sopenharmony_ci ep->service_id = ATH10K_HTC_SVC_ID_UNUSED; 61662306a36Sopenharmony_ci ep->max_ep_message_len = 0; 61762306a36Sopenharmony_ci ep->max_tx_queue_depth = 0; 61862306a36Sopenharmony_ci ep->eid = i; 61962306a36Sopenharmony_ci ep->htc = htc; 62062306a36Sopenharmony_ci ep->tx_credit_flow_enabled = true; 62162306a36Sopenharmony_ci } 62262306a36Sopenharmony_ci} 62362306a36Sopenharmony_ci 62462306a36Sopenharmony_cistatic u8 ath10k_htc_get_credit_allocation(struct ath10k_htc *htc, 62562306a36Sopenharmony_ci u16 service_id) 62662306a36Sopenharmony_ci{ 62762306a36Sopenharmony_ci u8 allocation = 0; 62862306a36Sopenharmony_ci 62962306a36Sopenharmony_ci /* The WMI control service is the only service with flow control. 63062306a36Sopenharmony_ci * Let it have all transmit credits. 63162306a36Sopenharmony_ci */ 63262306a36Sopenharmony_ci if (service_id == ATH10K_HTC_SVC_ID_WMI_CONTROL) 63362306a36Sopenharmony_ci allocation = htc->total_transmit_credits; 63462306a36Sopenharmony_ci 63562306a36Sopenharmony_ci return allocation; 63662306a36Sopenharmony_ci} 63762306a36Sopenharmony_ci 63862306a36Sopenharmony_cistatic int ath10k_htc_send_bundle(struct ath10k_htc_ep *ep, 63962306a36Sopenharmony_ci struct sk_buff *bundle_skb, 64062306a36Sopenharmony_ci struct sk_buff_head *tx_save_head) 64162306a36Sopenharmony_ci{ 64262306a36Sopenharmony_ci struct ath10k_hif_sg_item sg_item; 64362306a36Sopenharmony_ci struct ath10k_htc *htc = ep->htc; 64462306a36Sopenharmony_ci struct ath10k *ar = htc->ar; 64562306a36Sopenharmony_ci struct sk_buff *skb; 64662306a36Sopenharmony_ci int ret, cn = 0; 64762306a36Sopenharmony_ci unsigned int skb_len; 64862306a36Sopenharmony_ci 64962306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_HTC, "bundle skb len %d\n", bundle_skb->len); 65062306a36Sopenharmony_ci skb_len = bundle_skb->len; 65162306a36Sopenharmony_ci ret = ath10k_htc_consume_credit(ep, skb_len, true); 65262306a36Sopenharmony_ci 65362306a36Sopenharmony_ci if (!ret) { 65462306a36Sopenharmony_ci sg_item.transfer_id = ep->eid; 65562306a36Sopenharmony_ci sg_item.transfer_context = bundle_skb; 65662306a36Sopenharmony_ci sg_item.vaddr = bundle_skb->data; 65762306a36Sopenharmony_ci sg_item.len = bundle_skb->len; 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ci ret = ath10k_hif_tx_sg(htc->ar, ep->ul_pipe_id, &sg_item, 1); 66062306a36Sopenharmony_ci if (ret) 66162306a36Sopenharmony_ci ath10k_htc_release_credit(ep, skb_len); 66262306a36Sopenharmony_ci } 66362306a36Sopenharmony_ci 66462306a36Sopenharmony_ci if (ret) 66562306a36Sopenharmony_ci dev_kfree_skb_any(bundle_skb); 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_ci for (cn = 0; (skb = skb_dequeue_tail(tx_save_head)); cn++) { 66862306a36Sopenharmony_ci if (ret) { 66962306a36Sopenharmony_ci skb_pull(skb, sizeof(struct ath10k_htc_hdr)); 67062306a36Sopenharmony_ci skb_queue_head(&ep->tx_req_head, skb); 67162306a36Sopenharmony_ci } else { 67262306a36Sopenharmony_ci skb_queue_tail(&ep->tx_complete_head, skb); 67362306a36Sopenharmony_ci } 67462306a36Sopenharmony_ci } 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_ci if (!ret) 67762306a36Sopenharmony_ci queue_work(ar->workqueue_tx_complete, &ar->tx_complete_work); 67862306a36Sopenharmony_ci 67962306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_HTC, 68062306a36Sopenharmony_ci "bundle tx status %d eid %d req count %d count %d len %d\n", 68162306a36Sopenharmony_ci ret, ep->eid, skb_queue_len(&ep->tx_req_head), cn, skb_len); 68262306a36Sopenharmony_ci return ret; 68362306a36Sopenharmony_ci} 68462306a36Sopenharmony_ci 68562306a36Sopenharmony_cistatic void ath10k_htc_send_one_skb(struct ath10k_htc_ep *ep, struct sk_buff *skb) 68662306a36Sopenharmony_ci{ 68762306a36Sopenharmony_ci struct ath10k_htc *htc = ep->htc; 68862306a36Sopenharmony_ci struct ath10k *ar = htc->ar; 68962306a36Sopenharmony_ci int ret; 69062306a36Sopenharmony_ci 69162306a36Sopenharmony_ci ret = ath10k_htc_send(htc, ep->eid, skb); 69262306a36Sopenharmony_ci 69362306a36Sopenharmony_ci if (ret) 69462306a36Sopenharmony_ci skb_queue_head(&ep->tx_req_head, skb); 69562306a36Sopenharmony_ci 69662306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_HTC, "tx one status %d eid %d len %d pending count %d\n", 69762306a36Sopenharmony_ci ret, ep->eid, skb->len, skb_queue_len(&ep->tx_req_head)); 69862306a36Sopenharmony_ci} 69962306a36Sopenharmony_ci 70062306a36Sopenharmony_cistatic int ath10k_htc_send_bundle_skbs(struct ath10k_htc_ep *ep) 70162306a36Sopenharmony_ci{ 70262306a36Sopenharmony_ci struct ath10k_htc *htc = ep->htc; 70362306a36Sopenharmony_ci struct sk_buff *bundle_skb, *skb; 70462306a36Sopenharmony_ci struct sk_buff_head tx_save_head; 70562306a36Sopenharmony_ci struct ath10k_htc_hdr *hdr; 70662306a36Sopenharmony_ci u8 *bundle_buf; 70762306a36Sopenharmony_ci int ret = 0, credit_pad, credit_remainder, trans_len, bundles_left = 0; 70862306a36Sopenharmony_ci 70962306a36Sopenharmony_ci if (htc->ar->state == ATH10K_STATE_WEDGED) 71062306a36Sopenharmony_ci return -ECOMM; 71162306a36Sopenharmony_ci 71262306a36Sopenharmony_ci if (ep->tx_credit_flow_enabled && 71362306a36Sopenharmony_ci ep->tx_credits < ATH10K_MIN_CREDIT_PER_HTC_TX_BUNDLE) 71462306a36Sopenharmony_ci return 0; 71562306a36Sopenharmony_ci 71662306a36Sopenharmony_ci bundles_left = ATH10K_MAX_MSG_PER_HTC_TX_BUNDLE * ep->tx_credit_size; 71762306a36Sopenharmony_ci bundle_skb = dev_alloc_skb(bundles_left); 71862306a36Sopenharmony_ci 71962306a36Sopenharmony_ci if (!bundle_skb) 72062306a36Sopenharmony_ci return -ENOMEM; 72162306a36Sopenharmony_ci 72262306a36Sopenharmony_ci bundle_buf = bundle_skb->data; 72362306a36Sopenharmony_ci skb_queue_head_init(&tx_save_head); 72462306a36Sopenharmony_ci 72562306a36Sopenharmony_ci while (true) { 72662306a36Sopenharmony_ci skb = skb_dequeue(&ep->tx_req_head); 72762306a36Sopenharmony_ci if (!skb) 72862306a36Sopenharmony_ci break; 72962306a36Sopenharmony_ci 73062306a36Sopenharmony_ci credit_pad = 0; 73162306a36Sopenharmony_ci trans_len = skb->len + sizeof(*hdr); 73262306a36Sopenharmony_ci credit_remainder = trans_len % ep->tx_credit_size; 73362306a36Sopenharmony_ci 73462306a36Sopenharmony_ci if (credit_remainder != 0) { 73562306a36Sopenharmony_ci credit_pad = ep->tx_credit_size - credit_remainder; 73662306a36Sopenharmony_ci trans_len += credit_pad; 73762306a36Sopenharmony_ci } 73862306a36Sopenharmony_ci 73962306a36Sopenharmony_ci ret = ath10k_htc_consume_credit(ep, 74062306a36Sopenharmony_ci bundle_buf + trans_len - bundle_skb->data, 74162306a36Sopenharmony_ci false); 74262306a36Sopenharmony_ci if (ret) { 74362306a36Sopenharmony_ci skb_queue_head(&ep->tx_req_head, skb); 74462306a36Sopenharmony_ci break; 74562306a36Sopenharmony_ci } 74662306a36Sopenharmony_ci 74762306a36Sopenharmony_ci if (bundles_left < trans_len) { 74862306a36Sopenharmony_ci bundle_skb->len = bundle_buf - bundle_skb->data; 74962306a36Sopenharmony_ci ret = ath10k_htc_send_bundle(ep, bundle_skb, &tx_save_head); 75062306a36Sopenharmony_ci 75162306a36Sopenharmony_ci if (ret) { 75262306a36Sopenharmony_ci skb_queue_head(&ep->tx_req_head, skb); 75362306a36Sopenharmony_ci return ret; 75462306a36Sopenharmony_ci } 75562306a36Sopenharmony_ci 75662306a36Sopenharmony_ci if (skb_queue_len(&ep->tx_req_head) == 0) { 75762306a36Sopenharmony_ci ath10k_htc_send_one_skb(ep, skb); 75862306a36Sopenharmony_ci return ret; 75962306a36Sopenharmony_ci } 76062306a36Sopenharmony_ci 76162306a36Sopenharmony_ci if (ep->tx_credit_flow_enabled && 76262306a36Sopenharmony_ci ep->tx_credits < ATH10K_MIN_CREDIT_PER_HTC_TX_BUNDLE) { 76362306a36Sopenharmony_ci skb_queue_head(&ep->tx_req_head, skb); 76462306a36Sopenharmony_ci return 0; 76562306a36Sopenharmony_ci } 76662306a36Sopenharmony_ci 76762306a36Sopenharmony_ci bundles_left = 76862306a36Sopenharmony_ci ATH10K_MAX_MSG_PER_HTC_TX_BUNDLE * ep->tx_credit_size; 76962306a36Sopenharmony_ci bundle_skb = dev_alloc_skb(bundles_left); 77062306a36Sopenharmony_ci 77162306a36Sopenharmony_ci if (!bundle_skb) { 77262306a36Sopenharmony_ci skb_queue_head(&ep->tx_req_head, skb); 77362306a36Sopenharmony_ci return -ENOMEM; 77462306a36Sopenharmony_ci } 77562306a36Sopenharmony_ci bundle_buf = bundle_skb->data; 77662306a36Sopenharmony_ci skb_queue_head_init(&tx_save_head); 77762306a36Sopenharmony_ci } 77862306a36Sopenharmony_ci 77962306a36Sopenharmony_ci skb_push(skb, sizeof(struct ath10k_htc_hdr)); 78062306a36Sopenharmony_ci ath10k_htc_prepare_tx_skb(ep, skb); 78162306a36Sopenharmony_ci 78262306a36Sopenharmony_ci memcpy(bundle_buf, skb->data, skb->len); 78362306a36Sopenharmony_ci hdr = (struct ath10k_htc_hdr *)bundle_buf; 78462306a36Sopenharmony_ci hdr->flags |= ATH10K_HTC_FLAG_SEND_BUNDLE; 78562306a36Sopenharmony_ci hdr->pad_len = __cpu_to_le16(credit_pad); 78662306a36Sopenharmony_ci bundle_buf += trans_len; 78762306a36Sopenharmony_ci bundles_left -= trans_len; 78862306a36Sopenharmony_ci skb_queue_tail(&tx_save_head, skb); 78962306a36Sopenharmony_ci } 79062306a36Sopenharmony_ci 79162306a36Sopenharmony_ci if (bundle_buf != bundle_skb->data) { 79262306a36Sopenharmony_ci bundle_skb->len = bundle_buf - bundle_skb->data; 79362306a36Sopenharmony_ci ret = ath10k_htc_send_bundle(ep, bundle_skb, &tx_save_head); 79462306a36Sopenharmony_ci } else { 79562306a36Sopenharmony_ci dev_kfree_skb_any(bundle_skb); 79662306a36Sopenharmony_ci } 79762306a36Sopenharmony_ci 79862306a36Sopenharmony_ci return ret; 79962306a36Sopenharmony_ci} 80062306a36Sopenharmony_ci 80162306a36Sopenharmony_cistatic void ath10k_htc_bundle_tx_work(struct work_struct *work) 80262306a36Sopenharmony_ci{ 80362306a36Sopenharmony_ci struct ath10k *ar = container_of(work, struct ath10k, bundle_tx_work); 80462306a36Sopenharmony_ci struct ath10k_htc_ep *ep; 80562306a36Sopenharmony_ci struct sk_buff *skb; 80662306a36Sopenharmony_ci int i; 80762306a36Sopenharmony_ci 80862306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(ar->htc.endpoint); i++) { 80962306a36Sopenharmony_ci ep = &ar->htc.endpoint[i]; 81062306a36Sopenharmony_ci 81162306a36Sopenharmony_ci if (!ep->bundle_tx) 81262306a36Sopenharmony_ci continue; 81362306a36Sopenharmony_ci 81462306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_HTC, "bundle tx work eid %d count %d\n", 81562306a36Sopenharmony_ci ep->eid, skb_queue_len(&ep->tx_req_head)); 81662306a36Sopenharmony_ci 81762306a36Sopenharmony_ci if (skb_queue_len(&ep->tx_req_head) >= 81862306a36Sopenharmony_ci ATH10K_MIN_MSG_PER_HTC_TX_BUNDLE) { 81962306a36Sopenharmony_ci ath10k_htc_send_bundle_skbs(ep); 82062306a36Sopenharmony_ci } else { 82162306a36Sopenharmony_ci skb = skb_dequeue(&ep->tx_req_head); 82262306a36Sopenharmony_ci 82362306a36Sopenharmony_ci if (!skb) 82462306a36Sopenharmony_ci continue; 82562306a36Sopenharmony_ci ath10k_htc_send_one_skb(ep, skb); 82662306a36Sopenharmony_ci } 82762306a36Sopenharmony_ci } 82862306a36Sopenharmony_ci} 82962306a36Sopenharmony_ci 83062306a36Sopenharmony_cistatic void ath10k_htc_tx_complete_work(struct work_struct *work) 83162306a36Sopenharmony_ci{ 83262306a36Sopenharmony_ci struct ath10k *ar = container_of(work, struct ath10k, tx_complete_work); 83362306a36Sopenharmony_ci struct ath10k_htc_ep *ep; 83462306a36Sopenharmony_ci enum ath10k_htc_ep_id eid; 83562306a36Sopenharmony_ci struct sk_buff *skb; 83662306a36Sopenharmony_ci int i; 83762306a36Sopenharmony_ci 83862306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(ar->htc.endpoint); i++) { 83962306a36Sopenharmony_ci ep = &ar->htc.endpoint[i]; 84062306a36Sopenharmony_ci eid = ep->eid; 84162306a36Sopenharmony_ci if (ep->bundle_tx && eid == ar->htt.eid) { 84262306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_HTC, "bundle tx complete eid %d pending complete count%d\n", 84362306a36Sopenharmony_ci ep->eid, skb_queue_len(&ep->tx_complete_head)); 84462306a36Sopenharmony_ci 84562306a36Sopenharmony_ci while (true) { 84662306a36Sopenharmony_ci skb = skb_dequeue(&ep->tx_complete_head); 84762306a36Sopenharmony_ci if (!skb) 84862306a36Sopenharmony_ci break; 84962306a36Sopenharmony_ci ath10k_htc_notify_tx_completion(ep, skb); 85062306a36Sopenharmony_ci } 85162306a36Sopenharmony_ci } 85262306a36Sopenharmony_ci } 85362306a36Sopenharmony_ci} 85462306a36Sopenharmony_ci 85562306a36Sopenharmony_ciint ath10k_htc_send_hl(struct ath10k_htc *htc, 85662306a36Sopenharmony_ci enum ath10k_htc_ep_id eid, 85762306a36Sopenharmony_ci struct sk_buff *skb) 85862306a36Sopenharmony_ci{ 85962306a36Sopenharmony_ci struct ath10k_htc_ep *ep = &htc->endpoint[eid]; 86062306a36Sopenharmony_ci struct ath10k *ar = htc->ar; 86162306a36Sopenharmony_ci 86262306a36Sopenharmony_ci if (sizeof(struct ath10k_htc_hdr) + skb->len > ep->tx_credit_size) { 86362306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_HTC, "tx exceed max len %d\n", skb->len); 86462306a36Sopenharmony_ci return -ENOMEM; 86562306a36Sopenharmony_ci } 86662306a36Sopenharmony_ci 86762306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_HTC, "htc send hl eid %d bundle %d tx count %d len %d\n", 86862306a36Sopenharmony_ci eid, ep->bundle_tx, skb_queue_len(&ep->tx_req_head), skb->len); 86962306a36Sopenharmony_ci 87062306a36Sopenharmony_ci if (ep->bundle_tx) { 87162306a36Sopenharmony_ci skb_queue_tail(&ep->tx_req_head, skb); 87262306a36Sopenharmony_ci queue_work(ar->workqueue, &ar->bundle_tx_work); 87362306a36Sopenharmony_ci return 0; 87462306a36Sopenharmony_ci } else { 87562306a36Sopenharmony_ci return ath10k_htc_send(htc, eid, skb); 87662306a36Sopenharmony_ci } 87762306a36Sopenharmony_ci} 87862306a36Sopenharmony_ci 87962306a36Sopenharmony_civoid ath10k_htc_setup_tx_req(struct ath10k_htc_ep *ep) 88062306a36Sopenharmony_ci{ 88162306a36Sopenharmony_ci if (ep->htc->max_msgs_per_htc_bundle >= ATH10K_MIN_MSG_PER_HTC_TX_BUNDLE && 88262306a36Sopenharmony_ci !ep->bundle_tx) { 88362306a36Sopenharmony_ci ep->bundle_tx = true; 88462306a36Sopenharmony_ci skb_queue_head_init(&ep->tx_req_head); 88562306a36Sopenharmony_ci skb_queue_head_init(&ep->tx_complete_head); 88662306a36Sopenharmony_ci } 88762306a36Sopenharmony_ci} 88862306a36Sopenharmony_ci 88962306a36Sopenharmony_civoid ath10k_htc_stop_hl(struct ath10k *ar) 89062306a36Sopenharmony_ci{ 89162306a36Sopenharmony_ci struct ath10k_htc_ep *ep; 89262306a36Sopenharmony_ci int i; 89362306a36Sopenharmony_ci 89462306a36Sopenharmony_ci cancel_work_sync(&ar->bundle_tx_work); 89562306a36Sopenharmony_ci cancel_work_sync(&ar->tx_complete_work); 89662306a36Sopenharmony_ci 89762306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(ar->htc.endpoint); i++) { 89862306a36Sopenharmony_ci ep = &ar->htc.endpoint[i]; 89962306a36Sopenharmony_ci 90062306a36Sopenharmony_ci if (!ep->bundle_tx) 90162306a36Sopenharmony_ci continue; 90262306a36Sopenharmony_ci 90362306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_HTC, "stop tx work eid %d count %d\n", 90462306a36Sopenharmony_ci ep->eid, skb_queue_len(&ep->tx_req_head)); 90562306a36Sopenharmony_ci 90662306a36Sopenharmony_ci skb_queue_purge(&ep->tx_req_head); 90762306a36Sopenharmony_ci } 90862306a36Sopenharmony_ci} 90962306a36Sopenharmony_ci 91062306a36Sopenharmony_ciint ath10k_htc_wait_target(struct ath10k_htc *htc) 91162306a36Sopenharmony_ci{ 91262306a36Sopenharmony_ci struct ath10k *ar = htc->ar; 91362306a36Sopenharmony_ci int i, status = 0; 91462306a36Sopenharmony_ci unsigned long time_left; 91562306a36Sopenharmony_ci struct ath10k_htc_msg *msg; 91662306a36Sopenharmony_ci u16 message_id; 91762306a36Sopenharmony_ci 91862306a36Sopenharmony_ci time_left = wait_for_completion_timeout(&htc->ctl_resp, 91962306a36Sopenharmony_ci ATH10K_HTC_WAIT_TIMEOUT_HZ); 92062306a36Sopenharmony_ci if (!time_left) { 92162306a36Sopenharmony_ci /* Workaround: In some cases the PCI HIF doesn't 92262306a36Sopenharmony_ci * receive interrupt for the control response message 92362306a36Sopenharmony_ci * even if the buffer was completed. It is suspected 92462306a36Sopenharmony_ci * iomap writes unmasking PCI CE irqs aren't propagated 92562306a36Sopenharmony_ci * properly in KVM PCI-passthrough sometimes. 92662306a36Sopenharmony_ci */ 92762306a36Sopenharmony_ci ath10k_warn(ar, "failed to receive control response completion, polling..\n"); 92862306a36Sopenharmony_ci 92962306a36Sopenharmony_ci for (i = 0; i < CE_COUNT; i++) 93062306a36Sopenharmony_ci ath10k_hif_send_complete_check(htc->ar, i, 1); 93162306a36Sopenharmony_ci 93262306a36Sopenharmony_ci time_left = 93362306a36Sopenharmony_ci wait_for_completion_timeout(&htc->ctl_resp, 93462306a36Sopenharmony_ci ATH10K_HTC_WAIT_TIMEOUT_HZ); 93562306a36Sopenharmony_ci 93662306a36Sopenharmony_ci if (!time_left) 93762306a36Sopenharmony_ci status = -ETIMEDOUT; 93862306a36Sopenharmony_ci } 93962306a36Sopenharmony_ci 94062306a36Sopenharmony_ci if (status < 0) { 94162306a36Sopenharmony_ci ath10k_err(ar, "ctl_resp never came in (%d)\n", status); 94262306a36Sopenharmony_ci return status; 94362306a36Sopenharmony_ci } 94462306a36Sopenharmony_ci 94562306a36Sopenharmony_ci if (htc->control_resp_len < sizeof(msg->hdr) + sizeof(msg->ready)) { 94662306a36Sopenharmony_ci ath10k_err(ar, "Invalid HTC ready msg len:%d\n", 94762306a36Sopenharmony_ci htc->control_resp_len); 94862306a36Sopenharmony_ci return -ECOMM; 94962306a36Sopenharmony_ci } 95062306a36Sopenharmony_ci 95162306a36Sopenharmony_ci msg = (struct ath10k_htc_msg *)htc->control_resp_buffer; 95262306a36Sopenharmony_ci message_id = __le16_to_cpu(msg->hdr.message_id); 95362306a36Sopenharmony_ci 95462306a36Sopenharmony_ci if (message_id != ATH10K_HTC_MSG_READY_ID) { 95562306a36Sopenharmony_ci ath10k_err(ar, "Invalid HTC ready msg: 0x%x\n", message_id); 95662306a36Sopenharmony_ci return -ECOMM; 95762306a36Sopenharmony_ci } 95862306a36Sopenharmony_ci 95962306a36Sopenharmony_ci if (ar->hw_params.use_fw_tx_credits) 96062306a36Sopenharmony_ci htc->total_transmit_credits = __le16_to_cpu(msg->ready.credit_count); 96162306a36Sopenharmony_ci else 96262306a36Sopenharmony_ci htc->total_transmit_credits = 1; 96362306a36Sopenharmony_ci 96462306a36Sopenharmony_ci htc->target_credit_size = __le16_to_cpu(msg->ready.credit_size); 96562306a36Sopenharmony_ci 96662306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_HTC, 96762306a36Sopenharmony_ci "Target ready! transmit resources: %d size:%d actual credits:%d\n", 96862306a36Sopenharmony_ci htc->total_transmit_credits, 96962306a36Sopenharmony_ci htc->target_credit_size, 97062306a36Sopenharmony_ci msg->ready.credit_count); 97162306a36Sopenharmony_ci 97262306a36Sopenharmony_ci if ((htc->total_transmit_credits == 0) || 97362306a36Sopenharmony_ci (htc->target_credit_size == 0)) { 97462306a36Sopenharmony_ci ath10k_err(ar, "Invalid credit size received\n"); 97562306a36Sopenharmony_ci return -ECOMM; 97662306a36Sopenharmony_ci } 97762306a36Sopenharmony_ci 97862306a36Sopenharmony_ci /* The only way to determine if the ready message is an extended 97962306a36Sopenharmony_ci * message is from the size. 98062306a36Sopenharmony_ci */ 98162306a36Sopenharmony_ci if (htc->control_resp_len >= 98262306a36Sopenharmony_ci sizeof(msg->hdr) + sizeof(msg->ready_ext)) { 98362306a36Sopenharmony_ci htc->alt_data_credit_size = 98462306a36Sopenharmony_ci __le16_to_cpu(msg->ready_ext.reserved) & 98562306a36Sopenharmony_ci ATH10K_HTC_MSG_READY_EXT_ALT_DATA_MASK; 98662306a36Sopenharmony_ci htc->max_msgs_per_htc_bundle = 98762306a36Sopenharmony_ci min_t(u8, msg->ready_ext.max_msgs_per_htc_bundle, 98862306a36Sopenharmony_ci HTC_HOST_MAX_MSG_PER_RX_BUNDLE); 98962306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_HTC, 99062306a36Sopenharmony_ci "Extended ready message RX bundle size %d alt size %d\n", 99162306a36Sopenharmony_ci htc->max_msgs_per_htc_bundle, 99262306a36Sopenharmony_ci htc->alt_data_credit_size); 99362306a36Sopenharmony_ci } 99462306a36Sopenharmony_ci 99562306a36Sopenharmony_ci INIT_WORK(&ar->bundle_tx_work, ath10k_htc_bundle_tx_work); 99662306a36Sopenharmony_ci INIT_WORK(&ar->tx_complete_work, ath10k_htc_tx_complete_work); 99762306a36Sopenharmony_ci 99862306a36Sopenharmony_ci return 0; 99962306a36Sopenharmony_ci} 100062306a36Sopenharmony_ci 100162306a36Sopenharmony_civoid ath10k_htc_change_tx_credit_flow(struct ath10k_htc *htc, 100262306a36Sopenharmony_ci enum ath10k_htc_ep_id eid, 100362306a36Sopenharmony_ci bool enable) 100462306a36Sopenharmony_ci{ 100562306a36Sopenharmony_ci struct ath10k *ar = htc->ar; 100662306a36Sopenharmony_ci struct ath10k_htc_ep *ep = &ar->htc.endpoint[eid]; 100762306a36Sopenharmony_ci 100862306a36Sopenharmony_ci ep->tx_credit_flow_enabled = enable; 100962306a36Sopenharmony_ci} 101062306a36Sopenharmony_ci 101162306a36Sopenharmony_ciint ath10k_htc_connect_service(struct ath10k_htc *htc, 101262306a36Sopenharmony_ci struct ath10k_htc_svc_conn_req *conn_req, 101362306a36Sopenharmony_ci struct ath10k_htc_svc_conn_resp *conn_resp) 101462306a36Sopenharmony_ci{ 101562306a36Sopenharmony_ci struct ath10k *ar = htc->ar; 101662306a36Sopenharmony_ci struct ath10k_htc_msg *msg; 101762306a36Sopenharmony_ci struct ath10k_htc_conn_svc *req_msg; 101862306a36Sopenharmony_ci struct ath10k_htc_conn_svc_response resp_msg_dummy; 101962306a36Sopenharmony_ci struct ath10k_htc_conn_svc_response *resp_msg = &resp_msg_dummy; 102062306a36Sopenharmony_ci enum ath10k_htc_ep_id assigned_eid = ATH10K_HTC_EP_COUNT; 102162306a36Sopenharmony_ci struct ath10k_htc_ep *ep; 102262306a36Sopenharmony_ci struct sk_buff *skb; 102362306a36Sopenharmony_ci unsigned int max_msg_size = 0; 102462306a36Sopenharmony_ci int length, status; 102562306a36Sopenharmony_ci unsigned long time_left; 102662306a36Sopenharmony_ci bool disable_credit_flow_ctrl = false; 102762306a36Sopenharmony_ci u16 message_id, service_id, flags = 0; 102862306a36Sopenharmony_ci u8 tx_alloc = 0; 102962306a36Sopenharmony_ci 103062306a36Sopenharmony_ci /* special case for HTC pseudo control service */ 103162306a36Sopenharmony_ci if (conn_req->service_id == ATH10K_HTC_SVC_ID_RSVD_CTRL) { 103262306a36Sopenharmony_ci disable_credit_flow_ctrl = true; 103362306a36Sopenharmony_ci assigned_eid = ATH10K_HTC_EP_0; 103462306a36Sopenharmony_ci max_msg_size = ATH10K_HTC_MAX_CTRL_MSG_LEN; 103562306a36Sopenharmony_ci memset(&resp_msg_dummy, 0, sizeof(resp_msg_dummy)); 103662306a36Sopenharmony_ci goto setup; 103762306a36Sopenharmony_ci } 103862306a36Sopenharmony_ci 103962306a36Sopenharmony_ci tx_alloc = ath10k_htc_get_credit_allocation(htc, 104062306a36Sopenharmony_ci conn_req->service_id); 104162306a36Sopenharmony_ci if (!tx_alloc) 104262306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_BOOT, 104362306a36Sopenharmony_ci "boot htc service %s does not allocate target credits\n", 104462306a36Sopenharmony_ci htc_service_name(conn_req->service_id)); 104562306a36Sopenharmony_ci 104662306a36Sopenharmony_ci skb = ath10k_htc_build_tx_ctrl_skb(htc->ar); 104762306a36Sopenharmony_ci if (!skb) { 104862306a36Sopenharmony_ci ath10k_err(ar, "Failed to allocate HTC packet\n"); 104962306a36Sopenharmony_ci return -ENOMEM; 105062306a36Sopenharmony_ci } 105162306a36Sopenharmony_ci 105262306a36Sopenharmony_ci length = sizeof(msg->hdr) + sizeof(msg->connect_service); 105362306a36Sopenharmony_ci skb_put(skb, length); 105462306a36Sopenharmony_ci memset(skb->data, 0, length); 105562306a36Sopenharmony_ci 105662306a36Sopenharmony_ci msg = (struct ath10k_htc_msg *)skb->data; 105762306a36Sopenharmony_ci msg->hdr.message_id = 105862306a36Sopenharmony_ci __cpu_to_le16(ATH10K_HTC_MSG_CONNECT_SERVICE_ID); 105962306a36Sopenharmony_ci 106062306a36Sopenharmony_ci flags |= SM(tx_alloc, ATH10K_HTC_CONN_FLAGS_RECV_ALLOC); 106162306a36Sopenharmony_ci 106262306a36Sopenharmony_ci /* Only enable credit flow control for WMI ctrl service */ 106362306a36Sopenharmony_ci if (conn_req->service_id != ATH10K_HTC_SVC_ID_WMI_CONTROL) { 106462306a36Sopenharmony_ci flags |= ATH10K_HTC_CONN_FLAGS_DISABLE_CREDIT_FLOW_CTRL; 106562306a36Sopenharmony_ci disable_credit_flow_ctrl = true; 106662306a36Sopenharmony_ci } 106762306a36Sopenharmony_ci 106862306a36Sopenharmony_ci req_msg = &msg->connect_service; 106962306a36Sopenharmony_ci req_msg->flags = __cpu_to_le16(flags); 107062306a36Sopenharmony_ci req_msg->service_id = __cpu_to_le16(conn_req->service_id); 107162306a36Sopenharmony_ci 107262306a36Sopenharmony_ci reinit_completion(&htc->ctl_resp); 107362306a36Sopenharmony_ci 107462306a36Sopenharmony_ci status = ath10k_htc_send(htc, ATH10K_HTC_EP_0, skb); 107562306a36Sopenharmony_ci if (status) { 107662306a36Sopenharmony_ci kfree_skb(skb); 107762306a36Sopenharmony_ci return status; 107862306a36Sopenharmony_ci } 107962306a36Sopenharmony_ci 108062306a36Sopenharmony_ci /* wait for response */ 108162306a36Sopenharmony_ci time_left = wait_for_completion_timeout(&htc->ctl_resp, 108262306a36Sopenharmony_ci ATH10K_HTC_CONN_SVC_TIMEOUT_HZ); 108362306a36Sopenharmony_ci if (!time_left) { 108462306a36Sopenharmony_ci ath10k_err(ar, "Service connect timeout\n"); 108562306a36Sopenharmony_ci return -ETIMEDOUT; 108662306a36Sopenharmony_ci } 108762306a36Sopenharmony_ci 108862306a36Sopenharmony_ci /* we controlled the buffer creation, it's aligned */ 108962306a36Sopenharmony_ci msg = (struct ath10k_htc_msg *)htc->control_resp_buffer; 109062306a36Sopenharmony_ci resp_msg = &msg->connect_service_response; 109162306a36Sopenharmony_ci message_id = __le16_to_cpu(msg->hdr.message_id); 109262306a36Sopenharmony_ci service_id = __le16_to_cpu(resp_msg->service_id); 109362306a36Sopenharmony_ci 109462306a36Sopenharmony_ci if ((message_id != ATH10K_HTC_MSG_CONNECT_SERVICE_RESP_ID) || 109562306a36Sopenharmony_ci (htc->control_resp_len < sizeof(msg->hdr) + 109662306a36Sopenharmony_ci sizeof(msg->connect_service_response))) { 109762306a36Sopenharmony_ci ath10k_err(ar, "Invalid resp message ID 0x%x", message_id); 109862306a36Sopenharmony_ci return -EPROTO; 109962306a36Sopenharmony_ci } 110062306a36Sopenharmony_ci 110162306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_HTC, 110262306a36Sopenharmony_ci "HTC Service %s connect response: status: 0x%x, assigned ep: 0x%x\n", 110362306a36Sopenharmony_ci htc_service_name(service_id), 110462306a36Sopenharmony_ci resp_msg->status, resp_msg->eid); 110562306a36Sopenharmony_ci 110662306a36Sopenharmony_ci conn_resp->connect_resp_code = resp_msg->status; 110762306a36Sopenharmony_ci 110862306a36Sopenharmony_ci /* check response status */ 110962306a36Sopenharmony_ci if (resp_msg->status != ATH10K_HTC_CONN_SVC_STATUS_SUCCESS) { 111062306a36Sopenharmony_ci ath10k_err(ar, "HTC Service %s connect request failed: 0x%x)\n", 111162306a36Sopenharmony_ci htc_service_name(service_id), 111262306a36Sopenharmony_ci resp_msg->status); 111362306a36Sopenharmony_ci return -EPROTO; 111462306a36Sopenharmony_ci } 111562306a36Sopenharmony_ci 111662306a36Sopenharmony_ci assigned_eid = (enum ath10k_htc_ep_id)resp_msg->eid; 111762306a36Sopenharmony_ci max_msg_size = __le16_to_cpu(resp_msg->max_msg_size); 111862306a36Sopenharmony_ci 111962306a36Sopenharmony_cisetup: 112062306a36Sopenharmony_ci 112162306a36Sopenharmony_ci if (assigned_eid >= ATH10K_HTC_EP_COUNT) 112262306a36Sopenharmony_ci return -EPROTO; 112362306a36Sopenharmony_ci 112462306a36Sopenharmony_ci if (max_msg_size == 0) 112562306a36Sopenharmony_ci return -EPROTO; 112662306a36Sopenharmony_ci 112762306a36Sopenharmony_ci ep = &htc->endpoint[assigned_eid]; 112862306a36Sopenharmony_ci ep->eid = assigned_eid; 112962306a36Sopenharmony_ci 113062306a36Sopenharmony_ci if (ep->service_id != ATH10K_HTC_SVC_ID_UNUSED) 113162306a36Sopenharmony_ci return -EPROTO; 113262306a36Sopenharmony_ci 113362306a36Sopenharmony_ci /* return assigned endpoint to caller */ 113462306a36Sopenharmony_ci conn_resp->eid = assigned_eid; 113562306a36Sopenharmony_ci conn_resp->max_msg_len = __le16_to_cpu(resp_msg->max_msg_size); 113662306a36Sopenharmony_ci 113762306a36Sopenharmony_ci /* setup the endpoint */ 113862306a36Sopenharmony_ci ep->service_id = conn_req->service_id; 113962306a36Sopenharmony_ci ep->max_tx_queue_depth = conn_req->max_send_queue_depth; 114062306a36Sopenharmony_ci ep->max_ep_message_len = __le16_to_cpu(resp_msg->max_msg_size); 114162306a36Sopenharmony_ci ep->tx_credits = tx_alloc; 114262306a36Sopenharmony_ci ep->tx_credit_size = htc->target_credit_size; 114362306a36Sopenharmony_ci 114462306a36Sopenharmony_ci if (conn_req->service_id == ATH10K_HTC_SVC_ID_HTT_DATA_MSG && 114562306a36Sopenharmony_ci htc->alt_data_credit_size != 0) 114662306a36Sopenharmony_ci ep->tx_credit_size = htc->alt_data_credit_size; 114762306a36Sopenharmony_ci 114862306a36Sopenharmony_ci /* copy all the callbacks */ 114962306a36Sopenharmony_ci ep->ep_ops = conn_req->ep_ops; 115062306a36Sopenharmony_ci 115162306a36Sopenharmony_ci status = ath10k_hif_map_service_to_pipe(htc->ar, 115262306a36Sopenharmony_ci ep->service_id, 115362306a36Sopenharmony_ci &ep->ul_pipe_id, 115462306a36Sopenharmony_ci &ep->dl_pipe_id); 115562306a36Sopenharmony_ci if (status) { 115662306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_BOOT, "unsupported HTC service id: %d\n", 115762306a36Sopenharmony_ci ep->service_id); 115862306a36Sopenharmony_ci return status; 115962306a36Sopenharmony_ci } 116062306a36Sopenharmony_ci 116162306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_BOOT, 116262306a36Sopenharmony_ci "boot htc service '%s' ul pipe %d dl pipe %d eid %d ready\n", 116362306a36Sopenharmony_ci htc_service_name(ep->service_id), ep->ul_pipe_id, 116462306a36Sopenharmony_ci ep->dl_pipe_id, ep->eid); 116562306a36Sopenharmony_ci 116662306a36Sopenharmony_ci if (disable_credit_flow_ctrl && ep->tx_credit_flow_enabled) { 116762306a36Sopenharmony_ci ep->tx_credit_flow_enabled = false; 116862306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_BOOT, 116962306a36Sopenharmony_ci "boot htc service '%s' eid %d TX flow control disabled\n", 117062306a36Sopenharmony_ci htc_service_name(ep->service_id), assigned_eid); 117162306a36Sopenharmony_ci } 117262306a36Sopenharmony_ci 117362306a36Sopenharmony_ci return status; 117462306a36Sopenharmony_ci} 117562306a36Sopenharmony_ci 117662306a36Sopenharmony_cistruct sk_buff *ath10k_htc_alloc_skb(struct ath10k *ar, int size) 117762306a36Sopenharmony_ci{ 117862306a36Sopenharmony_ci struct sk_buff *skb; 117962306a36Sopenharmony_ci 118062306a36Sopenharmony_ci skb = dev_alloc_skb(size + sizeof(struct ath10k_htc_hdr)); 118162306a36Sopenharmony_ci if (!skb) 118262306a36Sopenharmony_ci return NULL; 118362306a36Sopenharmony_ci 118462306a36Sopenharmony_ci skb_reserve(skb, sizeof(struct ath10k_htc_hdr)); 118562306a36Sopenharmony_ci 118662306a36Sopenharmony_ci /* FW/HTC requires 4-byte aligned streams */ 118762306a36Sopenharmony_ci if (!IS_ALIGNED((unsigned long)skb->data, 4)) 118862306a36Sopenharmony_ci ath10k_warn(ar, "Unaligned HTC tx skb\n"); 118962306a36Sopenharmony_ci 119062306a36Sopenharmony_ci return skb; 119162306a36Sopenharmony_ci} 119262306a36Sopenharmony_ci 119362306a36Sopenharmony_cistatic void ath10k_htc_pktlog_process_rx(struct ath10k *ar, struct sk_buff *skb) 119462306a36Sopenharmony_ci{ 119562306a36Sopenharmony_ci trace_ath10k_htt_pktlog(ar, skb->data, skb->len); 119662306a36Sopenharmony_ci dev_kfree_skb_any(skb); 119762306a36Sopenharmony_ci} 119862306a36Sopenharmony_ci 119962306a36Sopenharmony_cistatic int ath10k_htc_pktlog_connect(struct ath10k *ar) 120062306a36Sopenharmony_ci{ 120162306a36Sopenharmony_ci struct ath10k_htc_svc_conn_resp conn_resp; 120262306a36Sopenharmony_ci struct ath10k_htc_svc_conn_req conn_req; 120362306a36Sopenharmony_ci int status; 120462306a36Sopenharmony_ci 120562306a36Sopenharmony_ci memset(&conn_req, 0, sizeof(conn_req)); 120662306a36Sopenharmony_ci memset(&conn_resp, 0, sizeof(conn_resp)); 120762306a36Sopenharmony_ci 120862306a36Sopenharmony_ci conn_req.ep_ops.ep_tx_complete = NULL; 120962306a36Sopenharmony_ci conn_req.ep_ops.ep_rx_complete = ath10k_htc_pktlog_process_rx; 121062306a36Sopenharmony_ci conn_req.ep_ops.ep_tx_credits = NULL; 121162306a36Sopenharmony_ci 121262306a36Sopenharmony_ci /* connect to control service */ 121362306a36Sopenharmony_ci conn_req.service_id = ATH10K_HTC_SVC_ID_HTT_LOG_MSG; 121462306a36Sopenharmony_ci status = ath10k_htc_connect_service(&ar->htc, &conn_req, &conn_resp); 121562306a36Sopenharmony_ci if (status) { 121662306a36Sopenharmony_ci ath10k_warn(ar, "failed to connect to PKTLOG service: %d\n", 121762306a36Sopenharmony_ci status); 121862306a36Sopenharmony_ci return status; 121962306a36Sopenharmony_ci } 122062306a36Sopenharmony_ci 122162306a36Sopenharmony_ci return 0; 122262306a36Sopenharmony_ci} 122362306a36Sopenharmony_ci 122462306a36Sopenharmony_cistatic bool ath10k_htc_pktlog_svc_supported(struct ath10k *ar) 122562306a36Sopenharmony_ci{ 122662306a36Sopenharmony_ci u8 ul_pipe_id; 122762306a36Sopenharmony_ci u8 dl_pipe_id; 122862306a36Sopenharmony_ci int status; 122962306a36Sopenharmony_ci 123062306a36Sopenharmony_ci status = ath10k_hif_map_service_to_pipe(ar, ATH10K_HTC_SVC_ID_HTT_LOG_MSG, 123162306a36Sopenharmony_ci &ul_pipe_id, 123262306a36Sopenharmony_ci &dl_pipe_id); 123362306a36Sopenharmony_ci if (status) { 123462306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_BOOT, "unsupported HTC pktlog service id: %d\n", 123562306a36Sopenharmony_ci ATH10K_HTC_SVC_ID_HTT_LOG_MSG); 123662306a36Sopenharmony_ci 123762306a36Sopenharmony_ci return false; 123862306a36Sopenharmony_ci } 123962306a36Sopenharmony_ci 124062306a36Sopenharmony_ci return true; 124162306a36Sopenharmony_ci} 124262306a36Sopenharmony_ci 124362306a36Sopenharmony_ciint ath10k_htc_start(struct ath10k_htc *htc) 124462306a36Sopenharmony_ci{ 124562306a36Sopenharmony_ci struct ath10k *ar = htc->ar; 124662306a36Sopenharmony_ci struct sk_buff *skb; 124762306a36Sopenharmony_ci int status = 0; 124862306a36Sopenharmony_ci struct ath10k_htc_msg *msg; 124962306a36Sopenharmony_ci 125062306a36Sopenharmony_ci skb = ath10k_htc_build_tx_ctrl_skb(htc->ar); 125162306a36Sopenharmony_ci if (!skb) 125262306a36Sopenharmony_ci return -ENOMEM; 125362306a36Sopenharmony_ci 125462306a36Sopenharmony_ci skb_put(skb, sizeof(msg->hdr) + sizeof(msg->setup_complete_ext)); 125562306a36Sopenharmony_ci memset(skb->data, 0, skb->len); 125662306a36Sopenharmony_ci 125762306a36Sopenharmony_ci msg = (struct ath10k_htc_msg *)skb->data; 125862306a36Sopenharmony_ci msg->hdr.message_id = 125962306a36Sopenharmony_ci __cpu_to_le16(ATH10K_HTC_MSG_SETUP_COMPLETE_EX_ID); 126062306a36Sopenharmony_ci 126162306a36Sopenharmony_ci if (ar->hif.bus == ATH10K_BUS_SDIO) { 126262306a36Sopenharmony_ci /* Extra setup params used by SDIO */ 126362306a36Sopenharmony_ci msg->setup_complete_ext.flags = 126462306a36Sopenharmony_ci __cpu_to_le32(ATH10K_HTC_SETUP_COMPLETE_FLAGS_RX_BNDL_EN); 126562306a36Sopenharmony_ci msg->setup_complete_ext.max_msgs_per_bundled_recv = 126662306a36Sopenharmony_ci htc->max_msgs_per_htc_bundle; 126762306a36Sopenharmony_ci } 126862306a36Sopenharmony_ci ath10k_dbg(ar, ATH10K_DBG_HTC, "HTC is using TX credit flow control\n"); 126962306a36Sopenharmony_ci 127062306a36Sopenharmony_ci status = ath10k_htc_send(htc, ATH10K_HTC_EP_0, skb); 127162306a36Sopenharmony_ci if (status) { 127262306a36Sopenharmony_ci kfree_skb(skb); 127362306a36Sopenharmony_ci return status; 127462306a36Sopenharmony_ci } 127562306a36Sopenharmony_ci 127662306a36Sopenharmony_ci if (ath10k_htc_pktlog_svc_supported(ar)) { 127762306a36Sopenharmony_ci status = ath10k_htc_pktlog_connect(ar); 127862306a36Sopenharmony_ci if (status) { 127962306a36Sopenharmony_ci ath10k_err(ar, "failed to connect to pktlog: %d\n", status); 128062306a36Sopenharmony_ci return status; 128162306a36Sopenharmony_ci } 128262306a36Sopenharmony_ci } 128362306a36Sopenharmony_ci 128462306a36Sopenharmony_ci return 0; 128562306a36Sopenharmony_ci} 128662306a36Sopenharmony_ci 128762306a36Sopenharmony_ci/* registered target arrival callback from the HIF layer */ 128862306a36Sopenharmony_ciint ath10k_htc_init(struct ath10k *ar) 128962306a36Sopenharmony_ci{ 129062306a36Sopenharmony_ci int status; 129162306a36Sopenharmony_ci struct ath10k_htc *htc = &ar->htc; 129262306a36Sopenharmony_ci struct ath10k_htc_svc_conn_req conn_req; 129362306a36Sopenharmony_ci struct ath10k_htc_svc_conn_resp conn_resp; 129462306a36Sopenharmony_ci 129562306a36Sopenharmony_ci spin_lock_init(&htc->tx_lock); 129662306a36Sopenharmony_ci 129762306a36Sopenharmony_ci ath10k_htc_reset_endpoint_states(htc); 129862306a36Sopenharmony_ci 129962306a36Sopenharmony_ci htc->ar = ar; 130062306a36Sopenharmony_ci 130162306a36Sopenharmony_ci /* setup our pseudo HTC control endpoint connection */ 130262306a36Sopenharmony_ci memset(&conn_req, 0, sizeof(conn_req)); 130362306a36Sopenharmony_ci memset(&conn_resp, 0, sizeof(conn_resp)); 130462306a36Sopenharmony_ci conn_req.ep_ops.ep_tx_complete = ath10k_htc_control_tx_complete; 130562306a36Sopenharmony_ci conn_req.ep_ops.ep_rx_complete = ath10k_htc_control_rx_complete; 130662306a36Sopenharmony_ci conn_req.max_send_queue_depth = ATH10K_NUM_CONTROL_TX_BUFFERS; 130762306a36Sopenharmony_ci conn_req.service_id = ATH10K_HTC_SVC_ID_RSVD_CTRL; 130862306a36Sopenharmony_ci 130962306a36Sopenharmony_ci /* connect fake service */ 131062306a36Sopenharmony_ci status = ath10k_htc_connect_service(htc, &conn_req, &conn_resp); 131162306a36Sopenharmony_ci if (status) { 131262306a36Sopenharmony_ci ath10k_err(ar, "could not connect to htc service (%d)\n", 131362306a36Sopenharmony_ci status); 131462306a36Sopenharmony_ci return status; 131562306a36Sopenharmony_ci } 131662306a36Sopenharmony_ci 131762306a36Sopenharmony_ci init_completion(&htc->ctl_resp); 131862306a36Sopenharmony_ci 131962306a36Sopenharmony_ci return 0; 132062306a36Sopenharmony_ci} 1321