diff options
author | Ido Yariv <ido@wizery.com> | 2010-09-30 07:28:28 -0400 |
---|---|---|
committer | Luciano Coelho <luciano.coelho@nokia.com> | 2010-10-05 09:27:33 -0400 |
commit | a19606b4333ff34e9b2863f37c20fe86b42be14c (patch) | |
tree | c26739bb4570549c6d2dfbf1574c30a4c7a07869 /drivers/net | |
parent | 1f37cbc9363462c99794699442da39f36be0aaf7 (diff) |
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: Ido Yariv <ido@wizery.com>
Tested-by: Juuso Oikarinen <juuso.oikarinen@nokia.com>
Signed-off-by: Luciano Coelho <luciano.coelho@nokia.com>
Diffstat (limited to 'drivers/net')
-rw-r--r-- | drivers/net/wireless/wl12xx/wl1271_tx.c | 99 |
1 files changed, 37 insertions, 62 deletions
diff --git a/drivers/net/wireless/wl12xx/wl1271_tx.c b/drivers/net/wireless/wl12xx/wl1271_tx.c index 1b8295c5dde4..e3dc13c4d01a 100644 --- a/drivers/net/wireless/wl12xx/wl1271_tx.c +++ b/drivers/net/wireless/wl12xx/wl1271_tx.c | |||
@@ -43,13 +43,17 @@ static int wl1271_tx_id(struct wl1271 *wl, struct sk_buff *skb) | |||
43 | return -EBUSY; | 43 | return -EBUSY; |
44 | } | 44 | } |
45 | 45 | ||
46 | static int wl1271_tx_allocate(struct wl1271 *wl, struct sk_buff *skb, u32 extra) | 46 | static int wl1271_tx_allocate(struct wl1271 *wl, struct sk_buff *skb, u32 extra, |
47 | u32 buf_offset) | ||
47 | { | 48 | { |
48 | struct wl1271_tx_hw_descr *desc; | 49 | struct wl1271_tx_hw_descr *desc; |
49 | u32 total_len = skb->len + sizeof(struct wl1271_tx_hw_descr) + extra; | 50 | u32 total_len = skb->len + sizeof(struct wl1271_tx_hw_descr) + extra; |
50 | u32 total_blocks; | 51 | u32 total_blocks; |
51 | int id, ret = -EBUSY; | 52 | int id, ret = -EBUSY; |
52 | 53 | ||
54 | if (buf_offset + total_len > WL1271_AGGR_BUFFER_SIZE) | ||
55 | return -EBUSY; | ||
56 | |||
53 | /* allocate free identifier for the packet */ | 57 | /* allocate free identifier for the packet */ |
54 | id = wl1271_tx_id(wl, skb); | 58 | id = wl1271_tx_id(wl, skb); |
55 | if (id < 0) | 59 | if (id < 0) |
@@ -82,7 +86,7 @@ static int wl1271_tx_allocate(struct wl1271 *wl, struct sk_buff *skb, u32 extra) | |||
82 | return ret; | 86 | return ret; |
83 | } | 87 | } |
84 | 88 | ||
85 | static int wl1271_tx_fill_hdr(struct wl1271 *wl, struct sk_buff *skb, | 89 | static void wl1271_tx_fill_hdr(struct wl1271 *wl, struct sk_buff *skb, |
86 | u32 extra, struct ieee80211_tx_info *control) | 90 | u32 extra, struct ieee80211_tx_info *control) |
87 | { | 91 | { |
88 | struct timespec ts; | 92 | struct timespec ts; |
@@ -133,59 +137,17 @@ static int wl1271_tx_fill_hdr(struct wl1271 *wl, struct sk_buff *skb, | |||
133 | desc->tx_attr = cpu_to_le16(tx_attr); | 137 | desc->tx_attr = cpu_to_le16(tx_attr); |
134 | 138 | ||
135 | wl1271_debug(DEBUG_TX, "tx_fill_hdr: pad: %d", pad); | 139 | wl1271_debug(DEBUG_TX, "tx_fill_hdr: pad: %d", pad); |
136 | return 0; | ||
137 | } | ||
138 | |||
139 | static int wl1271_tx_send_packet(struct wl1271 *wl, struct sk_buff *skb, | ||
140 | struct ieee80211_tx_info *control) | ||
141 | { | ||
142 | |||
143 | struct wl1271_tx_hw_descr *desc; | ||
144 | int len; | ||
145 | |||
146 | /* FIXME: This is a workaround for getting non-aligned packets. | ||
147 | This happens at least with EAPOL packets from the user space. | ||
148 | Our DMA requires packets to be aligned on a 4-byte boundary. | ||
149 | */ | ||
150 | if (unlikely((long)skb->data & 0x03)) { | ||
151 | int offset = (4 - (long)skb->data) & 0x03; | ||
152 | wl1271_debug(DEBUG_TX, "skb offset %d", offset); | ||
153 | |||
154 | /* check whether the current skb can be used */ | ||
155 | if (!skb_cloned(skb) && (skb_tailroom(skb) >= offset)) { | ||
156 | unsigned char *src = skb->data; | ||
157 | |||
158 | /* align the buffer on a 4-byte boundary */ | ||
159 | skb_reserve(skb, offset); | ||
160 | memmove(skb->data, src, skb->len); | ||
161 | } else { | ||
162 | wl1271_info("No handler, fixme!"); | ||
163 | return -EINVAL; | ||
164 | } | ||
165 | } | ||
166 | |||
167 | len = WL1271_TX_ALIGN(skb->len); | ||
168 | |||
169 | /* perform a fixed address block write with the packet */ | ||
170 | wl1271_write(wl, WL1271_SLV_MEM_DATA, skb->data, len, true); | ||
171 | |||
172 | /* write packet new counter into the write access register */ | ||
173 | wl->tx_packets_count++; | ||
174 | |||
175 | desc = (struct wl1271_tx_hw_descr *) skb->data; | ||
176 | wl1271_debug(DEBUG_TX, "tx id %u skb 0x%p payload %u (%u words)", | ||
177 | desc->id, skb, len, desc->length); | ||
178 | |||
179 | return 0; | ||
180 | } | 140 | } |
181 | 141 | ||
182 | /* caller must hold wl->mutex */ | 142 | /* caller must hold wl->mutex */ |
183 | static int wl1271_tx_frame(struct wl1271 *wl, struct sk_buff *skb) | 143 | static int wl1271_prepare_tx_frame(struct wl1271 *wl, struct sk_buff *skb, |
144 | u32 buf_offset) | ||
184 | { | 145 | { |
185 | struct ieee80211_tx_info *info; | 146 | struct ieee80211_tx_info *info; |
186 | u32 extra = 0; | 147 | u32 extra = 0; |
187 | int ret = 0; | 148 | int ret = 0; |
188 | u8 idx; | 149 | u8 idx; |
150 | u32 total_len; | ||
189 | 151 | ||
190 | if (!skb) | 152 | if (!skb) |
191 | return -EINVAL; | 153 | return -EINVAL; |
@@ -208,19 +170,22 @@ static int wl1271_tx_frame(struct wl1271 *wl, struct sk_buff *skb) | |||
208 | } | 170 | } |
209 | } | 171 | } |
210 | 172 | ||
211 | ret = wl1271_tx_allocate(wl, skb, extra); | 173 | ret = wl1271_tx_allocate(wl, skb, extra, buf_offset); |
212 | if (ret < 0) | 174 | if (ret < 0) |
213 | return ret; | 175 | return ret; |
214 | 176 | ||
215 | ret = wl1271_tx_fill_hdr(wl, skb, extra, info); | 177 | wl1271_tx_fill_hdr(wl, skb, extra, info); |
216 | if (ret < 0) | ||
217 | return ret; | ||
218 | 178 | ||
219 | ret = wl1271_tx_send_packet(wl, skb, info); | 179 | /* |
220 | if (ret < 0) | 180 | * The length of each packet is stored in terms of words. Thus, we must |
221 | return ret; | 181 | * pad the skb data to make sure its length is aligned. |
182 | * The number of padding bytes is computed and set in wl1271_tx_fill_hdr | ||
183 | */ | ||
184 | total_len = WL1271_TX_ALIGN(skb->len); | ||
185 | memcpy(wl->aggr_buf + buf_offset, skb->data, skb->len); | ||
186 | memset(wl->aggr_buf + buf_offset + skb->len, 0, total_len - skb->len); | ||
222 | 187 | ||
223 | return ret; | 188 | return total_len; |
224 | } | 189 | } |
225 | 190 | ||
226 | u32 wl1271_tx_enabled_rates_get(struct wl1271 *wl, u32 rate_set) | 191 | 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 | struct sk_buff *skb; | 210 | struct sk_buff *skb; |
246 | bool woken_up = false; | 211 | bool woken_up = false; |
247 | u32 sta_rates = 0; | 212 | u32 sta_rates = 0; |
248 | u32 prev_tx_packets_count; | 213 | u32 buf_offset; |
249 | int ret; | 214 | int ret; |
250 | 215 | ||
251 | /* check if the rates supported by the AP have changed */ | 216 | /* check if the rates supported by the AP have changed */ |
@@ -262,14 +227,15 @@ void wl1271_tx_work(struct work_struct *work) | |||
262 | if (unlikely(wl->state == WL1271_STATE_OFF)) | 227 | if (unlikely(wl->state == WL1271_STATE_OFF)) |
263 | goto out; | 228 | goto out; |
264 | 229 | ||
265 | prev_tx_packets_count = wl->tx_packets_count; | ||
266 | |||
267 | /* if rates have changed, re-configure the rate policy */ | 230 | /* if rates have changed, re-configure the rate policy */ |
268 | if (unlikely(sta_rates)) { | 231 | if (unlikely(sta_rates)) { |
269 | wl->rate_set = wl1271_tx_enabled_rates_get(wl, sta_rates); | 232 | wl->rate_set = wl1271_tx_enabled_rates_get(wl, sta_rates); |
270 | wl1271_acx_rate_policies(wl); | 233 | wl1271_acx_rate_policies(wl); |
271 | } | 234 | } |
272 | 235 | ||
236 | /* Prepare the transfer buffer, by aggregating all | ||
237 | * available packets */ | ||
238 | buf_offset = 0; | ||
273 | while ((skb = skb_dequeue(&wl->tx_queue))) { | 239 | while ((skb = skb_dequeue(&wl->tx_queue))) { |
274 | if (!woken_up) { | 240 | if (!woken_up) { |
275 | ret = wl1271_ps_elp_wakeup(wl, false); | 241 | ret = wl1271_ps_elp_wakeup(wl, false); |
@@ -278,21 +244,30 @@ void wl1271_tx_work(struct work_struct *work) | |||
278 | woken_up = true; | 244 | woken_up = true; |
279 | } | 245 | } |
280 | 246 | ||
281 | ret = wl1271_tx_frame(wl, skb); | 247 | ret = wl1271_prepare_tx_frame(wl, skb, buf_offset); |
282 | if (ret == -EBUSY) { | 248 | if (ret == -EBUSY) { |
283 | /* firmware buffer is full, lets stop transmitting. */ | 249 | /* |
250 | * Either the firmware buffer is full, or the | ||
251 | * aggregation buffer is. | ||
252 | * Queue back last skb, and stop aggregating. | ||
253 | */ | ||
284 | skb_queue_head(&wl->tx_queue, skb); | 254 | skb_queue_head(&wl->tx_queue, skb); |
285 | goto out_ack; | 255 | goto out_ack; |
286 | } else if (ret < 0) { | 256 | } else if (ret < 0) { |
287 | dev_kfree_skb(skb); | 257 | dev_kfree_skb(skb); |
288 | goto out_ack; | 258 | goto out_ack; |
289 | } | 259 | } |
260 | buf_offset += ret; | ||
261 | wl->tx_packets_count++; | ||
290 | } | 262 | } |
291 | 263 | ||
292 | out_ack: | 264 | out_ack: |
293 | /* interrupt the firmware with the new packets */ | 265 | if (buf_offset) { |
294 | if (prev_tx_packets_count != wl->tx_packets_count) | 266 | wl1271_write(wl, WL1271_SLV_MEM_DATA, wl->aggr_buf, |
267 | buf_offset, true); | ||
268 | /* interrupt the firmware with the new packets */ | ||
295 | wl1271_write32(wl, WL1271_HOST_WR_ACCESS, wl->tx_packets_count); | 269 | wl1271_write32(wl, WL1271_HOST_WR_ACCESS, wl->tx_packets_count); |
270 | } | ||
296 | 271 | ||
297 | out: | 272 | out: |
298 | if (woken_up) | 273 | if (woken_up) |