Commit 520eb82076993b7f55ef9b80771d264272e5127b
Committed by
John W. Linville
1 parent
ce7c9111a9
Exists in
master
and in
7 other branches
mac80211: implement dynamic power save
This patch implements dynamic power save for mac80211. Basically it means enabling power save mode after an idle period. Implementing it dynamically gives a good compromise of low power consumption and low latency. Some hardware have support for this in firmware, but some require the host to do it. The dynamic power save is implemented by adding an timeout to ieee80211_subif_start_xmit(). The timeout can be enabled from userspace with Wireless Extensions. For example, the command below enables the dynamic power save and sets the time timeout to 500 ms: iwconfig wlan0 power timeout 500m Power save now only works with devices which handle power save in firmware. It's also disabled by default and the heuristics when and how to enable is considered as a policy decision and will be left for the userspace to handle. In case the firmware has support for this, drivers can disable this feature with IEEE80211_HW_NO_STACK_DYNAMIC_PS. Big thanks to Johannes Berg for the help with the design and code. Signed-off-by: Kalle Valo <kalle.valo@nokia.com> Acked-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: John W. Linville <linville@tuxdriver.com>
Showing 6 changed files with 103 additions and 12 deletions Side-by-side Diff
include/net/mac80211.h
... | ... | @@ -854,6 +854,11 @@ |
854 | 854 | * |
855 | 855 | * @IEEE80211_HW_AMPDU_AGGREGATION: |
856 | 856 | * Hardware supports 11n A-MPDU aggregation. |
857 | + * | |
858 | + * @IEEE80211_HW_NO_STACK_DYNAMIC_PS: | |
859 | + * Hardware which has dynamic power save support, meaning | |
860 | + * that power save is enabled in idle periods, and don't need support | |
861 | + * from stack. | |
857 | 862 | */ |
858 | 863 | enum ieee80211_hw_flags { |
859 | 864 | IEEE80211_HW_RX_INCLUDES_FCS = 1<<1, |
... | ... | @@ -866,6 +871,7 @@ |
866 | 871 | IEEE80211_HW_NOISE_DBM = 1<<8, |
867 | 872 | IEEE80211_HW_SPECTRUM_MGMT = 1<<9, |
868 | 873 | IEEE80211_HW_AMPDU_AGGREGATION = 1<<10, |
874 | + IEEE80211_HW_NO_STACK_DYNAMIC_PS = 1<<11, | |
869 | 875 | }; |
870 | 876 | |
871 | 877 | /** |
net/mac80211/ieee80211_i.h
... | ... | @@ -540,6 +540,7 @@ |
540 | 540 | |
541 | 541 | enum queue_stop_reason { |
542 | 542 | IEEE80211_QUEUE_STOP_REASON_DRIVER, |
543 | + IEEE80211_QUEUE_STOP_REASON_PS, | |
543 | 544 | }; |
544 | 545 | |
545 | 546 | /* maximum number of hardware queues we support. */ |
546 | 547 | |
... | ... | @@ -693,7 +694,12 @@ |
693 | 694 | */ |
694 | 695 | int wifi_wme_noack_test; |
695 | 696 | unsigned int wmm_acm; /* bit field of ACM bits (BIT(802.1D tag)) */ |
697 | + | |
696 | 698 | bool powersave; |
699 | + int dynamic_ps_timeout; | |
700 | + struct work_struct dynamic_ps_enable_work; | |
701 | + struct work_struct dynamic_ps_disable_work; | |
702 | + struct timer_list dynamic_ps_timer; | |
697 | 703 | |
698 | 704 | #ifdef CONFIG_MAC80211_DEBUGFS |
699 | 705 | struct local_debugfsdentries { |
... | ... | @@ -976,6 +982,10 @@ |
976 | 982 | int ieee80211_set_freq(struct ieee80211_sub_if_data *sdata, int freq); |
977 | 983 | u64 ieee80211_mandatory_rates(struct ieee80211_local *local, |
978 | 984 | enum ieee80211_band band); |
985 | + | |
986 | +void ieee80211_dynamic_ps_enable_work(struct work_struct *work); | |
987 | +void ieee80211_dynamic_ps_disable_work(struct work_struct *work); | |
988 | +void ieee80211_dynamic_ps_timer(unsigned long data); | |
979 | 989 | |
980 | 990 | void ieee80211_wake_queues_by_reason(struct ieee80211_hw *hw, |
981 | 991 | enum queue_stop_reason reason); |
net/mac80211/main.c
... | ... | @@ -729,6 +729,13 @@ |
729 | 729 | |
730 | 730 | INIT_DELAYED_WORK(&local->scan_work, ieee80211_scan_work); |
731 | 731 | |
732 | + INIT_WORK(&local->dynamic_ps_enable_work, | |
733 | + ieee80211_dynamic_ps_enable_work); | |
734 | + INIT_WORK(&local->dynamic_ps_disable_work, | |
735 | + ieee80211_dynamic_ps_disable_work); | |
736 | + setup_timer(&local->dynamic_ps_timer, | |
737 | + ieee80211_dynamic_ps_timer, (unsigned long) local); | |
738 | + | |
732 | 739 | sta_info_init(local); |
733 | 740 | |
734 | 741 | tasklet_init(&local->tx_pending_tasklet, ieee80211_tx_pending, |
net/mac80211/mlme.c
... | ... | @@ -745,8 +745,14 @@ |
745 | 745 | ieee80211_bss_info_change_notify(sdata, bss_info_changed); |
746 | 746 | |
747 | 747 | if (local->powersave) { |
748 | - local->hw.conf.flags |= IEEE80211_CONF_PS; | |
749 | - ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS); | |
748 | + if (local->dynamic_ps_timeout > 0) | |
749 | + mod_timer(&local->dynamic_ps_timer, jiffies + | |
750 | + msecs_to_jiffies(local->dynamic_ps_timeout)); | |
751 | + else { | |
752 | + conf->flags |= IEEE80211_CONF_PS; | |
753 | + ieee80211_hw_config(local, | |
754 | + IEEE80211_CONF_CHANGE_PS); | |
755 | + } | |
750 | 756 | } |
751 | 757 | |
752 | 758 | netif_tx_start_all_queues(sdata->dev); |
... | ... | @@ -866,6 +872,9 @@ |
866 | 872 | local->oper_channel_type = NL80211_CHAN_NO_HT; |
867 | 873 | config_changed |= IEEE80211_CONF_CHANGE_HT; |
868 | 874 | |
875 | + del_timer_sync(&local->dynamic_ps_timer); | |
876 | + cancel_work_sync(&local->dynamic_ps_enable_work); | |
877 | + | |
869 | 878 | if (local->hw.conf.flags & IEEE80211_CONF_PS) { |
870 | 879 | local->hw.conf.flags &= ~IEEE80211_CONF_PS; |
871 | 880 | config_changed |= IEEE80211_CONF_CHANGE_PS; |
... | ... | @@ -2592,5 +2601,41 @@ |
2592 | 2601 | list_for_each_entry_rcu(sdata, &local->interfaces, list) |
2593 | 2602 | ieee80211_restart_sta_timer(sdata); |
2594 | 2603 | rcu_read_unlock(); |
2604 | +} | |
2605 | + | |
2606 | +void ieee80211_dynamic_ps_disable_work(struct work_struct *work) | |
2607 | +{ | |
2608 | + struct ieee80211_local *local = | |
2609 | + container_of(work, struct ieee80211_local, | |
2610 | + dynamic_ps_disable_work); | |
2611 | + | |
2612 | + if (local->hw.conf.flags & IEEE80211_CONF_PS) { | |
2613 | + local->hw.conf.flags &= ~IEEE80211_CONF_PS; | |
2614 | + ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS); | |
2615 | + } | |
2616 | + | |
2617 | + ieee80211_wake_queues_by_reason(&local->hw, | |
2618 | + IEEE80211_QUEUE_STOP_REASON_PS); | |
2619 | +} | |
2620 | + | |
2621 | +void ieee80211_dynamic_ps_enable_work(struct work_struct *work) | |
2622 | +{ | |
2623 | + struct ieee80211_local *local = | |
2624 | + container_of(work, struct ieee80211_local, | |
2625 | + dynamic_ps_enable_work); | |
2626 | + | |
2627 | + if (local->hw.conf.flags & IEEE80211_CONF_PS) | |
2628 | + return; | |
2629 | + | |
2630 | + local->hw.conf.flags |= IEEE80211_CONF_PS; | |
2631 | + | |
2632 | + ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS); | |
2633 | +} | |
2634 | + | |
2635 | +void ieee80211_dynamic_ps_timer(unsigned long data) | |
2636 | +{ | |
2637 | + struct ieee80211_local *local = (void *) data; | |
2638 | + | |
2639 | + queue_work(local->hw.workqueue, &local->dynamic_ps_enable_work); | |
2595 | 2640 | } |
net/mac80211/tx.c
... | ... | @@ -1473,6 +1473,19 @@ |
1473 | 1473 | goto fail; |
1474 | 1474 | } |
1475 | 1475 | |
1476 | + if (!(local->hw.flags & IEEE80211_HW_NO_STACK_DYNAMIC_PS) && | |
1477 | + local->dynamic_ps_timeout > 0) { | |
1478 | + if (local->hw.conf.flags & IEEE80211_CONF_PS) { | |
1479 | + ieee80211_stop_queues_by_reason(&local->hw, | |
1480 | + IEEE80211_QUEUE_STOP_REASON_PS); | |
1481 | + queue_work(local->hw.workqueue, | |
1482 | + &local->dynamic_ps_disable_work); | |
1483 | + } | |
1484 | + | |
1485 | + mod_timer(&local->dynamic_ps_timer, jiffies + | |
1486 | + msecs_to_jiffies(local->dynamic_ps_timeout)); | |
1487 | + } | |
1488 | + | |
1476 | 1489 | nh_pos = skb_network_header(skb) - skb->data; |
1477 | 1490 | h_pos = skb_transport_header(skb) - skb->data; |
1478 | 1491 |
net/mac80211/wext.c
... | ... | @@ -833,7 +833,7 @@ |
833 | 833 | struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); |
834 | 834 | struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); |
835 | 835 | struct ieee80211_conf *conf = &local->hw.conf; |
836 | - int ret = 0; | |
836 | + int ret = 0, timeout = 0; | |
837 | 837 | bool ps; |
838 | 838 | |
839 | 839 | if (sdata->vif.type != NL80211_IFTYPE_STATION) |
... | ... | @@ -841,6 +841,7 @@ |
841 | 841 | |
842 | 842 | if (wrq->disabled) { |
843 | 843 | ps = false; |
844 | + timeout = 0; | |
844 | 845 | goto set; |
845 | 846 | } |
846 | 847 | |
847 | 848 | |
848 | 849 | |
849 | 850 | |
850 | 851 | |
... | ... | @@ -850,22 +851,31 @@ |
850 | 851 | case IW_POWER_ALL_R: /* If explicitely state all */ |
851 | 852 | ps = true; |
852 | 853 | break; |
853 | - default: /* Otherwise we don't support it */ | |
854 | - return -EINVAL; | |
854 | + default: /* Otherwise we ignore */ | |
855 | + break; | |
855 | 856 | } |
856 | 857 | |
857 | - if (ps == local->powersave) | |
858 | - return ret; | |
858 | + if (wrq->flags & IW_POWER_TIMEOUT) | |
859 | + timeout = wrq->value / 1000; | |
859 | 860 | |
860 | 861 | set: |
862 | + if (ps == local->powersave && timeout == local->dynamic_ps_timeout) | |
863 | + return ret; | |
864 | + | |
861 | 865 | local->powersave = ps; |
866 | + local->dynamic_ps_timeout = timeout; | |
862 | 867 | |
863 | 868 | if (sdata->u.sta.flags & IEEE80211_STA_ASSOCIATED) { |
864 | - if (local->powersave) | |
865 | - conf->flags |= IEEE80211_CONF_PS; | |
866 | - else | |
867 | - conf->flags &= ~IEEE80211_CONF_PS; | |
868 | - | |
869 | + if (!(local->hw.flags & IEEE80211_HW_NO_STACK_DYNAMIC_PS) && | |
870 | + local->dynamic_ps_timeout > 0) | |
871 | + mod_timer(&local->dynamic_ps_timer, jiffies + | |
872 | + msecs_to_jiffies(local->dynamic_ps_timeout)); | |
873 | + else { | |
874 | + if (local->powersave) | |
875 | + conf->flags |= IEEE80211_CONF_PS; | |
876 | + else | |
877 | + conf->flags &= ~IEEE80211_CONF_PS; | |
878 | + } | |
869 | 879 | ret = ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS); |
870 | 880 | } |
871 | 881 |