/*
 * Copyright (c) 2013-2017 The Linux Foundation. All rights reserved.
 *
 * Previously licensed under the ISC license by Qualcomm Atheros, Inc.
 *
 *
 * 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.
 */

/*
 * This file was originally distributed by Qualcomm Atheros, Inc.
 * under proprietary terms before Copyright ownership was assigned
 * to the Linux Foundation.
 */
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/version.h>
#include <linux/atomic.h>

#include "hif_usb_internal.h"
#include <athdefs.h>
#include "a_types.h"
#include "athdefs.h"
#include "a_osapi.h"
#define ATH_MODULE_NAME hif
#include "a_debug.h"
#include "a_usb_defs.h"
#include "htc.h"
#include "htc_packet.h"
#include "qwlan_version.h"
#include "if_usb.h"
#include "vos_api.h"

#define IS_BULK_EP(attr) (((attr) & 3) == 0x02)
#define IS_INT_EP(attr)  (((attr) & 3) == 0x03)
#define IS_ISOC_EP(attr)  (((attr) & 3) == 0x01)
#define IS_DIR_IN(addr)  ((addr) & 0x80)

#define IS_FW_CRASH_DUMP(x)    ((x == FW_ASSERT_PATTERN) || \
                                (x == FW_REG_PATTERN) || \
                                ((x & FW_RAMDUMP_PATTERN_MASK) == FW_RAMDUMP_PATTERN))?1:0

static void usb_hif_post_recv_transfers(HIF_USB_PIPE *recv_pipe,
					int buffer_length);
static void usb_hif_post_recv_bundle_transfers(HIF_USB_PIPE *recv_pipe,
					       int buffer_length);
static void usb_hif_cleanup_recv_urb(HIF_URB_CONTEXT *urb_context);
static void usb_hif_free_urb_to_pipe(HIF_USB_PIPE *pipe,
				     HIF_URB_CONTEXT *urb_context)
{
	adf_os_spin_lock_irqsave(&pipe->device->cs_lock);
	pipe->urb_cnt++;
	DL_ListAdd(&pipe->urb_list_head, &urb_context->link);
	adf_os_spin_unlock_irqrestore(&pipe->device->cs_lock);
}

HIF_URB_CONTEXT *usb_hif_alloc_urb_from_pipe(HIF_USB_PIPE *pipe)
{
	HIF_URB_CONTEXT *urb_context = NULL;
	DL_LIST *item;

	adf_os_spin_lock_irqsave(&pipe->device->cs_lock);
	item = DL_ListRemoveItemFromHead(&pipe->urb_list_head);
	if (item != NULL) {
		urb_context = A_CONTAINING_STRUCT(item, HIF_URB_CONTEXT, link);
		pipe->urb_cnt--;
	}
	adf_os_spin_unlock_irqrestore(&pipe->device->cs_lock);

	return urb_context;
}

static HIF_URB_CONTEXT *usb_hif_dequeue_pending_transfer(HIF_USB_PIPE *pipe)
{
	HIF_URB_CONTEXT *urb_context = NULL;
	DL_LIST *item;

	adf_os_spin_lock_irqsave(&pipe->device->cs_lock);
	item = DL_ListRemoveItemFromHead(&pipe->urb_pending_list);
	if (item != NULL)
		urb_context = A_CONTAINING_STRUCT(item, HIF_URB_CONTEXT, link);
	adf_os_spin_unlock_irqrestore(&pipe->device->cs_lock);

	return urb_context;
}

void usb_hif_enqueue_pending_transfer(HIF_USB_PIPE *pipe,
				      HIF_URB_CONTEXT *urb_context)
{
	adf_os_spin_lock_irqsave(&pipe->device->cs_lock);
	DL_ListInsertTail(&pipe->urb_pending_list, &urb_context->link);
	adf_os_spin_unlock_irqrestore(&pipe->device->cs_lock);
}

void usb_hif_remove_pending_transfer(HIF_URB_CONTEXT *urb_context)
{
	adf_os_spin_lock_irqsave(&urb_context->pipe->device->cs_lock);
	DL_ListRemove(&urb_context->link);
	adf_os_spin_unlock_irqrestore(&urb_context->pipe->device->cs_lock);
}

