diff options
author | Arik Nemtsov <arik@wizery.com> | 2011-02-22 17:22:31 -0500 |
---|---|---|
committer | Luciano Coelho <coelho@ti.com> | 2011-02-23 04:18:46 -0500 |
commit | b622d992c21a85ce590afe2c18977ed28b457e0e (patch) | |
tree | b5a5552141932de9628cc569d2f2dee4df9870dc /drivers/net | |
parent | 09039f42a24084c10e7761ab28ef22932c62a46f (diff) |
wl12xx: AP-mode - management of links in PS-mode
Update the PS mode of each link according to a bitmap polled from
fw_status. Manually notify mac80211 about PS mode changes in connected
stations.
mac80211 will only be notified about PS start when the station is in PS
and there is a small number of TX blocks from this link ready in HW.
This is required for waking up the remote station since the TIM is
updated entirely by FW.
When a station enters mac80211-PS-mode, we drop all the skbs in the
low-level TX queues belonging to this sta with STAT_TX_FILTERED
to keep our queues clean.
Signed-off-by: Arik Nemtsov <arik@wizery.com>
Signed-off-by: Luciano Coelho <coelho@ti.com>
Diffstat (limited to 'drivers/net')
-rw-r--r-- | drivers/net/wireless/wl12xx/main.c | 73 | ||||
-rw-r--r-- | drivers/net/wireless/wl12xx/ps.c | 80 | ||||
-rw-r--r-- | drivers/net/wireless/wl12xx/ps.h | 2 | ||||
-rw-r--r-- | drivers/net/wireless/wl12xx/tx.c | 24 | ||||
-rw-r--r-- | drivers/net/wireless/wl12xx/wl12xx.h | 19 |
5 files changed, 186 insertions, 12 deletions
diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c index 5772a33d79ec..95aa19ae84e5 100644 --- a/drivers/net/wireless/wl12xx/main.c +++ b/drivers/net/wireless/wl12xx/main.c | |||
@@ -537,6 +537,57 @@ static int wl1271_plt_init(struct wl1271 *wl) | |||
537 | return ret; | 537 | return ret; |
538 | } | 538 | } |
539 | 539 | ||
540 | static void wl1271_irq_ps_regulate_link(struct wl1271 *wl, u8 hlid, u8 tx_blks) | ||
541 | { | ||
542 | bool fw_ps; | ||
543 | |||
544 | /* only regulate station links */ | ||
545 | if (hlid < WL1271_AP_STA_HLID_START) | ||
546 | return; | ||
547 | |||
548 | fw_ps = test_bit(hlid, (unsigned long *)&wl->ap_fw_ps_map); | ||
549 | |||
550 | /* | ||
551 | * Wake up from high level PS if the STA is asleep with too little | ||
552 | * blocks in FW or if the STA is awake. | ||
553 | */ | ||
554 | if (!fw_ps || tx_blks < WL1271_PS_STA_MAX_BLOCKS) | ||
555 | wl1271_ps_link_end(wl, hlid); | ||
556 | |||
557 | /* Start high-level PS if the STA is asleep with enough blocks in FW */ | ||
558 | else if (fw_ps && tx_blks >= WL1271_PS_STA_MAX_BLOCKS) | ||
559 | wl1271_ps_link_start(wl, hlid, true); | ||
560 | } | ||
561 | |||
562 | static void wl1271_irq_update_links_status(struct wl1271 *wl, | ||
563 | struct wl1271_fw_ap_status *status) | ||
564 | { | ||
565 | u32 cur_fw_ps_map; | ||
566 | u8 hlid; | ||
567 | |||
568 | cur_fw_ps_map = le32_to_cpu(status->link_ps_bitmap); | ||
569 | if (wl->ap_fw_ps_map != cur_fw_ps_map) { | ||
570 | wl1271_debug(DEBUG_PSM, | ||
571 | "link ps prev 0x%x cur 0x%x changed 0x%x", | ||
572 | wl->ap_fw_ps_map, cur_fw_ps_map, | ||
573 | wl->ap_fw_ps_map ^ cur_fw_ps_map); | ||
574 | |||
575 | wl->ap_fw_ps_map = cur_fw_ps_map; | ||
576 | } | ||
577 | |||
578 | for (hlid = WL1271_AP_STA_HLID_START; hlid < AP_MAX_LINKS; hlid++) { | ||
579 | u8 cnt = status->tx_lnk_free_blks[hlid] - | ||
580 | wl->links[hlid].prev_freed_blks; | ||
581 | |||
582 | wl->links[hlid].prev_freed_blks = | ||
583 | status->tx_lnk_free_blks[hlid]; | ||
584 | wl->links[hlid].allocated_blks -= cnt; | ||
585 | |||
586 | wl1271_irq_ps_regulate_link(wl, hlid, | ||
587 | wl->links[hlid].allocated_blks); | ||
588 | } | ||
589 | } | ||
590 | |||
540 | static void wl1271_fw_status(struct wl1271 *wl, | 591 | static void wl1271_fw_status(struct wl1271 *wl, |
541 | struct wl1271_fw_full_status *full_status) | 592 | struct wl1271_fw_full_status *full_status) |
542 | { | 593 | { |
@@ -574,16 +625,9 @@ static void wl1271_fw_status(struct wl1271 *wl, | |||
574 | if (total) | 625 | if (total) |
575 | clear_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags); | 626 | clear_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags); |
576 | 627 | ||
577 | if (wl->bss_type == BSS_TYPE_AP_BSS) { | 628 | /* for AP update num of allocated TX blocks per link and ps status */ |
578 | for (i = 0; i < AP_MAX_LINKS; i++) { | 629 | if (wl->bss_type == BSS_TYPE_AP_BSS) |
579 | u8 cnt = status->tx_lnk_free_blks[i] - | 630 | wl1271_irq_update_links_status(wl, &full_status->ap); |
580 | wl->links[i].prev_freed_blks; | ||
581 | |||
582 | wl->links[i].prev_freed_blks = | ||
583 | status->tx_lnk_free_blks[i]; | ||
584 | wl->links[i].allocated_blks -= cnt; | ||
585 | } | ||
586 | } | ||
587 | 631 | ||
588 | /* update the host-chipset time offset */ | 632 | /* update the host-chipset time offset */ |
589 | getnstimeofday(&ts); | 633 | getnstimeofday(&ts); |
@@ -1241,6 +1285,8 @@ static void __wl1271_op_remove_interface(struct wl1271 *wl) | |||
1241 | wl->filters = 0; | 1285 | wl->filters = 0; |
1242 | wl1271_free_ap_keys(wl); | 1286 | wl1271_free_ap_keys(wl); |
1243 | memset(wl->ap_hlid_map, 0, sizeof(wl->ap_hlid_map)); | 1287 | memset(wl->ap_hlid_map, 0, sizeof(wl->ap_hlid_map)); |
1288 | wl->ap_fw_ps_map = 0; | ||
1289 | wl->ap_ps_map = 0; | ||
1244 | 1290 | ||
1245 | for (i = 0; i < NUM_TX_QUEUES; i++) | 1291 | for (i = 0; i < NUM_TX_QUEUES; i++) |
1246 | wl->tx_blocks_freed[i] = 0; | 1292 | wl->tx_blocks_freed[i] = 0; |
@@ -2649,10 +2695,10 @@ static int wl1271_allocate_sta(struct wl1271 *wl, | |||
2649 | } | 2695 | } |
2650 | 2696 | ||
2651 | wl_sta = (struct wl1271_station *)sta->drv_priv; | 2697 | wl_sta = (struct wl1271_station *)sta->drv_priv; |
2652 | |||
2653 | __set_bit(id, wl->ap_hlid_map); | 2698 | __set_bit(id, wl->ap_hlid_map); |
2654 | wl_sta->hlid = WL1271_AP_STA_HLID_START + id; | 2699 | wl_sta->hlid = WL1271_AP_STA_HLID_START + id; |
2655 | *hlid = wl_sta->hlid; | 2700 | *hlid = wl_sta->hlid; |
2701 | memcpy(wl->links[wl_sta->hlid].addr, sta->addr, ETH_ALEN); | ||
2656 | return 0; | 2702 | return 0; |
2657 | } | 2703 | } |
2658 | 2704 | ||
@@ -2664,7 +2710,10 @@ static void wl1271_free_sta(struct wl1271 *wl, u8 hlid) | |||
2664 | return; | 2710 | return; |
2665 | 2711 | ||
2666 | __clear_bit(id, wl->ap_hlid_map); | 2712 | __clear_bit(id, wl->ap_hlid_map); |
2713 | memset(wl->links[hlid].addr, 0, ETH_ALEN); | ||
2667 | wl1271_tx_reset_link_queues(wl, hlid); | 2714 | wl1271_tx_reset_link_queues(wl, hlid); |
2715 | __clear_bit(hlid, &wl->ap_ps_map); | ||
2716 | __clear_bit(hlid, (unsigned long *)&wl->ap_fw_ps_map); | ||
2668 | } | 2717 | } |
2669 | 2718 | ||
2670 | static int wl1271_op_sta_add(struct ieee80211_hw *hw, | 2719 | static int wl1271_op_sta_add(struct ieee80211_hw *hw, |
@@ -3355,6 +3404,8 @@ struct ieee80211_hw *wl1271_alloc_hw(void) | |||
3355 | wl->set_bss_type = MAX_BSS_TYPE; | 3404 | wl->set_bss_type = MAX_BSS_TYPE; |
3356 | wl->fw_bss_type = MAX_BSS_TYPE; | 3405 | wl->fw_bss_type = MAX_BSS_TYPE; |
3357 | wl->last_tx_hlid = 0; | 3406 | wl->last_tx_hlid = 0; |
3407 | wl->ap_ps_map = 0; | ||
3408 | wl->ap_fw_ps_map = 0; | ||
3358 | 3409 | ||
3359 | memset(wl->tx_frames_map, 0, sizeof(wl->tx_frames_map)); | 3410 | memset(wl->tx_frames_map, 0, sizeof(wl->tx_frames_map)); |
3360 | for (i = 0; i < ACX_TX_DESCRIPTORS; i++) | 3411 | for (i = 0; i < ACX_TX_DESCRIPTORS; i++) |
diff --git a/drivers/net/wireless/wl12xx/ps.c b/drivers/net/wireless/wl12xx/ps.c index b7b3139e00fb..5c347b1bd17f 100644 --- a/drivers/net/wireless/wl12xx/ps.c +++ b/drivers/net/wireless/wl12xx/ps.c | |||
@@ -24,6 +24,7 @@ | |||
24 | #include "reg.h" | 24 | #include "reg.h" |
25 | #include "ps.h" | 25 | #include "ps.h" |
26 | #include "io.h" | 26 | #include "io.h" |
27 | #include "tx.h" | ||
27 | 28 | ||
28 | #define WL1271_WAKEUP_TIMEOUT 500 | 29 | #define WL1271_WAKEUP_TIMEOUT 500 |
29 | 30 | ||
@@ -172,3 +173,82 @@ int wl1271_ps_set_mode(struct wl1271 *wl, enum wl1271_cmd_ps_mode mode, | |||
172 | 173 | ||
173 | return ret; | 174 | return ret; |
174 | } | 175 | } |
176 | |||
177 | static void wl1271_ps_filter_frames(struct wl1271 *wl, u8 hlid) | ||
178 | { | ||
179 | int i, filtered = 0; | ||
180 | struct sk_buff *skb; | ||
181 | struct ieee80211_tx_info *info; | ||
182 | unsigned long flags; | ||
183 | |||
184 | /* filter all frames currently the low level queus for this hlid */ | ||
185 | for (i = 0; i < NUM_TX_QUEUES; i++) { | ||
186 | while ((skb = skb_dequeue(&wl->links[hlid].tx_queue[i]))) { | ||
187 | info = IEEE80211_SKB_CB(skb); | ||
188 | info->flags |= IEEE80211_TX_STAT_TX_FILTERED; | ||
189 | info->status.rates[0].idx = -1; | ||
190 | ieee80211_tx_status(wl->hw, skb); | ||
191 | filtered++; | ||
192 | } | ||
193 | } | ||
194 | |||
195 | spin_lock_irqsave(&wl->wl_lock, flags); | ||
196 | wl->tx_queue_count -= filtered; | ||
197 | spin_unlock_irqrestore(&wl->wl_lock, flags); | ||
198 | |||
199 | wl1271_handle_tx_low_watermark(wl); | ||
200 | } | ||
201 | |||
202 | void wl1271_ps_link_start(struct wl1271 *wl, u8 hlid, bool clean_queues) | ||
203 | { | ||
204 | struct ieee80211_sta *sta; | ||
205 | |||
206 | if (test_bit(hlid, &wl->ap_ps_map)) | ||
207 | return; | ||
208 | |||
209 | wl1271_debug(DEBUG_PSM, "start mac80211 PSM on hlid %d blks %d " | ||
210 | "clean_queues %d", hlid, wl->links[hlid].allocated_blks, | ||
211 | clean_queues); | ||
212 | |||
213 | rcu_read_lock(); | ||
214 | sta = ieee80211_find_sta(wl->vif, wl->links[hlid].addr); | ||
215 | if (!sta) { | ||
216 | wl1271_error("could not find sta %pM for starting ps", | ||
217 | wl->links[hlid].addr); | ||
218 | rcu_read_unlock(); | ||
219 | return; | ||
220 | } | ||
221 | |||
222 | ieee80211_sta_ps_transition_ni(sta, true); | ||
223 | rcu_read_unlock(); | ||
224 | |||
225 | /* do we want to filter all frames from this link's queues? */ | ||
226 | if (clean_queues) | ||
227 | wl1271_ps_filter_frames(wl, hlid); | ||
228 | |||
229 | __set_bit(hlid, &wl->ap_ps_map); | ||
230 | } | ||
231 | |||
232 | void wl1271_ps_link_end(struct wl1271 *wl, u8 hlid) | ||
233 | { | ||
234 | struct ieee80211_sta *sta; | ||
235 | |||
236 | if (!test_bit(hlid, &wl->ap_ps_map)) | ||
237 | return; | ||
238 | |||
239 | wl1271_debug(DEBUG_PSM, "end mac80211 PSM on hlid %d", hlid); | ||
240 | |||
241 | __clear_bit(hlid, &wl->ap_ps_map); | ||
242 | |||
243 | rcu_read_lock(); | ||
244 | sta = ieee80211_find_sta(wl->vif, wl->links[hlid].addr); | ||
245 | if (!sta) { | ||
246 | wl1271_error("could not find sta %pM for ending ps", | ||
247 | wl->links[hlid].addr); | ||
248 | goto end; | ||
249 | } | ||
250 | |||
251 | ieee80211_sta_ps_transition_ni(sta, false); | ||
252 | end: | ||
253 | rcu_read_unlock(); | ||
254 | } | ||
diff --git a/drivers/net/wireless/wl12xx/ps.h b/drivers/net/wireless/wl12xx/ps.h index 8415060f08e5..fc1f4c193593 100644 --- a/drivers/net/wireless/wl12xx/ps.h +++ b/drivers/net/wireless/wl12xx/ps.h | |||
@@ -32,5 +32,7 @@ int wl1271_ps_set_mode(struct wl1271 *wl, enum wl1271_cmd_ps_mode mode, | |||
32 | void wl1271_ps_elp_sleep(struct wl1271 *wl); | 32 | void wl1271_ps_elp_sleep(struct wl1271 *wl); |
33 | int wl1271_ps_elp_wakeup(struct wl1271 *wl, bool chip_awake); | 33 | int wl1271_ps_elp_wakeup(struct wl1271 *wl, bool chip_awake); |
34 | void wl1271_elp_work(struct work_struct *work); | 34 | void wl1271_elp_work(struct work_struct *work); |
35 | void wl1271_ps_link_start(struct wl1271 *wl, u8 hlid, bool clean_queues); | ||
36 | void wl1271_ps_link_end(struct wl1271 *wl, u8 hlid); | ||
35 | 37 | ||
36 | #endif /* __WL1271_PS_H__ */ | 38 | #endif /* __WL1271_PS_H__ */ |
diff --git a/drivers/net/wireless/wl12xx/tx.c b/drivers/net/wireless/wl12xx/tx.c index ea1382bd38f4..ac60d577319f 100644 --- a/drivers/net/wireless/wl12xx/tx.c +++ b/drivers/net/wireless/wl12xx/tx.c | |||
@@ -86,6 +86,26 @@ static void wl1271_tx_ap_update_inconnection_sta(struct wl1271 *wl, | |||
86 | wl1271_acx_set_inconnection_sta(wl, hdr->addr1); | 86 | wl1271_acx_set_inconnection_sta(wl, hdr->addr1); |
87 | } | 87 | } |
88 | 88 | ||
89 | static void wl1271_tx_regulate_link(struct wl1271 *wl, u8 hlid) | ||
90 | { | ||
91 | bool fw_ps; | ||
92 | u8 tx_blks; | ||
93 | |||
94 | /* only regulate station links */ | ||
95 | if (hlid < WL1271_AP_STA_HLID_START) | ||
96 | return; | ||
97 | |||
98 | fw_ps = test_bit(hlid, (unsigned long *)&wl->ap_fw_ps_map); | ||
99 | tx_blks = wl->links[hlid].allocated_blks; | ||
100 | |||
101 | /* | ||
102 | * if in FW PS and there is enough data in FW we can put the link | ||
103 | * into high-level PS and clean out its TX queues. | ||
104 | */ | ||
105 | if (fw_ps && tx_blks >= WL1271_PS_STA_MAX_BLOCKS) | ||
106 | wl1271_ps_link_start(wl, hlid, true); | ||
107 | } | ||
108 | |||
89 | u8 wl1271_tx_get_hlid(struct sk_buff *skb) | 109 | u8 wl1271_tx_get_hlid(struct sk_buff *skb) |
90 | { | 110 | { |
91 | struct ieee80211_tx_info *control = IEEE80211_SKB_CB(skb); | 111 | struct ieee80211_tx_info *control = IEEE80211_SKB_CB(skb); |
@@ -277,8 +297,10 @@ static int wl1271_prepare_tx_frame(struct wl1271 *wl, struct sk_buff *skb, | |||
277 | if (ret < 0) | 297 | if (ret < 0) |
278 | return ret; | 298 | return ret; |
279 | 299 | ||
280 | if (wl->bss_type == BSS_TYPE_AP_BSS) | 300 | if (wl->bss_type == BSS_TYPE_AP_BSS) { |
281 | wl1271_tx_ap_update_inconnection_sta(wl, skb); | 301 | wl1271_tx_ap_update_inconnection_sta(wl, skb); |
302 | wl1271_tx_regulate_link(wl, hlid); | ||
303 | } | ||
282 | 304 | ||
283 | wl1271_tx_fill_hdr(wl, skb, extra, info, hlid); | 305 | wl1271_tx_fill_hdr(wl, skb, extra, info, hlid); |
284 | 306 | ||
diff --git a/drivers/net/wireless/wl12xx/wl12xx.h b/drivers/net/wireless/wl12xx/wl12xx.h index 0e00c5be99d3..338acc9f60b3 100644 --- a/drivers/net/wireless/wl12xx/wl12xx.h +++ b/drivers/net/wireless/wl12xx/wl12xx.h | |||
@@ -153,6 +153,17 @@ extern u32 wl12xx_debug_level; | |||
153 | #define WL1271_AP_BROADCAST_HLID 1 | 153 | #define WL1271_AP_BROADCAST_HLID 1 |
154 | #define WL1271_AP_STA_HLID_START 2 | 154 | #define WL1271_AP_STA_HLID_START 2 |
155 | 155 | ||
156 | /* | ||
157 | * When in AP-mode, we allow (at least) this number of mem-blocks | ||
158 | * to be transmitted to FW for a STA in PS-mode. Only when packets are | ||
159 | * present in the FW buffers it will wake the sleeping STA. We want to put | ||
160 | * enough packets for the driver to transmit all of its buffered data before | ||
161 | * the STA goes to sleep again. But we don't want to take too much mem-blocks | ||
162 | * as it might hurt the throughput of active STAs. | ||
163 | * The number of blocks (18) is enough for 2 large packets. | ||
164 | */ | ||
165 | #define WL1271_PS_STA_MAX_BLOCKS (2 * 9) | ||
166 | |||
156 | #define WL1271_AP_BSS_INDEX 0 | 167 | #define WL1271_AP_BSS_INDEX 0 |
157 | #define WL1271_AP_DEF_INACTIV_SEC 300 | 168 | #define WL1271_AP_DEF_INACTIV_SEC 300 |
158 | #define WL1271_AP_DEF_BEACON_EXP 20 | 169 | #define WL1271_AP_DEF_BEACON_EXP 20 |
@@ -326,6 +337,8 @@ struct wl1271_link { | |||
326 | /* accounting for allocated / available TX blocks in FW */ | 337 | /* accounting for allocated / available TX blocks in FW */ |
327 | u8 allocated_blks; | 338 | u8 allocated_blks; |
328 | u8 prev_freed_blks; | 339 | u8 prev_freed_blks; |
340 | |||
341 | u8 addr[ETH_ALEN]; | ||
329 | }; | 342 | }; |
330 | 343 | ||
331 | struct wl1271 { | 344 | struct wl1271 { |
@@ -516,6 +529,12 @@ struct wl1271 { | |||
516 | 529 | ||
517 | /* the hlid of the link where the last transmitted skb came from */ | 530 | /* the hlid of the link where the last transmitted skb came from */ |
518 | int last_tx_hlid; | 531 | int last_tx_hlid; |
532 | |||
533 | /* AP-mode - a bitmap of links currently in PS mode according to FW */ | ||
534 | u32 ap_fw_ps_map; | ||
535 | |||
536 | /* AP-mode - a bitmap of links currently in PS mode in mac80211 */ | ||
537 | unsigned long ap_ps_map; | ||
519 | }; | 538 | }; |
520 | 539 | ||
521 | struct wl1271_station { | 540 | struct wl1271_station { |