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