static A_STATUS usb_hif_alloc_pipe_resources(HIF_USB_PIPE *pipe, int urb_cnt)
{
	A_STATUS status = A_OK;
	int i;
	HIF_URB_CONTEXT *urb_context;

	DL_LIST_INIT(&pipe->urb_list_head);
	DL_LIST_INIT(&pipe->urb_pending_list);

	for (i = 0; i < urb_cnt; i++) {
		urb_context = adf_os_mem_alloc(NULL, sizeof(*urb_context));
		if (NULL == urb_context) {
			status = A_NO_MEMORY;
			AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
					("urb_context is null\n"));
			break;
		}
		adf_os_mem_zero(urb_context, sizeof(HIF_URB_CONTEXT));
		urb_context->pipe = pipe;
		urb_context->urb = usb_alloc_urb(0, GFP_KERNEL);

		if (NULL == urb_context->urb) {
			status = A_NO_MEMORY;
			adf_os_mem_free(urb_context);
			AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
					("urb_context->urb is null\n"));
			break;
		}

		/* note we are only allocate the urb contexts here, the actual
		 * URB is
		 * allocated from the kernel as needed to do a transaction
		 */
		pipe->urb_alloc++;

		usb_hif_free_urb_to_pipe(pipe, urb_context);
	}

	AR_DEBUG_PRINTF(USB_HIF_DEBUG_ENUM, (
			 "athusb: alloc resources lpipe:%d hpipe:0x%X urbs:%d\n",
			 pipe->logical_pipe_num,
			 pipe->usb_pipe_handle,
			 pipe->urb_alloc));
	return status;
}

static void usb_hif_free_pipe_resources(HIF_USB_PIPE *pipe)
{
	HIF_URB_CONTEXT *urb_context;

	if (NULL == pipe->device) {
		/* nothing allocated for this pipe */
		AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("pipe->device is null\n"));
		return;
	}

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, (
			 "athusb: 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) {
		AR_DEBUG_PRINTF(ATH_DEBUG_ERR, (
				 "athusb: 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));
	}

	while (TRUE) {
		urb_context = usb_hif_alloc_urb_from_pipe(pipe);
		if (NULL == urb_context)
			break;

		if (urb_context->buf) {
			adf_nbuf_free(urb_context->buf);
			urb_context->buf = NULL;
		}

		usb_free_urb(urb_context->urb);
		urb_context->urb = NULL;
		adf_os_mem_free(urb_context);
	}

}

static A_UINT8 usb_hif_get_logical_pipe_num(HIF_DEVICE_USB *device,
					    A_UINT8 ep_address, int *urb_count)
{
	A_UINT8 pipe_num = HIF_USB_PIPE_INVALID;

	switch (ep_address) {
	case USB_EP_ADDR_APP_CTRL_IN:
		pipe_num = HIF_RX_CTRL_PIPE;
		*urb_count = RX_URB_COUNT;
		break;
	case USB_EP_ADDR_APP_DATA_IN:
		pipe_num = HIF_RX_DATA_PIPE;
		*urb_count = RX_URB_COUNT;
		break;
	case USB_EP_ADDR_APP_INT_IN:
		pipe_num = HIF_RX_INT_PIPE;
		*urb_count = RX_URB_COUNT;
		break;
	case USB_EP_ADDR_APP_DATA2_IN:
		pipe_num = HIF_RX_DATA2_PIPE;
		*urb_count = RX_URB_COUNT;
		break;
	case USB_EP_ADDR_APP_CTRL_OUT:
		pipe_num = HIF_TX_CTRL_PIPE;
		*urb_count = TX_URB_COUNT;
		break;
	case USB_EP_ADDR_APP_DATA_LP_OUT:
		pipe_num = HIF_TX_DATA_LP_PIPE;
		*urb_count = TX_URB_COUNT;
		break;
	case USB_EP_ADDR_APP_DATA_MP_OUT:
		pipe_num = HIF_TX_DATA_MP_PIPE;
		*urb_count = TX_URB_COUNT;
		break;
	case USB_EP_ADDR_APP_DATA_HP_OUT:
		pipe_num = HIF_TX_DATA_HP_PIPE;
		*urb_count = TX_URB_COUNT;
		break;
	default:
		/* note: there may be endpoints not currently used */
		break;
	}

	return pipe_num;
}

A_STATUS usb_hif_setup_pipe_resources(HIF_DEVICE_USB *device)
{
	struct usb_interface *interface = device->interface;
	struct usb_host_interface *iface_desc = interface->cur_altsetting;
	struct usb_endpoint_descriptor *endpoint;
	int i;
	int urbcount;
	A_STATUS status = A_OK;
	HIF_USB_PIPE *pipe;
	A_UINT8 pipe_num;

	/* walk decriptors and setup pipes */
	for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
		endpoint = &iface_desc->endpoint[i].desc;

		if (IS_BULK_EP(endpoint->bmAttributes)) {
			AR_DEBUG_PRINTF(USB_HIF_DEBUG_ENUM, (
					 "%s Bulk Ep:0x%2.2X " "maxpktsz:%d\n",
					 IS_DIR_IN(endpoint->bEndpointAddress) ?
					 "RX" : "TX",
					 endpoint->bEndpointAddress,
					 le16_to_cpu
					 (endpoint->wMaxPacketSize)));
		} else if (IS_INT_EP(endpoint->bmAttributes)) {
			AR_DEBUG_PRINTF(USB_HIF_DEBUG_ENUM, (
					 "%s Int Ep:0x%2.2X maxpktsz:%d interval:%d\n",
					 IS_DIR_IN(endpoint->bEndpointAddress) ?
					 "RX" : "TX",
					 endpoint->bEndpointAddress,
					 le16_to_cpu(endpoint->wMaxPacketSize),
					 endpoint->bInterval));
		} else if (IS_ISOC_EP(endpoint->bmAttributes)) {
			/* TODO for ISO */
			AR_DEBUG_PRINTF(USB_HIF_DEBUG_ENUM, (
					 "%s ISOC Ep:0x%2.2X maxpktsz:%d interval:%d\n",
					 IS_DIR_IN(endpoint->bEndpointAddress) ?
					 "RX" : "TX",
					 endpoint->bEndpointAddress,
					 le16_to_cpu(endpoint->wMaxPacketSize),
					 endpoint->bInterval));
		}
		urbcount = 0;

		pipe_num = usb_hif_get_logical_pipe_num(device,
							endpoint->
							bEndpointAddress,
							&urbcount);
		if (HIF_USB_PIPE_INVALID == pipe_num)
			continue;

		pipe = &device->pipes[pipe_num];
		if (pipe->device != NULL) {
			/* hmmm..pipe was already setup */
			continue;
		}

		pipe->device = device;
		pipe->logical_pipe_num = pipe_num;
		pipe->ep_address = endpoint->bEndpointAddress;
		pipe->max_packet_size = le16_to_cpu(endpoint->wMaxPacketSize);

		if (IS_BULK_EP(endpoint->bmAttributes)) {
			if (IS_DIR_IN(pipe->ep_address)) {
				pipe->usb_pipe_handle =
				    usb_rcvbulkpipe(device->udev,
						    pipe->ep_address);
			} else {
				pipe->usb_pipe_handle =
				    usb_sndbulkpipe(device->udev,
						    pipe->ep_address);
			}
		} else if (IS_INT_EP(endpoint->bmAttributes)) {
			if (IS_DIR_IN(pipe->ep_address)) {
				pipe->usb_pipe_handle =
				    usb_rcvintpipe(device->udev,
						   pipe->ep_address);
			} else {
				pipe->usb_pipe_handle =
				    usb_sndintpipe(device->udev,
						   pipe->ep_address);
			}
		} else if (IS_ISOC_EP(endpoint->bmAttributes)) {
			/* TODO for ISO */
			if (IS_DIR_IN(pipe->ep_address)) {
				pipe->usb_pipe_handle =
				    usb_rcvisocpipe(device->udev,
						    pipe->ep_address);
			} else {
				pipe->usb_pipe_handle =
				    usb_sndisocpipe(device->udev,
						    pipe->ep_address);
			}
		}
		pipe->ep_desc = endpoint;

		if (!IS_DIR_IN(pipe->ep_address))
			pipe->flags |= HIF_USB_PIPE_FLAG_TX;

		status = usb_hif_alloc_pipe_resources(pipe, urbcount);
		if (A_FAILED(status))
			break;

	}

	return status;
}

