2011-02-27 22:30:54

by Ido Yariv

[permalink] [raw]
Subject: [PATCH 0/7] wl12xx: Optimizing throughput and CPU usage

The following patches tweak the driver in multiple places to achieve maximal
throughput and minimal CPU usage.

Throughput improved significantly with these patches, up to 50% increase in
some cases.

Ido Yariv (7):
wl12xx: Reorder data handling in irq_work
wl12xx: Do end-of-transactions transfers only if needed
wl12xx: Change claiming of the SDIO bus
wl12xx: Switch to a threaded interrupt handler
wl12xx: Switch to level trigger interrupts
wl12xx: Avoid redundant TX work
wl12xx: Modify requested number of memory blocks

drivers/net/wireless/wl12xx/boot.h | 2 +-
drivers/net/wireless/wl12xx/debugfs.c | 2 +-
drivers/net/wireless/wl12xx/io.h | 1 +
drivers/net/wireless/wl12xx/main.c | 157 ++++++++++++++++++++------------
drivers/net/wireless/wl12xx/ps.c | 6 +-
drivers/net/wireless/wl12xx/ps.h | 2 +-
drivers/net/wireless/wl12xx/rx.c | 11 ++-
drivers/net/wireless/wl12xx/sdio.c | 26 ++----
drivers/net/wireless/wl12xx/spi.c | 19 ++---
drivers/net/wireless/wl12xx/tx.c | 15 +++-
drivers/net/wireless/wl12xx/wl12xx.h | 12 ++-
11 files changed, 147 insertions(+), 106 deletions(-)



2011-02-27 22:31:01

by Ido Yariv

[permalink] [raw]
Subject: [PATCH 3/7] wl12xx: Change claiming of the SDIO bus

The SDIO bus is claimed and released for each SDIO transaction. In
addition to the few CPU cycles it takes to claim and release the bus, it
may also cause undesired side effects such as the MMC host stopping its
internal clocks.

Since only the wl12xx_sdio driver drives this SDIO card, it is safe to
claim the SDIO host once (on power on), and release it only when turning
the power off.

This patch was inspired by Juuso Oikarinen's ([email protected])
patch "wl12xx: Change claiming of the (SDIO) bus".

Signed-off-by: Ido Yariv <[email protected]>
---
drivers/net/wireless/wl12xx/sdio.c | 10 ----------
1 files changed, 0 insertions(+), 10 deletions(-)

