Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

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
Loading
Loading
Loading
Loading
+37 −62
Original line number Original line Diff line number Diff line
@@ -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)
	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,
	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)
		}
		}
	}
	}


	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)
	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)
	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)
			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)