Commit 7e613e16 authored by Ivo van Doorn's avatar Ivo van Doorn Committed by John W. Linville

rt2x00: Move USB tx/rx done handling to workqueue

Move all TX and RX completion handling into a work structure,
which is handeled on the mac80211 workqueue. This simplifies
the code in rt2x00lib since it no longer needs to check if the
device is USB or PCI to decide which mac80211 function should be used.

In the watchdog some changes are needed since it can no longer rely
on the TX completion function to be run while looping through the
entries. (Both functions now work on the same workqueue, so this
would deadlock). So the watchdog now waits for the URB to return,
and handle the TX status report directly.

As a side-effect, the debugfs entry for the RX queue now correctly
displays the positions of the INDEX and INDEX_DONE counters. This
also implies that it is not possible to perform checks like queue_empty()
and queue_full() on the RX queue.
Signed-off-by: default avatarIvo van Doorn <IvDoorn@gmail.com>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent c17512d8
/* /*
Copyright (C) 2004 - 2009 Ivo van Doorn <IvDoorn@gmail.com> Copyright (C) 2010 Willow Garage <http://www.willowgarage.com>
Copyright (C) 2004 - 2010 Ivo van Doorn <IvDoorn@gmail.com>
Copyright (C) 2004 - 2009 Gertjan van Wingerde <gwingerde@gmail.com> Copyright (C) 2004 - 2009 Gertjan van Wingerde <gwingerde@gmail.com>
<http://rt2x00.serialmonkey.com> <http://rt2x00.serialmonkey.com>
...@@ -862,6 +863,12 @@ struct rt2x00_dev { ...@@ -862,6 +863,12 @@ struct rt2x00_dev {
*/ */
struct work_struct intf_work; struct work_struct intf_work;
/**
* Scheduled work for TX/RX done handling (USB devices)
*/
struct work_struct rxdone_work;
struct work_struct txdone_work;
/* /*
* Data queue arrays for RX, TX and Beacon. * Data queue arrays for RX, TX and Beacon.
* The Beacon array also contains the Atim queue * The Beacon array also contains the Atim queue
......
/* /*
Copyright (C) 2004 - 2009 Ivo van Doorn <IvDoorn@gmail.com> Copyright (C) 2010 Willow Garage <http://www.willowgarage.com>
Copyright (C) 2004 - 2010 Ivo van Doorn <IvDoorn@gmail.com>
<http://rt2x00.serialmonkey.com> <http://rt2x00.serialmonkey.com>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
...@@ -383,14 +384,6 @@ void rt2x00lib_txdone(struct queue_entry *entry, ...@@ -383,14 +384,6 @@ void rt2x00lib_txdone(struct queue_entry *entry,
* send the status report back. * send the status report back.
*/ */
if (!(skbdesc_flags & SKBDESC_NOT_MAC80211)) if (!(skbdesc_flags & SKBDESC_NOT_MAC80211))
/*
* Only PCI and SOC devices process the tx status in process
* context. Hence use ieee80211_tx_status for PCI and SOC
* devices and stick to ieee80211_tx_status_irqsafe for USB.
*/
if (rt2x00_is_usb(rt2x00dev))
ieee80211_tx_status_irqsafe(rt2x00dev->hw, entry->skb);
else
ieee80211_tx_status(rt2x00dev->hw, entry->skb); ieee80211_tx_status(rt2x00dev->hw, entry->skb);
else else
dev_kfree_skb_any(entry->skb); dev_kfree_skb_any(entry->skb);
...@@ -403,7 +396,6 @@ void rt2x00lib_txdone(struct queue_entry *entry, ...@@ -403,7 +396,6 @@ void rt2x00lib_txdone(struct queue_entry *entry,
rt2x00dev->ops->lib->clear_entry(entry); rt2x00dev->ops->lib->clear_entry(entry);
clear_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags);
rt2x00queue_index_inc(entry->queue, Q_INDEX_DONE); rt2x00queue_index_inc(entry->queue, Q_INDEX_DONE);
/* /*
...@@ -463,6 +455,10 @@ void rt2x00lib_rxdone(struct rt2x00_dev *rt2x00dev, ...@@ -463,6 +455,10 @@ void rt2x00lib_rxdone(struct rt2x00_dev *rt2x00dev,
struct ieee80211_rx_status *rx_status = &rt2x00dev->rx_status; struct ieee80211_rx_status *rx_status = &rt2x00dev->rx_status;
unsigned int header_length; unsigned int header_length;
int rate_idx; int rate_idx;
if (test_bit(ENTRY_DATA_IO_FAILED, &entry->flags))
goto submit_entry;
/* /*
* Allocate a new sk_buffer. If no new buffer available, drop the * Allocate a new sk_buffer. If no new buffer available, drop the
* received frame and reuse the existing buffer. * received frame and reuse the existing buffer.
...@@ -540,26 +536,17 @@ void rt2x00lib_rxdone(struct rt2x00_dev *rt2x00dev, ...@@ -540,26 +536,17 @@ void rt2x00lib_rxdone(struct rt2x00_dev *rt2x00dev,
*/ */
rt2x00debug_dump_frame(rt2x00dev, DUMP_FRAME_RXDONE, entry->skb); rt2x00debug_dump_frame(rt2x00dev, DUMP_FRAME_RXDONE, entry->skb);
memcpy(IEEE80211_SKB_RXCB(entry->skb), rx_status, sizeof(*rx_status)); memcpy(IEEE80211_SKB_RXCB(entry->skb), rx_status, sizeof(*rx_status));
/*
* Currently only PCI and SOC devices handle rx interrupts in process
* context. Hence, use ieee80211_rx_irqsafe for USB and ieee80211_rx_ni
* for PCI and SOC devices.
*/
if (rt2x00_is_usb(rt2x00dev))
ieee80211_rx_irqsafe(rt2x00dev->hw, entry->skb);
else
ieee80211_rx_ni(rt2x00dev->hw, entry->skb); ieee80211_rx_ni(rt2x00dev->hw, entry->skb);
/* /*
* Replace the skb with the freshly allocated one. * Replace the skb with the freshly allocated one.
*/ */
entry->skb = skb; entry->skb = skb;
entry->flags = 0;
submit_entry:
rt2x00dev->ops->lib->clear_entry(entry); rt2x00dev->ops->lib->clear_entry(entry);
rt2x00queue_index_inc(entry->queue, Q_INDEX); rt2x00queue_index_inc(entry->queue, Q_INDEX);
rt2x00queue_index_inc(entry->queue, Q_INDEX_DONE);
} }
EXPORT_SYMBOL_GPL(rt2x00lib_rxdone); EXPORT_SYMBOL_GPL(rt2x00lib_rxdone);
...@@ -1017,6 +1004,8 @@ void rt2x00lib_remove_dev(struct rt2x00_dev *rt2x00dev) ...@@ -1017,6 +1004,8 @@ void rt2x00lib_remove_dev(struct rt2x00_dev *rt2x00dev)
* Stop all work. * Stop all work.
*/ */
cancel_work_sync(&rt2x00dev->intf_work); cancel_work_sync(&rt2x00dev->intf_work);
cancel_work_sync(&rt2x00dev->rxdone_work);
cancel_work_sync(&rt2x00dev->txdone_work);
/* /*
* Uninitialize device. * Uninitialize device.
......
/* /*
Copyright (C) 2004 - 2009 Ivo van Doorn <IvDoorn@gmail.com> Copyright (C) 2010 Willow Garage <http://www.willowgarage.com>
Copyright (C) 2004 - 2010 Ivo van Doorn <IvDoorn@gmail.com>
Copyright (C) 2004 - 2009 Gertjan van Wingerde <gwingerde@gmail.com> Copyright (C) 2004 - 2009 Gertjan van Wingerde <gwingerde@gmail.com>
<http://rt2x00.serialmonkey.com> <http://rt2x00.serialmonkey.com>
...@@ -730,9 +731,9 @@ void rt2x00queue_init_queues(struct rt2x00_dev *rt2x00dev) ...@@ -730,9 +731,9 @@ void rt2x00queue_init_queues(struct rt2x00_dev *rt2x00dev)
rt2x00queue_reset(queue); rt2x00queue_reset(queue);
for (i = 0; i < queue->limit; i++) { for (i = 0; i < queue->limit; i++) {
queue->entries[i].flags = 0;
rt2x00dev->ops->lib->clear_entry(&queue->entries[i]); rt2x00dev->ops->lib->clear_entry(&queue->entries[i]);
if (queue->qid == QID_RX)
rt2x00queue_index_inc(queue, Q_INDEX);
} }
} }
} }
......
/* /*
Copyright (C) 2004 - 2009 Ivo van Doorn <IvDoorn@gmail.com> Copyright (C) 2004 - 2010 Ivo van Doorn <IvDoorn@gmail.com>
<http://rt2x00.serialmonkey.com> <http://rt2x00.serialmonkey.com>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
...@@ -363,12 +363,16 @@ struct txentry_desc { ...@@ -363,12 +363,16 @@ struct txentry_desc {
* the device has signaled it is done with it. * the device has signaled it is done with it.
* @ENTRY_DATA_PENDING: This entry contains a valid frame and is waiting * @ENTRY_DATA_PENDING: This entry contains a valid frame and is waiting
* for the signal to start sending. * for the signal to start sending.
* @ENTRY_DATA_IO_FAILED: Hardware indicated that an IO error occured
* while transfering the data to the hardware. No TX status report will
* be expected from the hardware.
*/ */
enum queue_entry_flags { enum queue_entry_flags {
ENTRY_BCN_ASSIGNED, ENTRY_BCN_ASSIGNED,
ENTRY_OWNER_DEVICE_DATA, ENTRY_OWNER_DEVICE_DATA,
ENTRY_OWNER_DEVICE_CRYPTO, ENTRY_OWNER_DEVICE_CRYPTO,
ENTRY_DATA_PENDING, ENTRY_DATA_PENDING,
ENTRY_DATA_IO_FAILED
}; };
/** /**
......
/* /*
Copyright (C) 2004 - 2009 Ivo van Doorn <IvDoorn@gmail.com> Copyright (C) 2010 Willow Garage <http://www.willowgarage.com>
Copyright (C) 2004 - 2010 Ivo van Doorn <IvDoorn@gmail.com>
<http://rt2x00.serialmonkey.com> <http://rt2x00.serialmonkey.com>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
...@@ -167,19 +168,12 @@ EXPORT_SYMBOL_GPL(rt2x00usb_regbusy_read); ...@@ -167,19 +168,12 @@ EXPORT_SYMBOL_GPL(rt2x00usb_regbusy_read);
/* /*
* TX data handlers. * TX data handlers.
*/ */
static void rt2x00usb_interrupt_txdone(struct urb *urb) static void rt2x00usb_work_txdone_entry(struct queue_entry *entry)
{ {
struct queue_entry *entry = (struct queue_entry *)urb->context;
struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev;
struct txdone_entry_desc txdesc; struct txdone_entry_desc txdesc;
if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags) ||
!test_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags))
return;
/* /*
* Obtain the status about this packet. * If the transfer to hardware succeeded, it does not mean the
* Note that when the status is 0 it does not mean the
* frame was send out correctly. It only means the frame * frame was send out correctly. It only means the frame
* was succesfully pushed to the hardware, we have no * was succesfully pushed to the hardware, we have no
* way to determine the transmission status right now. * way to determine the transmission status right now.
...@@ -187,15 +181,56 @@ static void rt2x00usb_interrupt_txdone(struct urb *urb) ...@@ -187,15 +181,56 @@ static void rt2x00usb_interrupt_txdone(struct urb *urb)
* in the register). * in the register).
*/ */
txdesc.flags = 0; txdesc.flags = 0;
if (!urb->status) if (test_bit(ENTRY_DATA_IO_FAILED, &entry->flags))
__set_bit(TXDONE_UNKNOWN, &txdesc.flags);
else
__set_bit(TXDONE_FAILURE, &txdesc.flags); __set_bit(TXDONE_FAILURE, &txdesc.flags);
else
__set_bit(TXDONE_UNKNOWN, &txdesc.flags);
txdesc.retry = 0; txdesc.retry = 0;
rt2x00lib_txdone(entry, &txdesc); rt2x00lib_txdone(entry, &txdesc);
} }
static void rt2x00usb_work_txdone(struct work_struct *work)
{
struct rt2x00_dev *rt2x00dev =
container_of(work, struct rt2x00_dev, txdone_work);
struct data_queue *queue;
struct queue_entry *entry;
tx_queue_for_each(rt2x00dev, queue) {
while (!rt2x00queue_empty(queue)) {
entry = rt2x00queue_get_entry(queue, Q_INDEX_DONE);
if (test_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags))
break;
rt2x00usb_work_txdone_entry(entry);
}
}
}
static void rt2x00usb_interrupt_txdone(struct urb *urb)
{
struct queue_entry *entry = (struct queue_entry *)urb->context;
struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev;
if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags) ||
!__test_and_clear_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags))
return;
/*
* Check if the frame was correctly uploaded
*/
if (urb->status)
__set_bit(ENTRY_DATA_IO_FAILED, &entry->flags);
/*
* Schedule the delayed work for reading the TX status
* from the device.
*/
ieee80211_queue_work(rt2x00dev->hw, &rt2x00dev->txdone_work);
}
static inline void rt2x00usb_kick_tx_entry(struct queue_entry *entry) static inline void rt2x00usb_kick_tx_entry(struct queue_entry *entry)
{ {
struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev;
...@@ -294,6 +329,7 @@ EXPORT_SYMBOL_GPL(rt2x00usb_kill_tx_queue); ...@@ -294,6 +329,7 @@ EXPORT_SYMBOL_GPL(rt2x00usb_kill_tx_queue);
static void rt2x00usb_watchdog_reset_tx(struct data_queue *queue) static void rt2x00usb_watchdog_reset_tx(struct data_queue *queue)
{ {
struct queue_entry *entry;
struct queue_entry_priv_usb *entry_priv; struct queue_entry_priv_usb *entry_priv;
unsigned short threshold = queue->threshold; unsigned short threshold = queue->threshold;
...@@ -313,14 +349,22 @@ static void rt2x00usb_watchdog_reset_tx(struct data_queue *queue) ...@@ -313,14 +349,22 @@ static void rt2x00usb_watchdog_reset_tx(struct data_queue *queue)
* Reset all currently uploaded TX frames. * Reset all currently uploaded TX frames.
*/ */
while (!rt2x00queue_empty(queue)) { while (!rt2x00queue_empty(queue)) {
entry_priv = rt2x00queue_get_entry(queue, Q_INDEX_DONE)->priv_data; entry = rt2x00queue_get_entry(queue, Q_INDEX_DONE);
entry_priv = entry->priv_data;
usb_kill_urb(entry_priv->urb); usb_kill_urb(entry_priv->urb);
/* /*
* We need a short delay here to wait for * We need a short delay here to wait for
* the URB to be canceled and invoked the tx_done handler. * the URB to be canceled
*/
do {
udelay(100);
} while (test_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags));
/*
* Invoke the TX done handler
*/ */
udelay(200); rt2x00usb_work_txdone_entry(entry);
} }
/* /*
...@@ -345,15 +389,41 @@ EXPORT_SYMBOL_GPL(rt2x00usb_watchdog); ...@@ -345,15 +389,41 @@ EXPORT_SYMBOL_GPL(rt2x00usb_watchdog);
/* /*
* RX data handlers. * RX data handlers.
*/ */
static void rt2x00usb_work_rxdone(struct work_struct *work)
{
struct rt2x00_dev *rt2x00dev =
container_of(work, struct rt2x00_dev, rxdone_work);
struct queue_entry *entry;
struct skb_frame_desc *skbdesc;
u8 rxd[32];
while (!rt2x00queue_empty(rt2x00dev->rx)) {
entry = rt2x00queue_get_entry(rt2x00dev->rx, Q_INDEX_DONE);
if (test_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags))
break;
/*
* Fill in desc fields of the skb descriptor
*/
skbdesc = get_skb_frame_desc(entry->skb);
skbdesc->desc = rxd;
skbdesc->desc_len = entry->queue->desc_size;
/*
* Send the frame to rt2x00lib for further processing.
*/
rt2x00lib_rxdone(rt2x00dev, entry);
}
}
static void rt2x00usb_interrupt_rxdone(struct urb *urb) static void rt2x00usb_interrupt_rxdone(struct urb *urb)
{ {
struct queue_entry *entry = (struct queue_entry *)urb->context; struct queue_entry *entry = (struct queue_entry *)urb->context;
struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev;
struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb);
u8 rxd[32];
if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags) || if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags) ||
!test_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags)) !__test_and_clear_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags))
return; return;
/* /*
...@@ -361,22 +431,14 @@ static void rt2x00usb_interrupt_rxdone(struct urb *urb) ...@@ -361,22 +431,14 @@ static void rt2x00usb_interrupt_rxdone(struct urb *urb)
* to be actually valid, or if the urb is signaling * to be actually valid, or if the urb is signaling
* a problem. * a problem.
*/ */
if (urb->actual_length < entry->queue->desc_size || urb->status) { if (urb->actual_length < entry->queue->desc_size || urb->status)
set_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags); __set_bit(ENTRY_DATA_IO_FAILED, &entry->flags);
usb_submit_urb(urb, GFP_ATOMIC);
return;
}
/* /*
* Fill in desc fields of the skb descriptor * Schedule the delayed work for reading the RX status
* from the device.
*/ */
skbdesc->desc = rxd; ieee80211_queue_work(rt2x00dev->hw, &rt2x00dev->rxdone_work);
skbdesc->desc_len = entry->queue->desc_size;
/*
* Send the frame to rt2x00lib for further processing.
*/
rt2x00lib_rxdone(rt2x00dev, entry);
} }
/* /*
...@@ -405,6 +467,8 @@ void rt2x00usb_clear_entry(struct queue_entry *entry) ...@@ -405,6 +467,8 @@ void rt2x00usb_clear_entry(struct queue_entry *entry)
struct queue_entry_priv_usb *entry_priv = entry->priv_data; struct queue_entry_priv_usb *entry_priv = entry->priv_data;
int pipe; int pipe;
entry->flags = 0;
if (entry->queue->qid == QID_RX) { if (entry->queue->qid == QID_RX) {
pipe = usb_rcvbulkpipe(usb_dev, entry->queue->usb_endpoint); pipe = usb_rcvbulkpipe(usb_dev, entry->queue->usb_endpoint);
usb_fill_bulk_urb(entry_priv->urb, usb_dev, pipe, usb_fill_bulk_urb(entry_priv->urb, usb_dev, pipe,
...@@ -413,8 +477,6 @@ void rt2x00usb_clear_entry(struct queue_entry *entry) ...@@ -413,8 +477,6 @@ void rt2x00usb_clear_entry(struct queue_entry *entry)
set_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags); set_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags);
usb_submit_urb(entry_priv->urb, GFP_ATOMIC); usb_submit_urb(entry_priv->urb, GFP_ATOMIC);
} else {
entry->flags = 0;
} }
} }
EXPORT_SYMBOL_GPL(rt2x00usb_clear_entry); EXPORT_SYMBOL_GPL(rt2x00usb_clear_entry);
...@@ -659,6 +721,9 @@ int rt2x00usb_probe(struct usb_interface *usb_intf, ...@@ -659,6 +721,9 @@ int rt2x00usb_probe(struct usb_interface *usb_intf,
rt2x00_set_chip_intf(rt2x00dev, RT2X00_CHIP_INTF_USB); rt2x00_set_chip_intf(rt2x00dev, RT2X00_CHIP_INTF_USB);
INIT_WORK(&rt2x00dev->rxdone_work, rt2x00usb_work_rxdone);
INIT_WORK(&rt2x00dev->txdone_work, rt2x00usb_work_txdone);
retval = rt2x00usb_alloc_reg(rt2x00dev); retval = rt2x00usb_alloc_reg(rt2x00dev);
if (retval) if (retval)
goto exit_free_device; goto exit_free_device;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment