// SPDX-License-Identifier: BSD-3-Clause-Clear
/*
* Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
* Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/skbuff.h>
#include <linux/ctype.h>
#include "debug.h"
#include "hif.h"
struct sk_buff *ath11k_htc_alloc_skb(struct ath11k_base *ab, int size)
{
struct sk_buff *skb;
skb = dev_alloc_skb(size + sizeof(struct ath11k_htc_hdr));
if (!skb)
return NULL;
skb_reserve(skb, sizeof(struct ath11k_htc_hdr));
/* FW/HTC requires 4-byte aligned streams */
if (!IS_ALIGNED((unsigned long)skb->data, 4))
ath11k_warn(ab, "Unaligned HTC tx skb\n");
return skb;
}
static void ath11k_htc_control_tx_complete(struct ath11k_base *ab,
struct sk_buff *skb)
{
kfree_skb(skb);
}
static struct sk_buff *ath11k_htc_build_tx_ctrl_skb(void *ab)
{
struct sk_buff *skb;
struct ath11k_skb_cb *skb_cb;
skb = dev_alloc_skb(ATH11K_HTC_CONTROL_BUFFER_SIZE);
if (!skb)
return NULL;
skb_reserve(skb, sizeof(struct ath11k_htc_hdr));
WARN_ON_ONCE(!IS_ALIGNED((unsigned long)skb->data, 4));
skb_cb = ATH11K_SKB_CB(skb);
memset(skb_cb, 0, sizeof(*skb_cb));
return skb;
}
static void ath11k_htc_prepare_tx_skb(struct ath11k_htc_ep *ep,
struct sk_buff *skb)
{
struct ath11k_htc_hdr *hdr;
hdr = (struct ath11k_htc_hdr *)skb->data;
memset(hdr, 0, sizeof(*hdr));
hdr->htc_info = FIELD_PREP(HTC_HDR_ENDPOINTID, ep->eid) |
FIELD_PREP(HTC_HDR_PAYLOADLEN,
(skb->len - sizeof(*hdr)));
if (ep->tx_credit_flow_enabled)
hdr->htc_info |= FIELD_PREP(HTC_HDR_FLAGS,
ATH11K_HTC_FLAG_NEED_CREDIT_UPDATE);
spin_lock_bh(&ep->htc->tx_lock);
hdr->ctrl_info = FIELD_PREP(HTC_HDR_CONTROLBYTES1, ep->seq_no++);
spin_unlock_bh(&ep->htc->tx_lock);
}
int ath11k_htc_send(struct ath11k_htc *htc,
enum ath11k_htc_ep_id eid,
struct sk_buff *skb)
{
struct ath11k_htc_ep *ep = &htc->endpoint[eid];
struct ath11k_skb_cb *skb_cb = ATH11K_SKB_CB(skb);
struct device *dev = htc->ab->dev;
struct ath11k_base *ab = htc->ab;
int credits = 0;
int ret;
bool credit_flow_enabled = (ab->hw_params.credit_flow &&
ep->tx_credit_flow_enabled);
if (eid >= ATH11K_HTC_EP_COUNT) {
ath11k_warn(ab, "Invalid endpoint id: %d\n", eid);
return -ENOENT;
}
skb_push(skb, sizeof(struct ath11k_htc_hdr));
if (credit_flow_enabled) {
credits = DIV_ROUND_UP(skb->len, htc->target_credit_size);
spin_lock_bh(&htc->tx_lock);
if (ep->tx_credits < credits) {
ath11k_dbg(ab, ATH11K_DBG_HTC,
"ep %d insufficient credits required %d total %d\n",
eid, credits, ep->tx_credits);
spin_unlock_bh(&htc->tx_lock);
ret = -EAGAIN;
goto err_pull;
}
ep->tx_credits -= credits;
ath11k_dbg(ab, ATH11K_DBG_HTC,
"ep %d credits consumed %d total %d\n",
eid, credits, ep->tx_credits);
spin_unlock_bh(&htc->tx_lock);
}
ath11k_htc_prepare_tx_skb(ep, skb);
skb_cb->eid = eid;
skb_cb->paddr = dma_map_single(dev, skb->data, skb->len, DMA_TO_DEVICE);
ret = dma_mapping_error(dev, skb_cb->paddr);
if (ret) {
ret = -EIO;
goto err_credits;
}
ath11k_dbg(ab, ATH11K_DBG_HTC, "tx skb %p eid %d paddr %pad\n",
skb, skb_cb->eid, &skb_cb->paddr);
ret = ath11k_ce_send(htc->ab, skb, ep->ul_pipe_id, ep->eid);
if (ret)
goto err_unmap;
return 0;
err_unmap:
dma_unmap_single(dev, skb_cb->paddr, skb->len, DMA_TO_DEVICE);
err_credits:
if (credit_flow_enabled) {
spin_lock_bh(&htc->tx_lock);
ep->tx_credits += credits;
ath11k_dbg(ab, ATH11K_DBG_HTC,
"ep %d credits reverted %d total %d\n",
eid, credits, ep->tx_credits);
spin_unlock_bh(&htc->tx_lock);
if (ep->ep_ops.ep_tx_credits)
ep->ep_ops.ep_tx_credits(htc->ab);
}
err_pull:
skb_pull(skb, sizeof(struct ath11k_htc_hdr));
return ret;
}
static void
ath11k_htc_process_credit_report(struct ath11k_htc *htc,
const struct ath11k_htc_credit_report *report,
int