diff --git a/drivers/net/wireless/wl12xx/sdio.c b/drivers/net/wireless/wl12xx/sdio.c
index d5e8748..b9c3747 100644
--- a/drivers/net/wireless/wl12xx/sdio.c
+++ b/drivers/net/wireless/wl12xx/sdio.c
@@ -106,8 +106,6 @@ static void wl1271_sdio_raw_read(struct wl1271 *wl, int addr, void *buf,
int ret;
struct sdio_func *func = wl_to_func(wl);

- sdio_claim_host(func);
-
if (unlikely(addr == HW_ACCESS_ELP_CTRL_REG_ADDR)) {
((u8 *)buf)[0] = sdio_f0_readb(func, addr, &ret);
wl1271_debug(DEBUG_SDIO, "sdio read 52 addr 0x%x, byte 0x%02x",
@@ -123,8 +121,6 @@ static void wl1271_sdio_raw_read(struct wl1271 *wl, int addr, void *buf,
wl1271_dump_ascii(DEBUG_SDIO, "data: ", buf, len);
}

- sdio_release_host(func);
-
if (ret)
wl1271_error("sdio read failed (%d)", ret);
}
@@ -135,8 +131,6 @@ static void wl1271_sdio_raw_write(struct wl1271 *wl, int addr, void *buf,
int ret;
struct sdio_func *func = wl_to_func(wl);

- sdio_claim_host(func);
-
if (unlikely(addr == HW_ACCESS_ELP_CTRL_REG_ADDR)) {
sdio_f0_writeb(func, ((u8 *)buf)[0], addr, &ret);
wl1271_debug(DEBUG_SDIO, "sdio write 52 addr 0x%x, byte 0x%02x",
@@ -152,8 +146,6 @@ static void wl1271_sdio_raw_write(struct wl1271 *wl, int addr, void *buf,
ret = sdio_memcpy_toio(func, addr, buf, len);
}

- sdio_release_host(func);
-
if (ret)
wl1271_error("sdio write failed (%d)", ret);
}
@@ -170,7 +162,6 @@ static int wl1271_sdio_power_on(struct wl1271 *wl)

sdio_claim_host(func);
sdio_enable_func(func);
- sdio_release_host(func);

out:
return ret;
@@ -180,7 +171,6 @@ static int wl1271_sdio_power_off(struct wl1271 *wl)
{
struct sdio_func *func = wl_to_func(wl);

- sdio_claim_host(func);
sdio_disable_func(func);
sdio_release_host(func);

--
1.7.1


2011-02-27 23:56:53

by Gabay, Benzy

[permalink] [raw]
Subject: RE: [PATCH 5/7] wl12xx: Switch to level trigger interrupts



> -----Original Message-----
> The interrupt of the wl12xx is a level interrupt in nature, since the
> interrupt line is not auto-reset. However, since resetting the
> interrupt
> requires bus transactions, this cannot be done from an interrupt
> context. Thus, requesting a level interrupt would require to disable
> the
> irq and re-enable it after the HW is acknowledged. Since we now request
> a threaded irq, this can also be done by specifying the IRQF_ONESHOT
> flag.
>
> Triggering on an edge can be problematic in some platforms, if the
> sampling frequency is not sufficient for detecting very frequent
> interrupts. In case an interrupt is missed, the driver will hang as the
> interrupt line will stay high until it is acknowledged by the driver,
> which will never happen.
>
> Fix this by requesting a level triggered interrupt, with the
> IRQF_ONESHOT flag.
>
> Signed-off-by: Ido Yariv <[email protected]>
> ---
> drivers/net/wireless/wl12xx/sdio.c | 2 +-
> drivers/net/wireless/wl12xx/spi.c | 2 +-
> 2 files changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/net/wireless/wl12xx/sdio.c
> b/drivers/net/wireless/wl12xx/sdio.c
> index 7ee9d29..a4f6417 100644
> --- a/drivers/net/wireless/wl12xx/sdio.c
> +++ b/drivers/net/wireless/wl12xx/sdio.c
> @@ -229,7 +229,7 @@ static int __devinit wl1271_probe(struct sdio_func
> *func,
> wl->ref_clock = wlan_data->board_ref_clock;
>
> ret = request_threaded_irq(wl->irq, wl1271_hardirq, wl1271_irq,
> - IRQF_TRIGGER_RISING,
> + IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
> DRIVER_NAME, wl);
> if (ret < 0) {
> wl1271_error("request_irq() failed: %d", ret);
> diff --git a/drivers/net/wireless/wl12xx/spi.c
> b/drivers/net/wireless/wl12xx/spi.c
> index df5a00f..18cf017 100644
> --- a/drivers/net/wireless/wl12xx/spi.c
> +++ b/drivers/net/wireless/wl12xx/spi.c
> @@ -409,7 +409,7 @@ static int __devinit wl1271_probe(struct spi_device
> *spi)
> }
>
> ret = request_threaded_irq(wl->irq, wl1271_hardirq, wl1271_irq,
> - IRQF_TRIGGER_RISING,
> + IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
> DRIVER_NAME, wl);
> if (ret < 0) {
> wl1271_error("request_irq() failed: %d", ret);
> --
> 1.7.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-
> wireless" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html

Ack for TI OMAP on all flavors

Benzy Gabay
Texas Instruments

2011-02-27 22:31:03

by Ido Yariv

[permalink] [raw]
Subject: [PATCH 4/7] wl12xx: Switch to a threaded interrupt handler

To achieve maximal throughput, it is very important to react to
interrupts as soon as possible. Currently the interrupt handler wakes up
a worker for handling interrupts in process context. A cleaner and more
efficient design would be to request a threaded interrupt handler. This
handler's priority is very high, and can do blocking operations such as
SDIO/SPI transactions.

Some work can be deferred, mostly calls to mac80211 APIs
(ieee80211_rx_ni and ieee80211_tx_status). By deferring such work to a
different worker, we can keep the irq handler thread more I/O
responsive. In addition, on multi-core systems the two threads can be
scheduled on different cores, which will improve overall performance.

The use of WL1271_FLAG_IRQ_PENDING & WL1271_FLAG_IRQ_RUNNING was
changed. For simplicity, always query the FW for more pending
interrupts. Since there are relatively long bursts of interrupts, the
extra FW status read overhead is negligible. In addition, this enables
registering the IRQ handler with the ONESHOT option.

Signed-off-by: Ido Yariv <[email protected]>
---
drivers/net/wireless/wl12xx/debugfs.c | 2 +-
drivers/net/wireless/wl12xx/io.h | 1 +
drivers/net/wireless/wl12xx/main.c | 114 +++++++++++++++++++--------------
drivers/net/wireless/wl12xx/ps.c | 6 +-
drivers/net/wireless/wl12xx/ps.h | 2 +-
drivers/net/wireless/wl12xx/rx.c | 3 +-
drivers/net/wireless/wl12xx/sdio.c | 16 ++---
drivers/net/wireless/wl12xx/spi.c | 19 ++----
drivers/net/wireless/wl12xx/tx.c | 5 +-
drivers/net/wireless/wl12xx/wl12xx.h | 11 ++-
10 files changed, 97 insertions(+), 82 deletions(-)

diff --git a/drivers/net/wireless/wl12xx/debugfs.c b/drivers/net/wireless/wl12xx/debugfs.c
index bebfa28..8e75b09 100644
--- a/drivers/net/wireless/wl12xx/debugfs.c
+++ b/drivers/net/wireless/wl12xx/debugfs.c
@@ -99,7 +99,7 @@ static void wl1271_debugfs_update_stats(struct wl1271 *wl)

mutex_lock(&wl->mutex);

- ret = wl1271_ps_elp_wakeup(wl, false);
+ ret = wl1271_ps_elp_wakeup(wl);
if (ret < 0)
goto out;

diff --git a/drivers/net/wireless/wl12xx/io.h b/drivers/net/wireless/wl12xx/io.h
index 844b32b..c1aac82 100644
--- a/drivers/net/wireless/wl12xx/io.h
+++ b/drivers/net/wireless/wl12xx/io.h
@@ -168,5 +168,6 @@ void wl1271_unregister_hw(struct wl1271 *wl);
int wl1271_init_ieee80211(struct wl1271 *wl);
struct ieee80211_hw *wl1271_alloc_hw(void);
int wl1271_free_hw(struct wl1271 *wl);
+irqreturn_t wl1271_irq(int irq, void *data);

#endif
diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c
index 443f829..9ceba52 100644
--- a/drivers/net/wireless/wl12xx/main.c
+++ b/drivers/net/wireless/wl12xx/main.c
@@ -374,7 +374,7 @@ static int wl1271_dev_notify(struct notifier_block *me, unsigned long what,
if (!test_bit(WL1271_FLAG_STA_ASSOCIATED, &wl->flags))
goto out;

- ret = wl1271_ps_elp_wakeup(wl, false);
+ ret = wl1271_ps_elp_wakeup(wl);
if (ret < 0)
goto out;

@@ -635,16 +635,12 @@ static void wl1271_fw_status(struct wl1271 *wl,
(s64)le32_to_cpu(status->fw_localtime);
}

-#define WL1271_IRQ_MAX_LOOPS 10
-
-static void wl1271_irq_work(struct work_struct *work)
+irqreturn_t wl1271_irq(int irq, void *cookie)
{
int ret;
u32 intr;
- int loopcount = WL1271_IRQ_MAX_LOOPS;
- unsigned long flags;
- struct wl1271 *wl =
- container_of(work, struct wl1271, irq_work);
+ struct wl1271 *wl = (struct wl1271 *)cookie;
+ bool done = false;

mutex_lock(&wl->mutex);

@@ -653,26 +649,27 @@ static void wl1271_irq_work(struct work_struct *work)
if (unlikely(wl->state == WL1271_STATE_OFF))
goto out;

- ret = wl1271_ps_elp_wakeup(wl, true);
+ ret = wl1271_ps_elp_wakeup(wl);
if (ret < 0)
goto out;

- spin_lock_irqsave(&wl->wl_lock, flags);
- while (test_bit(WL1271_FLAG_IRQ_PENDING, &wl->flags) && loopcount) {
- clear_bit(WL1271_FLAG_IRQ_PENDING, &wl->flags);
- spin_unlock_irqrestore(&wl->wl_lock, flags);
- loopcount--;
+ while (!done) {
+ /*
+ * In order to avoid a race with the hardirq, clear the flag
+ * before acknowledging the chip. Since the mutex is held,
+ * wl1271_ps_elp_wakeup cannot be called concurrently.
+ */
+ clear_bit(WL1271_FLAG_IRQ_RUNNING, &wl->flags);
+ smp_mb__after_clear_bit();

wl1271_fw_status(wl, wl->fw_status);
intr = le32_to_cpu(wl->fw_status->common.intr);
+ intr &= WL1271_INTR_MASK;
if (!intr) {
- wl1271_debug(DEBUG_IRQ, "Zero interrupt received.");
- spin_lock_irqsave(&wl->wl_lock, flags);
+ done = true;
continue;
}

- intr &= WL1271_INTR_MASK;
-
if (unlikely(intr & WL1271_ACX_INTR_WATCHDOG)) {
wl1271_error("watchdog interrupt received! "
"starting recovery.");
@@ -719,20 +716,37 @@ static void wl1271_irq_work(struct work_struct *work)

if (intr & WL1271_ACX_INTR_HW_AVAILABLE)
wl1271_debug(DEBUG_IRQ, "WL1271_ACX_INTR_HW_AVAILABLE");
-
- spin_lock_irqsave(&wl->wl_lock, flags);
}

- if (test_bit(WL1271_FLAG_IRQ_PENDING, &wl->flags))
- ieee80211_queue_work(wl->hw, &wl->irq_work);
- else
- clear_bit(WL1271_FLAG_IRQ_RUNNING, &wl->flags);
- spin_unlock_irqrestore(&wl->wl_lock, flags);
-
wl1271_ps_elp_sleep(wl);

out:
mutex_unlock(&wl->mutex);
+
+ return IRQ_HANDLED;
+}
+EXPORT_SYMBOL_GPL(wl1271_irq);
+
+static void wl1271_netstack_work(struct work_struct *work)
+{
+ struct wl1271 *wl =
+ container_of(work, struct wl1271, netstack_work);
+ struct sk_buff *skb;
+ bool done = false;
+
+ while (!done) {
+ done = true;
+ /* Pass all received frames to the network stack */
+ while ((skb = skb_dequeue(&wl->deferred_rx_queue)))
+ ieee80211_rx_ni(wl->hw, skb);
+
+ /* Return sent skbs to the network stack */
+ while ((skb = skb_dequeue(&wl->deferred_tx_queue))) {
+ ieee80211_tx_status(wl->hw, skb);
+ /* We did some work, so check the rx queue again */
+ done = false;
+ }
+ }
}

static int wl1271_fetch_firmware(struct wl1271 *wl)
@@ -974,7 +988,6 @@ int wl1271_plt_start(struct wl1271 *wl)
goto out;

irq_disable:
- wl1271_disable_interrupts(wl);
mutex_unlock(&wl->mutex);
/* Unlocking the mutex in the middle of handling is
inherently unsafe. In this case we deem it safe to do,
@@ -983,7 +996,8 @@ irq_disable:
work function will not do anything.) Also, any other
possible concurrent operations will fail due to the
current state, hence the wl1271 struct should be safe. */
- cancel_work_sync(&wl->irq_work);
+ wl1271_disable_interrupts(wl);
+ cancel_work_sync(&wl->netstack_work);
mutex_lock(&wl->mutex);
power_off:
wl1271_power_off(wl);
@@ -1010,14 +1024,14 @@ int __wl1271_plt_stop(struct wl1271 *wl)
goto out;
}

- wl1271_disable_interrupts(wl);
wl1271_power_off(wl);

wl->state = WL1271_STATE_OFF;
wl->rx_counter = 0;

mutex_unlock(&wl->mutex);
- cancel_work_sync(&wl->irq_work);
+ wl1271_disable_interrupts(wl);
+ cancel_work_sync(&wl->netstack_work);
cancel_work_sync(&wl->recovery_work);
mutex_lock(&wl->mutex);
out:
@@ -1171,7 +1185,6 @@ static int wl1271_op_add_interface(struct ieee80211_hw *hw,
break;

irq_disable:
- wl1271_disable_interrupts(wl);
mutex_unlock(&wl->mutex);
/* Unlocking the mutex in the middle of handling is
inherently unsafe. In this case we deem it safe to do,
@@ -1180,7 +1193,8 @@ irq_disable:
work function will not do anything.) Also, any other
possible concurrent operations will fail due to the
current state, hence the wl1271 struct should be safe. */
- cancel_work_sync(&wl->irq_work);
+ wl1271_disable_interrupts(wl);
+ cancel_work_sync(&wl->netstack_work);
mutex_lock(&wl->mutex);
power_off:
wl1271_power_off(wl);
@@ -1246,12 +1260,11 @@ static void __wl1271_op_remove_interface(struct wl1271 *wl)

wl->state = WL1271_STATE_OFF;

- wl1271_disable_interrupts(wl);
-
mutex_unlock(&wl->mutex);

+ wl1271_disable_interrupts(wl);
cancel_delayed_work_sync(&wl->scan_complete_work);
- cancel_work_sync(&wl->irq_work);
+ cancel_work_sync(&wl->netstack_work);
cancel_work_sync(&wl->tx_work);
cancel_delayed_work_sync(&wl->pspoll_work);
cancel_delayed_work_sync(&wl->elp_work);
@@ -1527,7 +1540,7 @@ static int wl1271_op_config(struct ieee80211_hw *hw, u32 changed)

is_ap = (wl->bss_type == BSS_TYPE_AP_BSS);

- ret = wl1271_ps_elp_wakeup(wl, false);
+ ret = wl1271_ps_elp_wakeup(wl);
if (ret < 0)
goto out;

@@ -1683,7 +1696,7 @@ static void wl1271_op_configure_filter(struct ieee80211_hw *hw,
if (unlikely(wl->state == WL1271_STATE_OFF))
goto out;

- ret = wl1271_ps_elp_wakeup(wl, false);
+ ret = wl1271_ps_elp_wakeup(wl);
if (ret < 0)
goto out;

@@ -1912,7 +1925,7 @@ static int wl1271_op_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
goto out_unlock;
}

- ret = wl1271_ps_elp_wakeup(wl, false);
+ ret = wl1271_ps_elp_wakeup(wl);
if (ret < 0)
goto out_unlock;

@@ -2015,7 +2028,7 @@ static int wl1271_op_hw_scan(struct ieee80211_hw *hw,
goto out;
}

- ret = wl1271_ps_elp_wakeup(wl, false);
+ ret = wl1271_ps_elp_wakeup(wl);
if (ret < 0)
goto out;

@@ -2041,7 +2054,7 @@ static int wl1271_op_set_frag_threshold(struct ieee80211_hw *hw, u32 value)
goto out;
}

- ret = wl1271_ps_elp_wakeup(wl, false);
+ ret = wl1271_ps_elp_wakeup(wl);
if (ret < 0)
goto out;

@@ -2069,7 +2082,7 @@ static int wl1271_op_set_rts_threshold(struct ieee80211_hw *hw, u32 value)
goto out;
}

- ret = wl1271_ps_elp_wakeup(wl, false);
+ ret = wl1271_ps_elp_wakeup(wl);
if (ret < 0)
goto out;

@@ -2548,7 +2561,7 @@ static void wl1271_op_bss_info_changed(struct ieee80211_hw *hw,
if (unlikely(wl->state == WL1271_STATE_OFF))
goto out;

- ret = wl1271_ps_elp_wakeup(wl, false);
+ ret = wl1271_ps_elp_wakeup(wl);
if (ret < 0)
goto out;

@@ -2603,7 +2616,7 @@ static int wl1271_op_conf_tx(struct ieee80211_hw *hw, u16 queue,
conf_tid->apsd_conf[0] = 0;
conf_tid->apsd_conf[1] = 0;
} else {
- ret = wl1271_ps_elp_wakeup(wl, false);
+ ret = wl1271_ps_elp_wakeup(wl);
if (ret < 0)
goto out;

@@ -2649,7 +2662,7 @@ static u64 wl1271_op_get_tsf(struct ieee80211_hw *hw)
if (unlikely(wl->state == WL1271_STATE_OFF))
goto out;

- ret = wl1271_ps_elp_wakeup(wl, false);
+ ret = wl1271_ps_elp_wakeup(wl);
if (ret < 0)
goto out;

@@ -2738,7 +2751,7 @@ static int wl1271_op_sta_add(struct ieee80211_hw *hw,
if (ret < 0)
goto out;

- ret = wl1271_ps_elp_wakeup(wl, false);
+ ret = wl1271_ps_elp_wakeup(wl);
if (ret < 0)
goto out_free_sta;

@@ -2781,7 +2794,7 @@ static int wl1271_op_sta_remove(struct ieee80211_hw *hw,
if (WARN_ON(!test_bit(id, wl->ap_hlid_map)))
goto out;

- ret = wl1271_ps_elp_wakeup(wl, false);
+ ret = wl1271_ps_elp_wakeup(wl);
if (ret < 0)
goto out;

@@ -2814,7 +2827,7 @@ int wl1271_op_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
goto out;
}

- ret = wl1271_ps_elp_wakeup(wl, false);
+ ret = wl1271_ps_elp_wakeup(wl);
if (ret < 0)
goto out;

@@ -3178,7 +3191,7 @@ static ssize_t wl1271_sysfs_store_bt_coex_state(struct device *dev,
if (wl->state == WL1271_STATE_OFF)
goto out;

- ret = wl1271_ps_elp_wakeup(wl, false);
+ ret = wl1271_ps_elp_wakeup(wl);
if (ret < 0)
goto out;

@@ -3378,9 +3391,12 @@ struct ieee80211_hw *wl1271_alloc_hw(void)
for (j = 0; j < AP_MAX_LINKS; j++)
skb_queue_head_init(&wl->links[j].tx_queue[i]);

+ skb_queue_head_init(&wl->deferred_rx_queue);
+ skb_queue_head_init(&wl->deferred_tx_queue);
+
INIT_DELAYED_WORK(&wl->elp_work, wl1271_elp_work);
INIT_DELAYED_WORK(&wl->pspoll_work, wl1271_pspoll_work);
- INIT_WORK(&wl->irq_work, wl1271_irq_work);
+ INIT_WORK(&wl->netstack_work, wl1271_netstack_work);
INIT_WORK(&wl->tx_work, wl1271_tx_work);
INIT_WORK(&wl->recovery_work, wl1271_recovery_work);
INIT_DELAYED_WORK(&wl->scan_complete_work, wl1271_scan_complete_work);
diff --git a/drivers/net/wireless/wl12xx/ps.c b/drivers/net/wireless/wl12xx/ps.c
index 5c347b1..971f13e 100644
--- a/drivers/net/wireless/wl12xx/ps.c
+++ b/drivers/net/wireless/wl12xx/ps.c
@@ -69,7 +69,7 @@ void wl1271_ps_elp_sleep(struct wl1271 *wl)
}
}

-int wl1271_ps_elp_wakeup(struct wl1271 *wl, bool chip_awake)
+int wl1271_ps_elp_wakeup(struct wl1271 *wl)
{
DECLARE_COMPLETION_ONSTACK(compl);
unsigned long flags;
@@ -87,7 +87,7 @@ int wl1271_ps_elp_wakeup(struct wl1271 *wl, bool chip_awake)
* the completion variable in one entity.
*/
spin_lock_irqsave(&wl->wl_lock, flags);
- if (work_pending(&wl->irq_work) || chip_awake)
+ if (test_bit(WL1271_FLAG_IRQ_RUNNING, &wl->flags))
pending = true;
else
wl->elp_compl = &compl;
@@ -149,7 +149,7 @@ int wl1271_ps_set_mode(struct wl1271 *wl, enum wl1271_cmd_ps_mode mode,
case STATION_ACTIVE_MODE:
default:
wl1271_debug(DEBUG_PSM, "leaving psm");
- ret = wl1271_ps_elp_wakeup(wl, false);
+ ret = wl1271_ps_elp_wakeup(wl);
if (ret < 0)
return ret;

diff --git a/drivers/net/wireless/wl12xx/ps.h b/drivers/net/wireless/wl12xx/ps.h
index fc1f4c1..c41bd0a 100644
--- a/drivers/net/wireless/wl12xx/ps.h
+++ b/drivers/net/wireless/wl12xx/ps.h
@@ -30,7 +30,7 @@
int wl1271_ps_set_mode(struct wl1271 *wl, enum wl1271_cmd_ps_mode mode,
u32 rates, bool send);
void wl1271_ps_elp_sleep(struct wl1271 *wl);
-int wl1271_ps_elp_wakeup(struct wl1271 *wl, bool chip_awake);
+int wl1271_ps_elp_wakeup(struct wl1271 *wl);
void wl1271_elp_work(struct work_struct *work);
void wl1271_ps_link_start(struct wl1271 *wl, u8 hlid, bool clean_queues);
void wl1271_ps_link_end(struct wl1271 *wl, u8 hlid);
diff --git a/drivers/net/wireless/wl12xx/rx.c b/drivers/net/wireless/wl12xx/rx.c
index a07e3f9..e490979 100644
--- a/drivers/net/wireless/wl12xx/rx.c
+++ b/drivers/net/wireless/wl12xx/rx.c
@@ -129,7 +129,8 @@ static int wl1271_rx_handle_data(struct wl1271 *wl, u8 *data, u32 length)

skb_trim(skb, skb->len - desc->pad_len);

- ieee80211_rx_ni(wl->hw, skb);
+ skb_queue_tail(&wl->deferred_rx_queue, skb);
+ ieee80211_queue_work(wl->hw, &wl->netstack_work);

return 0;
}
diff --git a/drivers/net/wireless/wl12xx/sdio.c b/drivers/net/wireless/wl12xx/sdio.c
index b9c3747..7ee9d29 100644
--- a/drivers/net/wireless/wl12xx/sdio.c
+++ b/drivers/net/wireless/wl12xx/sdio.c
@@ -60,7 +60,7 @@ static struct device *wl1271_sdio_wl_to_dev(struct wl1271 *wl)
return &(wl_to_func(wl)->dev);
}

-static irqreturn_t wl1271_irq(int irq, void *cookie)
+static irqreturn_t wl1271_hardirq(int irq, void *cookie)
{
struct wl1271 *wl = cookie;
unsigned long flags;
@@ -69,17 +69,14 @@ static irqreturn_t wl1271_irq(int irq, void *cookie)

/* complete the ELP completion */
spin_lock_irqsave(&wl->wl_lock, flags);
+ set_bit(WL1271_FLAG_IRQ_RUNNING, &wl->flags);
if (wl->elp_compl) {
complete(wl->elp_compl);
wl->elp_compl = NULL;
}
-
- if (!test_and_set_bit(WL1271_FLAG_IRQ_RUNNING, &wl->flags))
- ieee80211_queue_work(wl->hw, &wl->irq_work);
- set_bit(WL1271_FLAG_IRQ_PENDING, &wl->flags);
spin_unlock_irqrestore(&wl->wl_lock, flags);

- return IRQ_HANDLED;
+ return IRQ_WAKE_THREAD;
}

static void wl1271_sdio_disable_interrupts(struct wl1271 *wl)
@@ -231,14 +228,14 @@ static int __devinit wl1271_probe(struct sdio_func *func,
wl->irq = wlan_data->irq;
wl->ref_clock = wlan_data->board_ref_clock;

- ret = request_irq(wl->irq, wl1271_irq, 0, DRIVER_NAME, wl);
+ ret = request_threaded_irq(wl->irq, wl1271_hardirq, wl1271_irq,
+ IRQF_TRIGGER_RISING,
+ DRIVER_NAME, wl);
if (ret < 0) {
wl1271_error("request_irq() failed: %d", ret);
goto out_free;
}

- set_irq_type(wl->irq, IRQ_TYPE_EDGE_RISING);
-
disable_irq(wl->irq);

ret = wl1271_init_ieee80211(wl);
@@ -261,7 +258,6 @@ static int __devinit wl1271_probe(struct sdio_func *func,
out_irq:
free_irq(wl->irq, wl);

-
out_free:
wl1271_free_hw(wl);

diff --git a/drivers/net/wireless/wl12xx/spi.c b/drivers/net/wireless/wl12xx/spi.c
index 0132dad..df5a00f 100644
--- a/drivers/net/wireless/wl12xx/spi.c
+++ b/drivers/net/wireless/wl12xx/spi.c
@@ -320,28 +320,23 @@ static void wl1271_spi_raw_write(struct wl1271 *wl, int addr, void *buf,
spi_sync(wl_to_spi(wl), &m);
}

-static irqreturn_t wl1271_irq(int irq, void *cookie)
+static irqreturn_t wl1271_hardirq(int irq, void *cookie)
{
- struct wl1271 *wl;
+ struct wl1271 *wl = cookie;
unsigned long flags;

wl1271_debug(DEBUG_IRQ, "IRQ");

- wl = cookie;
-
/* complete the ELP completion */
spin_lock_irqsave(&wl->wl_lock, flags);
+ set_bit(WL1271_FLAG_IRQ_RUNNING, &wl->flags);
if (wl->elp_compl) {
complete(wl->elp_compl);
wl->elp_compl = NULL;
}
-
- if (!test_and_set_bit(WL1271_FLAG_IRQ_RUNNING, &wl->flags))
- ieee80211_queue_work(wl->hw, &wl->irq_work);
- set_bit(WL1271_FLAG_IRQ_PENDING, &wl->flags);
spin_unlock_irqrestore(&wl->wl_lock, flags);

- return IRQ_HANDLED;
+ return IRQ_WAKE_THREAD;
}

static int wl1271_spi_set_power(struct wl1271 *wl, bool enable)
@@ -413,14 +408,14 @@ static int __devinit wl1271_probe(struct spi_device *spi)
goto out_free;
}

- ret = request_irq(wl->irq, wl1271_irq, 0, DRIVER_NAME, wl);
+ ret = request_threaded_irq(wl->irq, wl1271_hardirq, wl1271_irq,
+ IRQF_TRIGGER_RISING,
+ DRIVER_NAME, wl);
if (ret < 0) {
wl1271_error("request_irq() failed: %d", ret);
goto out_free;
}

- set_irq_type(wl->irq, IRQ_TYPE_EDGE_RISING);
-
disable_irq(wl->irq);

ret = wl1271_init_ieee80211(wl);
diff --git a/drivers/net/wireless/wl12xx/tx.c b/drivers/net/wireless/wl12xx/tx.c
index 57bd3bd..af1bfef 100644
--- a/drivers/net/wireless/wl12xx/tx.c
+++ b/drivers/net/wireless/wl12xx/tx.c
@@ -464,7 +464,7 @@ void wl1271_tx_work_locked(struct wl1271 *wl)

while ((skb = wl1271_skb_dequeue(wl))) {
if (!woken_up) {
- ret = wl1271_ps_elp_wakeup(wl, false);
+ ret = wl1271_ps_elp_wakeup(wl);
if (ret < 0)
goto out_ack;
woken_up = true;
@@ -589,7 +589,8 @@ static void wl1271_tx_complete_packet(struct wl1271 *wl,
result->rate_class_index, result->status);

/* return the packet to the stack */
- ieee80211_tx_status(wl->hw, skb);
+ skb_queue_tail(&wl->deferred_tx_queue, skb);
+ ieee80211_queue_work(wl->hw, &wl->netstack_work);
wl1271_free_tx_id(wl, result->id);
}

diff --git a/drivers/net/wireless/wl12xx/wl12xx.h b/drivers/net/wireless/wl12xx/wl12xx.h
index 338acc9..45e6577 100644
--- a/drivers/net/wireless/wl12xx/wl12xx.h
+++ b/drivers/net/wireless/wl12xx/wl12xx.h
@@ -320,7 +320,6 @@ enum wl12xx_flags {
WL1271_FLAG_IN_ELP,
WL1271_FLAG_PSM,
WL1271_FLAG_PSM_REQUESTED,
- WL1271_FLAG_IRQ_PENDING,
WL1271_FLAG_IRQ_RUNNING,
WL1271_FLAG_IDLE,
WL1271_FLAG_IDLE_REQUESTED,
@@ -404,6 +403,12 @@ struct wl1271 {
struct sk_buff_head tx_queue[NUM_TX_QUEUES];
int tx_queue_count;

+ /* Frames received, not handled yet by mac80211 */
+ struct sk_buff_head deferred_rx_queue;
+
+ /* Frames sent, not returned yet to mac80211 */
+ struct sk_buff_head deferred_tx_queue;
+
struct work_struct tx_work;

/* Pending TX frames */
@@ -424,8 +429,8 @@ struct wl1271 {
/* Intermediate buffer, used for packet aggregation */
u8 *aggr_buf;

- /* The target interrupt mask */
- struct work_struct irq_work;
+ /* Network stack work */
+ struct work_struct netstack_work;

/* Hardware recovery work */
struct work_struct recovery_work;
--
1.7.1


2011-02-27 22:31:04

by Ido Yariv

[permalink] [raw]
Subject: [PATCH 5/7] wl12xx: Switch to level trigger interrupts

The interrupt of the wl12xx is a level interrupt in nature, since the
interrupt line is not auto-reset. However, since resetting the interrupt
requires bus transactions, this cannot be done from an interrupt
context. Thus, requesting a level interrupt would require to disable the
irq and re-enable it after the HW is acknowledged. Since we now request
a threaded irq, this can also be done by specifying the IRQF_ONESHOT
flag.

Triggering on an edge can be problematic in some platforms, if the
sampling frequency is not sufficient for detecting very frequent
interrupts. In case an interrupt is missed, the driver will hang as the
interrupt line will stay high until it is acknowledged by the driver,
which will never happen.

Fix this by requesting a level triggered interrupt, with the
IRQF_ONESHOT flag.

Signed-off-by: Ido Yariv <[email protected]>
---
drivers/net/wireless/wl12xx/sdio.c | 2 +-
drivers/net/wireless/wl12xx/spi.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/wl12xx/sdio.c b/drivers/net/wireless/wl12xx/sdio.c
index 7ee9d29..a4f6417 100644
--- a/drivers/net/wireless/wl12xx/sdio.c
+++ b/drivers/net/wireless/wl12xx/sdio.c
@@ -229,7 +229,7 @@ static int __devinit wl1271_probe(struct sdio_func *func,
wl->ref_clock = wlan_data->board_ref_clock;

ret = request_threaded_irq(wl->irq, wl1271_hardirq, wl1271_irq,
- IRQF_TRIGGER_RISING,
+ IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
DRIVER_NAME, wl);
if (ret < 0) {
wl1271_error("request_irq() failed: %d", ret);
diff --git a/drivers/net/wireless/wl12xx/spi.c b/drivers/net/wireless/wl12xx/spi.c
index df5a00f..18cf017 100644
--- a/drivers/net/wireless/wl12xx/spi.c
+++ b/drivers/net/wireless/wl12xx/spi.c
@@ -409,7 +409,7 @@ static int __devinit wl1271_probe(struct spi_device *spi)
}

ret = request_threaded_irq(wl->irq, wl1271_hardirq, wl1271_irq,
- IRQF_TRIGGER_RISING,
+ IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
DRIVER_NAME, wl);
if (ret < 0) {
wl1271_error("request_irq() failed: %d", ret);
--
1.7.1


2011-02-27 22:31:00

by Ido Yariv

[permalink] [raw]
Subject: [PATCH 2/7] wl12xx: Do end-of-transactions transfers only if needed

On newer hardware revisions, there is no need to write the host's
counter at the end of a RX transaction. The same applies to writing the
number of packets at the end of a TX transaction.

It is generally a good idea to avoid unnecessary SDIO/SPI transfers.
Throughput and CPU usage are improved when avoiding these.

Send the host's RX counter and the TX packet count only if needed, based
on the hardware revision.

The PG version mask was incorrect, so fix that as well.

Signed-off-by: Ido Yariv <[email protected]>
Signed-off-by: Ohad Ben-Cohen <[email protected]>
---
drivers/net/wireless/wl12xx/boot.h | 2 +-
drivers/net/wireless/wl12xx/rx.c | 8 +++++++-
drivers/net/wireless/wl12xx/tx.c | 10 ++++++++--
3 files changed, 16 insertions(+), 4 deletions(-)

diff --git a/drivers/net/wireless/wl12xx/boot.h b/drivers/net/wireless/wl12xx/boot.h
index d67dcff..55d7521 100644
--- a/drivers/net/wireless/wl12xx/boot.h
+++ b/drivers/net/wireless/wl12xx/boot.h
@@ -56,7 +56,7 @@ struct wl1271_static_data {
#define OCP_REG_CLK_PULL 0x0cb4

#define REG_FUSE_DATA_2_1 0x050a
-#define PG_VER_MASK 0x3c
+#define PG_VER_MASK 0xc
#define PG_VER_OFFSET 2

#define CMD_MBOX_ADDRESS 0x407B4
diff --git a/drivers/net/wireless/wl12xx/rx.c b/drivers/net/wireless/wl12xx/rx.c
index 3d13d7a..a07e3f9 100644
--- a/drivers/net/wireless/wl12xx/rx.c
+++ b/drivers/net/wireless/wl12xx/rx.c
@@ -198,7 +198,13 @@ void wl1271_rx(struct wl1271 *wl, struct wl1271_fw_common_status *status)
pkt_offset += pkt_length;
}
}
- wl1271_write32(wl, RX_DRIVER_COUNTER_ADDRESS, wl->rx_counter);
+
+ /*
+ * Write the driver's packet counter to the FW. This is only required
+ * for older hardware revisions
+ */
+ if (wl->hw_pg_ver < 3)
+ wl1271_write32(wl, RX_DRIVER_COUNTER_ADDRESS, wl->rx_counter);
}

void wl1271_set_default_filters(struct wl1271 *wl)
diff --git a/drivers/net/wireless/wl12xx/tx.c b/drivers/net/wireless/wl12xx/tx.c
index ac60d57..57bd3bd 100644
--- a/drivers/net/wireless/wl12xx/tx.c
+++ b/drivers/net/wireless/wl12xx/tx.c
@@ -506,8 +506,14 @@ out_ack:
sent_packets = true;
}
if (sent_packets) {
- /* interrupt the firmware with the new packets */
- wl1271_write32(wl, WL1271_HOST_WR_ACCESS, wl->tx_packets_count);
+ /*
+ * Interrupt the firmware with the new packets. This is only
+ * required for older hardware revisions
+ */
+ if (wl->hw_pg_ver < 3)
+ wl1271_write32(wl, WL1271_HOST_WR_ACCESS,
+ wl->tx_packets_count);
+
wl1271_handle_tx_low_watermark(wl);
}

--
1.7.1


2011-02-27 22:31:05

by Ido Yariv

[permalink] [raw]
Subject: [PATCH 6/7] wl12xx: Avoid redundant TX work

TX might be handled in the threaded IRQ handler, in which case, TX work
might be scheduled just to discover it has nothing to do.

Save a few context switches by cancelling redundant TX work in case TX
is about to be handled in the threaded IRQ handler. Also, avoid
scheduling TX work from wl1271_op_tx if not needed.

Signed-off-by: Ido Yariv <[email protected]>
---
drivers/net/wireless/wl12xx/main.c | 33 ++++++++++++++++++++++++++++-----
drivers/net/wireless/wl12xx/wl12xx.h | 1 +
2 files changed, 29 insertions(+), 5 deletions(-)

diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c
index 9ceba52..70995a1 100644
--- a/drivers/net/wireless/wl12xx/main.c
+++ b/drivers/net/wireless/wl12xx/main.c
@@ -641,6 +641,12 @@ irqreturn_t wl1271_irq(int irq, void *cookie)
u32 intr;
struct wl1271 *wl = (struct wl1271 *)cookie;
bool done = false;
+ unsigned long flags;
+
+ /* TX might be handled here, avoid redundant work */
+ set_bit(WL1271_FLAG_TX_PENDING, &wl->flags);
+
+ cancel_work_sync(&wl->tx_work);

mutex_lock(&wl->mutex);

@@ -685,13 +691,17 @@ irqreturn_t wl1271_irq(int irq, void *cookie)
wl1271_rx(wl, &wl->fw_status->common);

/* Check if any tx blocks were freed */
+ spin_lock_irqsave(&wl->wl_lock, flags);
if (!test_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags) &&
wl->tx_queue_count) {
+ spin_unlock_irqrestore(&wl->wl_lock, flags);
/*
* In order to avoid starvation of the TX path,
* call the work function directly.
*/
wl1271_tx_work_locked(wl);
+ } else {
+ spin_unlock_irqrestore(&wl->wl_lock, flags);
}

/* check for tx results */
@@ -721,6 +731,14 @@ irqreturn_t wl1271_irq(int irq, void *cookie)
wl1271_ps_elp_sleep(wl);

out:
+ spin_lock_irqsave(&wl->wl_lock, flags);
+ /* In case TX was not handled here, queue TX work */
+ clear_bit(WL1271_FLAG_TX_PENDING, &wl->flags);
+ if (!test_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags) &&
+ wl->tx_queue_count)
+ ieee80211_queue_work(wl->hw, &wl->tx_work);
+ spin_unlock_irqrestore(&wl->wl_lock, flags);
+
mutex_unlock(&wl->mutex);

return IRQ_HANDLED;
@@ -1055,7 +1073,13 @@ static int wl1271_op_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
int q;
u8 hlid = 0;

+ q = wl1271_tx_get_queue(skb_get_queue_mapping(skb));
+
+ if (wl->bss_type == BSS_TYPE_AP_BSS)
+ hlid = wl1271_tx_get_hlid(skb);
+
spin_lock_irqsave(&wl->wl_lock, flags);
+
wl->tx_queue_count++;

/*
@@ -1068,12 +1092,8 @@ static int wl1271_op_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
set_bit(WL1271_FLAG_TX_QUEUE_STOPPED, &wl->flags);
}

- spin_unlock_irqrestore(&wl->wl_lock, flags);
-
/* queue the packet */
- q = wl1271_tx_get_queue(skb_get_queue_mapping(skb));
if (wl->bss_type == BSS_TYPE_AP_BSS) {
- hlid = wl1271_tx_get_hlid(skb);
wl1271_debug(DEBUG_TX, "queue skb hlid %d q %d", hlid, q);
skb_queue_tail(&wl->links[hlid].tx_queue[q], skb);
} else {
@@ -1085,9 +1105,12 @@ static int wl1271_op_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
* before that, the tx_work will not be initialized!
*/

- if (!test_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags))
+ if (!test_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags) &&
+ !test_bit(WL1271_FLAG_TX_PENDING, &wl->flags))
ieee80211_queue_work(wl->hw, &wl->tx_work);

+ spin_unlock_irqrestore(&wl->wl_lock, flags);
+
return NETDEV_TX_OK;
}

diff --git a/drivers/net/wireless/wl12xx/wl12xx.h b/drivers/net/wireless/wl12xx/wl12xx.h
index 45e6577..8fdea60 100644
--- a/drivers/net/wireless/wl12xx/wl12xx.h
+++ b/drivers/net/wireless/wl12xx/wl12xx.h
@@ -317,6 +317,7 @@ enum wl12xx_flags {
WL1271_FLAG_JOINED,
WL1271_FLAG_GPIO_POWER,
WL1271_FLAG_TX_QUEUE_STOPPED,
+ WL1271_FLAG_TX_PENDING,
WL1271_FLAG_IN_ELP,
WL1271_FLAG_PSM,
WL1271_FLAG_PSM_REQUESTED,
--
1.7.1


2011-02-27 22:31:08

by Ido Yariv

[permalink] [raw]
Subject: [PATCH 7/7] wl12xx: Modify requested number of memory blocks

Tests have shown that the requested number of memory blocks is
sub-optimal. Slightly modify the requested number of memory blocks for
TX.

Signed-off-by: Ido Yariv <[email protected]>
---
drivers/net/wireless/wl12xx/main.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c
index 70995a1..07d702f 100644
--- a/drivers/net/wireless/wl12xx/main.c
+++ b/drivers/net/wireless/wl12xx/main.c
@@ -304,7 +304,7 @@ static struct conf_drv_settings default_conf = {
.rx_block_num = 70,
.tx_min_block_num = 40,
.dynamic_memory = 0,
- .min_req_tx_blocks = 104,
+ .min_req_tx_blocks = 100,
.min_req_rx_blocks = 22,
.tx_min = 27,
}
--
1.7.1


2011-02-27 22:30:58

by Ido Yariv

[permalink] [raw]
Subject: [PATCH 1/7] wl12xx: Reorder data handling in irq_work

The FW has a limited amount of memory for holding frames. In case it
runs out of memory reserved for RX frames, it'll have no other choice
but to drop packets received from the AP. Thus, it is important to
handle RX data interrupts as soon as possible, before handling anything else.

In addition, since there are enough TX descriptors to go around, it is
better to first send TX frames, and only then handle TX completions.

Fix this by changing the order of function calls in wl1271_irq_work.

Signed-off-by: Ido Yariv <[email protected]>
Signed-off-by: Ohad Ben-Cohen <[email protected]>
---
drivers/net/wireless/wl12xx/main.c | 10 +++++-----
1 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c
index 95aa19a..443f829 100644
--- a/drivers/net/wireless/wl12xx/main.c
+++ b/drivers/net/wireless/wl12xx/main.c
@@ -685,10 +685,7 @@ static void wl1271_irq_work(struct work_struct *work)
if (intr & WL1271_ACX_INTR_DATA) {
wl1271_debug(DEBUG_IRQ, "WL1271_ACX_INTR_DATA");

- /* check for tx results */
- if (wl->fw_status->common.tx_results_counter !=
- (wl->tx_results_count & 0xff))
- wl1271_tx_complete(wl);
+ wl1271_rx(wl, &wl->fw_status->common);

/* Check if any tx blocks were freed */
if (!test_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags) &&
@@ -700,7 +697,10 @@ static void wl1271_irq_work(struct work_struct *work)
wl1271_tx_work_locked(wl);
}

- wl1271_rx(wl, &wl->fw_status->common);
+ /* check for tx results */
+ if (wl->fw_status->common.tx_results_counter !=
+ (wl->tx_results_count & 0xff))
+ wl1271_tx_complete(wl);
}

if (intr & WL1271_ACX_INTR_EVENT_A) {
--
1.7.1