void usb_hif_cleanup_pipe_resources(HIF_DEVICE_USB *device)
{
	int i;

	for (i = 0; i < HIF_USB_PIPE_MAX; i++)
		usb_hif_free_pipe_resources(&device->pipes[i]);
}

static void usb_hif_flush_pending_transfers(HIF_USB_PIPE *pipe)
{
	HIF_URB_CONTEXT *urb_context;

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s pipe : %d\n", __func__,
					pipe->logical_pipe_num));

	while (1) {
		urb_context = usb_hif_dequeue_pending_transfer(pipe);
		if (NULL == urb_context) {
			AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
					("urb_context is NULL\n"));
			break;
		}
		AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("  pending urb ctxt: 0x%pK\n",
						urb_context));
		if (urb_context->urb != NULL) {
			AR_DEBUG_PRINTF(ATH_DEBUG_TRC,
					("  killing urb: 0x%pK\n",
					 urb_context->urb));
			/* killing the URB will cause the completion routines to
			 * run
			 */
			usb_kill_urb(urb_context->urb);
		}
	}
	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));
}

void usb_hif_flush_all(HIF_DEVICE_USB *device)
{
	int i;
	HIF_USB_PIPE *pipe;
	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));

	for (i = 0; i < HIF_USB_PIPE_MAX; i++) {
		if (device->pipes[i].device != NULL) {
			usb_hif_flush_pending_transfers(&device->pipes[i]);
			pipe = &device->pipes[i];
#ifndef HIF_USB_TASKLET
			flush_work(&pipe->io_complete_work);
#endif
		}
	}

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));
}

static void usb_hif_cleanup_recv_urb(HIF_URB_CONTEXT *urb_context)
{
	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));

	if (urb_context->buf != NULL) {
		adf_nbuf_free(urb_context->buf);
		urb_context->buf = NULL;
	}

	usb_hif_free_urb_to_pipe(urb_context->pipe, urb_context);
	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));
}

void usb_hif_cleanup_transmit_urb(HIF_URB_CONTEXT *urb_context)
{
	usb_hif_free_urb_to_pipe(urb_context->pipe, urb_context);
}

static void usb_hif_usb_recv_prestart_complete(struct urb *urb)
{
	HIF_URB_CONTEXT *urb_context = (HIF_URB_CONTEXT *) urb->context;
	A_STATUS status = A_OK;
	adf_nbuf_t buf = NULL;
	HIF_USB_PIPE *pipe = urb_context->pipe;

	AR_DEBUG_PRINTF(USB_HIF_DEBUG_BULK_IN, (
				"+%s: recv pipe: %d, stat:%d,len:%d urb:0x%pK\n",
				__func__,
				pipe->logical_pipe_num,
				urb->status, urb->actual_length,
				urb));

	/* this urb is not pending anymore */
	usb_hif_remove_pending_transfer(urb_context);

	do {
		if (urb->status != 0) {
			status = A_ECOMM;
			switch (urb->status) {
			case -ECONNRESET:
			case -ENOENT:
			case -ESHUTDOWN:
				/* NOTE: no need to spew these errors when
				 * device is removed
				 * or urb is killed due to driver shutdown
				 */
				status = A_ECANCELED;
				break;
			default:
				AR_DEBUG_PRINTF(ATH_DEBUG_ERR, (
					"%s recv pipe: %d (ep:0x%2.2X), failed:%d\n",
					__func__,
					pipe->logical_pipe_num,
					pipe->ep_address,
					urb->status));
				break;
			}
			break;
		}

		if (urb->actual_length == 0)
			break;

		buf = urb_context->buf;
		/* we are going to pass it up */
		urb_context->buf = NULL;
		adf_nbuf_put_tail(buf, urb->actual_length);
		if (AR_DEBUG_LVL_CHECK(USB_HIF_DEBUG_DUMP_DATA)) {
			A_UINT8 *data;
			A_UINT32 len;
			adf_nbuf_peek_header(buf, &data, &len);
			DebugDumpBytes(data, len, "hif recv data");
		}

		/* note: queue implements a lock */
		skb_queue_tail(&pipe->io_comp_queue, buf);
#ifdef HIF_USB_TASKLET
		tasklet_schedule(&pipe->io_complete_tasklet);
#else
		schedule_work(&pipe->io_complete_work);
#endif
	} while (FALSE);

	usb_hif_cleanup_recv_urb(urb_context);

	/* Prestart URBs runs out and now start working receive pipe. */
	adf_os_spin_lock_irqsave(&pipe->device->rx_prestart_lock);
	if (--pipe->urb_prestart_cnt == 0) {
		usb_hif_start_recv_pipes(pipe->device);
	}
	adf_os_spin_unlock_irqrestore(&pipe->device->rx_prestart_lock);

	AR_DEBUG_PRINTF(USB_HIF_DEBUG_BULK_IN, ("-%s\n", __func__));
}

static void usb_hif_usb_recv_complete(struct urb *urb)
{
	HIF_URB_CONTEXT *urb_context = (HIF_URB_CONTEXT *) urb->context;
	A_STATUS status = A_OK;
	adf_nbuf_t buf = NULL;
	HIF_USB_PIPE *pipe = urb_context->pipe;

	AR_DEBUG_PRINTF(USB_HIF_DEBUG_BULK_IN, (
			 "+%s: recv pipe: %d, stat:%d,len:%d urb:0x%pK\n",
			 __func__,
			 pipe->logical_pipe_num,
			 urb->status, urb->actual_length,
			 urb));

	/* this urb is not pending anymore */
	usb_hif_remove_pending_transfer(urb_context);

	do {

		if (urb->status != 0) {
			status = A_ECOMM;
			switch (urb->status) {
#ifdef RX_SG_SUPPORT
			case -EOVERFLOW:
				urb->actual_length = HIF_USB_RX_BUFFER_SIZE;
				status = A_OK;
				break;
#endif
			case -ECONNRESET:
			case -ENOENT:
			case -ESHUTDOWN:
				/* NOTE: no need to spew these errors when
				 * device is removed
				 * or urb is killed due to driver shutdown
				 */
				status = A_ECANCELED;
				break;
			default:
				AR_DEBUG_PRINTF(ATH_DEBUG_ERR, (
						 "%s recv pipe: %d (ep:0x%2.2X), failed:%d\n",
						 __func__,
						 pipe->logical_pipe_num,
						 pipe->ep_address,
						 urb->status));
				break;
			}
			break;
		}

		if (urb->actual_length == 0)
			break;

		buf = urb_context->buf;
		/* we are going to pass it up */
		urb_context->buf = NULL;
		adf_nbuf_put_tail(buf, urb->actual_length);
		if (AR_DEBUG_LVL_CHECK(USB_HIF_DEBUG_DUMP_DATA)) {
			A_UINT8 *data;
			A_UINT32 len;
			adf_nbuf_peek_header(buf, &data, &len);
			DebugDumpBytes(data, len, "hif recv data");
		}

		/* note: queue implements a lock */
		skb_queue_tail(&pipe->io_comp_queue, buf);
#ifdef HIF_USB_TASKLET
		tasklet_schedule(&pipe->io_complete_tasklet);
#else
		schedule_work(&pipe->io_complete_work);
#endif
	} while (FALSE);

	usb_hif_cleanup_recv_urb(urb_context);

	/* Only re-submit URB when STATUS is success and HIF is not at the
	   suspend state.
	 */
	if (A_SUCCESS(status) && !pipe->device->sc->suspend_state) {
		if (pipe->urb_cnt >= pipe->urb_cnt_thresh) {
			/* our free urbs are piling up, post more transfers */
			usb_hif_post_recv_transfers(pipe,
						    HIF_USB_RX_BUFFER_SIZE);
		}
	} else {
		AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
			("%s:  pipe: %d, fail to post URB: status(%d) suspend (%d)\n",
				__func__,
				pipe->logical_pipe_num,
				urb->status,
				pipe->device->sc->suspend_state));
	}

	AR_DEBUG_PRINTF(USB_HIF_DEBUG_BULK_IN, ("-%s\n", __func__));
}

