aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKalle Valo <kalle.valo@nokia.com>2008-12-18 16:35:27 -0500
committerJohn W. Linville <linville@tuxdriver.com>2008-12-19 15:24:00 -0500
commit520eb82076993b7f55ef9b80771d264272e5127b (patch)
treef98b74dbe404d4c3a55b5f649c25ca24958e62ba
parentce7c9111a97492d04c504f40736a669c235d664a (diff)
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>
-rw-r--r--include/net/mac80211.h6
-rw-r--r--net/mac80211/ieee80211_i.h10
-rw-r--r--net/mac80211/main.c7
-rw-r--r--net/mac80211/mlme.c49
-rw-r--r--net/mac80211/tx.c13
-rw-r--r--net/mac80211/wext.c30
6 files changed, 103 insertions, 12 deletions
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 9428d3e2f113..b3bd00a9d992 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -854,6 +854,11 @@ enum ieee80211_tkip_key_type {
854 * 854 *
855 * @IEEE80211_HW_AMPDU_AGGREGATION: 855 * @IEEE80211_HW_AMPDU_AGGREGATION:
856 * Hardware supports 11n A-MPDU aggregation. 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 */
858enum ieee80211_hw_flags { 863enum ieee80211_hw_flags {
859 IEEE80211_HW_RX_INCLUDES_FCS = 1<<1, 864 IEEE80211_HW_RX_INCLUDES_FCS = 1<<1,
@@ -866,6 +871,7 @@ enum ieee80211_hw_flags {
866 IEEE80211_HW_NOISE_DBM = 1<<8, 871 IEEE80211_HW_NOISE_DBM = 1<<8,
867 IEEE80211_HW_SPECTRUM_MGMT = 1<<9, 872 IEEE80211_HW_SPECTRUM_MGMT = 1<<9,
868 IEEE80211_HW_AMPDU_AGGREGATION = 1<<10, 873 IEEE80211_HW_AMPDU_AGGREGATION = 1<<10,
874 IEEE80211_HW_NO_STACK_DYNAMIC_PS = 1<<11,
869}; 875};
870 876
871/** 877/**
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index a74d6738b30a..f3eec989662b 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -540,6 +540,7 @@ enum {
540 540
541enum queue_stop_reason { 541enum queue_stop_reason {
542 IEEE80211_QUEUE_STOP_REASON_DRIVER, 542 IEEE80211_QUEUE_STOP_REASON_DRIVER,
543 IEEE80211_QUEUE_STOP_REASON_PS,
543}; 544};
544 545
545/* maximum number of hardware queues we support. */ 546/* maximum number of hardware queues we support. */
@@ -693,7 +694,12 @@ struct ieee80211_local {
693 */ 694 */
694 int wifi_wme_noack_test; 695 int wifi_wme_noack_test;
695 unsigned int wmm_acm; /* bit field of ACM bits (BIT(802.1D tag)) */ 696 unsigned int wmm_acm; /* bit field of ACM bits (BIT(802.1D tag)) */
697
696 bool powersave; 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#ifdef CONFIG_MAC80211_DEBUGFS 704#ifdef CONFIG_MAC80211_DEBUGFS
699 struct local_debugfsdentries { 705 struct local_debugfsdentries {
@@ -977,6 +983,10 @@ int ieee80211_set_freq(struct ieee80211_sub_if_data *sdata, int freq);
977u64 ieee80211_mandatory_rates(struct ieee80211_local *local, 983u64 ieee80211_mandatory_rates(struct ieee80211_local *local,
978 enum ieee80211_band band); 984 enum ieee80211_band band);
979 985
986void ieee80211_dynamic_ps_enable_work(struct work_struct *work);
987void ieee80211_dynamic_ps_disable_work(struct work_struct *work);
988void ieee80211_dynamic_ps_timer(unsigned long data);
989
980void ieee80211_wake_queues_by_reason(struct ieee80211_hw *hw, 990void ieee80211_wake_queues_by_reason(struct ieee80211_hw *hw,
981 enum queue_stop_reason reason); 991 enum queue_stop_reason reason);
982void ieee80211_stop_queues_by_reason(struct ieee80211_hw *hw, 992void ieee80211_stop_queues_by_reason(struct ieee80211_hw *hw,
diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index 21335382f530..24b14363d6e7 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -729,6 +729,13 @@ struct ieee80211_hw *ieee80211_alloc_hw(size_t priv_data_len,
729 729
730 INIT_DELAYED_WORK(&local->scan_work, ieee80211_scan_work); 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 sta_info_init(local); 739 sta_info_init(local);
733 740
734 tasklet_init(&local->tx_pending_tasklet, ieee80211_tx_pending, 741 tasklet_init(&local->tx_pending_tasklet, ieee80211_tx_pending,
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index dac8bd37dcf5..5ba721b6a399 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -745,8 +745,14 @@ static void ieee80211_set_associated(struct ieee80211_sub_if_data *sdata,
745 ieee80211_bss_info_change_notify(sdata, bss_info_changed); 745 ieee80211_bss_info_change_notify(sdata, bss_info_changed);
746 746
747 if (local->powersave) { 747 if (local->powersave) {
748 local->hw.conf.flags |= IEEE80211_CONF_PS; 748 if (local->dynamic_ps_timeout > 0)
749 ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS); 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 netif_tx_start_all_queues(sdata->dev); 758 netif_tx_start_all_queues(sdata->dev);
@@ -866,6 +872,9 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
866 local->oper_channel_type = NL80211_CHAN_NO_HT; 872 local->oper_channel_type = NL80211_CHAN_NO_HT;
867 config_changed |= IEEE80211_CONF_CHANGE_HT; 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 if (local->hw.conf.flags & IEEE80211_CONF_PS) { 878 if (local->hw.conf.flags & IEEE80211_CONF_PS) {
870 local->hw.conf.flags &= ~IEEE80211_CONF_PS; 879 local->hw.conf.flags &= ~IEEE80211_CONF_PS;
871 config_changed |= IEEE80211_CONF_CHANGE_PS; 880 config_changed |= IEEE80211_CONF_CHANGE_PS;
@@ -2593,3 +2602,39 @@ void ieee80211_mlme_notify_scan_completed(struct ieee80211_local *local)
2593 ieee80211_restart_sta_timer(sdata); 2602 ieee80211_restart_sta_timer(sdata);
2594 rcu_read_unlock(); 2603 rcu_read_unlock();
2595} 2604}
2605
2606void 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
2621void 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
2635void 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);
2640}
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index b098c58d216f..a4af3a124cce 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -1473,6 +1473,19 @@ int ieee80211_subif_start_xmit(struct sk_buff *skb,
1473 goto fail; 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 nh_pos = skb_network_header(skb) - skb->data; 1489 nh_pos = skb_network_header(skb) - skb->data;
1477 h_pos = skb_transport_header(skb) - skb->data; 1490 h_pos = skb_transport_header(skb) - skb->data;
1478 1491
diff --git a/net/mac80211/wext.c b/net/mac80211/wext.c
index f6640d047157..7162d5816f39 100644
--- a/net/mac80211/wext.c
+++ b/net/mac80211/wext.c
@@ -833,7 +833,7 @@ static int ieee80211_ioctl_siwpower(struct net_device *dev,
833 struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); 833 struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
834 struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); 834 struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
835 struct ieee80211_conf *conf = &local->hw.conf; 835 struct ieee80211_conf *conf = &local->hw.conf;
836 int ret = 0; 836 int ret = 0, timeout = 0;
837 bool ps; 837 bool ps;
838 838
839 if (sdata->vif.type != NL80211_IFTYPE_STATION) 839 if (sdata->vif.type != NL80211_IFTYPE_STATION)
@@ -841,6 +841,7 @@ static int ieee80211_ioctl_siwpower(struct net_device *dev,
841 841
842 if (wrq->disabled) { 842 if (wrq->disabled) {
843 ps = false; 843 ps = false;
844 timeout = 0;
844 goto set; 845 goto set;
845 } 846 }
846 847
@@ -850,22 +851,31 @@ static int ieee80211_ioctl_siwpower(struct net_device *dev,
850 case IW_POWER_ALL_R: /* If explicitely state all */ 851 case IW_POWER_ALL_R: /* If explicitely state all */
851 ps = true; 852 ps = true;
852 break; 853 break;
853 default: /* Otherwise we don't support it */ 854 default: /* Otherwise we ignore */
854 return -EINVAL; 855 break;
855 } 856 }
856 857
857 if (ps == local->powersave) 858 if (wrq->flags & IW_POWER_TIMEOUT)
858 return ret; 859 timeout = wrq->value / 1000;
859 860
860set: 861set:
862 if (ps == local->powersave && timeout == local->dynamic_ps_timeout)
863 return ret;
864
861 local->powersave = ps; 865 local->powersave = ps;
866 local->dynamic_ps_timeout = timeout;
862 867
863 if (sdata->u.sta.flags & IEEE80211_STA_ASSOCIATED) { 868 if (sdata->u.sta.flags & IEEE80211_STA_ASSOCIATED) {
864 if (local->powersave) 869 if (!(local->hw.flags & IEEE80211_HW_NO_STACK_DYNAMIC_PS) &&
865 conf->flags |= IEEE80211_CONF_PS; 870 local->dynamic_ps_timeout > 0)
866 else 871 mod_timer(&local->dynamic_ps_timer, jiffies +
867 conf->flags &= ~IEEE80211_CONF_PS; 872 msecs_to_jiffies(local->dynamic_ps_timeout));
868 873 else {
874 if (local->powersave)
875 conf->flags |= IEEE80211_CONF_PS;
876 else
877 conf->flags &= ~IEEE80211_CONF_PS;
878 }
869 ret = ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS); 879 ret = ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
870 } 880 }
871 881