Commit a19606b4 authored by Ido Yariv's avatar Ido Yariv Committed by Luciano Coelho

wl1271: Support firmware TX packet aggregation

Instead of sending one packet at a time to the firmware, try to
send all available packets at once.
This optimization decreases the number of transactions, which saves
CPU cycles and increases network throughput.
Signed-off-by: default avatarIdo Yariv <ido@wizery.com>
Tested-by: default avatarJuuso Oikarinen <juuso.oikarinen@nokia.com>
Signed-off-by: default avatarLuciano Coelho <luciano.coelho@nokia.com>
parent 1f37cbc9
...@@ -43,13 +43,17 @@ static int wl1271_tx_id(struct wl1271 *wl, struct sk_buff *skb) ...@@ -43,13 +43,17 @@ static int wl1271_tx_id(struct wl1271 *wl, struct sk_buff *skb)
return -EBUSY; return -EBUSY;
} }
static int wl1271_tx_allocate(struct wl1271 *wl, struct sk_buff *skb, u32 extra) static int wl1271_tx_allocate(struct wl1271 *wl, struct sk_buff *skb, u32 extra,
u32 buf_offset)
{ {
struct wl1271_tx_hw_descr *desc; struct wl1271_tx_hw_descr *desc;
u32 total_len = skb->len + sizeof(struct wl1271_tx_hw_descr) + extra; u32 total_len = skb->len + sizeof(struct wl1271_tx_hw_descr) + extra;
u32 total_blocks; u32 total_blocks;
int id, ret = -EBUSY; int id, ret = -EBUSY;
if (buf_offset + total_len > WL1271_AGGR_BUFFER_SIZE)
return -EBUSY;
/* allocate free identifier for the packet */ /* allocate free identifier for the packet */
id = wl1271_tx_id(wl, skb); id = wl1271_tx_id(wl, skb);
if (id < 0) if (id < 0)
...@@ -82,7 +86,7 @@ static int wl1271_tx_allocate(struct wl1271 *wl, struct sk_buff *skb, u32 extra) ...@@ -82,7 +86,7 @@ static int wl1271_tx_allocate(struct wl1271 *wl, struct sk_buff *skb, u32 extra)
return ret; return ret;
} }
static int wl1271_tx_fill_hdr(struct wl1271 *wl, struct sk_buff *skb, static void wl1271_tx_fill_hdr(struct wl1271 *wl, struct sk_buff *skb,
u32 extra, struct ieee80211_tx_info *control) u32 extra, struct ieee80211_tx_info *control)
{ {
struct timespec ts; struct timespec ts;
...@@ -133,59 +137,17 @@ static int wl1271_tx_fill_hdr(struct wl1271 *wl, struct sk_buff *skb, ...@@ -133,59 +137,17 @@ static int wl1271_tx_fill_hdr(struct wl1271 *wl, struct sk_buff *skb,
desc->tx_attr = cpu_to_le16(tx_attr); desc->tx_attr = cpu_to_le16(tx_attr);
wl1271_debug(DEBUG_TX, "tx_fill_hdr: pad: %d", pad); wl1271_debug(DEBUG_TX, "tx_fill_hdr: pad: %d", pad);
return 0;
}
static int wl1271_tx_send_packet(struct wl1271 *wl, struct sk_buff *skb,
struct ieee80211_tx_info *control)
{
struct wl1271_tx_hw_descr *desc;
int len;
/* FIXME: This is a workaround for getting non-aligned packets.
This happens at least with EAPOL packets from the user space.
Our DMA requires packets to be aligned on a 4-byte boundary.
*/
if (unlikely((long)skb->data & 0x03)) {
int offset = (4 - (long)skb->data) & 0x03;
wl1271_debug(DEBUG_TX, "skb offset %d", offset);
/* check whether the current skb can be used */
if (!skb_cloned(skb) && (skb_tailroom(skb) >= offset)) {
unsigned char *src = skb->data;
/* align the buffer on a 4-byte boundary */
skb_reserve(skb, offset);
memmove(skb->data, src, skb->len);
} else {
wl1271_info("No handler, fixme!");
return -EINVAL;
}
}
len = WL1271_TX_ALIGN(skb->len);
/* perform a fixed address block write with the packet */
wl1271_write(wl, WL1271_SLV_MEM_DATA, skb->data, len, true);
/* write packet new counter into the write access register */
wl->tx_packets_count++;
desc = (struct wl1271_tx_hw_descr *) skb->data;
wl1271_debug(DEBUG_TX, "tx id %u skb 0x%p payload %u (%u words)",
desc->id, skb, len, desc->length);
return 0;
} }
/* caller must hold wl->mutex */ /* caller must hold wl->mutex */
static int wl1271_tx_frame(struct wl1271 *wl, struct sk_buff *skb) static int wl1271_prepare_tx_frame(struct wl1271 *wl, struct sk_buff *skb,
u32 buf_offset)
{ {
struct ieee80211_tx_info *info; struct ieee80211_tx_info *info;
u32 extra = 0; u32 extra = 0;
int ret = 0; int ret = 0;
u8 idx; u8 idx;
u32 total_len;
if (!skb) if (!skb)
return -EINVAL; return -EINVAL;
...@@ -208,19 +170,22 @@ static int wl1271_tx_frame(struct wl1271 *wl, struct sk_buff *skb) ...@@ -208,19 +170,22 @@ static int wl1271_tx_frame(struct wl1271 *wl, struct sk_buff *skb)
} }
} }
ret = wl1271_tx_allocate(wl, skb, extra); ret = wl1271_tx_allocate(wl, skb, extra, buf_offset);
if (ret < 0) if (ret < 0)
return ret; return ret;
ret = wl1271_tx_fill_hdr(wl, skb, extra, info); wl1271_tx_fill_hdr(wl, skb, extra, info);
if (ret < 0)
return ret;
ret = wl1271_tx_send_packet(wl, skb, info); /*
if (ret < 0) * The length of each packet is stored in terms of words. Thus, we must
return ret; * pad the skb data to make sure its length is aligned.
* The number of padding bytes is computed and set in wl1271_tx_fill_hdr
*/
total_len = WL1271_TX_ALIGN(skb->len);
memcpy(wl->aggr_buf + buf_offset, skb->data, skb->len);
memset(wl->aggr_buf + buf_offset + skb->len, 0, total_len - skb->len);
return ret; return total_len;
} }
u32 wl1271_tx_enabled_rates_get(struct wl1271 *wl, u32 rate_set) u32 wl1271_tx_enabled_rates_get(struct wl1271 *wl, u32 rate_set)
...@@ -245,7 +210,7 @@ void wl1271_tx_work(struct work_struct *work) ...@@ -245,7 +210,7 @@ void wl1271_tx_work(struct work_struct *work)
struct sk_buff *skb; struct sk_buff *skb;
bool woken_up = false; bool woken_up = false;
u32 sta_rates = 0; u32 sta_rates = 0;
u32 prev_tx_packets_count; u32 buf_offset;
int ret; int ret;
/* check if the rates supported by the AP have changed */ /* check if the rates supported by the AP have changed */
...@@ -262,14 +227,15 @@ void wl1271_tx_work(struct work_struct *work) ...@@ -262,14 +227,15 @@ void wl1271_tx_work(struct work_struct *work)
if (unlikely(wl->state == WL1271_STATE_OFF)) if (unlikely(wl->state == WL1271_STATE_OFF))
goto out; goto out;
prev_tx_packets_count = wl->tx_packets_count;
/* if rates have changed, re-configure the rate policy */ /* if rates have changed, re-configure the rate policy */
if (unlikely(sta_rates)) { if (unlikely(sta_rates)) {
wl->rate_set = wl1271_tx_enabled_rates_get(wl, sta_rates); wl->rate_set = wl1271_tx_enabled_rates_get(wl, sta_rates);
wl1271_acx_rate_policies(wl); wl1271_acx_rate_policies(wl);
} }
/* Prepare the transfer buffer, by aggregating all
* available packets */
buf_offset = 0;
while ((skb = skb_dequeue(&wl->tx_queue))) { while ((skb = skb_dequeue(&wl->tx_queue))) {
if (!woken_up) { if (!woken_up) {
ret = wl1271_ps_elp_wakeup(wl, false); ret = wl1271_ps_elp_wakeup(wl, false);
...@@ -278,21 +244,30 @@ void wl1271_tx_work(struct work_struct *work) ...@@ -278,21 +244,30 @@ void wl1271_tx_work(struct work_struct *work)
woken_up = true; woken_up = true;
} }
ret = wl1271_tx_frame(wl, skb); ret = wl1271_prepare_tx_frame(wl, skb, buf_offset);
if (ret == -EBUSY) { if (ret == -EBUSY) {
/* firmware buffer is full, lets stop transmitting. */ /*
* Either the firmware buffer is full, or the
* aggregation buffer is.
* Queue back last skb, and stop aggregating.
*/
skb_queue_head(&wl->tx_queue, skb); skb_queue_head(&wl->tx_queue, skb);
goto out_ack; goto out_ack;
} else if (ret < 0) { } else if (ret < 0) {
dev_kfree_skb(skb); dev_kfree_skb(skb);
goto out_ack; goto out_ack;
} }
buf_offset += ret;
wl->tx_packets_count++;
} }
out_ack: out_ack:
if (buf_offset) {
wl1271_write(wl, WL1271_SLV_MEM_DATA, wl->aggr_buf,
buf_offset, true);
/* interrupt the firmware with the new packets */ /* interrupt the firmware with the new packets */
if (prev_tx_packets_count != wl->tx_packets_count)
wl1271_write32(wl, WL1271_HOST_WR_ACCESS, wl->tx_packets_count); wl1271_write32(wl, WL1271_HOST_WR_ACCESS, wl->tx_packets_count);
}
out: out:
if (woken_up) if (woken_up)
......
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