static void usb_hif_usb_recv_bundle_complete(struct urb *urb)
{
	HIF_URB_CONTEXT *urb_context = (HIF_URB_CONTEXT *) urb->context;
	A_STATUS status = A_OK;
	adf_nbuf_t buf = NULL;
	HIF_USB_PIPE *pipe = urb_context->pipe;
	A_UINT8 *netdata, *netdata_new;
	A_UINT32 netlen, netlen_new;
	HTC_FRAME_HDR *HtcHdr;
	A_UINT16 payloadLen;
	adf_nbuf_t new_skb = NULL;

	AR_DEBUG_PRINTF(USB_HIF_DEBUG_BULK_IN, (
			 "+%s: recv pipe: %d, stat:%d,len:%d urb:0x%pK\n",
			 __func__,
			 pipe->logical_pipe_num,
			 urb->status, urb->actual_length,
			 urb));

	/* this urb is not pending anymore */
	usb_hif_remove_pending_transfer(urb_context);

	do {

		if (urb->status != 0) {
			status = A_ECOMM;
			switch (urb->status) {
			case -ECONNRESET:
			case -ENOENT:
			case -ESHUTDOWN:
				/* NOTE: no need to spew these errors when
				 * device is removed
				 * or urb is killed due to driver shutdown
				 */
				status = A_ECANCELED;
				break;
			default:
				AR_DEBUG_PRINTF(ATH_DEBUG_ERR, (
						 "%s recv pipe: %d (ep:0x%2.2X), failed:%d\n",
						 __func__,
						 pipe->logical_pipe_num,
						 pipe->ep_address,
						 urb->status));
				break;
			}
			break;
		}

		if (urb->actual_length == 0)
			break;

		buf = urb_context->buf;

		if (AR_DEBUG_LVL_CHECK(USB_HIF_DEBUG_DUMP_DATA)) {
			A_UINT8 *data;
			A_UINT32 len;
			adf_nbuf_peek_header(buf, &data, &len);
			DebugDumpBytes(data, len, "hif recv data");
		}

		adf_nbuf_peek_header(buf, &netdata, &netlen);
		netlen = urb->actual_length;

		do {
			A_UINT16 frame_len;

			if (IS_FW_CRASH_DUMP(*(A_UINT32 *) netdata))
				frame_len = netlen;
			else {
				/* Hack into HTC header for bundle processing */
				HtcHdr = (HTC_FRAME_HDR *) netdata;
				if (HtcHdr->EndpointID >= ENDPOINT_MAX) {
					AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
						("athusb: Rx: invalid EndpointID=%d\n",
						HtcHdr->EndpointID));
					break;
				}

				payloadLen = HtcHdr->PayloadLen;
				payloadLen = A_LE2CPU16(payloadLen);

				if (payloadLen > HIF_USB_RX_BUFFER_SIZE) {
					AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
						("athusb: payloadLen too long %u\n",
						payloadLen));
					break;
				}
				frame_len = (HTC_HDR_LENGTH + payloadLen);
			}

			if (netlen >= frame_len) {
				/* allocate a new skb and copy */
				new_skb =
				    adf_nbuf_alloc(NULL, frame_len, 0, 4,
						   FALSE);
				if (new_skb == NULL) {
					AR_DEBUG_PRINTF(ATH_DEBUG_ERR, (
							 "athusb: allocate skb (len=%u) failed\n",
							 frame_len));
					break;
				}

				adf_nbuf_peek_header(new_skb, &netdata_new,
						     &netlen_new);
				adf_os_mem_copy(netdata_new, netdata,
						frame_len);
				adf_nbuf_put_tail(new_skb, frame_len);
				skb_queue_tail(&pipe->io_comp_queue, new_skb);
				new_skb = NULL;

				netdata += frame_len;
				netlen -= frame_len;
			} else {
				AR_DEBUG_PRINTF(ATH_DEBUG_ERR, (
					 "athusb: subframe length %d not fitted into bundle packet length %d\n"
					 , netlen, frame_len));
				break;
			}

		} while (netlen);
