mirror of
https://github.com/torvalds/linux.git
synced 2024-12-28 13:51:44 +00:00
84a0d4669c
The USB completion callback does not disable interrupts while acquiring the lock. We want to remove the local_irq_disable() invocation from __usb_hcd_giveback_urb() and therefore it is required for the callback handler to disable the interrupts while acquiring the lock. The callback may be invoked either in IRQ or BH context depending on the USB host controller. Use the _irqsave() variant of the locking primitives. Cc: QCA ath9k Development <ath9k-devel@qca.qualcomm.com> Cc: Kalle Valo <kvalo@codeaurora.org> Cc: "David S. Miller" <davem@davemloft.net> Cc: linux-wireless@vger.kernel.org Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Kalle Valo <kvalo@codeaurora.org>
1449 lines
32 KiB
C
1449 lines
32 KiB
C
/*
|
|
* Copyright (c) 2010-2011 Atheros Communications 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.
|
|
*/
|
|
|
|
#include <asm/unaligned.h>
|
|
#include "htc.h"
|
|
|
|
MODULE_FIRMWARE(HTC_7010_MODULE_FW);
|
|
MODULE_FIRMWARE(HTC_9271_MODULE_FW);
|
|
|
|
static const struct usb_device_id ath9k_hif_usb_ids[] = {
|
|
{ USB_DEVICE(0x0cf3, 0x9271) }, /* Atheros */
|
|
{ USB_DEVICE(0x0cf3, 0x1006) }, /* Atheros */
|
|
{ USB_DEVICE(0x0846, 0x9030) }, /* Netgear N150 */
|
|
{ USB_DEVICE(0x07b8, 0x9271) }, /* Altai WA1011N-GU */
|
|
{ USB_DEVICE(0x07D1, 0x3A10) }, /* Dlink Wireless 150 */
|
|
{ USB_DEVICE(0x13D3, 0x3327) }, /* Azurewave */
|
|
{ USB_DEVICE(0x13D3, 0x3328) }, /* Azurewave */
|
|
{ USB_DEVICE(0x13D3, 0x3346) }, /* IMC Networks */
|
|
{ USB_DEVICE(0x13D3, 0x3348) }, /* Azurewave */
|
|
{ USB_DEVICE(0x13D3, 0x3349) }, /* Azurewave */
|
|
{ USB_DEVICE(0x13D3, 0x3350) }, /* Azurewave */
|
|
{ USB_DEVICE(0x04CA, 0x4605) }, /* Liteon */
|
|
{ USB_DEVICE(0x040D, 0x3801) }, /* VIA */
|
|
{ USB_DEVICE(0x0cf3, 0xb003) }, /* Ubiquiti WifiStation Ext */
|
|
{ USB_DEVICE(0x0cf3, 0xb002) }, /* Ubiquiti WifiStation */
|
|
{ USB_DEVICE(0x057c, 0x8403) }, /* AVM FRITZ!WLAN 11N v2 USB */
|
|
{ USB_DEVICE(0x0471, 0x209e) }, /* Philips (or NXP) PTA01 */
|
|
{ USB_DEVICE(0x1eda, 0x2315) }, /* AirTies */
|
|
|
|
{ USB_DEVICE(0x0cf3, 0x7015),
|
|
.driver_info = AR9287_USB }, /* Atheros */
|
|
{ USB_DEVICE(0x1668, 0x1200),
|
|
.driver_info = AR9287_USB }, /* Verizon */
|
|
|
|
{ USB_DEVICE(0x0cf3, 0x7010),
|
|
.driver_info = AR9280_USB }, /* Atheros */
|
|
{ USB_DEVICE(0x0846, 0x9018),
|
|
.driver_info = AR9280_USB }, /* Netgear WNDA3200 */
|
|
{ USB_DEVICE(0x083A, 0xA704),
|
|
.driver_info = AR9280_USB }, /* SMC Networks */
|
|
{ USB_DEVICE(0x0411, 0x017f),
|
|
.driver_info = AR9280_USB }, /* Sony UWA-BR100 */
|
|
{ USB_DEVICE(0x0411, 0x0197),
|
|
.driver_info = AR9280_USB }, /* Buffalo WLI-UV-AG300P */
|
|
{ USB_DEVICE(0x04da, 0x3904),
|
|
.driver_info = AR9280_USB },
|
|
{ USB_DEVICE(0x0930, 0x0a08),
|
|
.driver_info = AR9280_USB }, /* Toshiba WLM-20U2 and GN-1080 */
|
|
|
|
{ USB_DEVICE(0x0cf3, 0x20ff),
|
|
.driver_info = STORAGE_DEVICE },
|
|
|
|
{ },
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(usb, ath9k_hif_usb_ids);
|
|
|
|
static int __hif_usb_tx(struct hif_device_usb *hif_dev);
|
|
|
|
static void hif_usb_regout_cb(struct urb *urb)
|
|
{
|
|
struct cmd_buf *cmd = (struct cmd_buf *)urb->context;
|
|
|
|
switch (urb->status) {
|
|
case 0:
|
|
break;
|
|
case -ENOENT:
|
|
case -ECONNRESET:
|
|
case -ENODEV:
|
|
case -ESHUTDOWN:
|
|
goto free;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (cmd) {
|
|
ath9k_htc_txcompletion_cb(cmd->hif_dev->htc_handle,
|
|
cmd->skb, true);
|
|
kfree(cmd);
|
|
}
|
|
|
|
return;
|
|
free:
|
|
kfree_skb(cmd->skb);
|
|
kfree(cmd);
|
|
}
|
|
|
|
static int hif_usb_send_regout(struct hif_device_usb *hif_dev,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct urb *urb;
|
|
struct cmd_buf *cmd;
|
|
int ret = 0;
|
|
|
|
urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
if (urb == NULL)
|
|
return -ENOMEM;
|
|
|
|
cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
|
|
if (cmd == NULL) {
|
|
usb_free_urb(urb);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
cmd->skb = skb;
|
|
cmd->hif_dev = hif_dev;
|
|
|
|
usb_fill_int_urb(urb, hif_dev->udev,
|
|
usb_sndintpipe(hif_dev->udev, USB_REG_OUT_PIPE),
|
|
skb->data, skb->len,
|
|
hif_usb_regout_cb, cmd, 1);
|
|
|
|
usb_anchor_urb(urb, &hif_dev->regout_submitted);
|
|
ret = usb_submit_urb(urb, GFP_KERNEL);
|
|
if (ret) {
|
|
usb_unanchor_urb(urb);
|
|
kfree(cmd);
|
|
}
|
|
usb_free_urb(urb);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void hif_usb_mgmt_cb(struct urb *urb)
|
|
{
|
|
struct cmd_buf *cmd = (struct cmd_buf *)urb->context;
|
|
struct hif_device_usb *hif_dev;
|
|
unsigned long flags;
|
|
bool txok = true;
|
|
|
|
if (!cmd || !cmd->skb || !cmd->hif_dev)
|
|
return;
|
|
|
|
hif_dev = cmd->hif_dev;
|
|
|
|
switch (urb->status) {
|
|
case 0:
|
|
break;
|
|
case -ENOENT:
|
|
case -ECONNRESET:
|
|
case -ENODEV:
|
|
case -ESHUTDOWN:
|
|
txok = false;
|
|
|
|
/*
|
|
* If the URBs are being flushed, no need to complete
|
|
* this packet.
|
|
*/
|
|
spin_lock_irqsave(&hif_dev->tx.tx_lock, flags);
|
|
if (hif_dev->tx.flags & HIF_USB_TX_FLUSH) {
|
|
spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
|
|
dev_kfree_skb_any(cmd->skb);
|
|
kfree(cmd);
|
|
return;
|
|
}
|
|
spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
|
|
|
|
break;
|
|
default:
|
|
txok = false;
|
|
break;
|
|
}
|
|
|
|
skb_pull(cmd->skb, 4);
|
|
ath9k_htc_txcompletion_cb(cmd->hif_dev->htc_handle,
|
|
cmd->skb, txok);
|
|
kfree(cmd);
|
|
}
|
|
|
|
static int hif_usb_send_mgmt(struct hif_device_usb *hif_dev,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct urb *urb;
|
|
struct cmd_buf *cmd;
|
|
int ret = 0;
|
|
__le16 *hdr;
|
|
|
|
urb = usb_alloc_urb(0, GFP_ATOMIC);
|
|
if (urb == NULL)
|
|
return -ENOMEM;
|
|
|
|
cmd = kzalloc(sizeof(*cmd), GFP_ATOMIC);
|
|
if (cmd == NULL) {
|
|
usb_free_urb(urb);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
cmd->skb = skb;
|
|
cmd->hif_dev = hif_dev;
|
|
|
|
hdr = skb_push(skb, 4);
|
|
*hdr++ = cpu_to_le16(skb->len - 4);
|
|
*hdr++ = cpu_to_le16(ATH_USB_TX_STREAM_MODE_TAG);
|
|
|
|
usb_fill_bulk_urb(urb, hif_dev->udev,
|
|
usb_sndbulkpipe(hif_dev->udev, USB_WLAN_TX_PIPE),
|
|
skb->data, skb->len,
|
|
hif_usb_mgmt_cb, cmd);
|
|
|
|
usb_anchor_urb(urb, &hif_dev->mgmt_submitted);
|
|
ret = usb_submit_urb(urb, GFP_ATOMIC);
|
|
if (ret) {
|
|
usb_unanchor_urb(urb);
|
|
kfree(cmd);
|
|
}
|
|
usb_free_urb(urb);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline void ath9k_skb_queue_purge(struct hif_device_usb *hif_dev,
|
|
struct sk_buff_head *list)
|
|
{
|
|
struct sk_buff *skb;
|
|
|
|
while ((skb = __skb_dequeue(list)) != NULL) {
|
|
dev_kfree_skb_any(skb);
|
|
}
|
|
}
|
|
|
|
static inline void ath9k_skb_queue_complete(struct hif_device_usb *hif_dev,
|
|
struct sk_buff_head *queue,
|
|
bool txok)
|
|
{
|
|
struct sk_buff *skb;
|
|
|
|
while ((skb = __skb_dequeue(queue)) != NULL) {
|
|
#ifdef CONFIG_ATH9K_HTC_DEBUGFS
|
|
int ln = skb->len;
|
|
#endif
|
|
ath9k_htc_txcompletion_cb(hif_dev->htc_handle,
|
|
skb, txok);
|
|
if (txok) {
|
|
TX_STAT_INC(skb_success);
|
|
TX_STAT_ADD(skb_success_bytes, ln);
|
|
}
|
|
else
|
|
TX_STAT_INC(skb_failed);
|
|
}
|
|
}
|
|
|
|
static void hif_usb_tx_cb(struct urb *urb)
|
|
{
|
|
struct tx_buf *tx_buf = (struct tx_buf *) urb->context;
|
|
struct hif_device_usb *hif_dev;
|
|
bool txok = true;
|
|
|
|
if (!tx_buf || !tx_buf->hif_dev)
|
|
return;
|
|
|
|
hif_dev = tx_buf->hif_dev;
|
|
|
|
switch (urb->status) {
|
|
case 0:
|
|
break;
|
|
case -ENOENT:
|
|
case -ECONNRESET:
|
|
case -ENODEV:
|
|
case -ESHUTDOWN:
|
|
txok = false;
|
|
|
|
/*
|
|
* If the URBs are being flushed, no need to add this
|
|
* URB to the free list.
|
|
*/
|
|
spin_lock(&hif_dev->tx.tx_lock);
|
|
if (hif_dev->tx.flags & HIF_USB_TX_FLUSH) {
|
|
spin_unlock(&hif_dev->tx.tx_lock);
|
|
ath9k_skb_queue_purge(hif_dev, &tx_buf->skb_queue);
|
|
return;
|
|
}
|
|
spin_unlock(&hif_dev->tx.tx_lock);
|
|
|
|
break;
|
|
default:
|
|
txok = false;
|
|
break;
|
|
}
|
|
|
|
ath9k_skb_queue_complete(hif_dev, &tx_buf->skb_queue, txok);
|
|
|
|
/* Re-initialize the SKB queue */
|
|
tx_buf->len = tx_buf->offset = 0;
|
|
__skb_queue_head_init(&tx_buf->skb_queue);
|
|
|
|
/* Add this TX buffer to the free list */
|
|
spin_lock(&hif_dev->tx.tx_lock);
|
|
list_move_tail(&tx_buf->list, &hif_dev->tx.tx_buf);
|
|
hif_dev->tx.tx_buf_cnt++;
|
|
if (!(hif_dev->tx.flags & HIF_USB_TX_STOP))
|
|
__hif_usb_tx(hif_dev); /* Check for pending SKBs */
|
|
TX_STAT_INC(buf_completed);
|
|
spin_unlock(&hif_dev->tx.tx_lock);
|
|
}
|
|
|
|
/* TX lock has to be taken */
|
|
static int __hif_usb_tx(struct hif_device_usb *hif_dev)
|
|
{
|
|
struct tx_buf *tx_buf = NULL;
|
|
struct sk_buff *nskb = NULL;
|
|
int ret = 0, i;
|
|
u16 tx_skb_cnt = 0;
|
|
u8 *buf;
|
|
__le16 *hdr;
|
|
|
|
if (hif_dev->tx.tx_skb_cnt == 0)
|
|
return 0;
|
|
|
|
/* Check if a free TX buffer is available */
|
|
if (list_empty(&hif_dev->tx.tx_buf))
|
|
return 0;
|
|
|
|
tx_buf = list_first_entry(&hif_dev->tx.tx_buf, struct tx_buf, list);
|
|
list_move_tail(&tx_buf->list, &hif_dev->tx.tx_pending);
|
|
hif_dev->tx.tx_buf_cnt--;
|
|
|
|
tx_skb_cnt = min_t(u16, hif_dev->tx.tx_skb_cnt, MAX_TX_AGGR_NUM);
|
|
|
|
for (i = 0; i < tx_skb_cnt; i++) {
|
|
nskb = __skb_dequeue(&hif_dev->tx.tx_skb_queue);
|
|
|
|
/* Should never be NULL */
|
|
BUG_ON(!nskb);
|
|
|
|
hif_dev->tx.tx_skb_cnt--;
|
|
|
|
buf = tx_buf->buf;
|
|
buf += tx_buf->offset;
|
|
hdr = (__le16 *)buf;
|
|
*hdr++ = cpu_to_le16(nskb->len);
|
|
*hdr++ = cpu_to_le16(ATH_USB_TX_STREAM_MODE_TAG);
|
|
buf += 4;
|
|
memcpy(buf, nskb->data, nskb->len);
|
|
tx_buf->len = nskb->len + 4;
|
|
|
|
if (i < (tx_skb_cnt - 1))
|
|
tx_buf->offset += (((tx_buf->len - 1) / 4) + 1) * 4;
|
|
|
|
if (i == (tx_skb_cnt - 1))
|
|
tx_buf->len += tx_buf->offset;
|
|
|
|
__skb_queue_tail(&tx_buf->skb_queue, nskb);
|
|
TX_STAT_INC(skb_queued);
|
|
}
|
|
|
|
usb_fill_bulk_urb(tx_buf->urb, hif_dev->udev,
|
|
usb_sndbulkpipe(hif_dev->udev, USB_WLAN_TX_PIPE),
|
|
tx_buf->buf, tx_buf->len,
|
|
hif_usb_tx_cb, tx_buf);
|
|
|
|
ret = usb_submit_urb(tx_buf->urb, GFP_ATOMIC);
|
|
if (ret) {
|
|
tx_buf->len = tx_buf->offset = 0;
|
|
ath9k_skb_queue_complete(hif_dev, &tx_buf->skb_queue, false);
|
|
__skb_queue_head_init(&tx_buf->skb_queue);
|
|
list_move_tail(&tx_buf->list, &hif_dev->tx.tx_buf);
|
|
hif_dev->tx.tx_buf_cnt++;
|
|
}
|
|
|
|
if (!ret)
|
|
TX_STAT_INC(buf_queued);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int hif_usb_send_tx(struct hif_device_usb *hif_dev, struct sk_buff *skb)
|
|
{
|
|
struct ath9k_htc_tx_ctl *tx_ctl;
|
|
unsigned long flags;
|
|
int ret = 0;
|
|
|
|
spin_lock_irqsave(&hif_dev->tx.tx_lock, flags);
|
|
|
|
if (hif_dev->tx.flags & HIF_USB_TX_STOP) {
|
|
spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Check if the max queue count has been reached */
|
|
if (hif_dev->tx.tx_skb_cnt > MAX_TX_BUF_NUM) {
|
|
spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
|
|
|
|
tx_ctl = HTC_SKB_CB(skb);
|
|
|
|
/* Mgmt/Beacon frames don't use the TX buffer pool */
|
|
if ((tx_ctl->type == ATH9K_HTC_MGMT) ||
|
|
(tx_ctl->type == ATH9K_HTC_BEACON)) {
|
|
ret = hif_usb_send_mgmt(hif_dev, skb);
|
|
}
|
|
|
|
spin_lock_irqsave(&hif_dev->tx.tx_lock, flags);
|
|
|
|
if ((tx_ctl->type == ATH9K_HTC_NORMAL) ||
|
|
(tx_ctl->type == ATH9K_HTC_AMPDU)) {
|
|
__skb_queue_tail(&hif_dev->tx.tx_skb_queue, skb);
|
|
hif_dev->tx.tx_skb_cnt++;
|
|
}
|
|
|
|
/* Check if AMPDUs have to be sent immediately */
|
|
if ((hif_dev->tx.tx_buf_cnt == MAX_TX_URB_NUM) &&
|
|
(hif_dev->tx.tx_skb_cnt < 2)) {
|
|
__hif_usb_tx(hif_dev);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void hif_usb_start(void *hif_handle)
|
|
{
|
|
struct hif_device_usb *hif_dev = hif_handle;
|
|
unsigned long flags;
|
|
|
|
hif_dev->flags |= HIF_USB_START;
|
|
|
|
spin_lock_irqsave(&hif_dev->tx.tx_lock, flags);
|
|
hif_dev->tx.flags &= ~HIF_USB_TX_STOP;
|
|
spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
|
|
}
|
|
|
|
static void hif_usb_stop(void *hif_handle)
|
|
{
|
|
struct hif_device_usb *hif_dev = hif_handle;
|
|
struct tx_buf *tx_buf = NULL, *tx_buf_tmp = NULL;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&hif_dev->tx.tx_lock, flags);
|
|
ath9k_skb_queue_complete(hif_dev, &hif_dev->tx.tx_skb_queue, false);
|
|
hif_dev->tx.tx_skb_cnt = 0;
|
|
hif_dev->tx.flags |= HIF_USB_TX_STOP;
|
|
spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
|
|
|
|
/* The pending URBs have to be canceled. */
|
|
list_for_each_entry_safe(tx_buf, tx_buf_tmp,
|
|
&hif_dev->tx.tx_pending, list) {
|
|
usb_kill_urb(tx_buf->urb);
|
|
}
|
|
|
|
usb_kill_anchored_urbs(&hif_dev->mgmt_submitted);
|
|
}
|
|
|
|
static int hif_usb_send(void *hif_handle, u8 pipe_id, struct sk_buff *skb)
|
|
{
|
|
struct hif_device_usb *hif_dev = hif_handle;
|
|
int ret = 0;
|
|
|
|
switch (pipe_id) {
|
|
case USB_WLAN_TX_PIPE:
|
|
ret = hif_usb_send_tx(hif_dev, skb);
|
|
break;
|
|
case USB_REG_OUT_PIPE:
|
|
ret = hif_usb_send_regout(hif_dev, skb);
|
|
break;
|
|
default:
|
|
dev_err(&hif_dev->udev->dev,
|
|
"ath9k_htc: Invalid TX pipe: %d\n", pipe_id);
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline bool check_index(struct sk_buff *skb, u8 idx)
|
|
{
|
|
struct ath9k_htc_tx_ctl *tx_ctl;
|
|
|
|
tx_ctl = HTC_SKB_CB(skb);
|
|
|
|
if ((tx_ctl->type == ATH9K_HTC_AMPDU) &&
|
|
(tx_ctl->sta_idx == idx))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static void hif_usb_sta_drain(void *hif_handle, u8 idx)
|
|
{
|
|
struct hif_device_usb *hif_dev = hif_handle;
|
|
struct sk_buff *skb, *tmp;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&hif_dev->tx.tx_lock, flags);
|
|
|
|
skb_queue_walk_safe(&hif_dev->tx.tx_skb_queue, skb, tmp) {
|
|
if (check_index(skb, idx)) {
|
|
__skb_unlink(skb, &hif_dev->tx.tx_skb_queue);
|
|
ath9k_htc_txcompletion_cb(hif_dev->htc_handle,
|
|
skb, false);
|
|
hif_dev->tx.tx_skb_cnt--;
|
|
TX_STAT_INC(skb_failed);
|
|
}
|
|
}
|
|
|
|
spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
|
|
}
|
|
|
|
static struct ath9k_htc_hif hif_usb = {
|
|
.transport = ATH9K_HIF_USB,
|
|
.name = "ath9k_hif_usb",
|
|
|
|
.control_ul_pipe = USB_REG_OUT_PIPE,
|
|
.control_dl_pipe = USB_REG_IN_PIPE,
|
|
|
|
.start = hif_usb_start,
|
|
.stop = hif_usb_stop,
|
|
.sta_drain = hif_usb_sta_drain,
|
|
.send = hif_usb_send,
|
|
};
|
|
|
|
static void ath9k_hif_usb_rx_stream(struct hif_device_usb *hif_dev,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct sk_buff *nskb, *skb_pool[MAX_PKT_NUM_IN_TRANSFER];
|
|
int index = 0, i, len = skb->len;
|
|
int rx_remain_len, rx_pkt_len;
|
|
u16 pool_index = 0;
|
|
u8 *ptr;
|
|
|
|
spin_lock(&hif_dev->rx_lock);
|
|
|
|
rx_remain_len = hif_dev->rx_remain_len;
|
|
rx_pkt_len = hif_dev->rx_transfer_len;
|
|
|
|
if (rx_remain_len != 0) {
|
|
struct sk_buff *remain_skb = hif_dev->remain_skb;
|
|
|
|
if (remain_skb) {
|
|
ptr = (u8 *) remain_skb->data;
|
|
|
|
index = rx_remain_len;
|
|
rx_remain_len -= hif_dev->rx_pad_len;
|
|
ptr += rx_pkt_len;
|
|
|
|
memcpy(ptr, skb->data, rx_remain_len);
|
|
|
|
rx_pkt_len += rx_remain_len;
|
|
hif_dev->rx_remain_len = 0;
|
|
skb_put(remain_skb, rx_pkt_len);
|
|
|
|
skb_pool[pool_index++] = remain_skb;
|
|
|
|
} else {
|
|
index = rx_remain_len;
|
|
}
|
|
}
|
|
|
|
spin_unlock(&hif_dev->rx_lock);
|
|
|
|
while (index < len) {
|
|
u16 pkt_len;
|
|
u16 pkt_tag;
|
|
u16 pad_len;
|
|
int chk_idx;
|
|
|
|
ptr = (u8 *) skb->data;
|
|
|
|
pkt_len = get_unaligned_le16(ptr + index);
|
|
pkt_tag = get_unaligned_le16(ptr + index + 2);
|
|
|
|
if (pkt_tag != ATH_USB_RX_STREAM_MODE_TAG) {
|
|
RX_STAT_INC(skb_dropped);
|
|
return;
|
|
}
|
|
|
|
pad_len = 4 - (pkt_len & 0x3);
|
|
if (pad_len == 4)
|
|
pad_len = 0;
|
|
|
|
chk_idx = index;
|
|
index = index + 4 + pkt_len + pad_len;
|
|
|
|
if (index > MAX_RX_BUF_SIZE) {
|
|
spin_lock(&hif_dev->rx_lock);
|
|
hif_dev->rx_remain_len = index - MAX_RX_BUF_SIZE;
|
|
hif_dev->rx_transfer_len =
|
|
MAX_RX_BUF_SIZE - chk_idx - 4;
|
|
hif_dev->rx_pad_len = pad_len;
|
|
|
|
nskb = __dev_alloc_skb(pkt_len + 32, GFP_ATOMIC);
|
|
if (!nskb) {
|
|
dev_err(&hif_dev->udev->dev,
|
|
"ath9k_htc: RX memory allocation error\n");
|
|
spin_unlock(&hif_dev->rx_lock);
|
|
goto err;
|
|
}
|
|
skb_reserve(nskb, 32);
|
|
RX_STAT_INC(skb_allocated);
|
|
|
|
memcpy(nskb->data, &(skb->data[chk_idx+4]),
|
|
hif_dev->rx_transfer_len);
|
|
|
|
/* Record the buffer pointer */
|
|
hif_dev->remain_skb = nskb;
|
|
spin_unlock(&hif_dev->rx_lock);
|
|
} else {
|
|
nskb = __dev_alloc_skb(pkt_len + 32, GFP_ATOMIC);
|
|
if (!nskb) {
|
|
dev_err(&hif_dev->udev->dev,
|
|
"ath9k_htc: RX memory allocation error\n");
|
|
goto err;
|
|
}
|
|
skb_reserve(nskb, 32);
|
|
RX_STAT_INC(skb_allocated);
|
|
|
|
memcpy(nskb->data, &(skb->data[chk_idx+4]), pkt_len);
|
|
skb_put(nskb, pkt_len);
|
|
skb_pool[pool_index++] = nskb;
|
|
}
|
|
}
|
|
|
|
err:
|
|
for (i = 0; i < pool_index; i++) {
|
|
RX_STAT_ADD(skb_completed_bytes, skb_pool[i]->len);
|
|
ath9k_htc_rx_msg(hif_dev->htc_handle, skb_pool[i],
|
|
skb_pool[i]->len, USB_WLAN_RX_PIPE);
|
|
RX_STAT_INC(skb_completed);
|
|
}
|
|
}
|
|
|
|
static void ath9k_hif_usb_rx_cb(struct urb *urb)
|
|
{
|
|
struct sk_buff *skb = (struct sk_buff *) urb->context;
|
|
struct hif_device_usb *hif_dev =
|
|
usb_get_intfdata(usb_ifnum_to_if(urb->dev, 0));
|
|
int ret;
|
|
|
|
if (!skb)
|
|
return;
|
|
|
|
if (!hif_dev)
|
|
goto free;
|
|
|
|
switch (urb->status) {
|
|
case 0:
|
|
break;
|
|
case -ENOENT:
|
|
case -ECONNRESET:
|
|
case -ENODEV:
|
|
case -ESHUTDOWN:
|
|
goto free;
|
|
default:
|
|
goto resubmit;
|
|
}
|
|
|
|
if (likely(urb->actual_length != 0)) {
|
|
skb_put(skb, urb->actual_length);
|
|
ath9k_hif_usb_rx_stream(hif_dev, skb);
|
|
}
|
|
|
|
resubmit:
|
|
skb_reset_tail_pointer(skb);
|
|
skb_trim(skb, 0);
|
|
|
|
usb_anchor_urb(urb, &hif_dev->rx_submitted);
|
|
ret = usb_submit_urb(urb, GFP_ATOMIC);
|
|
if (ret) {
|
|
usb_unanchor_urb(urb);
|
|
goto free;
|
|
}
|
|
|
|
return;
|
|
free:
|
|
kfree_skb(skb);
|
|
}
|
|
|
|
static void ath9k_hif_usb_reg_in_cb(struct urb *urb)
|
|
{
|
|
struct sk_buff *skb = (struct sk_buff *) urb->context;
|
|
struct sk_buff *nskb;
|
|
struct hif_device_usb *hif_dev =
|
|
usb_get_intfdata(usb_ifnum_to_if(urb->dev, 0));
|
|
int ret;
|
|
|
|
if (!skb)
|
|
return;
|
|
|
|
if (!hif_dev)
|
|
goto free;
|
|
|
|
switch (urb->status) {
|
|
case 0:
|
|
break;
|
|
case -ENOENT:
|
|
case -ECONNRESET:
|
|
case -ENODEV:
|
|
case -ESHUTDOWN:
|
|
goto free;
|
|
default:
|
|
skb_reset_tail_pointer(skb);
|
|
skb_trim(skb, 0);
|
|
|
|
goto resubmit;
|
|
}
|
|
|
|
if (likely(urb->actual_length != 0)) {
|
|
skb_put(skb, urb->actual_length);
|
|
|
|
/* Process the command first */
|
|
ath9k_htc_rx_msg(hif_dev->htc_handle, skb,
|
|
skb->len, USB_REG_IN_PIPE);
|
|
|
|
|
|
nskb = alloc_skb(MAX_REG_IN_BUF_SIZE, GFP_ATOMIC);
|
|
if (!nskb) {
|
|
dev_err(&hif_dev->udev->dev,
|
|
"ath9k_htc: REG_IN memory allocation failure\n");
|
|
urb->context = NULL;
|
|
return;
|
|
}
|
|
|
|
usb_fill_int_urb(urb, hif_dev->udev,
|
|
usb_rcvintpipe(hif_dev->udev,
|
|
USB_REG_IN_PIPE),
|
|
nskb->data, MAX_REG_IN_BUF_SIZE,
|
|
ath9k_hif_usb_reg_in_cb, nskb, 1);
|
|
}
|
|
|
|
resubmit:
|
|
usb_anchor_urb(urb, &hif_dev->reg_in_submitted);
|
|
ret = usb_submit_urb(urb, GFP_ATOMIC);
|
|
if (ret) {
|
|
usb_unanchor_urb(urb);
|
|
goto free;
|
|
}
|
|
|
|
return;
|
|
free:
|
|
kfree_skb(skb);
|
|
urb->context = NULL;
|
|
}
|
|
|
|
static void ath9k_hif_usb_dealloc_tx_urbs(struct hif_device_usb *hif_dev)
|
|
{
|
|
struct tx_buf *tx_buf = NULL, *tx_buf_tmp = NULL;
|
|
unsigned long flags;
|
|
|
|
list_for_each_entry_safe(tx_buf, tx_buf_tmp,
|
|
&hif_dev->tx.tx_buf, list) {
|
|
usb_kill_urb(tx_buf->urb);
|
|
list_del(&tx_buf->list);
|
|
usb_free_urb(tx_buf->urb);
|
|
kfree(tx_buf->buf);
|
|
kfree(tx_buf);
|
|
}
|
|
|
|
spin_lock_irqsave(&hif_dev->tx.tx_lock, flags);
|
|
hif_dev->tx.flags |= HIF_USB_TX_FLUSH;
|
|
spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
|
|
|
|
list_for_each_entry_safe(tx_buf, tx_buf_tmp,
|
|
&hif_dev->tx.tx_pending, list) {
|
|
usb_kill_urb(tx_buf->urb);
|
|
list_del(&tx_buf->list);
|
|
usb_free_urb(tx_buf->urb);
|
|
kfree(tx_buf->buf);
|
|
kfree(tx_buf);
|
|
}
|
|
|
|
usb_kill_anchored_urbs(&hif_dev->mgmt_submitted);
|
|
}
|
|
|
|
static int ath9k_hif_usb_alloc_tx_urbs(struct hif_device_usb *hif_dev)
|
|
{
|
|
struct tx_buf *tx_buf;
|
|
int i;
|
|
|
|
INIT_LIST_HEAD(&hif_dev->tx.tx_buf);
|
|
INIT_LIST_HEAD(&hif_dev->tx.tx_pending);
|
|
spin_lock_init(&hif_dev->tx.tx_lock);
|
|
__skb_queue_head_init(&hif_dev->tx.tx_skb_queue);
|
|
init_usb_anchor(&hif_dev->mgmt_submitted);
|
|
|
|
for (i = 0; i < MAX_TX_URB_NUM; i++) {
|
|
tx_buf = kzalloc(sizeof(struct tx_buf), GFP_KERNEL);
|
|
if (!tx_buf)
|
|
goto err;
|
|
|
|
tx_buf->buf = kzalloc(MAX_TX_BUF_SIZE, GFP_KERNEL);
|
|
if (!tx_buf->buf)
|
|
goto err;
|
|
|
|
tx_buf->urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
if (!tx_buf->urb)
|
|
goto err;
|
|
|
|
tx_buf->hif_dev = hif_dev;
|
|
__skb_queue_head_init(&tx_buf->skb_queue);
|
|
|
|
list_add_tail(&tx_buf->list, &hif_dev->tx.tx_buf);
|
|
}
|
|
|
|
hif_dev->tx.tx_buf_cnt = MAX_TX_URB_NUM;
|
|
|
|
return 0;
|
|
err:
|
|
if (tx_buf) {
|
|
kfree(tx_buf->buf);
|
|
kfree(tx_buf);
|
|
}
|
|
ath9k_hif_usb_dealloc_tx_urbs(hif_dev);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static void ath9k_hif_usb_dealloc_rx_urbs(struct hif_device_usb *hif_dev)
|
|
{
|
|
usb_kill_anchored_urbs(&hif_dev->rx_submitted);
|
|
}
|
|
|
|
static int ath9k_hif_usb_alloc_rx_urbs(struct hif_device_usb *hif_dev)
|
|
{
|
|
struct urb *urb = NULL;
|
|
struct sk_buff *skb = NULL;
|
|
int i, ret;
|
|
|
|
init_usb_anchor(&hif_dev->rx_submitted);
|
|
spin_lock_init(&hif_dev->rx_lock);
|
|
|
|
for (i = 0; i < MAX_RX_URB_NUM; i++) {
|
|
|
|
/* Allocate URB */
|
|
urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
if (urb == NULL) {
|
|
ret = -ENOMEM;
|
|
goto err_urb;
|
|
}
|
|
|
|
/* Allocate buffer */
|
|
skb = alloc_skb(MAX_RX_BUF_SIZE, GFP_KERNEL);
|
|
if (!skb) {
|
|
ret = -ENOMEM;
|
|
goto err_skb;
|
|
}
|
|
|
|
usb_fill_bulk_urb(urb, hif_dev->udev,
|
|
usb_rcvbulkpipe(hif_dev->udev,
|
|
USB_WLAN_RX_PIPE),
|
|
skb->data, MAX_RX_BUF_SIZE,
|
|
ath9k_hif_usb_rx_cb, skb);
|
|
|
|
/* Anchor URB */
|
|
usb_anchor_urb(urb, &hif_dev->rx_submitted);
|
|
|
|
/* Submit URB */
|
|
ret = usb_submit_urb(urb, GFP_KERNEL);
|
|
if (ret) {
|
|
usb_unanchor_urb(urb);
|
|
goto err_submit;
|
|
}
|
|
|
|
/*
|
|
* Drop reference count.
|
|
* This ensures that the URB is freed when killing them.
|
|
*/
|
|
usb_free_urb(urb);
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_submit:
|
|
kfree_skb(skb);
|
|
err_skb:
|
|
usb_free_urb(urb);
|
|
err_urb:
|
|
ath9k_hif_usb_dealloc_rx_urbs(hif_dev);
|
|
return ret;
|
|
}
|
|
|
|
static void ath9k_hif_usb_dealloc_reg_in_urbs(struct hif_device_usb *hif_dev)
|
|
{
|
|
usb_kill_anchored_urbs(&hif_dev->reg_in_submitted);
|
|
}
|
|
|
|
static int ath9k_hif_usb_alloc_reg_in_urbs(struct hif_device_usb *hif_dev)
|
|
{
|
|
struct urb *urb = NULL;
|
|
struct sk_buff *skb = NULL;
|
|
int i, ret;
|
|
|
|
init_usb_anchor(&hif_dev->reg_in_submitted);
|
|
|
|
for (i = 0; i < MAX_REG_IN_URB_NUM; i++) {
|
|
|
|
/* Allocate URB */
|
|
urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
if (urb == NULL) {
|
|
ret = -ENOMEM;
|
|
goto err_urb;
|
|
}
|
|
|
|
/* Allocate buffer */
|
|
skb = alloc_skb(MAX_REG_IN_BUF_SIZE, GFP_KERNEL);
|
|
if (!skb) {
|
|
ret = -ENOMEM;
|
|
goto err_skb;
|
|
}
|
|
|
|
usb_fill_int_urb(urb, hif_dev->udev,
|
|
usb_rcvintpipe(hif_dev->udev,
|
|
USB_REG_IN_PIPE),
|
|
skb->data, MAX_REG_IN_BUF_SIZE,
|
|
ath9k_hif_usb_reg_in_cb, skb, 1);
|
|
|
|
/* Anchor URB */
|
|
usb_anchor_urb(urb, &hif_dev->reg_in_submitted);
|
|
|
|
/* Submit URB */
|
|
ret = usb_submit_urb(urb, GFP_KERNEL);
|
|
if (ret) {
|
|
usb_unanchor_urb(urb);
|
|
goto err_submit;
|
|
}
|
|
|
|
/*
|
|
* Drop reference count.
|
|
* This ensures that the URB is freed when killing them.
|
|
*/
|
|
usb_free_urb(urb);
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_submit:
|
|
kfree_skb(skb);
|
|
err_skb:
|
|
usb_free_urb(urb);
|
|
err_urb:
|
|
ath9k_hif_usb_dealloc_reg_in_urbs(hif_dev);
|
|
return ret;
|
|
}
|
|
|
|
static int ath9k_hif_usb_alloc_urbs(struct hif_device_usb *hif_dev)
|
|
{
|
|
/* Register Write */
|
|
init_usb_anchor(&hif_dev->regout_submitted);
|
|
|
|
/* TX */
|
|
if (ath9k_hif_usb_alloc_tx_urbs(hif_dev) < 0)
|
|
goto err;
|
|
|
|
/* RX */
|
|
if (ath9k_hif_usb_alloc_rx_urbs(hif_dev) < 0)
|
|
goto err_rx;
|
|
|
|
/* Register Read */
|
|
if (ath9k_hif_usb_alloc_reg_in_urbs(hif_dev) < 0)
|
|
goto err_reg;
|
|
|
|
return 0;
|
|
err_reg:
|
|
ath9k_hif_usb_dealloc_rx_urbs(hif_dev);
|
|
err_rx:
|
|
ath9k_hif_usb_dealloc_tx_urbs(hif_dev);
|
|
err:
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static void ath9k_hif_usb_dealloc_urbs(struct hif_device_usb *hif_dev)
|
|
{
|
|
usb_kill_anchored_urbs(&hif_dev->regout_submitted);
|
|
ath9k_hif_usb_dealloc_reg_in_urbs(hif_dev);
|
|
ath9k_hif_usb_dealloc_tx_urbs(hif_dev);
|
|
ath9k_hif_usb_dealloc_rx_urbs(hif_dev);
|
|
}
|
|
|
|
static int ath9k_hif_usb_download_fw(struct hif_device_usb *hif_dev)
|
|
{
|
|
int transfer, err;
|
|
const void *data = hif_dev->fw_data;
|
|
size_t len = hif_dev->fw_size;
|
|
u32 addr = AR9271_FIRMWARE;
|
|
u8 *buf = kzalloc(4096, GFP_KERNEL);
|
|
u32 firm_offset;
|
|
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
while (len) {
|
|
transfer = min_t(size_t, len, 4096);
|
|
memcpy(buf, data, transfer);
|
|
|
|
err = usb_control_msg(hif_dev->udev,
|
|
usb_sndctrlpipe(hif_dev->udev, 0),
|
|
FIRMWARE_DOWNLOAD, 0x40 | USB_DIR_OUT,
|
|
addr >> 8, 0, buf, transfer,
|
|
USB_MSG_TIMEOUT);
|
|
if (err < 0) {
|
|
kfree(buf);
|
|
return err;
|
|
}
|
|
|
|
len -= transfer;
|
|
data += transfer;
|
|
addr += transfer;
|
|
}
|
|
kfree(buf);
|
|
|
|
if (IS_AR7010_DEVICE(hif_dev->usb_device_id->driver_info))
|
|
firm_offset = AR7010_FIRMWARE_TEXT;
|
|
else
|
|
firm_offset = AR9271_FIRMWARE_TEXT;
|
|
|
|
/*
|
|
* Issue FW download complete command to firmware.
|
|
*/
|
|
err = usb_control_msg(hif_dev->udev, usb_sndctrlpipe(hif_dev->udev, 0),
|
|
FIRMWARE_DOWNLOAD_COMP,
|
|
0x40 | USB_DIR_OUT,
|
|
firm_offset >> 8, 0, NULL, 0, USB_MSG_TIMEOUT);
|
|
if (err)
|
|
return -EIO;
|
|
|
|
dev_info(&hif_dev->udev->dev, "ath9k_htc: Transferred FW: %s, size: %ld\n",
|
|
hif_dev->fw_name, (unsigned long) hif_dev->fw_size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ath9k_hif_usb_dev_init(struct hif_device_usb *hif_dev)
|
|
{
|
|
int ret;
|
|
|
|
ret = ath9k_hif_usb_download_fw(hif_dev);
|
|
if (ret) {
|
|
dev_err(&hif_dev->udev->dev,
|
|
"ath9k_htc: Firmware - %s download failed\n",
|
|
hif_dev->fw_name);
|
|
return ret;
|
|
}
|
|
|
|
/* Alloc URBs */
|
|
ret = ath9k_hif_usb_alloc_urbs(hif_dev);
|
|
if (ret) {
|
|
dev_err(&hif_dev->udev->dev,
|
|
"ath9k_htc: Unable to allocate URBs\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ath9k_hif_usb_dev_deinit(struct hif_device_usb *hif_dev)
|
|
{
|
|
ath9k_hif_usb_dealloc_urbs(hif_dev);
|
|
}
|
|
|
|
/*
|
|
* If initialization fails or the FW cannot be retrieved,
|
|
* detach the device.
|
|
*/
|
|
static void ath9k_hif_usb_firmware_fail(struct hif_device_usb *hif_dev)
|
|
{
|
|
struct device *dev = &hif_dev->udev->dev;
|
|
struct device *parent = dev->parent;
|
|
|
|
complete_all(&hif_dev->fw_done);
|
|
|
|
if (parent)
|
|
device_lock(parent);
|
|
|
|
device_release_driver(dev);
|
|
|
|
if (parent)
|
|
device_unlock(parent);
|
|
}
|
|
|
|
static void ath9k_hif_usb_firmware_cb(const struct firmware *fw, void *context);
|
|
|
|
/* taken from iwlwifi */
|
|
static int ath9k_hif_request_firmware(struct hif_device_usb *hif_dev,
|
|
bool first)
|
|
{
|
|
char index[8], *chip;
|
|
int ret;
|
|
|
|
if (first) {
|
|
if (htc_use_dev_fw) {
|
|
hif_dev->fw_minor_index = FIRMWARE_MINOR_IDX_MAX + 1;
|
|
sprintf(index, "%s", "dev");
|
|
} else {
|
|
hif_dev->fw_minor_index = FIRMWARE_MINOR_IDX_MAX;
|
|
sprintf(index, "%d", hif_dev->fw_minor_index);
|
|
}
|
|
} else {
|
|
hif_dev->fw_minor_index--;
|
|
sprintf(index, "%d", hif_dev->fw_minor_index);
|
|
}
|
|
|
|
/* test for FW 1.3 */
|
|
if (MAJOR_VERSION_REQ == 1 && hif_dev->fw_minor_index == 3) {
|
|
const char *filename;
|
|
|
|
if (IS_AR7010_DEVICE(hif_dev->usb_device_id->driver_info))
|
|
filename = FIRMWARE_AR7010_1_1;
|
|
else
|
|
filename = FIRMWARE_AR9271;
|
|
|
|
/* expected fw locations:
|
|
* - htc_9271.fw (stable version 1.3, depricated)
|
|
*/
|
|
snprintf(hif_dev->fw_name, sizeof(hif_dev->fw_name),
|
|
"%s", filename);
|
|
|
|
} else if (hif_dev->fw_minor_index < FIRMWARE_MINOR_IDX_MIN) {
|
|
dev_err(&hif_dev->udev->dev, "no suitable firmware found!\n");
|
|
|
|
return -ENOENT;
|
|
} else {
|
|
if (IS_AR7010_DEVICE(hif_dev->usb_device_id->driver_info))
|
|
chip = "7010";
|
|
else
|
|
chip = "9271";
|
|
|
|
/* expected fw locations:
|
|
* - ath9k_htc/htc_9271-1.dev.0.fw (development version)
|
|
* - ath9k_htc/htc_9271-1.4.0.fw (stable version)
|
|
*/
|
|
snprintf(hif_dev->fw_name, sizeof(hif_dev->fw_name),
|
|
"%s/htc_%s-%d.%s.0.fw", HTC_FW_PATH,
|
|
chip, MAJOR_VERSION_REQ, index);
|
|
}
|
|
|
|
ret = request_firmware_nowait(THIS_MODULE, true, hif_dev->fw_name,
|
|
&hif_dev->udev->dev, GFP_KERNEL,
|
|
hif_dev, ath9k_hif_usb_firmware_cb);
|
|
if (ret) {
|
|
dev_err(&hif_dev->udev->dev,
|
|
"ath9k_htc: Async request for firmware %s failed\n",
|
|
hif_dev->fw_name);
|
|
return ret;
|
|
}
|
|
|
|
dev_info(&hif_dev->udev->dev, "ath9k_htc: Firmware %s requested\n",
|
|
hif_dev->fw_name);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void ath9k_hif_usb_firmware_cb(const struct firmware *fw, void *context)
|
|
{
|
|
struct hif_device_usb *hif_dev = context;
|
|
int ret;
|
|
|
|
if (!fw) {
|
|
ret = ath9k_hif_request_firmware(hif_dev, false);
|
|
if (!ret)
|
|
return;
|
|
|
|
dev_err(&hif_dev->udev->dev,
|
|
"ath9k_htc: Failed to get firmware %s\n",
|
|
hif_dev->fw_name);
|
|
goto err_fw;
|
|
}
|
|
|
|
hif_dev->htc_handle = ath9k_htc_hw_alloc(hif_dev, &hif_usb,
|
|
&hif_dev->udev->dev);
|
|
if (hif_dev->htc_handle == NULL)
|
|
goto err_dev_alloc;
|
|
|
|
hif_dev->fw_data = fw->data;
|
|
hif_dev->fw_size = fw->size;
|
|
|
|
/* Proceed with initialization */
|
|
|
|
ret = ath9k_hif_usb_dev_init(hif_dev);
|
|
if (ret)
|
|
goto err_dev_init;
|
|
|
|
ret = ath9k_htc_hw_init(hif_dev->htc_handle,
|
|
&hif_dev->interface->dev,
|
|
hif_dev->usb_device_id->idProduct,
|
|
hif_dev->udev->product,
|
|
hif_dev->usb_device_id->driver_info);
|
|
if (ret) {
|
|
ret = -EINVAL;
|
|
goto err_htc_hw_init;
|
|
}
|
|
|
|
release_firmware(fw);
|
|
hif_dev->flags |= HIF_USB_READY;
|
|
complete_all(&hif_dev->fw_done);
|
|
|
|
return;
|
|
|
|
err_htc_hw_init:
|
|
ath9k_hif_usb_dev_deinit(hif_dev);
|
|
err_dev_init:
|
|
ath9k_htc_hw_free(hif_dev->htc_handle);
|
|
err_dev_alloc:
|
|
release_firmware(fw);
|
|
err_fw:
|
|
ath9k_hif_usb_firmware_fail(hif_dev);
|
|
}
|
|
|
|
/*
|
|
* An exact copy of the function from zd1211rw.
|
|
*/
|
|
static int send_eject_command(struct usb_interface *interface)
|
|
{
|
|
struct usb_device *udev = interface_to_usbdev(interface);
|
|
struct usb_host_interface *iface_desc = &interface->altsetting[0];
|
|
struct usb_endpoint_descriptor *endpoint;
|
|
unsigned char *cmd;
|
|
u8 bulk_out_ep;
|
|
int r;
|
|
|
|
if (iface_desc->desc.bNumEndpoints < 2)
|
|
return -ENODEV;
|
|
|
|
/* Find bulk out endpoint */
|
|
for (r = 1; r >= 0; r--) {
|
|
endpoint = &iface_desc->endpoint[r].desc;
|
|
if (usb_endpoint_dir_out(endpoint) &&
|
|
usb_endpoint_xfer_bulk(endpoint)) {
|
|
bulk_out_ep = endpoint->bEndpointAddress;
|
|
break;
|
|
}
|
|
}
|
|
if (r == -1) {
|
|
dev_err(&udev->dev,
|
|
"ath9k_htc: Could not find bulk out endpoint\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
cmd = kzalloc(31, GFP_KERNEL);
|
|
if (cmd == NULL)
|
|
return -ENODEV;
|
|
|
|
/* USB bulk command block */
|
|
cmd[0] = 0x55; /* bulk command signature */
|
|
cmd[1] = 0x53; /* bulk command signature */
|
|
cmd[2] = 0x42; /* bulk command signature */
|
|
cmd[3] = 0x43; /* bulk command signature */
|
|
cmd[14] = 6; /* command length */
|
|
|
|
cmd[15] = 0x1b; /* SCSI command: START STOP UNIT */
|
|
cmd[19] = 0x2; /* eject disc */
|
|
|
|
dev_info(&udev->dev, "Ejecting storage device...\n");
|
|
r = usb_bulk_msg(udev, usb_sndbulkpipe(udev, bulk_out_ep),
|
|
cmd, 31, NULL, 2 * USB_MSG_TIMEOUT);
|
|
kfree(cmd);
|
|
if (r)
|
|
return r;
|
|
|
|
/* At this point, the device disconnects and reconnects with the real
|
|
* ID numbers. */
|
|
|
|
usb_set_intfdata(interface, NULL);
|
|
return 0;
|
|
}
|
|
|
|
static int ath9k_hif_usb_probe(struct usb_interface *interface,
|
|
const struct usb_device_id *id)
|
|
{
|
|
struct usb_device *udev = interface_to_usbdev(interface);
|
|
struct hif_device_usb *hif_dev;
|
|
int ret = 0;
|
|
|
|
if (id->driver_info == STORAGE_DEVICE)
|
|
return send_eject_command(interface);
|
|
|
|
hif_dev = kzalloc(sizeof(struct hif_device_usb), GFP_KERNEL);
|
|
if (!hif_dev) {
|
|
ret = -ENOMEM;
|
|
goto err_alloc;
|
|
}
|
|
|
|
usb_get_dev(udev);
|
|
|
|
hif_dev->udev = udev;
|
|
hif_dev->interface = interface;
|
|
hif_dev->usb_device_id = id;
|
|
#ifdef CONFIG_PM
|
|
udev->reset_resume = 1;
|
|
#endif
|
|
usb_set_intfdata(interface, hif_dev);
|
|
|
|
init_completion(&hif_dev->fw_done);
|
|
|
|
ret = ath9k_hif_request_firmware(hif_dev, true);
|
|
if (ret)
|
|
goto err_fw_req;
|
|
|
|
return ret;
|
|
|
|
err_fw_req:
|
|
usb_set_intfdata(interface, NULL);
|
|
kfree(hif_dev);
|
|
usb_put_dev(udev);
|
|
err_alloc:
|
|
return ret;
|
|
}
|
|
|
|
static void ath9k_hif_usb_reboot(struct usb_device *udev)
|
|
{
|
|
u32 reboot_cmd = 0xffffffff;
|
|
void *buf;
|
|
int ret;
|
|
|
|
buf = kmemdup(&reboot_cmd, 4, GFP_KERNEL);
|
|
if (!buf)
|
|
return;
|
|
|
|
ret = usb_interrupt_msg(udev, usb_sndintpipe(udev, USB_REG_OUT_PIPE),
|
|
buf, 4, NULL, USB_MSG_TIMEOUT);
|
|
if (ret)
|
|
dev_err(&udev->dev, "ath9k_htc: USB reboot failed\n");
|
|
|
|
kfree(buf);
|
|
}
|
|
|
|
static void ath9k_hif_usb_disconnect(struct usb_interface *interface)
|
|
{
|
|
struct usb_device *udev = interface_to_usbdev(interface);
|
|
struct hif_device_usb *hif_dev = usb_get_intfdata(interface);
|
|
bool unplugged = (udev->state == USB_STATE_NOTATTACHED) ? true : false;
|
|
|
|
if (!hif_dev)
|
|
return;
|
|
|
|
wait_for_completion(&hif_dev->fw_done);
|
|
|
|
if (hif_dev->flags & HIF_USB_READY) {
|
|
ath9k_htc_hw_deinit(hif_dev->htc_handle, unplugged);
|
|
ath9k_htc_hw_free(hif_dev->htc_handle);
|
|
ath9k_hif_usb_dev_deinit(hif_dev);
|
|
}
|
|
|
|
usb_set_intfdata(interface, NULL);
|
|
|
|
/* If firmware was loaded we should drop it
|
|
* go back to first stage bootloader. */
|
|
if (!unplugged && (hif_dev->flags & HIF_USB_READY))
|
|
ath9k_hif_usb_reboot(udev);
|
|
|
|
kfree(hif_dev);
|
|
dev_info(&udev->dev, "ath9k_htc: USB layer deinitialized\n");
|
|
usb_put_dev(udev);
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int ath9k_hif_usb_suspend(struct usb_interface *interface,
|
|
pm_message_t message)
|
|
{
|
|
struct hif_device_usb *hif_dev = usb_get_intfdata(interface);
|
|
|
|
/*
|
|
* The device has to be set to FULLSLEEP mode in case no
|
|
* interface is up.
|
|
*/
|
|
if (!(hif_dev->flags & HIF_USB_START))
|
|
ath9k_htc_suspend(hif_dev->htc_handle);
|
|
|
|
wait_for_completion(&hif_dev->fw_done);
|
|
|
|
if (hif_dev->flags & HIF_USB_READY)
|
|
ath9k_hif_usb_dealloc_urbs(hif_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ath9k_hif_usb_resume(struct usb_interface *interface)
|
|
{
|
|
struct hif_device_usb *hif_dev = usb_get_intfdata(interface);
|
|
struct htc_target *htc_handle = hif_dev->htc_handle;
|
|
int ret;
|
|
const struct firmware *fw;
|
|
|
|
ret = ath9k_hif_usb_alloc_urbs(hif_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (hif_dev->flags & HIF_USB_READY) {
|
|
/* request cached firmware during suspend/resume cycle */
|
|
ret = request_firmware(&fw, hif_dev->fw_name,
|
|
&hif_dev->udev->dev);
|
|
if (ret)
|
|
goto fail_resume;
|
|
|
|
hif_dev->fw_data = fw->data;
|
|
hif_dev->fw_size = fw->size;
|
|
ret = ath9k_hif_usb_download_fw(hif_dev);
|
|
release_firmware(fw);
|
|
if (ret)
|
|
goto fail_resume;
|
|
} else {
|
|
ath9k_hif_usb_dealloc_urbs(hif_dev);
|
|
return -EIO;
|
|
}
|
|
|
|
mdelay(100);
|
|
|
|
ret = ath9k_htc_resume(htc_handle);
|
|
|
|
if (ret)
|
|
goto fail_resume;
|
|
|
|
return 0;
|
|
|
|
fail_resume:
|
|
ath9k_hif_usb_dealloc_urbs(hif_dev);
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static struct usb_driver ath9k_hif_usb_driver = {
|
|
.name = KBUILD_MODNAME,
|
|
.probe = ath9k_hif_usb_probe,
|
|
.disconnect = ath9k_hif_usb_disconnect,
|
|
#ifdef CONFIG_PM
|
|
.suspend = ath9k_hif_usb_suspend,
|
|
.resume = ath9k_hif_usb_resume,
|
|
.reset_resume = ath9k_hif_usb_resume,
|
|
#endif
|
|
.id_table = ath9k_hif_usb_ids,
|
|
.soft_unbind = 1,
|
|
.disable_hub_initiated_lpm = 1,
|
|
};
|
|
|
|
int ath9k_hif_usb_init(void)
|
|
{
|
|
return usb_register(&ath9k_hif_usb_driver);
|
|
}
|
|
|
|
void ath9k_hif_usb_exit(void)
|
|
{
|
|
usb_deregister(&ath9k_hif_usb_driver);
|
|
}
|