summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/net/wireless/rt2x00/rt2800usb.c120
-rw-r--r--drivers/net/wireless/rt2x00/rt2x00.h10
-rw-r--r--drivers/net/wireless/rt2x00/rt2x00dev.c2
-rw-r--r--drivers/net/wireless/rt2x00/rt2x00queue.h12
-rw-r--r--drivers/net/wireless/rt2x00/rt2x00usb.c21
5 files changed, 101 insertions, 64 deletions
diff --git a/drivers/net/wireless/rt2x00/rt2800usb.c b/drivers/net/wireless/rt2x00/rt2800usb.c
index 4eaa6280f367..eacf94b2f382 100644
--- a/drivers/net/wireless/rt2x00/rt2800usb.c
+++ b/drivers/net/wireless/rt2x00/rt2800usb.c
@@ -114,45 +114,103 @@ static bool rt2800usb_txstatus_pending(struct rt2x00_dev *rt2x00dev)
return false;
}
+static inline bool rt2800usb_entry_txstatus_timeout(struct queue_entry *entry)
+{
+ bool tout;
+
+ if (!test_bit(ENTRY_DATA_STATUS_PENDING, &entry->flags))
+ return false;
+
+ tout = time_after(jiffies, entry->last_action + msecs_to_jiffies(100));
+ if (unlikely(tout))
+ WARNING(entry->queue->rt2x00dev,
+ "TX status timeout for entry %d in queue %d\n",
+ entry->entry_idx, entry->queue->qid);
+ return tout;
+
+}
+
+static bool rt2800usb_txstatus_timeout(struct rt2x00_dev *rt2x00dev)
+{
+ struct data_queue *queue;
+ struct queue_entry *entry;
+
+ tx_queue_for_each(rt2x00dev, queue) {
+ entry = rt2x00queue_get_entry(queue, Q_INDEX_DONE);
+ if (rt2800usb_entry_txstatus_timeout(entry))
+ return true;
+ }
+ return false;
+}
+
static bool rt2800usb_tx_sta_fifo_read_completed(struct rt2x00_dev *rt2x00dev,
int urb_status, u32 tx_status)
{
+ bool valid;
+
if (urb_status) {
- WARNING(rt2x00dev, "rt2x00usb_register_read_async failed: %d\n", urb_status);
- return false;
+ WARNING(rt2x00dev, "TX status read failed %d\n", urb_status);
+
+ goto stop_reading;
}
- /* try to read all TX_STA_FIFO entries before scheduling txdone_work */
- if (rt2x00_get_field32(tx_status, TX_STA_FIFO_VALID)) {
- if (!kfifo_put(&rt2x00dev->txstatus_fifo, &tx_status)) {
- WARNING(rt2x00dev, "TX status FIFO overrun, "
- "drop tx status report.\n");
- queue_work(rt2x00dev->workqueue, &rt2x00dev->txdone_work);
- } else
- return true;
- } else if (!kfifo_is_empty(&rt2x00dev->txstatus_fifo)) {
+ valid = rt2x00_get_field32(tx_status, TX_STA_FIFO_VALID);
+ if (valid) {
+ if (!kfifo_put(&rt2x00dev->txstatus_fifo, &tx_status))
+ WARNING(rt2x00dev, "TX status FIFO overrun\n");
+
queue_work(rt2x00dev->workqueue, &rt2x00dev->txdone_work);
+
+ /* Reschedule urb to read TX status again instantly */
+ return true;
} else if (rt2800usb_txstatus_pending(rt2x00dev)) {
- mod_timer(&rt2x00dev->txstatus_timer, jiffies + msecs_to_jiffies(2));
+ /* Read register after 250 us */
+ hrtimer_start(&rt2x00dev->txstatus_timer, ktime_set(0, 250000),
+ HRTIMER_MODE_REL);
+ return false;
}
- return false;
+stop_reading:
+ clear_bit(TX_STATUS_READING, &rt2x00dev->flags);
+ /*
+ * There is small race window above, between txstatus pending check and
+ * clear_bit someone could do rt2x00usb_interrupt_txdone, so recheck
+ * here again if status reading is needed.
+ */
+ if (rt2800usb_txstatus_pending(rt2x00dev) &&
+ test_and_set_bit(TX_STATUS_READING, &rt2x00dev->flags))
+ return true;
+ else
+ return false;
+}
+
+static void rt2800usb_async_read_tx_status(struct rt2x00_dev *rt2x00dev)
+{
+
+ if (test_and_set_bit(TX_STATUS_READING, &rt2x00dev->flags))
+ return;
+
+ /* Read TX_STA_FIFO register after 500 us */
+ hrtimer_start(&rt2x00dev->txstatus_timer, ktime_set(0, 500000),
+ HRTIMER_MODE_REL);
}
static void rt2800usb_tx_dma_done(struct queue_entry *entry)
{
struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev;
- rt2x00usb_register_read_async(rt2x00dev, TX_STA_FIFO,
- rt2800usb_tx_sta_fifo_read_completed);
+ rt2800usb_async_read_tx_status(rt2x00dev);
}
-static void rt2800usb_tx_sta_fifo_timeout(unsigned long data)
+static enum hrtimer_restart rt2800usb_tx_sta_fifo_timeout(struct hrtimer *timer)
{
- struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data;
+ struct rt2x00_dev *rt2x00dev =
+ container_of(timer, struct rt2x00_dev, txstatus_timer);
rt2x00usb_register_read_async(rt2x00dev, TX_STA_FIFO,
rt2800usb_tx_sta_fifo_read_completed);
+
+ return HRTIMER_NORESTART;
}
/*
@@ -540,7 +598,7 @@ static void rt2800usb_txdone_nostatus(struct rt2x00_dev *rt2x00dev)
if (test_bit(ENTRY_DATA_IO_FAILED, &entry->flags))
rt2x00lib_txdone_noinfo(entry, TXDONE_FAILURE);
- else if (rt2x00queue_status_timeout(entry))
+ else if (rt2800usb_entry_txstatus_timeout(entry))
rt2x00lib_txdone_noinfo(entry, TXDONE_UNKNOWN);
else
break;
@@ -553,17 +611,21 @@ static void rt2800usb_work_txdone(struct work_struct *work)
struct rt2x00_dev *rt2x00dev =
container_of(work, struct rt2x00_dev, txdone_work);
- rt2800usb_txdone(rt2x00dev);
+ while (!kfifo_is_empty(&rt2x00dev->txstatus_fifo) ||
+ rt2800usb_txstatus_timeout(rt2x00dev)) {
- rt2800usb_txdone_nostatus(rt2x00dev);
+ rt2800usb_txdone(rt2x00dev);
- /*
- * The hw may delay sending the packet after DMA complete
- * if the medium is busy, thus the TX_STA_FIFO entry is
- * also delayed -> use a timer to retrieve it.
- */
- if (rt2800usb_txstatus_pending(rt2x00dev))
- mod_timer(&rt2x00dev->txstatus_timer, jiffies + msecs_to_jiffies(2));
+ rt2800usb_txdone_nostatus(rt2x00dev);
+
+ /*
+ * The hw may delay sending the packet after DMA complete
+ * if the medium is busy, thus the TX_STA_FIFO entry is
+ * also delayed -> use a timer to retrieve it.
+ */
+ if (rt2800usb_txstatus_pending(rt2x00dev))
+ rt2800usb_async_read_tx_status(rt2x00dev);
+ }
}
/*
@@ -705,9 +767,7 @@ static int rt2800usb_probe_hw(struct rt2x00_dev *rt2x00dev)
__set_bit(REQUIRE_TXSTATUS_FIFO, &rt2x00dev->cap_flags);
__set_bit(REQUIRE_PS_AUTOWAKE, &rt2x00dev->cap_flags);
- setup_timer(&rt2x00dev->txstatus_timer,
- rt2800usb_tx_sta_fifo_timeout,
- (unsigned long) rt2x00dev);
+ rt2x00dev->txstatus_timer.function = rt2800usb_tx_sta_fifo_timeout,
/*
* Set the rssi offset.
diff --git a/drivers/net/wireless/rt2x00/rt2x00.h b/drivers/net/wireless/rt2x00/rt2x00.h
index 65275efcf279..471f87cab4ab 100644
--- a/drivers/net/wireless/rt2x00/rt2x00.h
+++ b/drivers/net/wireless/rt2x00/rt2x00.h
@@ -38,7 +38,7 @@
#include <linux/etherdevice.h>
#include <linux/input-polldev.h>
#include <linux/kfifo.h>
-#include <linux/timer.h>
+#include <linux/hrtimer.h>
#include <net/mac80211.h>
@@ -692,6 +692,12 @@ enum rt2x00_state_flags {
*/
CONFIG_CHANNEL_HT40,
CONFIG_POWERSAVING,
+
+ /*
+ * Mark we currently are sequentially reading TX_STA_FIFO register
+ * FIXME: this is for only rt2800usb, should go to private data
+ */
+ TX_STATUS_READING,
};
/*
@@ -974,7 +980,7 @@ struct rt2x00_dev {
/*
* Timer to ensure tx status reports are read (rt2800usb).
*/
- struct timer_list txstatus_timer;
+ struct hrtimer txstatus_timer;
/*
* Tasklet for processing tx status reports (rt2800pci).
diff --git a/drivers/net/wireless/rt2x00/rt2x00dev.c b/drivers/net/wireless/rt2x00/rt2x00dev.c
index 49a51b4195ef..cffcf2ec990f 100644
--- a/drivers/net/wireless/rt2x00/rt2x00dev.c
+++ b/drivers/net/wireless/rt2x00/rt2x00dev.c
@@ -1232,7 +1232,7 @@ void rt2x00lib_remove_dev(struct rt2x00_dev *rt2x00dev)
cancel_delayed_work_sync(&rt2x00dev->autowakeup_work);
cancel_work_sync(&rt2x00dev->sleep_work);
if (rt2x00_is_usb(rt2x00dev)) {
- del_timer_sync(&rt2x00dev->txstatus_timer);
+ hrtimer_cancel(&rt2x00dev->txstatus_timer);
cancel_work_sync(&rt2x00dev->rxdone_work);
cancel_work_sync(&rt2x00dev->txdone_work);
}
diff --git a/drivers/net/wireless/rt2x00/rt2x00queue.h b/drivers/net/wireless/rt2x00/rt2x00queue.h
index 349008d1fb28..5f1392c72673 100644
--- a/drivers/net/wireless/rt2x00/rt2x00queue.h
+++ b/drivers/net/wireless/rt2x00/rt2x00queue.h
@@ -636,18 +636,6 @@ static inline int rt2x00queue_threshold(struct data_queue *queue)
{
return rt2x00queue_available(queue) < queue->threshold;
}
-
-/**
- * rt2x00queue_status_timeout - Check if a timeout occurred for STATUS reports
- * @entry: Queue entry to check.
- */
-static inline int rt2x00queue_status_timeout(struct queue_entry *entry)
-{
- if (!test_bit(ENTRY_DATA_STATUS_PENDING, &entry->flags))
- return false;
- return time_after(jiffies, entry->last_action + msecs_to_jiffies(100));
-}
-
/**
* rt2x00queue_dma_timeout - Check if a timeout occurred for DMA transfers
* @entry: Queue entry to check.
diff --git a/drivers/net/wireless/rt2x00/rt2x00usb.c b/drivers/net/wireless/rt2x00/rt2x00usb.c
index 2eea3866504d..66094eb21b61 100644
--- a/drivers/net/wireless/rt2x00/rt2x00usb.c
+++ b/drivers/net/wireless/rt2x00/rt2x00usb.c
@@ -526,22 +526,6 @@ static void rt2x00usb_watchdog_tx_dma(struct data_queue *queue)
rt2x00queue_flush_queue(queue, true);
}
-static void rt2x00usb_watchdog_tx_status(struct data_queue *queue)
-{
- WARNING(queue->rt2x00dev, "TX queue %d status timed out,"
- " invoke forced tx handler\n", queue->qid);
-
- queue_work(queue->rt2x00dev->workqueue, &queue->rt2x00dev->txdone_work);
-}
-
-static int rt2x00usb_status_timeout(struct data_queue *queue)
-{
- struct queue_entry *entry;
-
- entry = rt2x00queue_get_entry(queue, Q_INDEX_DONE);
- return rt2x00queue_status_timeout(entry);
-}
-
static int rt2x00usb_dma_timeout(struct data_queue *queue)
{
struct queue_entry *entry;
@@ -558,8 +542,6 @@ void rt2x00usb_watchdog(struct rt2x00_dev *rt2x00dev)
if (!rt2x00queue_empty(queue)) {
if (rt2x00usb_dma_timeout(queue))
rt2x00usb_watchdog_tx_dma(queue);
- if (rt2x00usb_status_timeout(queue))
- rt2x00usb_watchdog_tx_status(queue);
}
}
}
@@ -829,7 +811,8 @@ int rt2x00usb_probe(struct usb_interface *usb_intf,
INIT_WORK(&rt2x00dev->rxdone_work, rt2x00usb_work_rxdone);
INIT_WORK(&rt2x00dev->txdone_work, rt2x00usb_work_txdone);
- init_timer(&rt2x00dev->txstatus_timer);
+ hrtimer_init(&rt2x00dev->txstatus_timer, CLOCK_MONOTONIC,
+ HRTIMER_MODE_REL);
retval = rt2x00usb_alloc_reg(rt2x00dev);
if (retval)