#ifdef HIF_USB_TASKLET
		tasklet_schedule(&pipe->io_complete_tasklet);
#else
		schedule_work(&pipe->io_complete_work);
#endif
	} while (FALSE);

	if (urb_context->buf == NULL) {
		AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
				("athusb: buffer in urb_context is NULL\n"));
	}

	/* reset urb_context->buf ==> seems not necessary */
	usb_hif_free_urb_to_pipe(urb_context->pipe, urb_context);

	if (A_SUCCESS(status)) {
		if (pipe->urb_cnt >= pipe->urb_cnt_thresh) {
			/* our free urbs are piling up, post more transfers */
			usb_hif_post_recv_bundle_transfers(pipe,
						pipe->device->rx_bundle_buf_len);
		}
	}

	AR_DEBUG_PRINTF(USB_HIF_DEBUG_BULK_IN, ("-%s\n", __func__));
}

/* post prestart recv urbs for a given pipe */
static void usb_hif_post_recv_prestart_transfers(HIF_USB_PIPE *recv_pipe,
						int prestart_urb)
{
	HIF_URB_CONTEXT *urb_context;
	a_uint8_t *data;
	a_uint32_t len;
	struct urb *urb;
	int i, usb_status, buffer_length = HIF_USB_RX_BUFFER_SIZE;

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));

	adf_os_spin_lock_irqsave(&recv_pipe->device->rx_prestart_lock);
	for (i = 0; i < prestart_urb; i++) {
		urb_context = usb_hif_alloc_urb_from_pipe(recv_pipe);
		if (NULL == urb_context)
			break;

		urb_context->buf =
			adf_nbuf_alloc(NULL, buffer_length, 0, 4, FALSE);
		if (NULL == urb_context->buf) {
			usb_hif_cleanup_recv_urb(urb_context);
			break;
		}

		adf_nbuf_peek_header(urb_context->buf, &data, &len);

		urb = urb_context->urb;

		usb_fill_bulk_urb(urb,
				  recv_pipe->device->udev,
				  recv_pipe->usb_pipe_handle,
				  data,
				  buffer_length,
				  usb_hif_usb_recv_prestart_complete,
				  urb_context);

		AR_DEBUG_PRINTF(USB_HIF_DEBUG_BULK_IN, (
			"athusb bulk recv submit:%d, 0x%X (ep:0x%2.2X), %d bytes, buf:0x%pK\n",
			recv_pipe->logical_pipe_num,
			recv_pipe->usb_pipe_handle,
			recv_pipe->ep_address, buffer_length,
			urb_context->buf));

		usb_hif_enqueue_pending_transfer(recv_pipe, urb_context);

		usb_status = usb_submit_urb(urb, GFP_ATOMIC);

		if (usb_status) {
			AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
					("athusb : usb bulk recv failed %d\n",
					  usb_status));
			usb_hif_remove_pending_transfer(urb_context);
			usb_hif_cleanup_recv_urb(urb_context);
			break;
		} else
			recv_pipe->urb_prestart_cnt++;

	}
	adf_os_spin_unlock_irqrestore(&recv_pipe->device->rx_prestart_lock);

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));
}

/* post recv urbs for a given pipe */
static void usb_hif_post_recv_transfers(HIF_USB_PIPE *recv_pipe,
					int buffer_length)
{
	HIF_URB_CONTEXT *urb_context;
	a_uint8_t *data;
	a_uint32_t len;
	struct urb *urb;
	int usb_status;

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));

	while (1) {

		urb_context = usb_hif_alloc_urb_from_pipe(recv_pipe);
		if (NULL == urb_context)
			break;

		urb_context->buf =
		    adf_nbuf_alloc(NULL, buffer_length, 0, 4, FALSE);
		if (NULL == urb_context->buf) {
			usb_hif_cleanup_recv_urb(urb_context);
			break;
		}

		adf_nbuf_peek_header(urb_context->buf, &data, &len);

		urb = urb_context->urb;

		usb_fill_bulk_urb(urb,
				  recv_pipe->device->udev,
				  recv_pipe->usb_pipe_handle,
				  data,
				  buffer_length,
				  usb_hif_usb_recv_complete, urb_context);

		AR_DEBUG_PRINTF(USB_HIF_DEBUG_BULK_IN, (
				 "athusb bulk recv submit:%d, 0x%X (ep:0x%2.2X), %d bytes, buf:0x%pK\n",
				 recv_pipe->logical_pipe_num,
				 recv_pipe->usb_pipe_handle,
				 recv_pipe->ep_address, buffer_length,
				 urb_context->buf));

		usb_hif_enqueue_pending_transfer(recv_pipe, urb_context);

		usb_status = usb_submit_urb(urb, GFP_ATOMIC);

		if (usb_status) {
			AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
					("athusb : usb bulk recv failed %d\n",
					 usb_status));
			usb_hif_remove_pending_transfer(urb_context);
			usb_hif_cleanup_recv_urb(urb_context);
			break;
		}
	}

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));

}

/* post recv urbs for a given pipe */
static void usb_hif_post_recv_bundle_transfers(HIF_USB_PIPE *recv_pipe,
					       int buffer_length)
{
	HIF_URB_CONTEXT *urb_context;
	a_uint8_t *data;
	a_uint32_t len;
	struct urb *urb;
	int usb_status;

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));

	while (1) {

		urb_context = usb_hif_alloc_urb_from_pipe(recv_pipe);
		if (NULL == urb_context)
			break;

		if (NULL == urb_context->buf) {
			urb_context->buf =
			    adf_nbuf_alloc(NULL, buffer_length, 0, 4, FALSE);
			if (NULL == urb_context->buf) {
				usb_hif_cleanup_recv_urb(urb_context);
				break;
			}
		}

		adf_nbuf_peek_header(urb_context->buf, &data, &len);

		urb = urb_context->urb;
		usb_fill_bulk_urb(urb,
				  recv_pipe->device->udev,
				  recv_pipe->usb_pipe_handle,
				  data,
				  buffer_length,
				  usb_hif_usb_recv_bundle_complete,
				  urb_context);

		AR_DEBUG_PRINTF(USB_HIF_DEBUG_BULK_IN, (
				 "athusb bulk recv submit:%d, 0x%X (ep:0x%2.2X), %d bytes, buf:0x%pK\n",
				 recv_pipe->logical_pipe_num,
				 recv_pipe->usb_pipe_handle,
				 recv_pipe->ep_address, buffer_length,
				 urb_context->buf));

		usb_hif_enqueue_pending_transfer(recv_pipe, urb_context);

		usb_status = usb_submit_urb(urb, GFP_ATOMIC);

		if (usb_status) {
			AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
					("athusb : usb bulk recv failed %d\n",
					 usb_status));
			usb_hif_remove_pending_transfer(urb_context);
			usb_hif_free_urb_to_pipe(urb_context->pipe,
						 urb_context);
			break;
		}

	}

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));

}

void usb_hif_prestart_recv_pipes(HIF_DEVICE_USB *device)
{
	HIF_USB_PIPE *pipe = &device->pipes[HIF_RX_DATA_PIPE];

	/*
	 * USB driver learn to support bundle or not until the firmware
	 * download and ready. Only allocate some URBs for control message
	 * communication during the initial phase then start the final
	 * working pipe after all information understood.
	 */
	usb_hif_post_recv_prestart_transfers(pipe, 8);
}

void usb_hif_start_recv_pipes(HIF_DEVICE_USB *device)
{
	HIF_USB_PIPE *pipe;
	a_uint32_t buf_len;

	printk("Enter:%s,Line:%d \n", __func__,__LINE__);

	pipe = &device->pipes[HIF_RX_DATA_PIPE];
	pipe->urb_cnt_thresh = pipe->urb_alloc / 2;

	printk("Post URBs to RX_DATA_PIPE: %d\n",
		device->pipes[HIF_RX_DATA_PIPE].urb_cnt);
	if (device->is_bundle_enabled) {
		usb_hif_post_recv_bundle_transfers(pipe,
					pipe->device->rx_bundle_buf_len);
	} else {
		buf_len = HIF_USB_RX_BUFFER_SIZE;
		usb_hif_post_recv_transfers(pipe, buf_len);
	}

	AR_DEBUG_PRINTF(USB_HIF_DEBUG_BULK_IN,
			("athusb bulk recv len %d\n",
			  buf_len));

	if (!hif_usb_disable_rxdata2) {
		printk("Post URBs to RX_DATA2_PIPE: %d\n",
			device->pipes[HIF_RX_DATA2_PIPE].urb_cnt);

		pipe = &device->pipes[HIF_RX_DATA2_PIPE];
		pipe->urb_cnt_thresh = pipe->urb_alloc / 2;
		usb_hif_post_recv_transfers(pipe, HIF_USB_RX_BUFFER_SIZE);
	}
#ifdef USB_HIF_TEST_INTERRUPT_IN
	printk("Post URBs to RX_INT_PIPE: %d\n",
		device->pipes[HIF_RX_INT_PIPE].urb_cnt);

	pipe = &device->pipes[HIF_RX_INT_PIPE];
	pipe->urb_cnt_thresh = pipe->urb_alloc / 2;
	usb_hif_post_recv_transfers(pipe, HIF_USB_RX_BUFFER_SIZE);
#endif

	printk("Exit:%s,Line:%d \n", __func__,__LINE__);
}

