| /* |
| * 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) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&pipe->device->cs_lock, flags); |
| pipe->urb_cnt++; |
| DL_ListAdd(&pipe->urb_list_head, &urb_context->link); |
| spin_unlock_irqrestore(&pipe->device->cs_lock, flags); |
| } |
| |
| HIF_URB_CONTEXT *usb_hif_alloc_urb_from_pipe(HIF_USB_PIPE *pipe) |
| { |
| HIF_URB_CONTEXT *urb_context = NULL; |
| DL_LIST *item; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&pipe->device->cs_lock, flags); |
| item = DL_ListRemoveItemFromHead(&pipe->urb_list_head); |
| if (item != NULL) { |
| urb_context = A_CONTAINING_STRUCT(item, HIF_URB_CONTEXT, link); |
| pipe->urb_cnt--; |
| } |
| spin_unlock_irqrestore(&pipe->device->cs_lock, flags); |
| |
| 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; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&pipe->device->cs_lock, flags); |
| item = DL_ListRemoveItemFromHead(&pipe->urb_pending_list); |
| if (item != NULL) |
| urb_context = A_CONTAINING_STRUCT(item, HIF_URB_CONTEXT, link); |
| spin_unlock_irqrestore(&pipe->device->cs_lock, flags); |
| |
| return urb_context; |
| } |
| |
| void usb_hif_enqueue_pending_transfer(HIF_USB_PIPE *pipe, |
| HIF_URB_CONTEXT *urb_context) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&pipe->device->cs_lock, flags); |
| DL_ListInsertTail(&pipe->urb_pending_list, &urb_context->link); |
| spin_unlock_irqrestore(&pipe->device->cs_lock, flags); |
| } |
| |
| void usb_hif_remove_pending_transfer(HIF_URB_CONTEXT *urb_context) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&urb_context->pipe->device->cs_lock, flags); |
| DL_ListRemove(&urb_context->link); |
| spin_unlock_irqrestore(&urb_context->pipe->device->cs_lock, flags); |
| } |
| |
| 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; |
| unsigned long flags; |
| |
| 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. */ |
| spin_lock_irqsave(&pipe->device->rx_prestart_lock, flags); |
| if (--pipe->urb_prestart_cnt == 0) { |
| usb_hif_start_recv_pipes(pipe->device); |
| } |
| spin_unlock_irqrestore(&pipe->device->rx_prestart_lock, flags); |
| |
| 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; |
| unsigned long flags; |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__)); |
| |
| spin_lock_irqsave(&recv_pipe->device->rx_prestart_lock, flags); |
| 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++; |
| |
| } |
| spin_unlock_irqrestore(&recv_pipe->device->rx_prestart_lock, flags); |
| |
| 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__)); |
| |
| } |