Move the beacon handler into mesh_neighbour_update where the STA
pointer is already available. This avoids additional overhead
and simplifies the handler.
The repositioning will also benefit mesh PS which uses T_r and
the updated T_offset value.
Allow calculating T_offset from probe response frames as
mandated in IEEE 802.11-2012.
Signed-off-by: Marco Porsch <[email protected]>
---
v4:
calculate T_offset from probe response frames (Thomas)
net/mac80211/ieee80211_i.h | 5 ++---
net/mac80211/mesh.c | 7 +------
net/mac80211/mesh.h | 4 +++-
net/mac80211/mesh_plink.c | 30 +++++++++++++++++++++++------
net/mac80211/mesh_sync.c | 45 ++++++++------------------------------------
5 files changed, 38 insertions(+), 53 deletions(-)
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 54d09ec..5284ffa 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -529,11 +529,10 @@ struct ieee80211_if_ibss {
*/
struct ieee802_11_elems;
struct ieee80211_mesh_sync_ops {
- void (*rx_bcn_presp)(struct ieee80211_sub_if_data *sdata,
- u16 stype,
+ void (*rx_bcn_presp)(struct sta_info *sta,
struct ieee80211_mgmt *mgmt,
struct ieee802_11_elems *elems,
- struct ieee80211_rx_status *rx_status);
+ u64 t_r);
void (*adjust_tbtt)(struct ieee80211_sub_if_data *sdata);
/* add other framework functions here */
};
diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c
index f5d1afa..bfc5ee6 100644
--- a/net/mac80211/mesh.c
+++ b/net/mac80211/mesh.c
@@ -885,7 +885,6 @@ static void ieee80211_mesh_rx_bcn_presp(struct ieee80211_sub_if_data *sdata,
struct ieee80211_rx_status *rx_status)
{
struct ieee80211_local *local = sdata->local;
- struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
struct ieee802_11_elems elems;
struct ieee80211_channel *channel;
size_t baselen;
@@ -921,11 +920,7 @@ static void ieee80211_mesh_rx_bcn_presp(struct ieee80211_sub_if_data *sdata,
return;
if (mesh_matches_local(sdata, &elems))
- mesh_neighbour_update(sdata, mgmt->sa, &elems);
-
- if (ifmsh->sync_ops)
- ifmsh->sync_ops->rx_bcn_presp(sdata,
- stype, mgmt, &elems, rx_status);
+ mesh_neighbour_update(sdata, mgmt, &elems, rx_status);
}
static void ieee80211_mesh_rx_mgmt_action(struct ieee80211_sub_if_data *sdata,
diff --git a/net/mac80211/mesh.h b/net/mac80211/mesh.h
index 6ffabbe..594c43a 100644
--- a/net/mac80211/mesh.h
+++ b/net/mac80211/mesh.h
@@ -283,7 +283,9 @@ int mesh_gate_num(struct ieee80211_sub_if_data *sdata);
/* Mesh plinks */
void mesh_neighbour_update(struct ieee80211_sub_if_data *sdata,
- u8 *hw_addr, struct ieee802_11_elems *ie);
+ struct ieee80211_mgmt *mgmt,
+ struct ieee802_11_elems *ie,
+ struct ieee80211_rx_status *rx_status);
bool mesh_peer_accepts_plinks(struct ieee802_11_elems *ie);
u32 mesh_accept_plinks_update(struct ieee80211_sub_if_data *sdata);
void mesh_plink_broken(struct sta_info *sta);
diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c
index 08df966..91dea29 100644
--- a/net/mac80211/mesh_plink.c
+++ b/net/mac80211/mesh_plink.c
@@ -12,6 +12,7 @@
#include "ieee80211_i.h"
#include "rate.h"
#include "mesh.h"
+#include "driver-ops.h"
#define PLINK_GET_LLID(p) (p + 2)
#define PLINK_GET_PLID(p) (p + 4)
@@ -492,28 +493,45 @@ mesh_sta_info_get(struct ieee80211_sub_if_data *sdata,
*
* @sdata: local meshif
* @addr: peer's address
- * @elems: IEs from beacon or mesh peering frame
+ * @elems: IEs from beacon or mesh probe response
*
* Initiates peering if appropriate.
*/
void mesh_neighbour_update(struct ieee80211_sub_if_data *sdata,
- u8 *hw_addr,
- struct ieee802_11_elems *elems)
+ struct ieee80211_mgmt *mgmt,
+ struct ieee802_11_elems *elems,
+ struct ieee80211_rx_status *rx_status)
{
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
struct sta_info *sta;
u32 changed = 0;
+ u64 t_r;
+
+ /*
+ * If available, calculate the time the beacon timestamp field was
+ * received from the rx_status->mactime field. Otherwise get the
+ * current TSF as approximation before entering rcu-read section.
+ */
+ if (ieee80211_have_rx_timestamp(rx_status))
+ t_r = ieee80211_calculate_rx_timestamp(local, rx_status,
+ 24 + 12 + elems->total_len + FCS_LEN, 24);
+ else
+ t_r = drv_get_tsf(local, sdata);
- sta = mesh_sta_info_get(sdata, hw_addr, elems);
+ sta = mesh_sta_info_get(sdata, mgmt->sa, elems);
if (!sta)
goto out;
if (mesh_peer_accepts_plinks(elems) &&
sta->plink_state == NL80211_PLINK_LISTEN &&
- sdata->u.mesh.accepting_plinks &&
- sdata->u.mesh.mshcfg.auto_open_plinks &&
+ ifmsh->accepting_plinks && ifmsh->mshcfg.auto_open_plinks &&
rssi_threshold_check(sta, sdata))
changed = mesh_plink_open(sta);
+ if (ifmsh->sync_ops)
+ ifmsh->sync_ops->rx_bcn_presp(sta, mgmt, elems, t_r);
+
ieee80211_mps_frame_release(sta, elems);
out:
rcu_read_unlock();
diff --git a/net/mac80211/mesh_sync.c b/net/mac80211/mesh_sync.c
index 05a256b..07bc63c 100644
--- a/net/mac80211/mesh_sync.c
+++ b/net/mac80211/mesh_sync.c
@@ -75,35 +75,17 @@ void mesh_sync_adjust_tbtt(struct ieee80211_sub_if_data *sdata)
drv_set_tsf(local, sdata, tsf + tsfdelta);
}
-static void mesh_sync_offset_rx_bcn_presp(struct ieee80211_sub_if_data *sdata,
- u16 stype,
- struct ieee80211_mgmt *mgmt,
- struct ieee802_11_elems *elems,
- struct ieee80211_rx_status *rx_status)
+static void mesh_sync_offset_rx_bcn_presp(struct sta_info *sta,
+ struct ieee80211_mgmt *mgmt,
+ struct ieee802_11_elems *elems,
+ u64 t_r)
{
+ struct ieee80211_sub_if_data *sdata = sta->sdata;
struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
- struct ieee80211_local *local = sdata->local;
- struct sta_info *sta;
- u64 t_t, t_r;
+ u64 t_t;
WARN_ON(ifmsh->mesh_sp_id != IEEE80211_SYNC_METHOD_NEIGHBOR_OFFSET);
- /* standard mentions only beacons */
- if (stype != IEEE80211_STYPE_BEACON)
- return;
-
- /* The current tsf is a first approximation for the timestamp
- * for the received beacon. Further down we try to get a
- * better value from the rx_status->mactime field if
- * available. Also we have to call drv_get_tsf() before
- * entering the rcu-read section.*/
- t_r = drv_get_tsf(local, sdata);
-
- rcu_read_lock();
- sta = sta_info_get(sdata, mgmt->sa);
- if (!sta)
- goto no_sync;
-
/* check offset sync conditions (13.13.2.2.1)
*
* TODO also sync to
@@ -114,17 +96,9 @@ static void mesh_sync_offset_rx_bcn_presp(struct ieee80211_sub_if_data *sdata,
clear_sta_flag(sta, WLAN_STA_TOFFSET_KNOWN);
msync_dbg(sdata, "STA %pM : is adjusting TBTT\n",
sta->sta.addr);
- goto no_sync;
+ return;
}
- if (ieee80211_have_rx_timestamp(rx_status))
- /* time when timestamp field was received */
- t_r = ieee80211_calculate_rx_timestamp(local, rx_status,
- 24 + 12 +
- elems->total_len +
- FCS_LEN,
- 24);
-
/* Timing offset calculation (see 13.13.2.2.2) */
t_t = le64_to_cpu(mgmt->u.beacon.timestamp);
sta->t_offset = t_t - t_r;
@@ -144,7 +118,7 @@ static void mesh_sync_offset_rx_bcn_presp(struct ieee80211_sub_if_data *sdata,
sta->sta.addr,
(long long) t_clockdrift);
clear_sta_flag(sta, WLAN_STA_TOFFSET_KNOWN);
- goto no_sync;
+ return;
}
spin_lock_bh(&ifmsh->sync_offset_lock);
@@ -159,9 +133,6 @@ static void mesh_sync_offset_rx_bcn_presp(struct ieee80211_sub_if_data *sdata,
sta->sta.addr,
(long long) sta->t_offset);
}
-
-no_sync:
- rcu_read_unlock();
}
static void mesh_sync_offset_adjust_tbtt(struct ieee80211_sub_if_data *sdata)
--
1.7.9.5
On Mon, Mar 04, 2013 at 06:54:34PM +0100, Marco Porsch wrote:
> Configure the device for PS mode if the local mesh PS parameters
> allow so and the driver supports it.
>
> Add two callbacks to ieee80211_ops for mesh powersave:
> - mesh_ps_doze - put the device to sleep now, wake up at given
> TBTT
> - mesh_ps_wakeup - wake the device up now for frame RX
> These ops may be extended in the future to allow drivers/HW to
> implement mesh PS themselves. (Current design goal was to
> concentrate most mesh PS routines in mac80211 to keep driver
> modifications minimal.)
>
> Track beacon timing information of peers we are in PS mode
> towards. Calculate the next TBTT per STA. Stay awake to receive
> multicast traffic after DTIM beacons.
>
> When going to doze state, get the most imminent STA TBTT and
> configure the device to trigger a wakeup on time to catch that
> beacon. After successful receipt put the device to doze again.
> Set a timeout for the case that the beacon is not received on
> time. In this case calculate the following TBTT and go to doze
> again.
>
> For mesh Awake Windows wakeup on PreTBTT/SWBA (beacon_get_tim)
> and start a timer which triggers a doze call on expiry.
> Similarly, stay awake for the Awake Window duration after
> sending probe response frames.
>
> Signed-off-by: Marco Porsch <[email protected]>
I've been looking at power save in mac80211 over the past few days with
an eye towards allowing multiple interface to be supported, as a result
of comments Johannes made at [1]. It seems like adding driver callbacks
for PS which are specific to the interface type is contrary to this
goal.
The basic idea that's been forming on my mind is add PS states to vifs
and make the managed, mesh, etc. code manipulate vif PS states rather
than hw states. Then a PS module would manage the hw state based on the
aggregate of the vif states.
I don't have a lot of the details worked out yet, and my knowledge of PS
in mesh networks (and of mesh network operation in general) is pretty
rudimentary at this point. But afaict any modes which support PS define
the same two hw states, awake and doze. I wonder whether we should
instead aim for a single interface into the driver for PS that's capable
of supporting all interface types.
Anyway, I just wanted to throw this out for discussion.
Thanks,
Seth
[1] http://article.gmane.org/gmane.linux.kernel.wireless.general/104064
Am 04.03.2013 21:25, schrieb Johannes Berg:
> On Mon, 2013-03-04 at 13:23 -0600, Seth Forshee wrote:
>
>> I've been looking at power save in mac80211 over the past few days with
>> an eye towards allowing multiple interface to be supported, as a result
>> of comments Johannes made at [1]. It seems like adding driver callbacks
>> for PS which are specific to the interface type is contrary to this
>> goal.
>
> Yeah, this is a concern. I didn't really stand in the way of doing mesh
> powersave though, and it seemed that the new interface here would
> actually be somewhat suitable, since for mesh any kind of powersave code
> needs to know when to wake up/sleep. I would've liked to see less
> reliance on host timers directly in core mac80211 even for the going to
> sleep part though... basically I'm not sure for mesh just having PS
> state will cut it. For managed mode this is easy because it only needs
> to sync with a single AP, but for mesh that's a bit more complicated and
> I think the whole sync should stay in mac80211. In the general case,
> just having the TSF might not be enough, but that can be solved as
> needed.
>
>> The basic idea that's been forming on my mind is add PS states to vifs
>> and make the managed, mesh, etc. code manipulate vif PS states rather
>> than hw states. Then a PS module would manage the hw state based on the
>> aggregate of the vif states.
>
> Yeah, that about matches what I was thinking. But like I said above,
> while this is fairly simple for managed mode, at least as required
> today, it's clearly not as simple for mesh.
>
> For managed mode, we also assume that we can send packets while the
> device is sleeping, and the device will do the right thing to wake up
> for beacons from the AP etc. For mesh, there are many more wakeup
> sources, from what I can tell.
I also assume that the device deals with TX while sleeping.
The complete list of wakeup sources for mesh are:
- awake window after beacon and probe response TX
- authentication, peering, scanning procedures
- frame release in mesh peer service periods
- RX peer's beacons and DTIM multicasts (only in mesh light sleep mode)
>> I don't have a lot of the details worked out yet, and my knowledge of PS
>> in mesh networks (and of mesh network operation in general) is pretty
>> rudimentary at this point. But afaict any modes which support PS define
>> the same two hw states, awake and doze. I wonder whether we should
>> instead aim for a single interface into the driver for PS that's capable
>> of supporting all interface types.
>
> Such an interface would probably have to be the interface now defined
> for mesh, telling the device when to wake up and go to sleep? But this
> interface is rather inefficient for most chipsets...
I guess the case of a single managed mode vif should be handled
separately to benefit from the HW support most devices provide.
--Marco
Add ath9k_ops for .mesh_ps_doze and .mesh_ps_wakeup.
React to doze/wakeup calls issued by mac80211.
Add a PS status flag PS_MAC80211_CTL to store last mesh PS
command from mac80211.
Initialize HW beacon wakeup registers.
On doze call configure the device to wakeup at the given TSF
value.
Signed-off-by: Marco Porsch <[email protected]>
---
drivers/net/wireless/ath/ath9k/ath9k.h | 1 +
drivers/net/wireless/ath/ath9k/beacon.c | 21 ++++++++++-
drivers/net/wireless/ath/ath9k/hw.c | 17 +++++++--
drivers/net/wireless/ath/ath9k/main.c | 62 ++++++++++++++++++++++++++++++-
4 files changed, 94 insertions(+), 7 deletions(-)
diff --git a/drivers/net/wireless/ath/ath9k/ath9k.h b/drivers/net/wireless/ath/ath9k/ath9k.h
index 97c90b2..b82727b 100644
--- a/drivers/net/wireless/ath/ath9k/ath9k.h
+++ b/drivers/net/wireless/ath/ath9k/ath9k.h
@@ -652,6 +652,7 @@ enum sc_op_flags {
#define PS_WAIT_FOR_TX_ACK BIT(3)
#define PS_BEACON_SYNC BIT(4)
#define PS_WAIT_FOR_ANI BIT(5)
+#define PS_MAC80211_CTL BIT(6)
struct ath_rate_table;
diff --git a/drivers/net/wireless/ath/ath9k/beacon.c b/drivers/net/wireless/ath/ath9k/beacon.c
index dd37719..7ef698b 100644
--- a/drivers/net/wireless/ath/ath9k/beacon.c
+++ b/drivers/net/wireless/ath/ath9k/beacon.c
@@ -599,6 +599,23 @@ static void ath9k_beacon_config_adhoc(struct ath_softc *sc,
ath9k_beacon_init(sc, nexttbtt, intval);
}
+static void ath9k_beacon_config_mesh(struct ath_softc *sc,
+ struct ath_beacon_config *conf)
+{
+ struct ath9k_beacon_state bs;
+
+ /*
+ * when PS is enabled, ath9k_hw_setrxabort is set.
+ * to wake up again to receive peers' beacons, we set an
+ * arbitrary initial value for sleepduration here
+ */
+ memset(&bs, 0, sizeof(bs));
+ bs.bs_sleepduration = IEEE80211_MS_TO_TU(100);
+ ath9k_hw_set_sta_beacon_timers(sc->sc_ah, &bs);
+
+ ath9k_beacon_config_adhoc(sc, conf);
+}
+
bool ath9k_allow_beacon_config(struct ath_softc *sc, struct ieee80211_vif *vif)
{
struct ath_common *common = ath9k_hw_common(sc->sc_ah);
@@ -707,9 +724,11 @@ void ath9k_set_beacon(struct ath_softc *sc)
ath9k_beacon_config_ap(sc, cur_conf);
break;
case NL80211_IFTYPE_ADHOC:
- case NL80211_IFTYPE_MESH_POINT:
ath9k_beacon_config_adhoc(sc, cur_conf);
break;
+ case NL80211_IFTYPE_MESH_POINT:
+ ath9k_beacon_config_mesh(sc, cur_conf);
+ break;
case NL80211_IFTYPE_STATION:
ath9k_beacon_config_sta(sc, cur_conf);
break;
diff --git a/drivers/net/wireless/ath/ath9k/hw.c b/drivers/net/wireless/ath/ath9k/hw.c
index 42cf3c7..0e0fe03 100644
--- a/drivers/net/wireless/ath/ath9k/hw.c
+++ b/drivers/net/wireless/ath/ath9k/hw.c
@@ -2254,16 +2254,24 @@ void ath9k_hw_beaconinit(struct ath_hw *ah, u32 next_beacon, u32 beacon_period)
}
EXPORT_SYMBOL(ath9k_hw_beaconinit);
+/**
+ * ath9k_hw_set_sta_beacon_timers
+ *
+ * in mesh mode overwriting AR_NEXT_TBTT_TIMER and setting AR_TBTT_TIMER_EN
+ * would shift the own TBTT
+ */
void ath9k_hw_set_sta_beacon_timers(struct ath_hw *ah,
const struct ath9k_beacon_state *bs)
{
u32 nextTbtt, beaconintval, dtimperiod, beacontimeout;
+ u32 ar_timer_mode = AR_DTIM_TIMER_EN | AR_TIM_TIMER_EN;
struct ath9k_hw_capabilities *pCap = &ah->caps;
struct ath_common *common = ath9k_hw_common(ah);
ENABLE_REGWRITE_BUFFER(ah);
- REG_WRITE(ah, AR_NEXT_TBTT_TIMER, TU_TO_USEC(bs->bs_nexttbtt));
+ if (ah->opmode != NL80211_IFTYPE_MESH_POINT)
+ REG_WRITE(ah, AR_NEXT_TBTT_TIMER, TU_TO_USEC(bs->bs_nexttbtt));
REG_WRITE(ah, AR_BEACON_PERIOD,
TU_TO_USEC(bs->bs_intval));
@@ -2317,9 +2325,10 @@ void ath9k_hw_set_sta_beacon_timers(struct ath_hw *ah,
REGWRITE_BUFFER_FLUSH(ah);
- REG_SET_BIT(ah, AR_TIMER_MODE,
- AR_TBTT_TIMER_EN | AR_TIM_TIMER_EN |
- AR_DTIM_TIMER_EN);
+ if (ah->opmode != NL80211_IFTYPE_MESH_POINT)
+ ar_timer_mode |= AR_TBTT_TIMER_EN;
+
+ REG_SET_BIT(ah, AR_TIMER_MODE, ar_timer_mode);
/* TSF Out of Range Threshold */
REG_WRITE(ah, AR_TSFOOR_THRESHOLD, bs->bs_tsfoor_threshold);
diff --git a/drivers/net/wireless/ath/ath9k/main.c b/drivers/net/wireless/ath/ath9k/main.c
index 5432f12..16b7e5d 100644
--- a/drivers/net/wireless/ath/ath9k/main.c
+++ b/drivers/net/wireless/ath/ath9k/main.c
@@ -132,7 +132,8 @@ void ath9k_ps_restore(struct ath_softc *sc)
PS_WAIT_FOR_CAB |
PS_WAIT_FOR_PSPOLL_DATA |
PS_WAIT_FOR_TX_ACK |
- PS_WAIT_FOR_ANI))) {
+ PS_WAIT_FOR_ANI |
+ PS_MAC80211_CTL))) {
mode = ATH9K_PM_NETWORK_SLEEP;
if (ath9k_hw_btcoex_is_enabled(sc->sc_ah))
ath9k_btcoex_stop_gen_timer(sc);
@@ -1164,7 +1165,7 @@ static int ath9k_config(struct ieee80211_hw *hw, u32 changed)
* We just prepare to enable PS. We have to wait until our AP has
* ACK'd our null data frame to disable RX otherwise we'll ignore
* those ACKs and end up retransmitting the same null data frames.
- * IEEE80211_CONF_CHANGE_PS is only passed by mac80211 for STA mode.
+ * IEEE80211_CONF_CHANGE_PS is passed by mac80211 for STA or mesh mode.
*/
if (changed & IEEE80211_CONF_CHANGE_PS) {
unsigned long flags;
@@ -2321,6 +2322,58 @@ static void ath9k_sw_scan_complete(struct ieee80211_hw *hw)
sc->scanning = 0;
}
+#ifdef CONFIG_MAC80211_MESH
+
+static void ath9k_mesh_wakeup_set(struct ath_softc *sc, u64 nexttbtt)
+{
+ struct ath_hw *ah = sc->sc_ah;
+ struct ath9k_beacon_state bs;
+ u32 nexttbtttu = TSF_TO_TU(nexttbtt >> 32, nexttbtt);
+
+ memset(&bs, 0, sizeof(bs));
+ bs.bs_nexttbtt = nexttbtttu;
+ bs.bs_nextdtim = nexttbtttu;
+ /* arbitrary high values to avoid frequent wakeups */
+ bs.bs_intval = 1000;
+ bs.bs_dtimperiod = 4000;
+ bs.bs_sleepduration = 1000;
+
+ ath9k_hw_set_sta_beacon_timers(ah, &bs);
+}
+
+static void ath9k_mesh_ps_doze(struct ieee80211_hw *hw, u64 nexttbtt)
+{
+ struct ath_softc *sc = hw->priv;
+ unsigned long flags;
+
+ ath9k_ps_wakeup(sc);
+ spin_lock_irqsave(&sc->sc_pm_lock, flags);
+ /* in mesh mode mac80211 checks beacons and CAB */
+ sc->ps_flags &= ~(PS_WAIT_FOR_BEACON |
+ PS_WAIT_FOR_CAB |
+ PS_MAC80211_CTL);
+ spin_unlock_irqrestore(&sc->sc_pm_lock, flags);
+
+ if (nexttbtt)
+ ath9k_mesh_wakeup_set(sc, nexttbtt);
+
+ ath9k_ps_restore(sc);
+}
+
+static void ath9k_mesh_ps_wakeup(struct ieee80211_hw *hw)
+{
+ struct ath_softc *sc = hw->priv;
+ unsigned long flags;
+
+ ath9k_ps_wakeup(sc);
+ spin_lock_irqsave(&sc->sc_pm_lock, flags);
+ sc->ps_flags |= PS_MAC80211_CTL;
+ spin_unlock_irqrestore(&sc->sc_pm_lock, flags);
+ ath9k_ps_restore(sc);
+}
+
+#endif
+
struct ieee80211_ops ath9k_ops = {
.tx = ath9k_tx,
.start = ath9k_start,
@@ -2368,4 +2421,9 @@ struct ieee80211_ops ath9k_ops = {
#endif
.sw_scan_start = ath9k_sw_scan_start,
.sw_scan_complete = ath9k_sw_scan_complete,
+
+#ifdef CONFIG_MAC80211_MESH
+ .mesh_ps_doze = ath9k_mesh_ps_doze,
+ .mesh_ps_wakeup = ath9k_mesh_ps_wakeup,
+#endif
};
--
1.7.9.5
On 03/04/2013 11:23 AM, Seth Forshee wrote:
> On Mon, Mar 04, 2013 at 06:54:34PM +0100, Marco Porsch wrote:
>> Configure the device for PS mode if the local mesh PS parameters
>> allow so and the driver supports it.
>>
>> Add two callbacks to ieee80211_ops for mesh powersave:
>> - mesh_ps_doze - put the device to sleep now, wake up at given
>> TBTT
>> - mesh_ps_wakeup - wake the device up now for frame RX
>> These ops may be extended in the future to allow drivers/HW to
>> implement mesh PS themselves. (Current design goal was to
>> concentrate most mesh PS routines in mac80211 to keep driver
>> modifications minimal.)
>>
>> Track beacon timing information of peers we are in PS mode
>> towards. Calculate the next TBTT per STA. Stay awake to receive
>> multicast traffic after DTIM beacons.
>>
>> When going to doze state, get the most imminent STA TBTT and
>> configure the device to trigger a wakeup on time to catch that
>> beacon. After successful receipt put the device to doze again.
>> Set a timeout for the case that the beacon is not received on
>> time. In this case calculate the following TBTT and go to doze
>> again.
>>
>> For mesh Awake Windows wakeup on PreTBTT/SWBA (beacon_get_tim)
>> and start a timer which triggers a doze call on expiry.
>> Similarly, stay awake for the Awake Window duration after
>> sending probe response frames.
>>
>> Signed-off-by: Marco Porsch <[email protected]>
>
> I've been looking at power save in mac80211 over the past few days with
> an eye towards allowing multiple interface to be supported, as a result
> of comments Johannes made at [1]. It seems like adding driver callbacks
> for PS which are specific to the interface type is contrary to this
> goal.
>
> The basic idea that's been forming on my mind is add PS states to vifs
> and make the managed, mesh, etc. code manipulate vif PS states rather
> than hw states. Then a PS module would manage the hw state based on the
> aggregate of the vif states.
>
> I don't have a lot of the details worked out yet, and my knowledge of PS
> in mesh networks (and of mesh network operation in general) is pretty
> rudimentary at this point. But afaict any modes which support PS define
> the same two hw states, awake and doze. I wonder whether we should
> instead aim for a single interface into the driver for PS that's capable
> of supporting all interface types.
>
> Anyway, I just wanted to throw this out for discussion.
I would love to see multi-vif (station) power-saving support happen. I'm low
on development cycles at the moment, but at the least we can do
some testing....
Thanks,
Ben
>
> Thanks,
> Seth
>
> [1] http://article.gmane.org/gmane.linux.kernel.wireless.general/104064
>
> _______________________________________________
> ath9k-devel mailing list
> [email protected]
> https://lists.ath9k.org/mailman/listinfo/ath9k-devel
>
--
Ben Greear <[email protected]>
Candela Technologies Inc http://www.candelatech.com
Am 04.03.2013 20:23, schrieb Seth Forshee:
> On Mon, Mar 04, 2013 at 06:54:34PM +0100, Marco Porsch wrote:
>> Configure the device for PS mode if the local mesh PS parameters
>> allow so and the driver supports it.
>>
>> Add two callbacks to ieee80211_ops for mesh powersave:
>> - mesh_ps_doze - put the device to sleep now, wake up at given
>> TBTT
>> - mesh_ps_wakeup - wake the device up now for frame RX
>> These ops may be extended in the future to allow drivers/HW to
>> implement mesh PS themselves. (Current design goal was to
>> concentrate most mesh PS routines in mac80211 to keep driver
>> modifications minimal.)
>>
>> Track beacon timing information of peers we are in PS mode
>> towards. Calculate the next TBTT per STA. Stay awake to receive
>> multicast traffic after DTIM beacons.
>>
>> When going to doze state, get the most imminent STA TBTT and
>> configure the device to trigger a wakeup on time to catch that
>> beacon. After successful receipt put the device to doze again.
>> Set a timeout for the case that the beacon is not received on
>> time. In this case calculate the following TBTT and go to doze
>> again.
>>
>> For mesh Awake Windows wakeup on PreTBTT/SWBA (beacon_get_tim)
>> and start a timer which triggers a doze call on expiry.
>> Similarly, stay awake for the Awake Window duration after
>> sending probe response frames.
>>
>> Signed-off-by: Marco Porsch<[email protected]>
>
> I've been looking at power save in mac80211 over the past few days with
> an eye towards allowing multiple interface to be supported, as a result
> of comments Johannes made at [1]. It seems like adding driver callbacks
> for PS which are specific to the interface type is contrary to this
> goal.
>
> The basic idea that's been forming on my mind is add PS states to vifs
> and make the managed, mesh, etc. code manipulate vif PS states rather
> than hw states. Then a PS module would manage the hw state based on the
> aggregate of the vif states.
>
> I don't have a lot of the details worked out yet, and my knowledge of PS
> in mesh networks (and of mesh network operation in general) is pretty
> rudimentary at this point. But afaict any modes which support PS define
> the same two hw states, awake and doze. I wonder whether we should
> instead aim for a single interface into the driver for PS that's capable
> of supporting all interface types.
>
> Anyway, I just wanted to throw this out for discussion.
>
> Thanks,
> Seth
>
> [1] http://article.gmane.org/gmane.linux.kernel.wireless.general/104064
>
Please mind that these callbacks are not necessarily bound to mesh mode
(although the name suggests so) and may as well be incorporated into a
more general interface that also suits managed and ad-hoc mode later. It
is just that currently only mesh uses this and I lack the overview of
mode and driver combinations to create a one-fits-all solution.
--Marco
On Mon, 2013-03-04 at 13:23 -0600, Seth Forshee wrote:
> I've been looking at power save in mac80211 over the past few days with
> an eye towards allowing multiple interface to be supported, as a result
> of comments Johannes made at [1]. It seems like adding driver callbacks
> for PS which are specific to the interface type is contrary to this
> goal.
Yeah, this is a concern. I didn't really stand in the way of doing mesh
powersave though, and it seemed that the new interface here would
actually be somewhat suitable, since for mesh any kind of powersave code
needs to know when to wake up/sleep. I would've liked to see less
reliance on host timers directly in core mac80211 even for the going to
sleep part though... basically I'm not sure for mesh just having PS
state will cut it. For managed mode this is easy because it only needs
to sync with a single AP, but for mesh that's a bit more complicated and
I think the whole sync should stay in mac80211. In the general case,
just having the TSF might not be enough, but that can be solved as
needed.
> The basic idea that's been forming on my mind is add PS states to vifs
> and make the managed, mesh, etc. code manipulate vif PS states rather
> than hw states. Then a PS module would manage the hw state based on the
> aggregate of the vif states.
Yeah, that about matches what I was thinking. But like I said above,
while this is fairly simple for managed mode, at least as required
today, it's clearly not as simple for mesh.
For managed mode, we also assume that we can send packets while the
device is sleeping, and the device will do the right thing to wake up
for beacons from the AP etc. For mesh, there are many more wakeup
sources, from what I can tell.
> I don't have a lot of the details worked out yet, and my knowledge of PS
> in mesh networks (and of mesh network operation in general) is pretty
> rudimentary at this point. But afaict any modes which support PS define
> the same two hw states, awake and doze. I wonder whether we should
> instead aim for a single interface into the driver for PS that's capable
> of supporting all interface types.
Such an interface would probably have to be the interface now defined
for mesh, telling the device when to wake up and go to sleep? But this
interface is rather inefficient for most chipsets...
johannes
On Mon, Mar 04, 2013 at 09:25:33PM +0100, Johannes Berg wrote:
> On Mon, 2013-03-04 at 13:23 -0600, Seth Forshee wrote:
>
> > I've been looking at power save in mac80211 over the past few days with
> > an eye towards allowing multiple interface to be supported, as a result
> > of comments Johannes made at [1]. It seems like adding driver callbacks
> > for PS which are specific to the interface type is contrary to this
> > goal.
>
> Yeah, this is a concern. I didn't really stand in the way of doing mesh
> powersave though, and it seemed that the new interface here would
> actually be somewhat suitable, since for mesh any kind of powersave code
> needs to know when to wake up/sleep. I would've liked to see less
> reliance on host timers directly in core mac80211 even for the going to
> sleep part though... basically I'm not sure for mesh just having PS
> state will cut it. For managed mode this is easy because it only needs
> to sync with a single AP, but for mesh that's a bit more complicated and
> I think the whole sync should stay in mac80211. In the general case,
> just having the TSF might not be enough, but that can be solved as
> needed.
I read about the basics of mesh PS last week but don't have a firm
enough grasp on the details to understand what all would be needed for
an implementation. From these patches it looks like at minimum the
drivers need the next TBTT. That could be incorporated into something
more generic easily enough.
It's not the end of the world if these patches go in, it will just have
to be sorted out if/when I make some progress on multi-vif PS. I wasn't
planning on trying to lift any of the current restrictions on multiple
vifs right away anyhow, so it should be manageable.
> > The basic idea that's been forming on my mind is add PS states to vifs
> > and make the managed, mesh, etc. code manipulate vif PS states rather
> > than hw states. Then a PS module would manage the hw state based on the
> > aggregate of the vif states.
>
> Yeah, that about matches what I was thinking. But like I said above,
> while this is fairly simple for managed mode, at least as required
> today, it's clearly not as simple for mesh.
Glad to hear I'm headed down the right path :-)
I've been trying to take all modes into consideration, but I had hoped
the implementation details of mesh were something I wouldn't have to be
immediately concerned with, since as you point out all of this is easier
for managed mode.
> For managed mode, we also assume that we can send packets while the
> device is sleeping, and the device will do the right thing to wake up
> for beacons from the AP etc. For mesh, there are many more wakeup
> sources, from what I can tell.
>
> > I don't have a lot of the details worked out yet, and my knowledge of PS
> > in mesh networks (and of mesh network operation in general) is pretty
> > rudimentary at this point. But afaict any modes which support PS define
> > the same two hw states, awake and doze. I wonder whether we should
> > instead aim for a single interface into the driver for PS that's capable
> > of supporting all interface types.
>
> Such an interface would probably have to be the interface now defined
> for mesh, telling the device when to wake up and go to sleep? But this
> interface is rather inefficient for most chipsets...
I was thinking something along those lines as well, either the
interfaces would be similar to the ones here or the data would be
available in ieee80211_conf. Frankly I find having both the PS callback
and IEEE80211_CONF_PS confusing, but maybe if I look at the patches more
closely it will make sense.
Seth
Configure the device for PS mode if the local mesh PS parameters
allow so and the driver supports it.
Add two callbacks to ieee80211_ops for mesh powersave:
- mesh_ps_doze - put the device to sleep now, wake up at given
TBTT
- mesh_ps_wakeup - wake the device up now for frame RX
These ops may be extended in the future to allow drivers/HW to
implement mesh PS themselves. (Current design goal was to
concentrate most mesh PS routines in mac80211 to keep driver
modifications minimal.)
Track beacon timing information of peers we are in PS mode
towards. Calculate the next TBTT per STA. Stay awake to receive
multicast traffic after DTIM beacons.
When going to doze state, get the most imminent STA TBTT and
configure the device to trigger a wakeup on time to catch that
beacon. After successful receipt put the device to doze again.
Set a timeout for the case that the beacon is not received on
time. In this case calculate the following TBTT and go to doze
again.
For mesh Awake Windows wakeup on PreTBTT/SWBA (beacon_get_tim)
and start a timer which triggers a doze call on expiry.
Similarly, stay awake for the Awake Window duration after
sending probe response frames.
Signed-off-by: Marco Porsch <[email protected]>
---
v4:
Awake Window after sending probe responses
include/net/mac80211.h | 10 ++
net/mac80211/driver-ops.h | 24 ++++
net/mac80211/ieee80211_i.h | 6 +-
net/mac80211/mesh.c | 12 ++
net/mac80211/mesh.h | 13 ++
net/mac80211/mesh_plink.c | 5 +
net/mac80211/mesh_ps.c | 298 ++++++++++++++++++++++++++++++++++++++++++++
net/mac80211/sta_info.h | 10 ++
net/mac80211/trace.h | 30 +++++
net/mac80211/tx.c | 2 +
10 files changed, 409 insertions(+), 1 deletion(-)
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index cdd7cea..61f7db6 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -2571,6 +2571,11 @@ enum ieee80211_roc_type {
* @ipv6_addr_change: IPv6 address assignment on the given interface changed.
* Currently, this is only called for managed or P2P client interfaces.
* This callback is optional; it must not sleep.
+ *
+ * @mesh_ps_doze: Put the device to doze state now; schedule wakeup at given
+ * TSF value (if non-zero). This callback is optional and may sleep.
+ * @mesh_ps_wakeup: Wake the device up now. This callback is optional and may
+ * sleep.
*/
struct ieee80211_ops {
void (*tx)(struct ieee80211_hw *hw,
@@ -2760,6 +2765,11 @@ struct ieee80211_ops {
struct ieee80211_vif *vif,
struct inet6_dev *idev);
#endif
+
+#ifdef CONFIG_MAC80211_MESH
+ void (*mesh_ps_doze)(struct ieee80211_hw *hw, u64 nexttbtt);
+ void (*mesh_ps_wakeup)(struct ieee80211_hw *hw);
+#endif
};
/**
diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h
index 832acea..accb0ae 100644
--- a/net/mac80211/driver-ops.h
+++ b/net/mac80211/driver-ops.h
@@ -1091,4 +1091,28 @@ static inline void drv_ipv6_addr_change(struct ieee80211_local *local,
}
#endif
+#ifdef CONFIG_MAC80211_MESH
+
+static inline void drv_mesh_ps_doze(struct ieee80211_local *local, u64 nexttbtt)
+{
+ might_sleep();
+
+ trace_drv_mesh_ps_doze(local, nexttbtt);
+ if (local->ops->mesh_ps_doze)
+ local->ops->mesh_ps_doze(&local->hw, nexttbtt);
+ trace_drv_return_void(local);
+}
+
+static inline void drv_mesh_ps_wakeup(struct ieee80211_local *local)
+{
+ might_sleep();
+
+ trace_drv_mesh_ps_wakeup(local);
+ if (local->ops->mesh_ps_wakeup)
+ local->ops->mesh_ps_wakeup(&local->hw);
+ trace_drv_return_void(local);
+}
+
+#endif
+
#endif /* __MAC80211_DRIVER_OPS */
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 5284ffa..4565b8c 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -597,6 +597,7 @@ struct ieee80211_if_mesh {
int ps_peers_light_sleep;
int ps_peers_deep_sleep;
struct ps_data ps;
+ struct timer_list awake_window_end_timer;
};
#ifdef CONFIG_MAC80211_MESH
@@ -1088,7 +1089,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;
@@ -1107,6 +1108,9 @@ struct ieee80211_local {
int user_power_level; /* in dBm, for all interfaces */
+ /* mesh power save can be enabled for multiple (but only mesh) vif */
+ bool mps_enabled;
+
enum ieee80211_smps_mode smps_mode;
struct work_struct restart_work;
diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c
index bfc5ee6..bdd6208 100644
--- a/net/mac80211/mesh.c
+++ b/net/mac80211/mesh.c
@@ -159,6 +159,7 @@ void mesh_sta_cleanup(struct sta_info *sta)
if (sdata->u.mesh.security == IEEE80211_MESH_SEC_NONE) {
changed |= mesh_plink_deactivate(sta);
del_timer_sync(&sta->plink_timer);
+ del_timer_sync(&sta->nexttbtt_timer);
}
if (changed)
@@ -874,6 +875,7 @@ ieee80211_mesh_rx_probe_req(struct ieee80211_sub_if_data *sdata,
memcpy(hdr->da, mgmt->sa, ETH_ALEN);
IEEE80211_SKB_CB(presp)->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT;
ieee80211_tx_skb(sdata, presp);
+ ieee80211_mps_awake_window_start(sdata);
out:
rcu_read_unlock();
}
@@ -973,6 +975,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 &&
@@ -994,6 +997,12 @@ 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);
+
+ 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)
@@ -1033,6 +1042,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 594c43a..704144b 100644
--- a/net/mac80211/mesh.h
+++ b/net/mac80211/mesh.h
@@ -58,6 +58,8 @@ 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: perform checks for PS mode and configure hardware
+ * @MESH_WORK_PS_DOZE: perform checks for doze state and put hardware to doze
*/
enum mesh_deferred_task_flags {
MESH_WORK_HOUSEKEEPING,
@@ -65,6 +67,8 @@ 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,
};
/**
@@ -256,6 +260,15 @@ void ieee80211_mpsp_trigger_process(u8 *qc, 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,
+ const struct ieee80211_tim_ie *tim,
+ u64 tsf);
+void ieee80211_mps_sta_tbtt_timeout(unsigned long data);
+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);
/* Mesh paths */
int mesh_nexthop_lookup(struct ieee80211_sub_if_data *sdata,
diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c
index 91dea29..aaa1d85 100644
--- a/net/mac80211/mesh_plink.c
+++ b/net/mac80211/mesh_plink.c
@@ -422,6 +422,8 @@ __mesh_sta_info_alloc(struct ieee80211_sub_if_data *sdata, u8 *hw_addr)
sta->plink_state = NL80211_PLINK_LISTEN;
init_timer(&sta->plink_timer);
+ setup_timer(&sta->nexttbtt_timer, ieee80211_mps_sta_tbtt_timeout,
+ (unsigned long) sta);
sta_info_pre_move_state(sta, IEEE80211_STA_AUTH);
sta_info_pre_move_state(sta, IEEE80211_STA_ASSOC);
@@ -533,6 +535,9 @@ void mesh_neighbour_update(struct ieee80211_sub_if_data *sdata,
ifmsh->sync_ops->rx_bcn_presp(sta, mgmt, elems, t_r);
ieee80211_mps_frame_release(sta, elems);
+
+ if (ieee80211_is_beacon(mgmt->frame_control))
+ ieee80211_mps_sta_tbtt_update(sta, mgmt, elems->tim, t_r);
out:
rcu_read_unlock();
ieee80211_mbss_info_change_notify(sdata, changed);
diff --git a/net/mac80211/mesh_ps.c b/net/mac80211/mesh_ps.c
index 3b7bfc0..d877816 100644
--- a/net/mac80211/mesh_ps.c
+++ b/net/mac80211/mesh_ps.c
@@ -9,6 +9,18 @@
#include "mesh.h"
#include "wme.h"
+#include "driver-ops.h"
+
+
+#define BEACON_TIMEOUT 20000 /* 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);
+}
/* mesh PS management */
@@ -137,6 +149,8 @@ u32 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);
+
return changed;
}
@@ -329,6 +343,8 @@ static void mps_set_sta_nonpeer_pm(struct sta_info *sta,
void ieee80211_mps_rx_h_sta_process(struct sta_info *sta,
struct ieee80211_hdr *hdr)
{
+ struct ieee80211_sub_if_data *sdata = sta->sdata;
+
if (is_unicast_ether_addr(hdr->addr1) &&
ieee80211_is_data_qos(hdr->frame_control)) {
/*
@@ -346,6 +362,13 @@ void ieee80211_mps_rx_h_sta_process(struct sta_info *sta,
* (see IEEE802.11-2012 8.2.4.1.7)
*/
mps_set_sta_nonpeer_pm(sta, hdr);
+
+ /* resume doze after multicast receipt */
+ if (sdata->local->mps_enabled &&
+ is_multicast_ether_addr(hdr->addr1) &&
+ !ieee80211_has_moredata(hdr->frame_control) &&
+ test_and_clear_sta_flag(sta, WLAN_STA_MPS_WAIT_FOR_CAB))
+ mps_queue_work(sdata, MESH_WORK_PS_DOZE);
}
}
@@ -529,6 +552,7 @@ static void mps_frame_deliver(struct sta_info *sta, int n_frames)
void ieee80211_mpsp_trigger_process(u8 *qc, struct sta_info *sta,
bool tx, bool acked)
{
+ struct ieee80211_local *local = sta->sdata->local;
u8 rspi = qc[1] & (IEEE80211_QOS_CTL_RSPI >> 8);
u8 eosp = qc[0] & IEEE80211_QOS_CTL_EOSP;
@@ -551,6 +575,15 @@ void ieee80211_mpsp_trigger_process(u8 *qc, struct sta_info *sta,
if (rspi && !test_and_set_sta_flag(sta, WLAN_STA_MPSP_OWNER))
mps_frame_deliver(sta, -1);
}
+
+ if (!local->mps_enabled)
+ return;
+
+ if (test_sta_flag(sta, WLAN_STA_MPSP_OWNER) ||
+ test_sta_flag(sta, WLAN_STA_MPSP_RECIPIENT))
+ drv_mesh_ps_wakeup(local);
+ else
+ mps_queue_work(sta->sdata, MESH_WORK_PS_DOZE);
}
/**
@@ -596,3 +629,268 @@ 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->ops->mesh_ps_doze ||
+ !local->ops->mesh_ps_wakeup)
+ return false;
+
+ mutex_lock(&local->iflist_mtx);
+ list_for_each_entry(sdata, &local->interfaces, list) {
+ if (!ieee80211_sdata_running(sdata))
+ continue;
+
+ /* disallow PS if an AP or any other non-mesh vif is found */
+ 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 and links in active mode.
+ * Assume a valid power mode 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;
+}
+
+/**
+ * ieee80211_mps_hw_conf - check conditions for mesh PS and configure driver
+ *
+ * @local: mac80211 hw info struct
+ */
+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)
+ 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;
+
+ /* receive all peer beacons once before doze */
+ if (enable)
+ drv_mesh_ps_wakeup(local);
+}
+
+static void mps_sta_nexttbtt_calc(struct sta_info *sta,
+ const struct ieee80211_tim_ie *tim,
+ u64 tsf_local)
+{
+ u64 tsf_peer;
+ int skip = 1;
+ u32 nexttbtt_interval;
+
+ /* simple Deep Sleep implementation: only wake up for DTIM beacons */
+ if (tim && sta->local_pm == NL80211_MESH_POWER_DEEP_SLEEP)
+ 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 * skip -
+ do_div(tsf_peer, sta->beacon_interval * skip);
+
+ mps_dbg(sta->sdata, "updating %pM next TBTT in %dus (%lldus awake)\n",
+ sta->sta.addr, nexttbtt_interval,
+ (long long) tsf_local - sta->nexttbtt_tsf);
+
+ sta->nexttbtt_tsf = tsf_local + nexttbtt_interval;
+ sta->nexttbtt_jiffies = jiffies + usecs_to_jiffies(nexttbtt_interval);
+ mod_timer(&sta->nexttbtt_timer, sta->nexttbtt_jiffies +
+ usecs_to_jiffies(BEACON_TIMEOUT));
+}
+
+/**
+ * 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,
+ const 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;
+
+ sta->beacon_interval = ieee80211_tu_to_usec(
+ le16_to_cpu(mgmt->u.beacon.beacon_int));
+ if (tim && tim->bitmap_ctrl & 0x01) /* multicasts after DTIM? */
+ set_sta_flag(sta, WLAN_STA_MPS_WAIT_FOR_CAB);
+ else
+ clear_sta_flag(sta, WLAN_STA_MPS_WAIT_FOR_CAB);
+
+ mps_sta_nexttbtt_calc(sta, tim, tsf_local);
+
+ mps_queue_work(sdata, MESH_WORK_PS_DOZE);
+}
+
+/**
+ * ieee80211_mps_sta_tbtt_timeout - timer callback for missed peer beacons
+ */
+void ieee80211_mps_sta_tbtt_timeout(unsigned long data)
+{
+ struct sta_info *sta = (void *) data;
+ struct ieee80211_sub_if_data *sdata = sta->sdata;
+
+ spin_lock_bh(&sta->lock);
+
+ if (!sdata->local->mps_enabled ||
+ sta->plink_state != NL80211_PLINK_ESTAB) {
+ spin_unlock_bh(&sta->lock);
+ return;
+ }
+
+ sta->nexttbtt_tsf += sta->beacon_interval;
+ sta->nexttbtt_jiffies += usecs_to_jiffies(sta->beacon_interval);
+ mod_timer(&sta->nexttbtt_timer, sta->nexttbtt_jiffies +
+ usecs_to_jiffies(BEACON_TIMEOUT));
+ mps_queue_work(sdata, MESH_WORK_PS_DOZE);
+ mps_dbg(sdata, "beacon miss %pM\n", sta->sta.addr);
+
+ spin_unlock_bh(&sta->lock);
+}
+
+/**
+ * ieee80211_mps_awake_window_start - start Awake Window on SWBA/PRETBTT
+ *
+ * @sdata: local mesh subif
+ *
+ * TODO called from ieee80211_beacon_get_tim as time reference for TBTT,
+ * but mac80211 API guarantees neither exact timing nor periodicity
+ */
+void ieee80211_mps_awake_window_start(struct ieee80211_sub_if_data *sdata)
+{
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
+ unsigned long timeout;
+
+ if (!local->mps_enabled)
+ return;
+
+ mps_dbg(sdata, "awake window start (%dTU)\n",
+ ifmsh->mshcfg.dot11MeshAwakeWindowDuration);
+
+ timeout = jiffies + usecs_to_jiffies(ieee80211_tu_to_usec(
+ ifmsh->mshcfg.dot11MeshAwakeWindowDuration));
+ mod_timer(&ifmsh->awake_window_end_timer, timeout);
+
+ drv_mesh_ps_wakeup(local);
+}
+
+/**
+ * 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;
+
+ if (!sdata->local->mps_enabled)
+ return;
+
+ mps_dbg(sdata, "awake window end\n");
+ 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) ||
+ timer_pending(&sdata->u.mesh.awake_window_end_timer)) {
+ allow = false;
+ break;
+ }
+ }
+ mutex_unlock(&local->iflist_mtx);
+
+ return allow;
+}
+
+static bool mps_doze_check_sta(struct ieee80211_local *local, u64 *nexttbtt)
+{
+ struct sta_info *sta;
+ bool allow = true;
+ u64 nexttbtt_min = ULLONG_MAX;
+
+ 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_CAB) ||
+ test_sta_flag(sta, WLAN_STA_MPSP_OWNER) ||
+ test_sta_flag(sta, WLAN_STA_MPSP_RECIPIENT) ||
+ !timer_pending(&sta->nexttbtt_timer) ||
+ time_after(jiffies, sta->nexttbtt_jiffies)) {
+ allow = false;
+ break;
+ } else if (sta->nexttbtt_tsf < nexttbtt_min) {
+ nexttbtt_min = sta->nexttbtt_tsf;
+ }
+ }
+ mutex_unlock(&local->sta_mtx);
+
+ *nexttbtt = (nexttbtt_min != ULLONG_MAX ? nexttbtt_min : 0);
+
+ return allow;
+}
+
+/**
+ * ieee80211_mps_doze - trigger radio doze state after checking conditions
+ *
+ * @local: mac80211 hw info struct
+ */
+void ieee80211_mps_doze(struct ieee80211_local *local)
+{
+ u64 nexttbtt;
+
+ if (!local->mps_enabled ||
+ !mps_doze_check_vif(local) ||
+ !mps_doze_check_sta(local, &nexttbtt))
+ return;
+
+ drv_mesh_ps_doze(local, nexttbtt);
+}
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index e5868c3..ab43b52 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -58,6 +58,7 @@
* @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_CAB: multicast frames from this STA are imminent.
*/
enum ieee80211_sta_info_flags {
WLAN_STA_AUTH,
@@ -82,6 +83,7 @@ enum ieee80211_sta_info_flags {
WLAN_STA_TOFFSET_KNOWN,
WLAN_STA_MPSP_OWNER,
WLAN_STA_MPSP_RECIPIENT,
+ WLAN_STA_MPS_WAIT_FOR_CAB,
};
#define ADDBA_RESP_INTERVAL HZ
@@ -287,6 +289,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)
+ * @nexttbtt_tsf: next TBTT in local TSF units
+ * @nexttbtt_jiffies: next TBTT in jiffies units
+ * @nexttbtt_timer: timeout for missed beacons
* @debugfs: debug filesystem info
* @dead: set to true when sta is unlinked
* @uploaded: set to true when sta is uploaded to the driver
@@ -387,6 +393,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;
+ u64 nexttbtt_tsf;
+ unsigned long nexttbtt_jiffies;
+ struct timer_list nexttbtt_timer;
#endif
#ifdef CONFIG_MAC80211_DEBUGFS
diff --git a/net/mac80211/trace.h b/net/mac80211/trace.h
index e7db2b8..41fbcbe 100644
--- a/net/mac80211/trace.h
+++ b/net/mac80211/trace.h
@@ -1453,6 +1453,36 @@ DEFINE_EVENT(local_sdata_evt, drv_ipv6_addr_change,
);
#endif
+#ifdef CONFIG_MAC80211_MESH
+
+TRACE_EVENT(drv_mesh_ps_doze,
+ TP_PROTO(struct ieee80211_local *local, u64 nexttbtt),
+
+ TP_ARGS(local, nexttbtt),
+
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(u64, nexttbtt)
+ ),
+
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->nexttbtt = nexttbtt;
+ ),
+
+ TP_printk(
+ LOCAL_PR_FMT " nexttbtt:%llu",
+ LOCAL_PR_ARG, (unsigned long long)__entry->nexttbtt
+ )
+);
+
+DEFINE_EVENT(local_only_evt, drv_mesh_ps_wakeup,
+ TP_PROTO(struct ieee80211_local *local),
+ TP_ARGS(local)
+);
+
+#endif
+
/*
* Tracing for API calls that drivers call.
*/
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index cb4b9ab..c0ab733 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -2475,6 +2475,8 @@ struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw,
memcpy(skb_put(skb, bcn->head_len), bcn->head, bcn->head_len);
ieee80211_beacon_add_tim(sdata, &ifmsh->ps, skb);
memcpy(skb_put(skb, bcn->tail_len), bcn->tail, bcn->tail_len);
+
+ ieee80211_mps_awake_window_start(sdata);
} else {
WARN_ON(1);
goto out;
--
1.7.9.5