// SPDX-License-Identifier: ISC
/*
* Copyright (c) 2007-2011 Atheros Communications Inc.
* Copyright (c) 2011-2012,2017 Qualcomm Atheros, Inc.
* Copyright (c) 2016-2017 Erik Stromdahl <erik.stromdahl@gmail.com>
*/
#include <linux/module.h>
#include <linux/usb.h>
#include "debug.h"
#include "core.h"
#include "bmi.h"
#include "hif.h"
#include "htc.h"
#include "usb.h"
static void ath10k_usb_post_recv_transfers(struct ath10k *ar,
struct ath10k_usb_pipe *recv_pipe);
/* inlined helper functions */
static inline enum ath10k_htc_ep_id
eid_from_htc_hdr(struct ath10k_htc_hdr *htc_hdr)
{
return (enum ath10k_htc_ep_id)htc_hdr->eid;
}
static inline bool is_trailer_only_msg(struct ath10k_htc_hdr *htc_hdr)
{
return __le16_to_cpu(htc_hdr->len) == htc_hdr->trailer_len;
}
/* pipe/urb operations */
static struct ath10k_urb_context *
ath10k_usb_alloc_urb_from_pipe(struct ath10k_usb_pipe *pipe)
{
struct ath10k_urb_context *urb_context = NULL;
unsigned long flags;
/* bail if this pipe is not initialized */
if (!pipe->ar_usb)
return NULL;
spin_lock_irqsave(&pipe->ar_usb->cs_lock, flags);
if (!list_empty(&pipe->urb_list_head)) {
urb_context = list_first_entry(&pipe->urb_list_head,
struct ath10k_urb_context, link);
list_del(&urb_context->link);
pipe->urb_cnt--;
}
spin_unlock_irqrestore(&pipe->ar_usb->cs_lock, flags);
return urb_context;
}
static void ath10k_usb_free_urb_to_pipe(struct ath10k_usb_pipe *pipe,
struct ath10k_urb_context *urb_context)
{
unsigned long flags;
/* bail if this pipe is not initialized */
if (!pipe->ar_usb)
return;
spin_lock_irqsave(&pipe->ar_usb->cs_lock, flags);
pipe->urb_cnt++;
list_add(&urb_context->link, &pipe->urb_list_head);
spin_unlock_irqrestore(&pipe->ar_usb->cs_lock, flags);
}
static void ath10k_usb_cleanup_recv_urb(struct ath10k_urb_context *urb_context)
{
dev_kfree_skb(urb_context->skb);
urb_context->skb = NULL;
ath10k_usb_free_urb_to_pipe(urb_context->pipe, urb_context);
}
static void ath10k_usb_free_pipe_resources(struct ath10k *ar,
struct ath10k_usb_pipe *pipe)
{
struct ath10k_urb_context *urb_context;
if (!pipe->ar_usb) {
/* nothing allocated for this pipe */
return;
}
ath10k_dbg(ar, ATH10K_DBG_USB,
"usb free resources lpipe %d hpipe 0x%x urbs %d avail %d\n",
pipe->logical_pipe_num, pipe->usb_pipe_handle,
pipe->urb_alloc, pipe->urb_cnt);
if (pipe->urb_alloc != pipe->urb_cnt) {
ath10k_dbg(ar, ATH10K_DBG_USB,
"usb urb leak lpipe %d hpipe 0x%x urbs %d avail %d\n",
pipe->logical_pipe_num, pipe->usb_pipe_handle,
pipe->urb_alloc, pipe->urb_cnt);
}
for (;;) {
urb_context = ath10k_usb_alloc_urb_from_pipe(pipe);
if (!urb_context)
break;
kfree(urb_context);
}
}
static void ath10k_usb_cleanup_pipe_resources(struct ath10k *ar)
{
struct ath10k_usb *ar_usb = ath10k_usb_priv(ar);
int i;
for (i = 0; i < ATH10K_USB_PIPE_MAX; i++)
ath10k_usb_free_pipe_resources(ar, &ar_usb->pipes[i]);
}
/* hif usb rx/tx completion functions */
static void ath10k_usb_recv_complete(struct urb *urb)
{
struct ath10k_urb_context *urb_context = urb->context;
struct ath10k_usb_pipe *pipe = urb_context->pipe;
struct ath10k *ar = pipe->