A_STATUS usb_hif_submit_ctrl_out(HIF_DEVICE_USB *device,
				 a_uint8_t req,
				 a_uint16_t value,
				 a_uint16_t index, void *data, a_uint32_t size)
{
	a_int32_t result = 0;
	A_STATUS ret = A_OK;
	a_uint8_t *buf = NULL;

	do {

		if (size > 0) {
			buf = vos_mem_malloc(size);
			if (NULL == buf) {
				ret = A_NO_MEMORY;
				break;
			}
			memcpy(buf, (a_uint8_t *) data, size);
		}

		AR_DEBUG_PRINTF(USB_HIF_DEBUG_CTRL_TRANS, (
				 "ctrl-out req:0x%2.2X, value:0x%4.4X index:0x%4.4X, datasize:%d\n",
				 req, value, index, size));

		result = usb_control_msg(device->udev,
					 usb_sndctrlpipe(device->udev, 0),
					 req,
					 USB_DIR_OUT | USB_TYPE_VENDOR |
					 USB_RECIP_DEVICE, value, index, buf,
					 size, 2 * HZ);

		if (result < 0) {
			AR_DEBUG_PRINTF(ATH_DEBUG_ERR, (
					 "%s failed,result = %d\n",
					 __func__, result));
			ret = A_ERROR;
		}

	} while (FALSE);

	if (buf != NULL)
		vos_mem_free(buf);

	return ret;
}

A_STATUS usb_hif_submit_ctrl_in(HIF_DEVICE_USB *device,
				a_uint8_t req,
				a_uint16_t value,
				a_uint16_t index, void *data, a_uint32_t size)
{
	a_int32_t result = 0;
	A_STATUS ret = A_OK;
	a_uint8_t *buf = NULL;

	do {

		if (size > 0) {
			buf = vos_mem_malloc(size);
			if (NULL == buf) {
				ret = A_NO_MEMORY;
				break;
			}
		}

		AR_DEBUG_PRINTF(USB_HIF_DEBUG_CTRL_TRANS, (
				 "ctrl-in req:0x%2.2X, value:0x%4.4X index:0x%4.4X, datasize:%d\n",
				 req, value, index, size));

		result = usb_control_msg(device->udev,
					 usb_rcvctrlpipe(device->udev, 0),
					 req,
					 USB_DIR_IN | USB_TYPE_VENDOR |
					 USB_RECIP_DEVICE, value, index, buf,
					 size, 2 * HZ);

		if (result < 0) {
			AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
					("%s failed,result = %d\n",
					 __func__, result));
			ret = A_ERROR;
			break;
		}

		memcpy((a_uint8_t *) data, buf, size);

	} while (FALSE);

	if (buf != NULL)
		vos_mem_free(buf);

	return ret;
}

#ifdef HIF_USB_TASKLET
void usb_hif_io_comp_tasklet(long unsigned int context)
#else
void usb_hif_io_comp_work(struct work_struct *work)
#endif
{
#ifdef HIF_USB_TASKLET
	HIF_USB_PIPE *pipe = (HIF_USB_PIPE *) context;
#else
	HIF_USB_PIPE *pipe = container_of(work, HIF_USB_PIPE, io_complete_work);
#endif
	adf_nbuf_t buf;
	HIF_DEVICE_USB *device;
	HTC_FRAME_HDR *HtcHdr;
	struct hif_usb_softc *sc;
	A_UINT8 *data;
	A_UINT32 len;

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));
	device = pipe->device;
	sc = device->sc;

	while ((buf = skb_dequeue(&pipe->io_comp_queue))) {
		a_mem_trace(buf);
		if (pipe->flags & HIF_USB_PIPE_FLAG_TX) {
			AR_DEBUG_PRINTF(USB_HIF_DEBUG_BULK_OUT,
					("+athusb xmit callback " "buf:0x%pK\n",
					 buf));
			HtcHdr = (HTC_FRAME_HDR *) adf_nbuf_get_frag_vaddr(buf, 0);

#ifdef ATH_11AC_TXCOMPACT
#error ATH_11AC_TXCOMPACT only support for High Latency mode
#else
			device->htcCallbacks.txCompletionHandler(device->
								 htcCallbacks.
								 Context, buf,
								 HtcHdr->
								 EndpointID);
#endif
			AR_DEBUG_PRINTF(USB_HIF_DEBUG_BULK_OUT,
					("-athusb xmit callback\n"));
		} else {
			AR_DEBUG_PRINTF(USB_HIF_DEBUG_BULK_IN,
					("+athusb recv callback buf:" "0x%pK\n",
					 buf));
			adf_nbuf_peek_header(buf, &data, &len);

			if (IS_FW_CRASH_DUMP(*((A_UINT32 *) data))) {
				sc->fw_data = data;
				sc->fw_data_len = len;
				device->htcCallbacks.fwEventHandler(
					device->htcCallbacks.Context, A_USB_ERROR);
				dev_kfree_skb(buf);
			} else {
				device->htcCallbacks.rxCompletionHandler(
				device->htcCallbacks.Context, buf,
				pipe->logical_pipe_num);
				AR_DEBUG_PRINTF(USB_HIF_DEBUG_BULK_IN,
					("-athusb recv callback\n"));
			}
		}
	}

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));

}
