diff mbox

[RFC,2/3] mac80211: mesh power save doze scheduling

Message ID 1358936360-7795-3-git-send-email-marco@cozybit.com (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Marco Porsch Jan. 23, 2013, 10:19 a.m. UTC
Configure the HW for PS mode if the local mesh PS parameters
allow so.

Expose a callback ieee80211_mps_init for drivers to register
mesh powersave ops:
- hw_doze - put the radio to sleep now
- hw_wakeup - wake the radio up for frame RX
These ops may be extended in the future to allow drivers/HW to
implement mesh PS themselves. (The current design goal was to
concentrate mesh PS routines in mac80211 to keep driver
modifications minimal.

Track the beacon timing information of peers we are in PS mode
towards. Set a per-STA hrtimer which will trigger a wakeup right
before the peer's next TBTT.
Also use the same hrtimer to go to sleep mode after not
receiving a beacon in a defined time margin. In this case
calculate the next TBTT and increase the margin.

For mesh Awake Windows wakeup on SWBA (beacon_get_tim) and start
a timer which triggers a hw_doze call on expiry.

Signed-off-by: Marco Porsch <marco@cozybit.com>
---
 include/net/mac80211.h     |   34 +++++
 net/mac80211/ieee80211_i.h |    9 +-
 net/mac80211/mesh.c        |   17 +++
 net/mac80211/mesh.h        |   17 +++
 net/mac80211/mesh_plink.c  |    1 +
 net/mac80211/mesh_ps.c     |  357 ++++++++++++++++++++++++++++++++++++++++++++
 net/mac80211/sta_info.c    |    4 +
 net/mac80211/sta_info.h    |   13 ++
 net/mac80211/tx.c          |    2 +
 9 files changed, 453 insertions(+), 1 deletion(-)

Comments

Thomas Pedersen Jan. 23, 2013, 7:16 p.m. UTC | #1
On Wed, Jan 23, 2013 at 2:19 AM, Marco Porsch <marco@cozybit.com> wrote:
> Configure the HW for PS mode if the local mesh PS parameters
> allow so.
>
> Expose a callback ieee80211_mps_init for drivers to register
> mesh powersave ops:
> - hw_doze - put the radio to sleep now
> - hw_wakeup - wake the radio up for frame RX
> These ops may be extended in the future to allow drivers/HW to
> implement mesh PS themselves. (The current design goal was to
> concentrate mesh PS routines in mac80211 to keep driver
> modifications minimal.
>
> Track the beacon timing information of peers we are in PS mode
> towards. Set a per-STA hrtimer which will trigger a wakeup right
> before the peer's next TBTT.
> Also use the same hrtimer to go to sleep mode after not
> receiving a beacon in a defined time margin. In this case
> calculate the next TBTT and increase the margin.
>
> For mesh Awake Windows wakeup on SWBA (beacon_get_tim) and start
> a timer which triggers a hw_doze call on expiry.
>
> Signed-off-by: Marco Porsch <marco@cozybit.com>
> ---
>  include/net/mac80211.h     |   34 +++++
>  net/mac80211/ieee80211_i.h |    9 +-
>  net/mac80211/mesh.c        |   17 +++
>  net/mac80211/mesh.h        |   17 +++
>  net/mac80211/mesh_plink.c  |    1 +
>  net/mac80211/mesh_ps.c     |  357 ++++++++++++++++++++++++++++++++++++++++++++
>  net/mac80211/sta_info.c    |    4 +
>  net/mac80211/sta_info.h    |   13 ++
>  net/mac80211/tx.c          |    2 +
>  9 files changed, 453 insertions(+), 1 deletion(-)
>
> diff --git a/include/net/mac80211.h b/include/net/mac80211.h
> index 23daed3..ca6979d 100644
> --- a/include/net/mac80211.h
> +++ b/include/net/mac80211.h
> @@ -3952,6 +3952,40 @@ void ieee80211_stop_rx_ba_session(struct ieee80211_vif *vif, u16 ba_rx_bitmap,
>   */
>  void ieee80211_send_bar(struct ieee80211_vif *vif, u8 *ra, u16 tid, u16 ssn);
>
> +/**
> + * struct ieee80211_mps_ops - callbacks from mac80211 to driver for mesh PS
> + *
> + * This structure contains callbacks that the driver has to or may handle for
> + * mesh powersave.
> + * TODO Add further callbacks for HW that performs certain mesh PS tasks on its
> + * own (e.g. assign list of STA to track).
> + *
> + * @hw_doze: put the radio to doze state to conserve power
> + * @hw_wakeup: wake up the radio to receive frames again
> + */
> +struct ieee80211_mps_ops {
> +       void (*hw_doze)(struct ieee80211_hw *hw);
> +       void (*hw_wakeup)(struct ieee80211_hw *hw);
> +};
> +
> +#ifdef CONFIG_MAC80211_MESH
> +/**
> + * ieee80211_mps_init - register driver callbacks for mesh PS
> + *
> + * @hw: the hardware
> + * @ops: callbacks for this device
> + *
> + * called by driver on mesh interface add/remove
> + * TODO add HW capability flags
> + */
> +int ieee80211_mps_init(struct ieee80211_hw *hw,
> +                      const struct ieee80211_mps_ops *ops);
> +#else
> +static inline int ieee80211_mps_init(struct ieee80211_hw *hw,
> +                                    const struct ieee80211_mps_ops *ops)
> +{ return 0; }
> +#endif
> +
>  /* Rate control API */
>
>  /**
> diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
> index e08b4c0..c08f423 100644
> --- a/net/mac80211/ieee80211_i.h
> +++ b/net/mac80211/ieee80211_i.h
> @@ -629,6 +629,8 @@ struct ieee80211_if_mesh {
>         int ps_peers_deep_sleep;
>         struct ps_data ps;
>         atomic_t num_mpsp; /* counts both owner and recipient independently */
> +       struct timer_list awake_window_end_timer;
> +       bool in_awake_window;
>  };
>
>  #ifdef CONFIG_MAC80211_MESH
> @@ -1126,7 +1128,7 @@ struct ieee80211_local {
>         bool pspolling;
>         bool offchannel_ps_enabled;
>         /*
> -        * PS can only be enabled when we have exactly one managed
> +        * managed mode PS can only be enabled when we have exactly one managed
>          * interface (and monitors) in PS, this then points there.
>          */
>         struct ieee80211_sub_if_data *ps_sdata;
> @@ -1146,6 +1148,11 @@ struct ieee80211_local {
>
>         int user_power_level; /* in dBm, for all interfaces */
>
> +       /* mesh power save */
> +       bool mps_enabled;
> +       bool mps_hw_doze;
> +       const struct ieee80211_mps_ops *mps_ops;
> +
>         enum ieee80211_smps_mode smps_mode;
>
>         struct work_struct restart_work;
> diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c
> index 8ce5d60..740d035 100644
> --- a/net/mac80211/mesh.c
> +++ b/net/mac80211/mesh.c
> @@ -803,6 +803,7 @@ void ieee80211_mesh_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
>
>  void ieee80211_mesh_work(struct ieee80211_sub_if_data *sdata)
>  {
> +       struct ieee80211_local *local = sdata->local;
>         struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
>
>         if (ifmsh->preq_queue_len &&
> @@ -824,6 +825,19 @@ void ieee80211_mesh_work(struct ieee80211_sub_if_data *sdata)
>
>         if (test_and_clear_bit(MESH_WORK_DRIFT_ADJUST, &ifmsh->wrkq_flags))
>                 mesh_sync_adjust_tbtt(sdata);
> +
> +       if (test_and_clear_bit(MESH_WORK_PS_HW_CONF, &ifmsh->wrkq_flags))
> +               ieee80211_mps_hw_conf(local);
> +
> +       /* in case both fired simultaneously, wakeup overrides doze */
> +       if (test_bit(MESH_WORK_PS_DOZE, &ifmsh->wrkq_flags) &&
> +           test_bit(MESH_WORK_PS_WAKEUP, &ifmsh->wrkq_flags))
> +               clear_bit(MESH_WORK_PS_DOZE, &ifmsh->wrkq_flags);
> +
> +       if (test_and_clear_bit(MESH_WORK_PS_WAKEUP, &ifmsh->wrkq_flags))
> +               ieee80211_mps_wakeup(local);
> +       else if (test_and_clear_bit(MESH_WORK_PS_DOZE, &ifmsh->wrkq_flags))
> +               ieee80211_mps_doze(local);
>  }
>
>  void ieee80211_mesh_notify_scan_completed(struct ieee80211_local *local)
> @@ -863,6 +877,9 @@ void ieee80211_mesh_init_sdata(struct ieee80211_sub_if_data *sdata)
>         setup_timer(&ifmsh->mesh_path_root_timer,
>                     ieee80211_mesh_path_root_timer,
>                     (unsigned long) sdata);
> +       setup_timer(&ifmsh->awake_window_end_timer,
> +                   ieee80211_mps_awake_window_end,
> +                   (unsigned long) sdata);
>         INIT_LIST_HEAD(&ifmsh->preq_queue.list);
>         skb_queue_head_init(&ifmsh->ps.bc_buf);
>         spin_lock_init(&ifmsh->mesh_preq_queue_lock);
> diff --git a/net/mac80211/mesh.h b/net/mac80211/mesh.h
> index fa1423e..ce35b78 100644
> --- a/net/mac80211/mesh.h
> +++ b/net/mac80211/mesh.h
> @@ -58,6 +58,10 @@ enum mesh_path_flags {
>   * @MESH_WORK_ROOT: the mesh root station needs to send a frame
>   * @MESH_WORK_DRIFT_ADJUST: time to compensate for clock drift relative to other
>   * mesh nodes
> + * @MESH_WORK_PS_HW_CONF: configure hardware according to the link-specific
> + * mesh power modes
> + * @MESH_WORK_PS_DOZE: put the hardware to sleep after checking all conditions
> + * @MESH_WORK_PS_WAKEUP: wakeup the hardware immediately
>   */
>  enum mesh_deferred_task_flags {
>         MESH_WORK_HOUSEKEEPING,
> @@ -65,6 +69,9 @@ enum mesh_deferred_task_flags {
>         MESH_WORK_GROW_MPP_TABLE,
>         MESH_WORK_ROOT,
>         MESH_WORK_DRIFT_ADJUST,
> +       MESH_WORK_PS_HW_CONF,
> +       MESH_WORK_PS_DOZE,
> +       MESH_WORK_PS_WAKEUP,
>  };
>
>  /**
> @@ -258,6 +265,16 @@ void ieee80211_mpsp_trigger_process(struct ieee80211_hdr *hdr,
>                                     struct sta_info *sta, bool tx, bool acked);
>  void ieee80211_mps_frame_release(struct sta_info *sta,
>                                  struct ieee802_11_elems *elems);
> +void ieee80211_mps_hw_conf(struct ieee80211_local *local);
> +void ieee80211_mps_sta_tbtt_update(struct sta_info *sta,
> +                                  struct ieee80211_mgmt *mgmt,
> +                                  struct ieee80211_tim_ie *tim,
> +                                  u64 tsf);
> +enum hrtimer_restart ieee80211_mps_sta_tbtt_timer(struct hrtimer *timer);
> +void ieee80211_mps_awake_window_start(struct ieee80211_sub_if_data *sdata);
> +void ieee80211_mps_awake_window_end(unsigned long data);
> +void ieee80211_mps_doze(struct ieee80211_local *local);
> +void ieee80211_mps_wakeup(struct ieee80211_local *local);
>
>  /* Mesh paths */
>  int mesh_nexthop_lookup(struct sk_buff *skb,
> diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c
> index af6fbfd..f41b4bb 100644
> --- a/net/mac80211/mesh_plink.c
> +++ b/net/mac80211/mesh_plink.c
> @@ -419,6 +419,7 @@ void mesh_neighbour_update(struct ieee80211_sub_if_data *sdata,
>                 ifmsh->sync_ops->rx_bcn(sta, mgmt, elems, rx_status, tsf);
>
>         ieee80211_mps_frame_release(sta, elems);
> +       ieee80211_mps_sta_tbtt_update(sta, mgmt, elems->tim, tsf);
>  out:
>         rcu_read_unlock();
>  }
> diff --git a/net/mac80211/mesh_ps.c b/net/mac80211/mesh_ps.c
> index 788b935..d7fffd9 100644
> --- a/net/mac80211/mesh_ps.c
> +++ b/net/mac80211/mesh_ps.c
> @@ -9,12 +9,32 @@
>
>  #include "mesh.h"
>  #include "wme.h"
> +#include <linux/export.h>
> +#include <linux/hrtimer.h>
>
> +/*
> + * time to wakeup before and stay awake after peer TBTT until beacon receipt.
> + * required to cope with stack delay and HW wakeup time before TBTT and delayed
> + * beacons after TBTT
> + * TODO adjust this value for different hardware or make it adaptive
> + */
> +#define TBTT_MARGIN    5000    /* in us units */
> +
> +
> +static inline void mps_queue_work(struct ieee80211_sub_if_data *sdata,
> +                                 enum mesh_deferred_task_flags flag)
> +{
> +       set_bit(flag, &sdata->u.mesh.wrkq_flags);
> +       ieee80211_queue_work(&sdata->local->hw, &sdata->work);
> +}
>
>  static inline bool test_and_set_mpsp_flag(struct sta_info *sta,
>                                           enum ieee80211_sta_info_flags flag)
>  {
>         if (!test_and_set_sta_flag(sta, flag)) {
> +               if (sta->sdata->local->mps_enabled &&
> +                   atomic_read(&sta->sdata->u.mesh.num_mpsp) == 0)
> +                       mps_queue_work(sta->sdata, MESH_WORK_PS_WAKEUP);
>                 atomic_inc(&sta->sdata->u.mesh.num_mpsp);
>                 return false;
>         }
> @@ -26,6 +46,9 @@ static inline bool test_and_clear_mpsp_flag(struct sta_info *sta,
>  {
>         if (test_and_clear_sta_flag(sta, flag)) {
>                 atomic_dec(&sta->sdata->u.mesh.num_mpsp);
> +               if (sta->sdata->local->mps_enabled &&
> +                   atomic_read(&sta->sdata->u.mesh.num_mpsp) == 0)
> +                       mps_queue_work(sta->sdata, MESH_WORK_PS_DOZE);
>                 return true;
>         }
>         return false;
> @@ -148,6 +171,8 @@ void ieee80211_mps_local_status_update(struct ieee80211_sub_if_data *sdata)
>
>         ifmsh->ps_peers_light_sleep = light_sleep_cnt;
>         ifmsh->ps_peers_deep_sleep = deep_sleep_cnt;
> +
> +       mps_queue_work(sdata, MESH_WORK_PS_HW_CONF);
>  }
>
>  /**
> @@ -605,3 +630,335 @@ void ieee80211_mps_frame_release(struct sta_info *sta,
>         else
>                 mps_frame_deliver(sta, 1);
>  }
> +
> +
> +/* mesh PS driver configuration and doze scheduling */
> +
> +static bool mps_hw_conf_check(struct ieee80211_local *local)
> +{
> +       struct ieee80211_sub_if_data *sdata;
> +       struct ieee80211_if_mesh *ifmsh;
> +       bool enable = true;
> +
> +       if (!local->mps_ops)
> +               return false;
> +
> +       mutex_lock(&local->iflist_mtx);
> +       list_for_each_entry(sdata, &local->interfaces, list) {
> +               if (!ieee80211_sdata_running(sdata))
> +                       continue;
> +
> +               /* If an AP or any other non-mesh vif is found, disable PS */
> +               if (ieee80211_sdata_running(sdata) &&
> +                   sdata->vif.type != NL80211_IFTYPE_MESH_POINT) {
> +                       enable = false;
> +                       break;
> +               }
> +
> +               ifmsh = &sdata->u.mesh;
> +
> +               /*
> +                * check for non-peer power mode, check for links in active
> +                * mode. Assume a valid power mode is set for each established
> +                * peer link
> +                */
> +               if (ifmsh->nonpeer_pm == NL80211_MESH_POWER_ACTIVE ||
> +                   ifmsh->ps_peers_light_sleep + ifmsh->ps_peers_deep_sleep
> +                               < atomic_read(&ifmsh->estab_plinks)) {
> +                       enable = false;
> +                       break;
> +               }
> +       }
> +       mutex_unlock(&local->iflist_mtx);
> +
> +       return enable;
> +}
> +
> +/**
> + * mps_hw_conf_sta_prepare - mark peers to catch beacon once before first doze
> + */
> +static void mps_hw_conf_sta_prepare(struct ieee80211_local *local)
> +{
> +       struct sta_info *sta;
> +
> +       mutex_lock(&local->sta_mtx);
> +       list_for_each_entry(sta, &local->sta_list, list) {
> +               if (!ieee80211_vif_is_mesh(&sta->sdata->vif) ||
> +                   !ieee80211_sdata_running(sta->sdata) ||
> +                   sta->plink_state != NL80211_PLINK_ESTAB)
> +                       continue;
> +               else
> +                       set_sta_flag(sta, WLAN_STA_MPS_WAIT_FOR_BEACON);
> +       }
> +       mutex_unlock(&local->sta_mtx);
> +}
> +
> +/**
> + * ieee80211_mps_hw_conf - check conditions for mesh PS and configure driver
> + *
> + * @local: local interface data
> + */
> +void ieee80211_mps_hw_conf(struct ieee80211_local *local)
> +{
> +       bool enable;
> +
> +       enable = mps_hw_conf_check(local);
> +
> +       if (local->mps_enabled == enable)
> +               return;
> +
> +       if (enable) {
> +               mps_hw_conf_sta_prepare(local);
> +               local->hw.conf.flags |= IEEE80211_CONF_PS;
> +       } else {
> +               local->hw.conf.flags &= ~IEEE80211_CONF_PS;
> +       }
> +
> +       ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
> +       local->mps_enabled = enable;
> +}
> +
> +static void mps_sta_tbtt_set_timer(struct sta_info *sta,
> +                                  struct ieee80211_tim_ie *tim,
> +                                  u64 tsf_local)
> +{
> +       u64 tsf_peer;
> +       int skip = 1;
> +       u32 nexttbtt_interval;
> +       ktime_t now;
> +
> +       /* simple Deep Sleep implementation: only wake up for DTIM beacons */
> +       if (sta->local_pm == NL80211_MESH_POWER_DEEP_SLEEP &&
> +           tim->dtim_count == 0)
> +               skip = tim->dtim_period;

Still need to schedule for the next DTIM if this isn't a DTIM beacon so:

skip = tim->dtim_count ? tim->dtim_count : tim->dtim_period; ?

> +       /*
> +        * determine time to peer TBTT (TSF % beacon_interval = 0).
> +        * This approach is robust to delayed beacons.
> +        */
> +       tsf_peer = tsf_local + sta->t_offset;
> +       nexttbtt_interval = sta->beacon_interval -
> +                       do_div(tsf_peer, sta->beacon_interval * skip);
> +       now = hrtimer_cb_get_time(&sta->mps_beacon_timer);
> +
> +       mps_dbg(sta->sdata, "updating %pM next TBTT in %dus (%llus awake)\n",
> +               sta->sta.addr, nexttbtt_interval,
> +               (long long) ktime_to_us(ktime_sub(now, sta->tbtt_wakeup)));
> +
> +       sta->tbtt_wakeup = ktime_add_us(now, nexttbtt_interval - TBTT_MARGIN);
> +       sta->tbtt_miss = ktime_add_us(now, nexttbtt_interval + TBTT_MARGIN);
> +
> +       hrtimer_start(&sta->mps_beacon_timer, sta->tbtt_wakeup,
> +                     HRTIMER_MODE_ABS);
> +}
> +
> +/**
> + * ieee80211_mps_sta_tbtt_update - update peer beacon wakeup schedule
> + *
> + * @sta: mesh STA
> + * @mgmt: beacon frame
> + * @tim: TIM IE of beacon frame
> + * @tsf_local: current HW TSF
> + */
> +void ieee80211_mps_sta_tbtt_update(struct sta_info *sta,
> +                                  struct ieee80211_mgmt *mgmt,
> +                                  struct ieee80211_tim_ie *tim,
> +                                  u64 tsf_local)
> +{
> +       struct ieee80211_sub_if_data *sdata = sta->sdata;
> +
> +       if (!sdata->local->mps_enabled ||
> +           sta->plink_state != NL80211_PLINK_ESTAB)
> +               return;
> +
> +       hrtimer_cancel(&sta->mps_beacon_timer);
> +       clear_sta_flag(sta, WLAN_STA_MPS_WAIT_FOR_BEACON);
> +
> +       sta->beacon_interval = le16_to_cpu(mgmt->u.beacon.beacon_int) * 1024;
> +       /* pending multicasts after DTIM beacon? TODO reset after RX */
> +       if (tim->bitmap_ctrl & 0x01)
> +               set_sta_flag(sta, WLAN_STA_MPS_WAIT_FOR_CAB);
> +       else
> +               clear_sta_flag(sta, WLAN_STA_MPS_WAIT_FOR_CAB);
> +
> +       mps_sta_tbtt_set_timer(sta, tim, tsf_local);
> +
> +       mps_queue_work(sdata, MESH_WORK_PS_DOZE);
> +}
> +
> +/**
> + * ieee80211_mps_sta_tbtt_timer - hrtimer callback for mesh PS doze/wakeup
> + *
> + * Used for both waking up before TBTT and resuming doze in case the beacon
> + * is not received on time.
> + * XXX what lock should be used here? hrtimer callbacks are hard IRQ context
> + */
> +enum hrtimer_restart ieee80211_mps_sta_tbtt_timer(struct hrtimer *timer)
> +{
> +       /*
> +        * This STA is valid because the timer is canceled on STA removal
> +        * after having made sure it cannot be armed (by deleting the plink.)
> +        */
> +       struct sta_info *sta = container_of(timer, struct sta_info,
> +                                           mps_beacon_timer);
> +       struct ieee80211_sub_if_data *sdata = sta->sdata;
> +
> +       if (!sdata->local->mps_enabled ||
> +           sta->plink_state != NL80211_PLINK_ESTAB)
> +               return HRTIMER_NORESTART;
> +
> +       if (!test_sta_flag(sta, WLAN_STA_MPS_WAIT_FOR_BEACON)) {
> +               mps_dbg(sdata, "wakeup for %pM (margin %dus)\n",
> +                       sta->sta.addr, TBTT_MARGIN);
> +
> +               set_sta_flag(sta, WLAN_STA_MPS_WAIT_FOR_BEACON);
> +               hrtimer_set_expires(&sta->mps_beacon_timer, sta->tbtt_miss);
> +
> +               mps_queue_work(sdata, MESH_WORK_PS_WAKEUP);
> +       } else {
> +               mps_dbg(sdata, "beacon miss %pM\n", sta->sta.addr);
> +
> +               clear_sta_flag(sta, WLAN_STA_MPS_WAIT_FOR_BEACON);
> +
> +               /* increase the margin for each beacon miss TODO deep sleep */
> +               sta->tbtt_wakeup = ktime_add_us(sta->tbtt_wakeup,
> +                               sta->beacon_interval - TBTT_MARGIN);
> +               sta->tbtt_miss = ktime_add_us(sta->tbtt_miss,
> +                               sta->beacon_interval + TBTT_MARGIN);
> +               hrtimer_set_expires(&sta->mps_beacon_timer, sta->tbtt_wakeup);
> +
> +               mps_queue_work(sdata, MESH_WORK_PS_DOZE);
> +       }
> +
> +       return HRTIMER_RESTART;
> +}
> +
> +/**
> + * ieee80211_mps_awake_window_start - start Awake Window on SWBA
> + *
> + * @sdata: local mesh subif
> + */
> +void ieee80211_mps_awake_window_start(struct ieee80211_sub_if_data *sdata)
> +{
> +       struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
> +
> +       if (!sdata->local->mps_enabled)
> +               return;
> +
> +       mps_dbg(sdata, "awake window start (%dTU)\n",
> +               ifmsh->mshcfg.dot11MeshAwakeWindowDuration);
> +
> +       ifmsh->in_awake_window = true;
> +       mod_timer(&ifmsh->awake_window_end_timer, jiffies + usecs_to_jiffies(
> +                       ifmsh->mshcfg.dot11MeshAwakeWindowDuration * 1024));
> +
> +       mps_queue_work(sdata, MESH_WORK_PS_WAKEUP);
> +}
> +
> +/**
> + * ieee80211_mps_awake_window_end - timer callback for end of Awake Window
> + */
> +void ieee80211_mps_awake_window_end(unsigned long data)
> +{
> +       struct ieee80211_sub_if_data *sdata = (void *) data;
> +       struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
> +
> +       mps_dbg(sdata, "awake window end\n");
> +
> +       ifmsh->in_awake_window = false;
> +
> +       if (!sdata->local->mps_enabled)
> +               return;
> +
> +       mps_queue_work(sdata, MESH_WORK_PS_DOZE);
> +}
> +
> +static bool mps_doze_check_vif(struct ieee80211_local *local)
> +{
> +       struct ieee80211_sub_if_data *sdata;
> +       bool allow = true;
> +
> +       mutex_lock(&local->iflist_mtx);
> +       list_for_each_entry(sdata, &local->interfaces, list) {
> +               if (!ieee80211_sdata_running(sdata))
> +                       continue;
> +
> +               if (!ieee80211_vif_is_mesh(&sdata->vif) ||
> +                   sdata->u.mesh.in_awake_window ||

Can't you just check if the awake_window_end timer is queued and get
rid of this bool?

> +                   atomic_read(&sdata->u.mesh.num_mpsp)) {
> +                       allow = false;
> +                       break;
> +               }
> +       }
> +       mutex_unlock(&local->iflist_mtx);
> +
> +       return allow;
> +}
> +
> +static bool mps_doze_check_sta(struct ieee80211_local *local)
> +{
> +       struct sta_info *sta;
> +       bool allow = true;
> +
> +       mutex_lock(&local->sta_mtx);
> +       list_for_each_entry(sta, &local->sta_list, list) {
> +               if (!ieee80211_vif_is_mesh(&sta->sdata->vif) ||
> +                   !ieee80211_sdata_running(sta->sdata) ||
> +                   sta->plink_state != NL80211_PLINK_ESTAB) {
> +                       continue;
> +               } else if (test_sta_flag(sta, WLAN_STA_MPS_WAIT_FOR_BEACON) ||
> +                          test_sta_flag(sta, WLAN_STA_MPS_WAIT_FOR_CAB)) {
> +                       allow = false;
> +                       break;
> +               }
> +       }
> +       mutex_unlock(&local->sta_mtx);
> +
> +       return allow;
> +}
> +
> +/**
> + * ieee80211_mps_doze - check conditions and trigger radio doze state
> + *
> + * @local: local interface data
> + */
> +void ieee80211_mps_doze(struct ieee80211_local *local)
> +{
> +       if (!local->mps_enabled ||
> +           local->mps_hw_doze ||
> +           !mps_doze_check_vif(local) ||
> +           !mps_doze_check_sta(local))
> +               return;
> +
> +       local->mps_hw_doze = true;

Only set this if local->mps_ops exist?

> +       if (local->mps_ops)
> +               local->mps_ops->hw_doze(&local->hw);
> +}
> +
> +/**
> + * ieee80211_mps_wakeup - trigger radio wakeup immediately
> + *
> + * @local: local interface data
> + */
> +void ieee80211_mps_wakeup(struct ieee80211_local *local)
> +{
> +       if (!local->mps_hw_doze)
> +               return;
> +
> +       local->mps_hw_doze = false;
> +       if (local->mps_ops)
> +               local->mps_ops->hw_wakeup(&local->hw);
> +}
> +
> +int ieee80211_mps_init(struct ieee80211_hw *hw,
> +                      const struct ieee80211_mps_ops *ops)
> +{
> +       struct ieee80211_local *local = hw_to_local(hw);
> +
> +       local->mps_ops = ops;
> +       if (!ops)
> +               local->mps_enabled = false;
> +
> +       return 0;
> +}
> +EXPORT_SYMBOL(ieee80211_mps_init);
> diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
> index 3d447a1..3e30788 100644
> --- a/net/mac80211/sta_info.c
> +++ b/net/mac80211/sta_info.c
> @@ -142,6 +142,7 @@ static void cleanup_single_sta(struct sta_info *sta)
>                 mesh_accept_plinks_update(sdata);
>                 mesh_plink_deactivate(sta);
>                 del_timer_sync(&sta->plink_timer);
> +               hrtimer_cancel(&sta->mps_beacon_timer);
>         }
>  #endif
>
> @@ -385,6 +386,9 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
>  #ifdef CONFIG_MAC80211_MESH
>         sta->plink_state = NL80211_PLINK_LISTEN;
>         init_timer(&sta->plink_timer);
> +       hrtimer_init(&sta->mps_beacon_timer, CLOCK_MONOTONIC,
> +                    HRTIMER_MODE_REL);
> +       sta->mps_beacon_timer.function = ieee80211_mps_sta_tbtt_timer;
>  #endif
>
>         return sta;
> diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
> index 5a1deba..3bd1f96 100644
> --- a/net/mac80211/sta_info.h
> +++ b/net/mac80211/sta_info.h
> @@ -16,6 +16,7 @@
>  #include <linux/average.h>
>  #include <linux/etherdevice.h>
>  #include "key.h"
> +#include <linux/hrtimer.h>
>
>  /**
>   * enum ieee80211_sta_info_flags - Stations flags
> @@ -58,6 +59,8 @@
>   * @WLAN_STA_TOFFSET_KNOWN: toffset calculated for this station is valid.
>   * @WLAN_STA_MPSP_OWNER: local STA is owner of a mesh Peer Service Period.
>   * @WLAN_STA_MPSP_RECIPIENT: local STA is recipient of a MPSP.
> + * @WLAN_STA_MPS_WAIT_FOR_BEACON: STA beacon is imminent
> + * @WLAN_STA_MPS_WAIT_FOR_CAB: STA multicast frames are imminent
>   */
>  enum ieee80211_sta_info_flags {
>         WLAN_STA_AUTH,
> @@ -82,6 +85,8 @@ enum ieee80211_sta_info_flags {
>         WLAN_STA_TOFFSET_KNOWN,
>         WLAN_STA_MPSP_OWNER,
>         WLAN_STA_MPSP_RECIPIENT,
> +       WLAN_STA_MPS_WAIT_FOR_BEACON,
> +       WLAN_STA_MPS_WAIT_FOR_CAB,
>  };
>
>  #define ADDBA_RESP_INTERVAL HZ
> @@ -289,6 +294,10 @@ struct sta_ampdu_mlme {
>   * @local_pm: local link-specific power save mode
>   * @peer_pm: peer-specific power save mode towards local STA
>   * @nonpeer_pm: STA power save mode towards non-peer neighbors
> + * @beacon_interval: beacon interval of neighbor STA (in us)
> + * @mps_beacon_timer: timer to trigger wakeup and sleep events for beacons RX
> + * @tbtt_wakeup: absolute time to wakeup for this peer beacon
> + * @tbtt_miss: absolute time to give up waiting for this peer beacon
>   * @debugfs: debug filesystem info
>   * @dead: set to true when sta is unlinked
>   * @uploaded: set to true when sta is uploaded to the driver
> @@ -390,6 +399,10 @@ struct sta_info {
>         enum nl80211_mesh_power_mode local_pm;
>         enum nl80211_mesh_power_mode peer_pm;
>         enum nl80211_mesh_power_mode nonpeer_pm;
> +       u32 beacon_interval;
> +       struct hrtimer mps_beacon_timer;
> +       ktime_t tbtt_wakeup;
> +       ktime_t tbtt_miss;
>  #endif
>
>  #ifdef CONFIG_MAC80211_DEBUGFS
> diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
> index 1890441..e09f597 100644
> --- a/net/mac80211/tx.c
> +++ b/net/mac80211/tx.c
> @@ -2494,6 +2494,8 @@ struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw,
>                         pr_err("o11s: couldn't add ies!\n");
>                         goto out;
>                 }
> +
> +               ieee80211_mps_awake_window_start(sdata);
>         } else {
>                 WARN_ON(1);
>                 goto out;
> --
> 1.7.9.5
>
> _______________________________________________
> Devel mailing list
> Devel@lists.open80211s.org
> http://lists.open80211s.org/cgi-bin/mailman/listinfo/devel
Johannes Berg Jan. 31, 2013, 1:51 p.m. UTC | #2
On Wed, 2013-01-23 at 11:19 +0100, Marco Porsch wrote:

> Expose a callback ieee80211_mps_init for drivers to register
> mesh powersave ops:
> - hw_doze - put the radio to sleep now
> - hw_wakeup - wake the radio up for frame RX
> These ops may be extended in the future to allow drivers/HW to
> implement mesh PS themselves. (The current design goal was to
> concentrate mesh PS routines in mac80211 to keep driver
> modifications minimal.
> 
> Track the beacon timing information of peers we are in PS mode
> towards. Set a per-STA hrtimer which will trigger a wakeup right
> before the peer's next TBTT.
> Also use the same hrtimer to go to sleep mode after not
> receiving a beacon in a defined time margin. In this case
> calculate the next TBTT and increase the margin.
> 
> For mesh Awake Windows wakeup on SWBA (beacon_get_tim) and start
> a timer which triggers a hw_doze call on expiry.

Hmm. I'm not completely happy with this hrtimer stuff in mac80211.
There's a lot of (USB) hardware that could never implement such a thing,
but could, conceivably, implement this differently?

> @@ -1146,6 +1148,11 @@ struct ieee80211_local {
>  
>  	int user_power_level; /* in dBm, for all interfaces */
>  
> +	/* mesh power save */
> +	bool mps_enabled;
> +	bool mps_hw_doze;

Generally, this also seems wrong, you're making the assumption that mesh
will be the only interface. That might actually be true for many use
cases, but is it really an assumption we should still put into the stack
today, with multi-channel etc? I'm not convinced of that.

So I think you should (at least attempt to) make an implementation that
is less tied to the exact timing implementation. Maybe program the
wakeup TBTT in advance. Maybe with a small library the driver can
connect to this that uses hrtimers to implement it? That could then also
assume that callbacks need not sleep, thus allowing to reduce
TBTT_MARGIN.

In theory I think with ath9k you could even use hardware timers &
interrupts to wake the hardware, thus probably being able to reduce
TBTT_MARGIN significantly.


[haven't really looked at the rest of the implementation]

johannes

--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Marco Porsch Jan. 31, 2013, 3:23 p.m. UTC | #3
On 01/31/2013 02:51 PM, Johannes Berg wrote:
> On Wed, 2013-01-23 at 11:19 +0100, Marco Porsch wrote:
>
>> Expose a callback ieee80211_mps_init for drivers to register
>> mesh powersave ops:
>> - hw_doze - put the radio to sleep now
>> - hw_wakeup - wake the radio up for frame RX
>> These ops may be extended in the future to allow drivers/HW to
>> implement mesh PS themselves. (The current design goal was to
>> concentrate mesh PS routines in mac80211 to keep driver
>> modifications minimal.
>>
>> Track the beacon timing information of peers we are in PS mode
>> towards. Set a per-STA hrtimer which will trigger a wakeup right
>> before the peer's next TBTT.
>> Also use the same hrtimer to go to sleep mode after not
>> receiving a beacon in a defined time margin. In this case
>> calculate the next TBTT and increase the margin.
>>
>> For mesh Awake Windows wakeup on SWBA (beacon_get_tim) and start
>> a timer which triggers a hw_doze call on expiry.
>
> Hmm. I'm not completely happy with this hrtimer stuff in mac80211.
> There's a lot of (USB) hardware that could never implement such a thing,
> but could, conceivably, implement this differently?

The use of hrtimers for MPS is debatable currently. The approach of 
calculating the peer's TSF should be accurate to the usec. Good timing 
here directly affects the energy savings. On the other handside the 
margin of 5ms used shows that something is not working as expected yet. 
If this cannot be fixed, I may as well use regular timers here. How 
strongly do you oppose hrtimers? :)

>> @@ -1146,6 +1148,11 @@ struct ieee80211_local {
>>
>>   	int user_power_level; /* in dBm, for all interfaces */
>>
>> +	/* mesh power save */
>> +	bool mps_enabled;
>> +	bool mps_hw_doze;
>
> Generally, this also seems wrong, you're making the assumption that mesh
> will be the only interface. That might actually be true for many use
> cases, but is it really an assumption we should still put into the stack
> today, with multi-channel etc? I'm not convinced of that.

When trying to enable MPS in mps_hw_conf_check, I check if there are any 
non-mesh interfaces. If yes, it is not enabled.
So these status variables are just used to sync multi-mesh-vif MPS on a 
single device.

Multiple mesh interfaces are ok. An AP vif is a no-go for any PS mode. 
Mesh + client is theoretically fine, but handled completely differently 
- also a no for now.

> So I think you should (at least attempt to) make an implementation that
> is less tied to the exact timing implementation. Maybe program the
> wakeup TBTT in advance.  Maybe with a small library the driver can
> connect to this that uses hrtimers to implement it? That could then also
> assume that callbacks need not sleep, thus allowing to reduce
> TBTT_MARGIN.
 >
> In theory I think with ath9k you could even use hardware timers &
> interrupts to wake the hardware, thus probably being able to reduce
> TBTT_MARGIN significantly.

Earlier, I had successfully implemented wakeups using ath9k HW before we 
at cozybit decided to concentrate all code in mac80211. Concerning ath9k 
it worked fine but required more callbacks to mac80211 and will 
eventually add redundant code that has to be maintained to multiple drivers.

--Marco
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Johannes Berg Jan. 31, 2013, 3:34 p.m. UTC | #4
On Thu, 2013-01-31 at 16:23 +0100, Marco Porsch wrote:
> On 01/31/2013 02:51 PM, Johannes Berg wrote:
> > On Wed, 2013-01-23 at 11:19 +0100, Marco Porsch wrote:
> >
> >> Expose a callback ieee80211_mps_init for drivers to register
> >> mesh powersave ops:
> >> - hw_doze - put the radio to sleep now
> >> - hw_wakeup - wake the radio up for frame RX
> >> These ops may be extended in the future to allow drivers/HW to
> >> implement mesh PS themselves. (The current design goal was to
> >> concentrate mesh PS routines in mac80211 to keep driver
> >> modifications minimal.
> >>
> >> Track the beacon timing information of peers we are in PS mode
> >> towards. Set a per-STA hrtimer which will trigger a wakeup right
> >> before the peer's next TBTT.
> >> Also use the same hrtimer to go to sleep mode after not
> >> receiving a beacon in a defined time margin. In this case
> >> calculate the next TBTT and increase the margin.
> >>
> >> For mesh Awake Windows wakeup on SWBA (beacon_get_tim) and start
> >> a timer which triggers a hw_doze call on expiry.
> >
> > Hmm. I'm not completely happy with this hrtimer stuff in mac80211.
> > There's a lot of (USB) hardware that could never implement such a thing,
> > but could, conceivably, implement this differently?
> 
> The use of hrtimers for MPS is debatable currently. The approach of 
> calculating the peer's TSF should be accurate to the usec. Good timing 
> here directly affects the energy savings. On the other handside the 
> margin of 5ms used shows that something is not working as expected yet. 
> If this cannot be fixed, I may as well use regular timers here. How 
> strongly do you oppose hrtimers? :)

Oh it's not the user of hrtimers vs. timers (although first using an
hrtimer and then kicking off to a work struct seems ... pointless and
questionable), it's more the fact that you're starting to put real-time
behaviour into mac80211. I'm not entirely convinced that is the right
approach for "core" mac80211.

> > So I think you should (at least attempt to) make an implementation that
> > is less tied to the exact timing implementation. Maybe program the
> > wakeup TBTT in advance.  Maybe with a small library the driver can
> > connect to this that uses hrtimers to implement it? That could then also
> > assume that callbacks need not sleep, thus allowing to reduce
> > TBTT_MARGIN.
>  >
> > In theory I think with ath9k you could even use hardware timers &
> > interrupts to wake the hardware, thus probably being able to reduce
> > TBTT_MARGIN significantly.
> 
> Earlier, I had successfully implemented wakeups using ath9k HW before we 
> at cozybit decided to concentrate all code in mac80211. Concerning ath9k 
> it worked fine but required more callbacks to mac80211 and will 
> eventually add redundant code that has to be maintained to multiple drivers.

The question isn't so much about concentrating the code, I think you can
still do that for most devices, and use hrtimers more effectively, if
you somewhat put it off to the side in a less integrated way. I'm
thinking that, for example, if I ever wanted to implement this with our
current device (not likely, but just for perspective) we'd definitely
want to program the firmware to do all the real-time behaviour. In fact,
I suspect we probably could with the new MVM firmware API.

Therefore, I think it would be better to have a more generic
time-oriented interface in mac80211, and put the actual implementation
of the events a bit separately in a way that it is optional for drivers
and not used in the API.

johannes

--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 23daed3..ca6979d 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -3952,6 +3952,40 @@  void ieee80211_stop_rx_ba_session(struct ieee80211_vif *vif, u16 ba_rx_bitmap,
  */
 void ieee80211_send_bar(struct ieee80211_vif *vif, u8 *ra, u16 tid, u16 ssn);
 
+/**
+ * struct ieee80211_mps_ops - callbacks from mac80211 to driver for mesh PS
+ *
+ * This structure contains callbacks that the driver has to or may handle for
+ * mesh powersave.
+ * TODO Add further callbacks for HW that performs certain mesh PS tasks on its
+ * own (e.g. assign list of STA to track).
+ *
+ * @hw_doze: put the radio to doze state to conserve power
+ * @hw_wakeup: wake up the radio to receive frames again
+ */
+struct ieee80211_mps_ops {
+	void (*hw_doze)(struct ieee80211_hw *hw);
+	void (*hw_wakeup)(struct ieee80211_hw *hw);
+};
+
+#ifdef CONFIG_MAC80211_MESH
+/**
+ * ieee80211_mps_init - register driver callbacks for mesh PS
+ *
+ * @hw: the hardware
+ * @ops: callbacks for this device
+ *
+ * called by driver on mesh interface add/remove
+ * TODO add HW capability flags
+ */
+int ieee80211_mps_init(struct ieee80211_hw *hw,
+		       const struct ieee80211_mps_ops *ops);
+#else
+static inline int ieee80211_mps_init(struct ieee80211_hw *hw,
+				     const struct ieee80211_mps_ops *ops)
+{ return 0; }
+#endif
+
 /* Rate control API */
 
 /**
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index e08b4c0..c08f423 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -629,6 +629,8 @@  struct ieee80211_if_mesh {
 	int ps_peers_deep_sleep;
 	struct ps_data ps;
 	atomic_t num_mpsp; /* counts both owner and recipient independently */
+	struct timer_list awake_window_end_timer;
+	bool in_awake_window;
 };
 
 #ifdef CONFIG_MAC80211_MESH
@@ -1126,7 +1128,7 @@  struct ieee80211_local {
 	bool pspolling;
 	bool offchannel_ps_enabled;
 	/*
-	 * PS can only be enabled when we have exactly one managed
+	 * managed mode PS can only be enabled when we have exactly one managed
 	 * interface (and monitors) in PS, this then points there.
 	 */
 	struct ieee80211_sub_if_data *ps_sdata;
@@ -1146,6 +1148,11 @@  struct ieee80211_local {
 
 	int user_power_level; /* in dBm, for all interfaces */
 
+	/* mesh power save */
+	bool mps_enabled;
+	bool mps_hw_doze;
+	const struct ieee80211_mps_ops *mps_ops;
+
 	enum ieee80211_smps_mode smps_mode;
 
 	struct work_struct restart_work;
diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c
index 8ce5d60..740d035 100644
--- a/net/mac80211/mesh.c
+++ b/net/mac80211/mesh.c
@@ -803,6 +803,7 @@  void ieee80211_mesh_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
 
 void ieee80211_mesh_work(struct ieee80211_sub_if_data *sdata)
 {
+	struct ieee80211_local *local = sdata->local;
 	struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
 
 	if (ifmsh->preq_queue_len &&
@@ -824,6 +825,19 @@  void ieee80211_mesh_work(struct ieee80211_sub_if_data *sdata)
 
 	if (test_and_clear_bit(MESH_WORK_DRIFT_ADJUST, &ifmsh->wrkq_flags))
 		mesh_sync_adjust_tbtt(sdata);
+
+	if (test_and_clear_bit(MESH_WORK_PS_HW_CONF, &ifmsh->wrkq_flags))
+		ieee80211_mps_hw_conf(local);
+
+	/* in case both fired simultaneously, wakeup overrides doze */
+	if (test_bit(MESH_WORK_PS_DOZE, &ifmsh->wrkq_flags) &&
+	    test_bit(MESH_WORK_PS_WAKEUP, &ifmsh->wrkq_flags))
+		clear_bit(MESH_WORK_PS_DOZE, &ifmsh->wrkq_flags);
+
+	if (test_and_clear_bit(MESH_WORK_PS_WAKEUP, &ifmsh->wrkq_flags))
+		ieee80211_mps_wakeup(local);
+	else if (test_and_clear_bit(MESH_WORK_PS_DOZE, &ifmsh->wrkq_flags))
+		ieee80211_mps_doze(local);
 }
 
 void ieee80211_mesh_notify_scan_completed(struct ieee80211_local *local)
@@ -863,6 +877,9 @@  void ieee80211_mesh_init_sdata(struct ieee80211_sub_if_data *sdata)
 	setup_timer(&ifmsh->mesh_path_root_timer,
 		    ieee80211_mesh_path_root_timer,
 		    (unsigned long) sdata);
+	setup_timer(&ifmsh->awake_window_end_timer,
+		    ieee80211_mps_awake_window_end,
+		    (unsigned long) sdata);
 	INIT_LIST_HEAD(&ifmsh->preq_queue.list);
 	skb_queue_head_init(&ifmsh->ps.bc_buf);
 	spin_lock_init(&ifmsh->mesh_preq_queue_lock);
diff --git a/net/mac80211/mesh.h b/net/mac80211/mesh.h
index fa1423e..ce35b78 100644
--- a/net/mac80211/mesh.h
+++ b/net/mac80211/mesh.h
@@ -58,6 +58,10 @@  enum mesh_path_flags {
  * @MESH_WORK_ROOT: the mesh root station needs to send a frame
  * @MESH_WORK_DRIFT_ADJUST: time to compensate for clock drift relative to other
  * mesh nodes
+ * @MESH_WORK_PS_HW_CONF: configure hardware according to the link-specific
+ * mesh power modes
+ * @MESH_WORK_PS_DOZE: put the hardware to sleep after checking all conditions
+ * @MESH_WORK_PS_WAKEUP: wakeup the hardware immediately
  */
 enum mesh_deferred_task_flags {
 	MESH_WORK_HOUSEKEEPING,
@@ -65,6 +69,9 @@  enum mesh_deferred_task_flags {
 	MESH_WORK_GROW_MPP_TABLE,
 	MESH_WORK_ROOT,
 	MESH_WORK_DRIFT_ADJUST,
+	MESH_WORK_PS_HW_CONF,
+	MESH_WORK_PS_DOZE,
+	MESH_WORK_PS_WAKEUP,
 };
 
 /**
@@ -258,6 +265,16 @@  void ieee80211_mpsp_trigger_process(struct ieee80211_hdr *hdr,
 				    struct sta_info *sta, bool tx, bool acked);
 void ieee80211_mps_frame_release(struct sta_info *sta,
 				 struct ieee802_11_elems *elems);
+void ieee80211_mps_hw_conf(struct ieee80211_local *local);
+void ieee80211_mps_sta_tbtt_update(struct sta_info *sta,
+				   struct ieee80211_mgmt *mgmt,
+				   struct ieee80211_tim_ie *tim,
+				   u64 tsf);
+enum hrtimer_restart ieee80211_mps_sta_tbtt_timer(struct hrtimer *timer);
+void ieee80211_mps_awake_window_start(struct ieee80211_sub_if_data *sdata);
+void ieee80211_mps_awake_window_end(unsigned long data);
+void ieee80211_mps_doze(struct ieee80211_local *local);
+void ieee80211_mps_wakeup(struct ieee80211_local *local);
 
 /* Mesh paths */
 int mesh_nexthop_lookup(struct sk_buff *skb,
diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c
index af6fbfd..f41b4bb 100644
--- a/net/mac80211/mesh_plink.c
+++ b/net/mac80211/mesh_plink.c
@@ -419,6 +419,7 @@  void mesh_neighbour_update(struct ieee80211_sub_if_data *sdata,
 		ifmsh->sync_ops->rx_bcn(sta, mgmt, elems, rx_status, tsf);
 
 	ieee80211_mps_frame_release(sta, elems);
+	ieee80211_mps_sta_tbtt_update(sta, mgmt, elems->tim, tsf);
 out:
 	rcu_read_unlock();
 }
diff --git a/net/mac80211/mesh_ps.c b/net/mac80211/mesh_ps.c
index 788b935..d7fffd9 100644
--- a/net/mac80211/mesh_ps.c
+++ b/net/mac80211/mesh_ps.c
@@ -9,12 +9,32 @@ 
 
 #include "mesh.h"
 #include "wme.h"
+#include <linux/export.h>
+#include <linux/hrtimer.h>
 
+/*
+ * time to wakeup before and stay awake after peer TBTT until beacon receipt.
+ * required to cope with stack delay and HW wakeup time before TBTT and delayed
+ * beacons after TBTT
+ * TODO adjust this value for different hardware or make it adaptive
+ */
+#define TBTT_MARGIN	5000	/* in us units */
+
+
+static inline void mps_queue_work(struct ieee80211_sub_if_data *sdata,
+				  enum mesh_deferred_task_flags flag)
+{
+	set_bit(flag, &sdata->u.mesh.wrkq_flags);
+	ieee80211_queue_work(&sdata->local->hw, &sdata->work);
+}
 
 static inline bool test_and_set_mpsp_flag(struct sta_info *sta,
 					  enum ieee80211_sta_info_flags flag)
 {
 	if (!test_and_set_sta_flag(sta, flag)) {
+		if (sta->sdata->local->mps_enabled &&
+		    atomic_read(&sta->sdata->u.mesh.num_mpsp) == 0)
+			mps_queue_work(sta->sdata, MESH_WORK_PS_WAKEUP);
 		atomic_inc(&sta->sdata->u.mesh.num_mpsp);
 		return false;
 	}
@@ -26,6 +46,9 @@  static inline bool test_and_clear_mpsp_flag(struct sta_info *sta,
 {
 	if (test_and_clear_sta_flag(sta, flag)) {
 		atomic_dec(&sta->sdata->u.mesh.num_mpsp);
+		if (sta->sdata->local->mps_enabled &&
+		    atomic_read(&sta->sdata->u.mesh.num_mpsp) == 0)
+			mps_queue_work(sta->sdata, MESH_WORK_PS_DOZE);
 		return true;
 	}
 	return false;
@@ -148,6 +171,8 @@  void ieee80211_mps_local_status_update(struct ieee80211_sub_if_data *sdata)
 
 	ifmsh->ps_peers_light_sleep = light_sleep_cnt;
 	ifmsh->ps_peers_deep_sleep = deep_sleep_cnt;
+
+	mps_queue_work(sdata, MESH_WORK_PS_HW_CONF);
 }
 
 /**
@@ -605,3 +630,335 @@  void ieee80211_mps_frame_release(struct sta_info *sta,
 	else
 		mps_frame_deliver(sta, 1);
 }
+
+
+/* mesh PS driver configuration and doze scheduling */
+
+static bool mps_hw_conf_check(struct ieee80211_local *local)
+{
+	struct ieee80211_sub_if_data *sdata;
+	struct ieee80211_if_mesh *ifmsh;
+	bool enable = true;
+
+	if (!local->mps_ops)
+		return false;
+
+	mutex_lock(&local->iflist_mtx);
+	list_for_each_entry(sdata, &local->interfaces, list) {
+		if (!ieee80211_sdata_running(sdata))
+			continue;
+
+		/* If an AP or any other non-mesh vif is found, disable PS */
+		if (ieee80211_sdata_running(sdata) &&
+		    sdata->vif.type != NL80211_IFTYPE_MESH_POINT) {
+			enable = false;
+			break;
+		}
+
+		ifmsh = &sdata->u.mesh;
+
+		/*
+		 * check for non-peer power mode, check for links in active
+		 * mode. Assume a valid power mode is set for each established
+		 * peer link
+		 */
+		if (ifmsh->nonpeer_pm == NL80211_MESH_POWER_ACTIVE ||
+		    ifmsh->ps_peers_light_sleep + ifmsh->ps_peers_deep_sleep
+				< atomic_read(&ifmsh->estab_plinks)) {
+			enable = false;
+			break;
+		}
+	}
+	mutex_unlock(&local->iflist_mtx);
+
+	return enable;
+}
+
+/**
+ * mps_hw_conf_sta_prepare - mark peers to catch beacon once before first doze
+ */
+static void mps_hw_conf_sta_prepare(struct ieee80211_local *local)
+{
+	struct sta_info *sta;
+
+	mutex_lock(&local->sta_mtx);
+	list_for_each_entry(sta, &local->sta_list, list) {
+		if (!ieee80211_vif_is_mesh(&sta->sdata->vif) ||
+		    !ieee80211_sdata_running(sta->sdata) ||
+		    sta->plink_state != NL80211_PLINK_ESTAB)
+			continue;
+		else
+			set_sta_flag(sta, WLAN_STA_MPS_WAIT_FOR_BEACON);
+	}
+	mutex_unlock(&local->sta_mtx);
+}
+
+/**
+ * ieee80211_mps_hw_conf - check conditions for mesh PS and configure driver
+ *
+ * @local: local interface data
+ */
+void ieee80211_mps_hw_conf(struct ieee80211_local *local)
+{
+	bool enable;
+
+	enable = mps_hw_conf_check(local);
+
+	if (local->mps_enabled == enable)
+		return;
+
+	if (enable) {
+		mps_hw_conf_sta_prepare(local);
+		local->hw.conf.flags |= IEEE80211_CONF_PS;
+	} else {
+		local->hw.conf.flags &= ~IEEE80211_CONF_PS;
+	}
+
+	ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
+	local->mps_enabled = enable;
+}
+
+static void mps_sta_tbtt_set_timer(struct sta_info *sta,
+				   struct ieee80211_tim_ie *tim,
+				   u64 tsf_local)
+{
+	u64 tsf_peer;
+	int skip = 1;
+	u32 nexttbtt_interval;
+	ktime_t now;
+
+	/* simple Deep Sleep implementation: only wake up for DTIM beacons */
+	if (sta->local_pm == NL80211_MESH_POWER_DEEP_SLEEP &&
+	    tim->dtim_count == 0)
+		skip = tim->dtim_period;
+
+	/*
+	 * determine time to peer TBTT (TSF % beacon_interval = 0).
+	 * This approach is robust to delayed beacons.
+	 */
+	tsf_peer = tsf_local + sta->t_offset;
+	nexttbtt_interval = sta->beacon_interval -
+			do_div(tsf_peer, sta->beacon_interval * skip);
+	now = hrtimer_cb_get_time(&sta->mps_beacon_timer);
+
+	mps_dbg(sta->sdata, "updating %pM next TBTT in %dus (%llus awake)\n",
+		sta->sta.addr, nexttbtt_interval,
+		(long long) ktime_to_us(ktime_sub(now, sta->tbtt_wakeup)));
+
+	sta->tbtt_wakeup = ktime_add_us(now, nexttbtt_interval - TBTT_MARGIN);
+	sta->tbtt_miss = ktime_add_us(now, nexttbtt_interval + TBTT_MARGIN);
+
+	hrtimer_start(&sta->mps_beacon_timer, sta->tbtt_wakeup,
+		      HRTIMER_MODE_ABS);
+}
+
+/**
+ * ieee80211_mps_sta_tbtt_update - update peer beacon wakeup schedule
+ *
+ * @sta: mesh STA
+ * @mgmt: beacon frame
+ * @tim: TIM IE of beacon frame
+ * @tsf_local: current HW TSF
+ */
+void ieee80211_mps_sta_tbtt_update(struct sta_info *sta,
+				   struct ieee80211_mgmt *mgmt,
+				   struct ieee80211_tim_ie *tim,
+				   u64 tsf_local)
+{
+	struct ieee80211_sub_if_data *sdata = sta->sdata;
+
+	if (!sdata->local->mps_enabled ||
+	    sta->plink_state != NL80211_PLINK_ESTAB)
+		return;
+
+	hrtimer_cancel(&sta->mps_beacon_timer);
+	clear_sta_flag(sta, WLAN_STA_MPS_WAIT_FOR_BEACON);
+
+	sta->beacon_interval = le16_to_cpu(mgmt->u.beacon.beacon_int) * 1024;
+	/* pending multicasts after DTIM beacon? TODO reset after RX */
+	if (tim->bitmap_ctrl & 0x01)
+		set_sta_flag(sta, WLAN_STA_MPS_WAIT_FOR_CAB);
+	else
+		clear_sta_flag(sta, WLAN_STA_MPS_WAIT_FOR_CAB);
+
+	mps_sta_tbtt_set_timer(sta, tim, tsf_local);
+
+	mps_queue_work(sdata, MESH_WORK_PS_DOZE);
+}
+
+/**
+ * ieee80211_mps_sta_tbtt_timer - hrtimer callback for mesh PS doze/wakeup
+ *
+ * Used for both waking up before TBTT and resuming doze in case the beacon
+ * is not received on time.
+ * XXX what lock should be used here? hrtimer callbacks are hard IRQ context
+ */
+enum hrtimer_restart ieee80211_mps_sta_tbtt_timer(struct hrtimer *timer)
+{
+	/*
+	 * This STA is valid because the timer is canceled on STA removal
+	 * after having made sure it cannot be armed (by deleting the plink.)
+	 */
+	struct sta_info *sta = container_of(timer, struct sta_info,
+					    mps_beacon_timer);
+	struct ieee80211_sub_if_data *sdata = sta->sdata;
+
+	if (!sdata->local->mps_enabled ||
+	    sta->plink_state != NL80211_PLINK_ESTAB)
+		return HRTIMER_NORESTART;
+
+	if (!test_sta_flag(sta, WLAN_STA_MPS_WAIT_FOR_BEACON)) {
+		mps_dbg(sdata, "wakeup for %pM (margin %dus)\n",
+			sta->sta.addr, TBTT_MARGIN);
+
+		set_sta_flag(sta, WLAN_STA_MPS_WAIT_FOR_BEACON);
+		hrtimer_set_expires(&sta->mps_beacon_timer, sta->tbtt_miss);
+
+		mps_queue_work(sdata, MESH_WORK_PS_WAKEUP);
+	} else {
+		mps_dbg(sdata, "beacon miss %pM\n", sta->sta.addr);
+
+		clear_sta_flag(sta, WLAN_STA_MPS_WAIT_FOR_BEACON);
+
+		/* increase the margin for each beacon miss TODO deep sleep */
+		sta->tbtt_wakeup = ktime_add_us(sta->tbtt_wakeup,
+				sta->beacon_interval - TBTT_MARGIN);
+		sta->tbtt_miss = ktime_add_us(sta->tbtt_miss,
+				sta->beacon_interval + TBTT_MARGIN);
+		hrtimer_set_expires(&sta->mps_beacon_timer, sta->tbtt_wakeup);
+
+		mps_queue_work(sdata, MESH_WORK_PS_DOZE);
+	}
+
+	return HRTIMER_RESTART;
+}
+
+/**
+ * ieee80211_mps_awake_window_start - start Awake Window on SWBA
+ *
+ * @sdata: local mesh subif
+ */
+void ieee80211_mps_awake_window_start(struct ieee80211_sub_if_data *sdata)
+{
+	struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
+
+	if (!sdata->local->mps_enabled)
+		return;
+
+	mps_dbg(sdata, "awake window start (%dTU)\n",
+		ifmsh->mshcfg.dot11MeshAwakeWindowDuration);
+
+	ifmsh->in_awake_window = true;
+	mod_timer(&ifmsh->awake_window_end_timer, jiffies + usecs_to_jiffies(
+			ifmsh->mshcfg.dot11MeshAwakeWindowDuration * 1024));
+
+	mps_queue_work(sdata, MESH_WORK_PS_WAKEUP);
+}
+
+/**
+ * ieee80211_mps_awake_window_end - timer callback for end of Awake Window
+ */
+void ieee80211_mps_awake_window_end(unsigned long data)
+{
+	struct ieee80211_sub_if_data *sdata = (void *) data;
+	struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
+
+	mps_dbg(sdata, "awake window end\n");
+
+	ifmsh->in_awake_window = false;
+
+	if (!sdata->local->mps_enabled)
+		return;
+
+	mps_queue_work(sdata, MESH_WORK_PS_DOZE);
+}
+
+static bool mps_doze_check_vif(struct ieee80211_local *local)
+{
+	struct ieee80211_sub_if_data *sdata;
+	bool allow = true;
+
+	mutex_lock(&local->iflist_mtx);
+	list_for_each_entry(sdata, &local->interfaces, list) {
+		if (!ieee80211_sdata_running(sdata))
+			continue;
+
+		if (!ieee80211_vif_is_mesh(&sdata->vif) ||
+		    sdata->u.mesh.in_awake_window ||
+		    atomic_read(&sdata->u.mesh.num_mpsp)) {
+			allow = false;
+			break;
+		}
+	}
+	mutex_unlock(&local->iflist_mtx);
+
+	return allow;
+}
+
+static bool mps_doze_check_sta(struct ieee80211_local *local)
+{
+	struct sta_info *sta;
+	bool allow = true;
+
+	mutex_lock(&local->sta_mtx);
+	list_for_each_entry(sta, &local->sta_list, list) {
+		if (!ieee80211_vif_is_mesh(&sta->sdata->vif) ||
+		    !ieee80211_sdata_running(sta->sdata) ||
+		    sta->plink_state != NL80211_PLINK_ESTAB) {
+			continue;
+		} else if (test_sta_flag(sta, WLAN_STA_MPS_WAIT_FOR_BEACON) ||
+			   test_sta_flag(sta, WLAN_STA_MPS_WAIT_FOR_CAB)) {
+			allow = false;
+			break;
+		}
+	}
+	mutex_unlock(&local->sta_mtx);
+
+	return allow;
+}
+
+/**
+ * ieee80211_mps_doze - check conditions and trigger radio doze state
+ *
+ * @local: local interface data
+ */
+void ieee80211_mps_doze(struct ieee80211_local *local)
+{
+	if (!local->mps_enabled ||
+	    local->mps_hw_doze ||
+	    !mps_doze_check_vif(local) ||
+	    !mps_doze_check_sta(local))
+		return;
+
+	local->mps_hw_doze = true;
+	if (local->mps_ops)
+		local->mps_ops->hw_doze(&local->hw);
+}
+
+/**
+ * ieee80211_mps_wakeup - trigger radio wakeup immediately
+ *
+ * @local: local interface data
+ */
+void ieee80211_mps_wakeup(struct ieee80211_local *local)
+{
+	if (!local->mps_hw_doze)
+		return;
+
+	local->mps_hw_doze = false;
+	if (local->mps_ops)
+		local->mps_ops->hw_wakeup(&local->hw);
+}
+
+int ieee80211_mps_init(struct ieee80211_hw *hw,
+		       const struct ieee80211_mps_ops *ops)
+{
+	struct ieee80211_local *local = hw_to_local(hw);
+
+	local->mps_ops = ops;
+	if (!ops)
+		local->mps_enabled = false;
+
+	return 0;
+}
+EXPORT_SYMBOL(ieee80211_mps_init);
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 3d447a1..3e30788 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -142,6 +142,7 @@  static void cleanup_single_sta(struct sta_info *sta)
 		mesh_accept_plinks_update(sdata);
 		mesh_plink_deactivate(sta);
 		del_timer_sync(&sta->plink_timer);
+		hrtimer_cancel(&sta->mps_beacon_timer);
 	}
 #endif
 
@@ -385,6 +386,9 @@  struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
 #ifdef CONFIG_MAC80211_MESH
 	sta->plink_state = NL80211_PLINK_LISTEN;
 	init_timer(&sta->plink_timer);
+	hrtimer_init(&sta->mps_beacon_timer, CLOCK_MONOTONIC,
+		     HRTIMER_MODE_REL);
+	sta->mps_beacon_timer.function = ieee80211_mps_sta_tbtt_timer;
 #endif
 
 	return sta;
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 5a1deba..3bd1f96 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -16,6 +16,7 @@ 
 #include <linux/average.h>
 #include <linux/etherdevice.h>
 #include "key.h"
+#include <linux/hrtimer.h>
 
 /**
  * enum ieee80211_sta_info_flags - Stations flags
@@ -58,6 +59,8 @@ 
  * @WLAN_STA_TOFFSET_KNOWN: toffset calculated for this station is valid.
  * @WLAN_STA_MPSP_OWNER: local STA is owner of a mesh Peer Service Period.
  * @WLAN_STA_MPSP_RECIPIENT: local STA is recipient of a MPSP.
+ * @WLAN_STA_MPS_WAIT_FOR_BEACON: STA beacon is imminent
+ * @WLAN_STA_MPS_WAIT_FOR_CAB: STA multicast frames are imminent
  */
 enum ieee80211_sta_info_flags {
 	WLAN_STA_AUTH,
@@ -82,6 +85,8 @@  enum ieee80211_sta_info_flags {
 	WLAN_STA_TOFFSET_KNOWN,
 	WLAN_STA_MPSP_OWNER,
 	WLAN_STA_MPSP_RECIPIENT,
+	WLAN_STA_MPS_WAIT_FOR_BEACON,
+	WLAN_STA_MPS_WAIT_FOR_CAB,
 };
 
 #define ADDBA_RESP_INTERVAL HZ
@@ -289,6 +294,10 @@  struct sta_ampdu_mlme {
  * @local_pm: local link-specific power save mode
  * @peer_pm: peer-specific power save mode towards local STA
  * @nonpeer_pm: STA power save mode towards non-peer neighbors
+ * @beacon_interval: beacon interval of neighbor STA (in us)
+ * @mps_beacon_timer: timer to trigger wakeup and sleep events for beacons RX
+ * @tbtt_wakeup: absolute time to wakeup for this peer beacon
+ * @tbtt_miss: absolute time to give up waiting for this peer beacon
  * @debugfs: debug filesystem info
  * @dead: set to true when sta is unlinked
  * @uploaded: set to true when sta is uploaded to the driver
@@ -390,6 +399,10 @@  struct sta_info {
 	enum nl80211_mesh_power_mode local_pm;
 	enum nl80211_mesh_power_mode peer_pm;
 	enum nl80211_mesh_power_mode nonpeer_pm;
+	u32 beacon_interval;
+	struct hrtimer mps_beacon_timer;
+	ktime_t tbtt_wakeup;
+	ktime_t tbtt_miss;
 #endif
 
 #ifdef CONFIG_MAC80211_DEBUGFS
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 1890441..e09f597 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -2494,6 +2494,8 @@  struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw,
 			pr_err("o11s: couldn't add ies!\n");
 			goto out;
 		}
+
+		ieee80211_mps_awake_window_start(sdata);
 	} else {
 		WARN_ON(1);
 		goto out;