/*
* 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>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#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;
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;
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 =