2013-01-07 15:05:02

by Marco Porsch

[permalink] [raw]
Subject: [PATCHv2 0/6] mesh power save - basics

Commits #5 and #6 cover the basic subset of functions, that each mesh node
must support to communicate in the presence of power saving nodes.
The current patchset does not implement any actual power savings yet (no
hardware configuration, no doze state).

Mesh powersave is different from client mode powersave in the sense that
1) there is not just a singular power mode towards the access point, but a
link-specific power mode towards each peer instead
2) peers also maintain a power mode towards us (the local STA)
3) each node has to buffer frames towards power saving neighbors

changes since PATCHv1:
- explicitly clear QoS control field in mps_qos_null_get
- beautify FILL_IN_MESH_PARAM_IF_SET macro

Marco Porsch (6):
nl80211: add range checks to mesh parameters
mac80211: update mesh peer link counter during userspace peering
mac80211: move add_tim to subfunction
{cfg,nl,mac}80211: set beacon interval and DTIM period on mesh join
{cfg,nl}80211: mesh power mode primitives and userspace access
mac80211: mesh power save basics

include/linux/ieee80211.h | 17 ++
include/net/cfg80211.h | 25 ++
include/uapi/linux/nl80211.h | 47 +++
net/mac80211/Kconfig | 11 +
net/mac80211/Makefile | 3 +-
net/mac80211/cfg.c | 54 +++-
net/mac80211/debug.h | 10 +
net/mac80211/debugfs_netdev.c | 5 +
net/mac80211/debugfs_sta.c | 5 +-
net/mac80211/ieee80211_i.h | 7 +
net/mac80211/mesh.c | 37 ++-
net/mac80211/mesh.h | 35 ++-
net/mac80211/mesh_hwmp.c | 7 +
net/mac80211/mesh_pathtbl.c | 1 +
net/mac80211/mesh_plink.c | 35 ++-
net/mac80211/mesh_ps.c | 640 +++++++++++++++++++++++++++++++++++++++++
net/mac80211/rx.c | 7 +
net/mac80211/sta_info.c | 18 +-
net/mac80211/sta_info.h | 17 ++
net/mac80211/status.c | 5 +
net/mac80211/tx.c | 81 ++++--
net/mac80211/util.c | 4 +
net/mac80211/wme.c | 13 +-
net/wireless/mesh.c | 8 +
net/wireless/nl80211.c | 137 ++++++---
25 files changed, 1137 insertions(+), 92 deletions(-)
create mode 100644 net/mac80211/mesh_ps.c

--
1.7.9.5



2013-01-16 22:14:21

by Johannes Berg

[permalink] [raw]
Subject: Re: [PATCHv2 6/6] mac80211: mesh power save basics

On Mon, 2013-01-07 at 16:04 +0100, Marco Porsch wrote:

> mode determines when non-peer mesh STA may send Probe Requests and Mesh Peering

Please break lines to less than 72 characters, I personally prefer
around 60 or so but I'll apply 72 too.

> +static inline bool ieee80211_has_qos_mesh_ps(__le16 qc)
> +{
> + return (qc & cpu_to_le16(IEEE80211_QOS_CTL_MESH_PS_LEVEL)) != 0;

bool means you don't need the !=0 and parentheses.


> @@ -1208,6 +1214,7 @@ struct ieee802_11_elems {
> struct ieee80211_meshconf_ie *mesh_config;
> u8 *mesh_id;
> u8 *peering;
> + u8 *awake_window;

maybe that should be an __le16 pointer?

> + /* we need some delay here, otherwise the announcement
> + * Null would arrive before the CONFIRM
> + */
> + ieee80211_mps_set_sta_local_pm(sta, mshcfg->power_mode,
> + 100);

???

How can a *delay* ever cause correctness? That doesn't seem right.

> + /* announce peer-specific power mode transition
> + * see IEEE802.11-2012 13.14.3.2 and 13.14.3.3
> + */

I feel a bit bad about asking this, but mac80211 actually doesn't use
the "networking" style, all other comments have

/*
* announce ...
* ...
*/

so please adjust yours as well.

I think I deleted this, but nonetheless:


> static void mpsp_trigger_send(struct sta_info *sta,
> bool rspi, bool eosp)

that easily fits on one line.


> +/**
> + * mps_frame_deliver - transmit frames during mesh powersave
> + *
> + * @sta: STA info to transmit to
> + * @n_frames: number of frames to transmit. -1 for all
> + */
> +static void mps_frame_deliver(struct sta_info *sta, int n_frames)
> +{
> + struct ieee80211_sub_if_data *sdata = sta->sdata;
> + struct ieee80211_local *local = sdata->local;
> + int ac;
> + struct sk_buff_head frames;
> + struct sk_buff *skb;
> + bool more_data = false;
> +
> + skb_queue_head_init(&frames);

You really don't need a spinlock for on-stack queues, so you should use
the __ versions of the functions modifying "frames".

> + if (!skb)
> + break;
> + n_frames--;

I guess you're assuming there never will be > 2^31 frames ;-)

> + skb_queue_tail(&frames, skb);

__skb_queue_tail(), etc.

> + }
> +
> + if (!skb_queue_empty(&sta->tx_filtered[ac]) ||
> + !skb_queue_empty(&sta->ps_tx_buf[ac])) {
> + more_data = true;
> + }

No need for the braces

> + /* in a MPSP make sure the last skb is a QoS Data frame */
> + if (test_sta_flag(sta, WLAN_STA_MPSP_OWNER))
> + mpsp_qos_null_append(sta, &frames);



> + mps_dbg(sta->sdata, "sending %d frames to PS STA %pM\n",
> + skb_queue_len(&frames), sta->sta.addr);
> +
> + /* prepare collected frames for transmission */
> + skb_queue_walk(&frames, skb) {
> + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
> + struct ieee80211_hdr *hdr = (void *) skb->data;
> +
> + /* Tell TX path to send this frame even though the
> + * STA may still remain is PS mode after this frame
> + * exchange.
> + */
> + info->flags |= IEEE80211_TX_CTL_NO_PS_BUFFER;

I'm not convinced that the PS integration with drivers here is correct
at all, but I don't really care all that much either.


> + * @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 -> stay awake
> + * @WLAN_STA_MPS_WAIT_FOR_CAB: STA multicast frames are imminent -> stay awake

??

There's also a compiler warning -- variable shadowing -- that this
probably solves:

@@ -487,8 +487,6 @@ static void mps_frame_deliver(struct sta_info *sta,
int n_frames)

/* collect frame(s) from buffers */
for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
- struct sk_buff *skb;
-
while (n_frames != 0) {
skb = skb_dequeue(&sta->tx_filtered[ac]);
if (!skb) {


johannes


2013-01-17 16:40:10

by Marco Porsch

[permalink] [raw]
Subject: Re: [PATCHv2 6/6] mac80211: mesh power save basics

On 01/16/2013 11:14 PM, Johannes Berg wrote:
> On Mon, 2013-01-07 at 16:04 +0100, Marco Porsch wrote:
>
>> mode determines when non-peer mesh STA may send Probe Requests and Mesh Peering
>
> Please break lines to less than 72 characters, I personally prefer
> around 60 or so but I'll apply 72 too.
>
>> +static inline bool ieee80211_has_qos_mesh_ps(__le16 qc)
>> +{
>> + return (qc & cpu_to_le16(IEEE80211_QOS_CTL_MESH_PS_LEVEL)) != 0;
>
> bool means you don't need the !=0 and parentheses.

Actually, then it pops the following warning:
CHECK net/mac80211/mesh_ps.c
include/linux/ieee80211.h:584:19: warning: incorrect type in return
expression (different base types)
include/linux/ieee80211.h:584:19: expected bool
include/linux/ieee80211.h:584:19: got restricted __le16


Other functions in that file have following style:
static inline int ieee80211_has_pm(__le16 fc)
{
return (fc & cpu_to_le16(IEEE80211_FCTL_PM)) != 0;
}

What do you recommend?

>> @@ -1208,6 +1214,7 @@ struct ieee802_11_elems {
>> struct ieee80211_meshconf_ie *mesh_config;
>> u8 *mesh_id;
>> u8 *peering;
>> + u8 *awake_window;
>
> maybe that should be an __le16 pointer?
>
>> + /* we need some delay here, otherwise the announcement
>> + * Null would arrive before the CONFIRM
>> + */
>> + ieee80211_mps_set_sta_local_pm(sta, mshcfg->power_mode,
>> + 100);
>
> ???
>
> How can a *delay* ever cause correctness? That doesn't seem right.
>
>> + /* announce peer-specific power mode transition
>> + * see IEEE802.11-2012 13.14.3.2 and 13.14.3.3
>> + */
>
> I feel a bit bad about asking this, but mac80211 actually doesn't use
> the "networking" style, all other comments have
>
> /*
> * announce ...
> * ...
> */
>
> so please adjust yours as well.
>
> I think I deleted this, but nonetheless:
>
>
>> static void mpsp_trigger_send(struct sta_info *sta,
>> bool rspi, bool eosp)
>
> that easily fits on one line.
>
>
>> +/**
>> + * mps_frame_deliver - transmit frames during mesh powersave
>> + *
>> + * @sta: STA info to transmit to
>> + * @n_frames: number of frames to transmit. -1 for all
>> + */
>> +static void mps_frame_deliver(struct sta_info *sta, int n_frames)
>> +{
>> + struct ieee80211_sub_if_data *sdata = sta->sdata;
>> + struct ieee80211_local *local = sdata->local;
>> + int ac;
>> + struct sk_buff_head frames;
>> + struct sk_buff *skb;
>> + bool more_data = false;
>> +
>> + skb_queue_head_init(&frames);
>
> You really don't need a spinlock for on-stack queues, so you should use
> the __ versions of the functions modifying "frames".
>
>> + if (!skb)
>> + break;
>> + n_frames--;
>
> I guess you're assuming there never will be > 2^31 frames ;-)
>
>> + skb_queue_tail(&frames, skb);
>
> __skb_queue_tail(), etc.
>
>> + }
>> +
>> + if (!skb_queue_empty(&sta->tx_filtered[ac]) ||
>> + !skb_queue_empty(&sta->ps_tx_buf[ac])) {
>> + more_data = true;
>> + }
>
> No need for the braces
>
>> + /* in a MPSP make sure the last skb is a QoS Data frame */
>> + if (test_sta_flag(sta, WLAN_STA_MPSP_OWNER))
>> + mpsp_qos_null_append(sta, &frames);
>
>
>
>> + mps_dbg(sta->sdata, "sending %d frames to PS STA %pM\n",
>> + skb_queue_len(&frames), sta->sta.addr);
>> +
>> + /* prepare collected frames for transmission */
>> + skb_queue_walk(&frames, skb) {
>> + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
>> + struct ieee80211_hdr *hdr = (void *) skb->data;
>> +
>> + /* Tell TX path to send this frame even though the
>> + * STA may still remain is PS mode after this frame
>> + * exchange.
>> + */
>> + info->flags |= IEEE80211_TX_CTL_NO_PS_BUFFER;
>
> I'm not convinced that the PS integration with drivers here is correct
> at all, but I don't really care all that much either.
>
>
>> + * @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 -> stay awake
>> + * @WLAN_STA_MPS_WAIT_FOR_CAB: STA multicast frames are imminent -> stay awake
>
> ??
>
> There's also a compiler warning -- variable shadowing -- that this
> probably solves:
>
> @@ -487,8 +487,6 @@ static void mps_frame_deliver(struct sta_info *sta,
> int n_frames)
>
> /* collect frame(s) from buffers */
> for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
> - struct sk_buff *skb;
> -
> while (n_frames != 0) {
> skb = skb_dequeue(&sta->tx_filtered[ac]);
> if (!skb) {
>
>
> johannes
>


2013-01-07 18:47:37

by Thomas Pedersen

[permalink] [raw]
Subject: Re: [PATCHv2 1/6] nl80211: add range checks to mesh parameters

On Mon, Jan 7, 2013 at 7:04 AM, Marco Porsch <[email protected]> wrote:
> Ranges are taken from IEEE 802.11-2012, common sense or current implementation
> requirements.
>
> Signed-off-by: Marco Porsch <[email protected]>
Acked-by: Thomas Pedersen <[email protected]>

I believe Javier acked an earlier submission of this patch as well.

Thomas

2013-01-16 21:56:59

by Johannes Berg

[permalink] [raw]
Subject: Re: [PATCHv2 0/6] mesh power save - basics

On Mon, 2013-01-07 at 16:04 +0100, Marco Porsch wrote:
> Commits #5 and #6 cover the basic subset of functions, that each mesh node
> must support to communicate in the presence of power saving nodes.
> The current patchset does not implement any actual power savings yet (no
> hardware configuration, no doze state).
>
> Mesh powersave is different from client mode powersave in the sense that
> 1) there is not just a singular power mode towards the access point, but a
> link-specific power mode towards each peer instead
> 2) peers also maintain a power mode towards us (the local STA)
> 3) each node has to buffer frames towards power saving neighbors
>
> changes since PATCHv1:
> - explicitly clear QoS control field in mps_qos_null_get
> - beautify FILL_IN_MESH_PARAM_IF_SET macro
>
> Marco Porsch (6):
> nl80211: add range checks to mesh parameters
> mac80211: update mesh peer link counter during userspace peering
> mac80211: move add_tim to subfunction
> {cfg,nl,mac}80211: set beacon interval and DTIM period on mesh join
> {cfg,nl}80211: mesh power mode primitives and userspace access

I applied patches 1-5, comments on 6 following shortly.

johannes


2013-01-07 15:05:04

by Marco Porsch

[permalink] [raw]
Subject: [PATCHv2 2/6] mac80211: update mesh peer link counter during userspace peering

The established peer link count is indicated in mesh beacons and used for
other internal tasks. Previously it was not updated when authenticated peering
is performed in userspace.

Signed-off-by: Marco Porsch <[email protected]>
---
net/mac80211/cfg.c | 22 +++++++++++++++++++---
net/mac80211/mesh.h | 14 ++++++++++++++
net/mac80211/mesh_plink.c | 14 --------------
3 files changed, 33 insertions(+), 17 deletions(-)

diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 47e0aca..def81d6 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -1211,18 +1211,33 @@ static int sta_apply_parameters(struct ieee80211_local *local,

if (ieee80211_vif_is_mesh(&sdata->vif)) {
#ifdef CONFIG_MAC80211_MESH
- if (sdata->u.mesh.security & IEEE80211_MESH_SEC_SECURED)
+ if (sdata->u.mesh.security & IEEE80211_MESH_SEC_SECURED) {
+ u32 changed = 0;
+
switch (params->plink_state) {
- case NL80211_PLINK_LISTEN:
case NL80211_PLINK_ESTAB:
+ if (sta->plink_state != NL80211_PLINK_ESTAB)
+ changed = mesh_plink_inc_estab_count(
+ sdata);
+ sta->plink_state = params->plink_state;
+ break;
+ case NL80211_PLINK_LISTEN:
case NL80211_PLINK_BLOCKED:
+ case NL80211_PLINK_OPN_SNT:
+ case NL80211_PLINK_OPN_RCVD:
+ case NL80211_PLINK_CNF_RCVD:
+ case NL80211_PLINK_HOLDING:
+ if (sta->plink_state == NL80211_PLINK_ESTAB)
+ changed = mesh_plink_dec_estab_count(
+ sdata);
sta->plink_state = params->plink_state;
break;
default:
/* nothing */
break;
}
- else
+ ieee80211_bss_info_change_notify(sdata, changed);
+ } else {
switch (params->plink_action) {
case PLINK_ACTION_OPEN:
mesh_plink_open(sta);
@@ -1231,6 +1246,7 @@ static int sta_apply_parameters(struct ieee80211_local *local,
mesh_plink_block(sta);
break;
}
+ }
#endif
}

diff --git a/net/mac80211/mesh.h b/net/mac80211/mesh.h
index 84c28c6..c07f6fc 100644
--- a/net/mac80211/mesh.h
+++ b/net/mac80211/mesh.h
@@ -307,6 +307,20 @@ extern int mesh_paths_generation;
#ifdef CONFIG_MAC80211_MESH
extern int mesh_allocated;

+static inline
+u32 mesh_plink_inc_estab_count(struct ieee80211_sub_if_data *sdata)
+{
+ atomic_inc(&sdata->u.mesh.estab_plinks);
+ return mesh_accept_plinks_update(sdata);
+}
+
+static inline
+u32 mesh_plink_dec_estab_count(struct ieee80211_sub_if_data *sdata)
+{
+ atomic_dec(&sdata->u.mesh.estab_plinks);
+ return mesh_accept_plinks_update(sdata);
+}
+
static inline int mesh_plink_free_count(struct ieee80211_sub_if_data *sdata)
{
return sdata->u.mesh.mshcfg.dot11MeshMaxPeerLinks -
diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c
index 4b274e9..9e04166 100644
--- a/net/mac80211/mesh_plink.c
+++ b/net/mac80211/mesh_plink.c
@@ -41,20 +41,6 @@ static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata,
enum ieee80211_self_protected_actioncode action,
u8 *da, __le16 llid, __le16 plid, __le16 reason);

-static inline
-u32 mesh_plink_inc_estab_count(struct ieee80211_sub_if_data *sdata)
-{
- atomic_inc(&sdata->u.mesh.estab_plinks);
- return mesh_accept_plinks_update(sdata);
-}
-
-static inline
-u32 mesh_plink_dec_estab_count(struct ieee80211_sub_if_data *sdata)
-{
- atomic_dec(&sdata->u.mesh.estab_plinks);
- return mesh_accept_plinks_update(sdata);
-}
-
/**
* mesh_plink_fsm_restart - restart a mesh peer link finite state machine
*
--
1.7.9.5


2013-01-07 15:11:26

by Marco Porsch

[permalink] [raw]
Subject: [PATCHv2 1/6] nl80211: add range checks to mesh parameters

Ranges are taken from IEEE 802.11-2012, common sense or current implementation
requirements.

Signed-off-by: Marco Porsch <[email protected]>
---
net/wireless/nl80211.c | 79 +++++++++++++++++++++++++++---------------------
1 file changed, 44 insertions(+), 35 deletions(-)

diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index f45706a..63bf956 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -3967,13 +3967,15 @@ static int nl80211_parse_mesh_config(struct genl_info *info,
struct nlattr *tb[NL80211_MESHCONF_ATTR_MAX + 1];
u32 mask = 0;

-#define FILL_IN_MESH_PARAM_IF_SET(table, cfg, param, mask, attr_num, nla_fn) \
-do {\
- if (table[attr_num]) {\
- cfg->param = nla_fn(table[attr_num]); \
- mask |= (1 << (attr_num - 1)); \
- } \
-} while (0);\
+#define FILL_IN_MESH_PARAM_IF_SET(tb, cfg, param, min, max, mask, attr, fn) \
+do { \
+ if (tb[attr]) { \
+ if (fn(tb[attr]) < min || fn(tb[attr]) > max) \
+ return -EINVAL; \
+ cfg->param = fn(tb[attr]); \
+ mask |= (1 << (attr - 1)); \
+ } \
+} while (0)


if (!info->attrs[NL80211_ATTR_MESH_CONFIG])
@@ -3988,81 +3990,88 @@ do {\
BUILD_BUG_ON(NL80211_MESHCONF_ATTR_MAX > 32);

/* Fill in the params struct */
- FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshRetryTimeout,
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshRetryTimeout, 1, 255,
mask, NL80211_MESHCONF_RETRY_TIMEOUT,
nla_get_u16);
- FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshConfirmTimeout,
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshConfirmTimeout, 1, 255,
mask, NL80211_MESHCONF_CONFIRM_TIMEOUT,
nla_get_u16);
- FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshHoldingTimeout,
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshHoldingTimeout, 1, 255,
mask, NL80211_MESHCONF_HOLDING_TIMEOUT,
nla_get_u16);
- FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshMaxPeerLinks,
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshMaxPeerLinks, 0, 255,
mask, NL80211_MESHCONF_MAX_PEER_LINKS,
nla_get_u16);
- FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshMaxRetries,
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshMaxRetries, 0, 16,
mask, NL80211_MESHCONF_MAX_RETRIES,
nla_get_u8);
- FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshTTL,
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshTTL, 1, 255,
mask, NL80211_MESHCONF_TTL, nla_get_u8);
- FILL_IN_MESH_PARAM_IF_SET(tb, cfg, element_ttl,
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, element_ttl, 1, 255,
mask, NL80211_MESHCONF_ELEMENT_TTL,
nla_get_u8);
- FILL_IN_MESH_PARAM_IF_SET(tb, cfg, auto_open_plinks,
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, auto_open_plinks, 0, 1,
mask, NL80211_MESHCONF_AUTO_OPEN_PLINKS,
nla_get_u8);
- FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshNbrOffsetMaxNeighbor, mask,
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshNbrOffsetMaxNeighbor,
+ 1, 255, mask,
NL80211_MESHCONF_SYNC_OFFSET_MAX_NEIGHBOR,
nla_get_u32);
- FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshHWMPmaxPREQretries,
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshHWMPmaxPREQretries, 0, 255,
mask, NL80211_MESHCONF_HWMP_MAX_PREQ_RETRIES,
nla_get_u8);
- FILL_IN_MESH_PARAM_IF_SET(tb, cfg, path_refresh_time,
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, path_refresh_time, 1, 65535,
mask, NL80211_MESHCONF_PATH_REFRESH_TIME,
nla_get_u32);
- FILL_IN_MESH_PARAM_IF_SET(tb, cfg, min_discovery_timeout,
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, min_discovery_timeout, 1, 65535,
mask, NL80211_MESHCONF_MIN_DISCOVERY_TIMEOUT,
nla_get_u16);
- FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshHWMPactivePathTimeout, mask,
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshHWMPactivePathTimeout,
+ 1, 65535, mask,
NL80211_MESHCONF_HWMP_ACTIVE_PATH_TIMEOUT,
nla_get_u32);
FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshHWMPpreqMinInterval,
- mask, NL80211_MESHCONF_HWMP_PREQ_MIN_INTERVAL,
+ 1, 65535, mask,
+ NL80211_MESHCONF_HWMP_PREQ_MIN_INTERVAL,
nla_get_u16);
FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshHWMPperrMinInterval,
- mask, NL80211_MESHCONF_HWMP_PERR_MIN_INTERVAL,
+ 1, 65535, mask,
+ NL80211_MESHCONF_HWMP_PERR_MIN_INTERVAL,
nla_get_u16);
FILL_IN_MESH_PARAM_IF_SET(tb, cfg,
- dot11MeshHWMPnetDiameterTraversalTime, mask,
+ dot11MeshHWMPnetDiameterTraversalTime,
+ 1, 65535, mask,
NL80211_MESHCONF_HWMP_NET_DIAM_TRVS_TIME,
nla_get_u16);
- FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshHWMPRootMode, mask,
- NL80211_MESHCONF_HWMP_ROOTMODE, nla_get_u8);
- FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshHWMPRannInterval, mask,
- NL80211_MESHCONF_HWMP_RANN_INTERVAL,
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshHWMPRootMode, 0, 4,
+ mask, NL80211_MESHCONF_HWMP_ROOTMODE,
+ nla_get_u8);
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshHWMPRannInterval, 1, 65535,
+ mask, NL80211_MESHCONF_HWMP_RANN_INTERVAL,
nla_get_u16);
FILL_IN_MESH_PARAM_IF_SET(tb, cfg,
- dot11MeshGateAnnouncementProtocol, mask,
- NL80211_MESHCONF_GATE_ANNOUNCEMENTS,
+ dot11MeshGateAnnouncementProtocol, 0, 1,
+ mask, NL80211_MESHCONF_GATE_ANNOUNCEMENTS,
nla_get_u8);
- FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshForwarding,
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshForwarding, 0, 1,
mask, NL80211_MESHCONF_FORWARDING,
nla_get_u8);
- FILL_IN_MESH_PARAM_IF_SET(tb, cfg, rssi_threshold,
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, rssi_threshold, 1, 255,
mask, NL80211_MESHCONF_RSSI_THRESHOLD,
nla_get_u32);
- FILL_IN_MESH_PARAM_IF_SET(tb, cfg, ht_opmode,
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, ht_opmode, 0, 16,
mask, NL80211_MESHCONF_HT_OPMODE,
nla_get_u16);
FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshHWMPactivePathToRootTimeout,
- mask,
+ 1, 65535, mask,
NL80211_MESHCONF_HWMP_PATH_TO_ROOT_TIMEOUT,
nla_get_u32);
- FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshHWMProotInterval,
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshHWMProotInterval, 1, 65535,
mask, NL80211_MESHCONF_HWMP_ROOT_INTERVAL,
nla_get_u16);
FILL_IN_MESH_PARAM_IF_SET(tb, cfg,
- dot11MeshHWMPconfirmationInterval, mask,
+ dot11MeshHWMPconfirmationInterval,
+ 1, 65535, mask,
NL80211_MESHCONF_HWMP_CONFIRMATION_INTERVAL,
nla_get_u16);
if (mask_out)
--
1.7.9.5


2013-01-07 15:05:07

by Marco Porsch

[permalink] [raw]
Subject: [PATCHv2 4/6] {cfg,nl,mac}80211: set beacon interval and DTIM period on mesh join

Move the default mesh beacon interval and DTIM period to cfg80211 and
make it accessible to nl80211. This enables setting both values when joining
an MBSS.
Previously the DTIM parameter was not set by mac80211 so the driver's default
value was used.

Signed-off-by: Marco Porsch <[email protected]>
---
include/net/cfg80211.h | 4 ++++
net/mac80211/cfg.c | 3 +++
net/mac80211/mesh.c | 1 -
net/mac80211/mesh.h | 2 --
net/wireless/mesh.c | 5 +++++
net/wireless/nl80211.c | 15 +++++++++++++++
6 files changed, 27 insertions(+), 3 deletions(-)

diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 8e6a6b7..4e4b1b0 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -1034,6 +1034,8 @@ struct mesh_config {
* @ie_len: length of vendor information elements
* @is_authenticated: this mesh requires authentication
* @is_secure: this mesh uses security
+ * @dtim_period: DTIM period to use
+ * @beacon_interval: beacon interval to use
* @mcast_rate: multicat rate for Mesh Node [6Mbps is the default for 802.11a]
*
* These parameters are fixed when the mesh is created.
@@ -1049,6 +1051,8 @@ struct mesh_setup {
u8 ie_len;
bool is_authenticated;
bool is_secure;
+ u8 dtim_period;
+ u16 beacon_interval;
int mcast_rate[IEEE80211_NUM_BANDS];
};

diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index def81d6..abfd5d3 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -1631,6 +1631,9 @@ static int copy_mesh_setup(struct ieee80211_if_mesh *ifmsh,
memcpy(sdata->vif.bss_conf.mcast_rate, setup->mcast_rate,
sizeof(setup->mcast_rate));

+ sdata->vif.bss_conf.beacon_int = setup->beacon_interval;
+ sdata->vif.bss_conf.dtim_period = setup->dtim_period;
+
return 0;
}

diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c
index 649ad51..35013fa 100644
--- a/net/mac80211/mesh.c
+++ b/net/mac80211/mesh.c
@@ -624,7 +624,6 @@ void ieee80211_start_mesh(struct ieee80211_sub_if_data *sdata)
ieee80211_queue_work(&local->hw, &sdata->work);
sdata->vif.bss_conf.ht_operation_mode =
ifmsh->mshcfg.ht_opmode;
- sdata->vif.bss_conf.beacon_int = MESH_DEFAULT_BEACON_INTERVAL;
sdata->vif.bss_conf.basic_rates =
ieee80211_mandatory_rates(sdata->local,
ieee80211_get_sdata_band(sdata));
diff --git a/net/mac80211/mesh.h b/net/mac80211/mesh.h
index c07f6fc..aff3015 100644
--- a/net/mac80211/mesh.h
+++ b/net/mac80211/mesh.h
@@ -191,8 +191,6 @@ struct mesh_rmc {
#define IEEE80211_MESH_PEER_INACTIVITY_LIMIT (1800 * HZ)
#define IEEE80211_MESH_HOUSEKEEPING_INTERVAL (60 * HZ)

-#define MESH_DEFAULT_BEACON_INTERVAL 1000 /* in 1024 us units */
-
#define MESH_PATH_EXPIRE (600 * HZ)

/* Default maximum number of plinks per interface */
diff --git a/net/wireless/mesh.c b/net/wireless/mesh.c
index f9d6ce5..0fe8ceb 100644
--- a/net/wireless/mesh.c
+++ b/net/wireless/mesh.c
@@ -44,6 +44,9 @@

#define MESH_SYNC_NEIGHBOR_OFFSET_MAX 50

+#define MESH_DEFAULT_BEACON_INTERVAL 1000 /* in 1024 us units (=TUs) */
+#define MESH_DEFAULT_DTIM_PERIOD 2
+
const struct mesh_config default_mesh_config = {
.dot11MeshRetryTimeout = MESH_RET_T,
.dot11MeshConfirmTimeout = MESH_CONF_T,
@@ -79,6 +82,8 @@ const struct mesh_setup default_mesh_setup = {
.ie = NULL,
.ie_len = 0,
.is_secure = false,
+ .beacon_interval = MESH_DEFAULT_BEACON_INTERVAL,
+ .dtim_period = MESH_DEFAULT_DTIM_PERIOD,
};

int __cfg80211_join_mesh(struct cfg80211_registered_device *rdev,
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 63bf956..a32c7767 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -6661,6 +6661,21 @@ static int nl80211_join_mesh(struct sk_buff *skb, struct genl_info *info)
nla_get_u32(info->attrs[NL80211_ATTR_MCAST_RATE])))
return -EINVAL;

+ if (info->attrs[NL80211_ATTR_BEACON_INTERVAL]) {
+ setup.beacon_interval =
+ nla_get_u32(info->attrs[NL80211_ATTR_BEACON_INTERVAL]);
+ if (setup.beacon_interval < 10 ||
+ setup.beacon_interval > 10000)
+ return -EINVAL;
+ }
+
+ if (info->attrs[NL80211_ATTR_DTIM_PERIOD]) {
+ setup.dtim_period =
+ nla_get_u32(info->attrs[NL80211_ATTR_DTIM_PERIOD]);
+ if (setup.dtim_period < 1 || setup.dtim_period > 100)
+ return -EINVAL;
+ }
+
if (info->attrs[NL80211_ATTR_MESH_SETUP]) {
/* parse additional setup parameters if given */
err = nl80211_parse_mesh_setup(info, &setup);
--
1.7.9.5


2013-01-07 18:53:56

by Thomas Pedersen

[permalink] [raw]
Subject: Re: [PATCHv2 2/6] mac80211: update mesh peer link counter during userspace peering

On Mon, Jan 7, 2013 at 7:04 AM, Marco Porsch <[email protected]> wrote:
> The established peer link count is indicated in mesh beacons and used for
> other internal tasks. Previously it was not updated when authenticated peering
> is performed in userspace.
>
> Signed-off-by: Marco Porsch <[email protected]>
Acked-by: Thomas Pedersen <[email protected]>

2013-01-16 11:31:52

by Johannes Berg

[permalink] [raw]
Subject: Re: [PATCHv2 0/6] mesh power save - basics

On Wed, 2013-01-16 at 10:49 +0100, Marco Porsch wrote:
> Hi Johannes,
>
> I just checked back on the status of my patches and see no further
> comments while they are not applied yet. Is there anything odd about them?

No, I've just been very backlogged for a while ...

johannes


2013-01-17 14:08:19

by Marco Porsch

[permalink] [raw]
Subject: Re: [PATCHv2 6/6] mac80211: mesh power save basics

On 01/16/2013 11:14 PM, Johannes Berg wrote:
> On Mon, 2013-01-07 at 16:04 +0100, Marco Porsch wrote:
>
>> mode determines when non-peer mesh STA may send Probe Requests and Mesh Peering
>
> Please break lines to less than 72 characters, I personally prefer
> around 60 or so but I'll apply 72 too.
>
>> +static inline bool ieee80211_has_qos_mesh_ps(__le16 qc)
>> +{
>> + return (qc & cpu_to_le16(IEEE80211_QOS_CTL_MESH_PS_LEVEL)) != 0;
>
> bool means you don't need the !=0 and parentheses.
>
>
>> @@ -1208,6 +1214,7 @@ struct ieee802_11_elems {
>> struct ieee80211_meshconf_ie *mesh_config;
>> u8 *mesh_id;
>> u8 *peering;
>> + u8 *awake_window;
>
> maybe that should be an __le16 pointer?
>
>> + /* we need some delay here, otherwise the announcement
>> + * Null would arrive before the CONFIRM
>> + */
>> + ieee80211_mps_set_sta_local_pm(sta, mshcfg->power_mode,
>> + 100);
>
> ???
>
> How can a *delay* ever cause correctness? That doesn't seem right.

That was needed because a QoS Null used to inform the peer was often
received before the Peer Link Confirm frame and thus thrown away.
Luckily, after testing that once again, the problem has magically
vanished. I'll remove the whole timer thing--never liked having that
anyway.

>> + /* announce peer-specific power mode transition
>> + * see IEEE802.11-2012 13.14.3.2 and 13.14.3.3
>> + */
>
> I feel a bit bad about asking this, but mac80211 actually doesn't use
> the "networking" style, all other comments have
>
> /*
> * announce ...
> * ...
> */
>
> so please adjust yours as well.

Oops. checkpatch fooled me here.

> I think I deleted this, but nonetheless:
>
>
>> static void mpsp_trigger_send(struct sta_info *sta,
>> bool rspi, bool eosp)
>
> that easily fits on one line.
>
>
>> +/**
>> + * mps_frame_deliver - transmit frames during mesh powersave
>> + *
>> + * @sta: STA info to transmit to
>> + * @n_frames: number of frames to transmit. -1 for all
>> + */
>> +static void mps_frame_deliver(struct sta_info *sta, int n_frames)
>> +{
>> + struct ieee80211_sub_if_data *sdata = sta->sdata;
>> + struct ieee80211_local *local = sdata->local;
>> + int ac;
>> + struct sk_buff_head frames;
>> + struct sk_buff *skb;
>> + bool more_data = false;
>> +
>> + skb_queue_head_init(&frames);
>
> You really don't need a spinlock for on-stack queues, so you should use
> the __ versions of the functions modifying "frames".

Yeah, right. The skb_queue_head_init needs to stay, though, because of
ieee80211_add_pending_skbs_fn.

>
>> + if (!skb)
>> + break;
>> + n_frames--;
>
> I guess you're assuming there never will be > 2^31 frames ;-)

In that case something else would crash first, I hope =)
Btw, that code is very similar to ieee80211_sta_ps_deliver_response.

>> + skb_queue_tail(&frames, skb);
>
> __skb_queue_tail(), etc.
>
>> + }
>> +
>> + if (!skb_queue_empty(&sta->tx_filtered[ac]) ||
>> + !skb_queue_empty(&sta->ps_tx_buf[ac])) {
>> + more_data = true;
>> + }
>
> No need for the braces
>
>> + /* in a MPSP make sure the last skb is a QoS Data frame */
>> + if (test_sta_flag(sta, WLAN_STA_MPSP_OWNER))
>> + mpsp_qos_null_append(sta, &frames);
>
>
>
>> + mps_dbg(sta->sdata, "sending %d frames to PS STA %pM\n",
>> + skb_queue_len(&frames), sta->sta.addr);
>> +
>> + /* prepare collected frames for transmission */
>> + skb_queue_walk(&frames, skb) {
>> + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
>> + struct ieee80211_hdr *hdr = (void *) skb->data;
>> +
>> + /* Tell TX path to send this frame even though the
>> + * STA may still remain is PS mode after this frame
>> + * exchange.
>> + */
>> + info->flags |= IEEE80211_TX_CTL_NO_PS_BUFFER;
>
> I'm not convinced that the PS integration with drivers here is correct
> at all, but I don't really care all that much either.

That flag is picked up by the mac80211 TX handlers to bypass the TX
buffer. We currently do not support any driver/HW that buffers frames
itself; even aggregation is causing trouble already.

>> + * @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 -> stay awake
>> + * @WLAN_STA_MPS_WAIT_FOR_CAB: STA multicast frames are imminent -> stay awake
>
> ??

Oops, that belongs into a following commit on device sleep for actual
power savings that I am currently testing.

> There's also a compiler warning -- variable shadowing -- that this
> probably solves:
>
> @@ -487,8 +487,6 @@ static void mps_frame_deliver(struct sta_info *sta,
> int n_frames)
>
> /* collect frame(s) from buffers */
> for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
> - struct sk_buff *skb;
> -
> while (n_frames != 0) {
> skb = skb_dequeue(&sta->tx_filtered[ac]);
> if (!skb) {
>
>
> johannes
>


2013-01-07 15:05:08

by Marco Porsch

[permalink] [raw]
Subject: [PATCHv2 5/6] {cfg,nl}80211: mesh power mode primitives and userspace access

Add the nl80211_mesh_power_mode enumeration which holds possible values for
the mesh power mode. These modes are unknown, active, light sleep and
deep sleep.

Add power_mode entry to the mesh config structure to hold the user-configured
default mesh power mode. This value will be used for new peer links.

Add the dot11MeshAwakeWindowDuration value to the mesh config. The awake window
is a duration in TU describing how long the STA will stay awake after
transmitting its beacon in PS mode.

Add access routines to:
get/set local link-specific power mode (STA)
get remote STA's link-specific power mode (STA)
get remote STA's non-peer power mode (STA)

get/set default mesh power mode (mesh config)
get/set mesh awake window duration (mesh config)

All config changes may be done at mesh runtime and take effect immediately.

Signed-off-by: Marco Porsch <[email protected]>
Signed-off-by: Ivan Bezyazychnyy <[email protected]>
Signed-off-by: Mike Krinkin <[email protected]>
---
include/net/cfg80211.h | 21 +++++++++++++++++++
include/uapi/linux/nl80211.h | 47 ++++++++++++++++++++++++++++++++++++++++++
net/wireless/mesh.c | 3 +++
net/wireless/nl80211.c | 43 +++++++++++++++++++++++++++++++++++++-
4 files changed, 113 insertions(+), 1 deletion(-)

diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 4e4b1b0..ecafecb 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -608,6 +608,8 @@ enum station_parameters_apply_mask {
* @sta_modify_mask: bitmap indicating which parameters changed
* (for those that don't have a natural "no change" value),
* see &enum station_parameters_apply_mask
+ * @local_pm: local link-specific mesh power save mode (no change when set
+ * to unknown)
*/
struct station_parameters {
u8 *supported_rates;
@@ -623,6 +625,7 @@ struct station_parameters {
struct ieee80211_vht_cap *vht_capa;
u8 uapsd_queues;
u8 max_sp;
+ enum nl80211_mesh_power_mode local_pm;
};

/**
@@ -653,6 +656,9 @@ struct station_parameters {
* @STATION_INFO_STA_FLAGS: @sta_flags filled
* @STATION_INFO_BEACON_LOSS_COUNT: @beacon_loss_count filled
* @STATION_INFO_T_OFFSET: @t_offset filled
+ * @STATION_INFO_LOCAL_PM: @local_pm filled
+ * @STATION_INFO_PEER_PM: @peer_pm filled
+ * @STATION_INFO_NONPEER_PM: @nonpeer_pm filled
*/
enum station_info_flags {
STATION_INFO_INACTIVE_TIME = 1<<0,
@@ -676,6 +682,9 @@ enum station_info_flags {
STATION_INFO_STA_FLAGS = 1<<18,
STATION_INFO_BEACON_LOSS_COUNT = 1<<19,
STATION_INFO_T_OFFSET = 1<<20,
+ STATION_INFO_LOCAL_PM = 1<<21,
+ STATION_INFO_PEER_PM = 1<<22,
+ STATION_INFO_NONPEER_PM = 1<<23,
};

/**
@@ -789,6 +798,9 @@ struct sta_bss_parameters {
* @sta_flags: station flags mask & values
* @beacon_loss_count: Number of times beacon loss event has triggered.
* @t_offset: Time offset of the station relative to this host.
+ * @local_pm: local mesh STA power save mode
+ * @peer_pm: peer mesh STA power save mode
+ * @nonpeer_pm: non-peer mesh STA power save mode
*/
struct station_info {
u32 filled;
@@ -818,6 +830,9 @@ struct station_info {

u32 beacon_loss_count;
s64 t_offset;
+ enum nl80211_mesh_power_mode local_pm;
+ enum nl80211_mesh_power_mode peer_pm;
+ enum nl80211_mesh_power_mode nonpeer_pm;

/*
* Note: Add a new enum station_info_flags value for each new field and
@@ -993,6 +1008,10 @@ struct bss_parameters {
* @dot11MeshHWMPconfirmationInterval: The minimum interval of time (in TUs)
* during which a mesh STA can send only one Action frame containing
* a PREQ element for root path confirmation.
+ * @power_mode: The default mesh power save mode which will be the initial
+ * setting for new peer links.
+ * @dot11MeshAwakeWindowDuration: The duration in TUs the STA will remain awake
+ * after transmitting its beacon.
*/
struct mesh_config {
u16 dot11MeshRetryTimeout;
@@ -1020,6 +1039,8 @@ struct mesh_config {
u32 dot11MeshHWMPactivePathToRootTimeout;
u16 dot11MeshHWMProotInterval;
u16 dot11MeshHWMPconfirmationInterval;
+ enum nl80211_mesh_power_mode power_mode;
+ u16 dot11MeshAwakeWindowDuration;
};

/**
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index e3e19f8..863fc8e 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -1310,6 +1310,9 @@ enum nl80211_commands {
* if not given in START_AP 0 is assumed, if not given in SET_BSS
* no change is made.
*
+ * @NL80211_ATTR_LOCAL_MESH_POWER_MODE: local mesh STA link-specific power mode
+ * defined in &enum nl80211_mesh_power_mode.
+ *
* @NL80211_ATTR_MAX: highest attribute number currently defined
* @__NL80211_ATTR_AFTER_LAST: internal use
*/
@@ -1580,6 +1583,8 @@ enum nl80211_attrs {
NL80211_ATTR_P2P_CTWINDOW,
NL80211_ATTR_P2P_OPPPS,

+ NL80211_ATTR_LOCAL_MESH_POWER_MODE,
+
/* add attributes here, update the policy in nl80211.c */

__NL80211_ATTR_AFTER_LAST,
@@ -1834,6 +1839,10 @@ enum nl80211_sta_bss_param {
* @NL80211_STA_INFO_STA_FLAGS: Contains a struct nl80211_sta_flag_update.
* @NL80211_STA_INFO_BEACON_LOSS: count of times beacon loss was detected (u32)
* @NL80211_STA_INFO_T_OFFSET: timing offset with respect to this STA (s64)
+ * @NL80211_STA_INFO_LOCAL_PM: local mesh STA link-specific power mode
+ * @NL80211_STA_INFO_PEER_PM: peer mesh STA link-specific power mode
+ * @NL80211_STA_INFO_NONPEER_PM: neighbor mesh STA power save mode towards
+ * non-peer STA
* @__NL80211_STA_INFO_AFTER_LAST: internal
* @NL80211_STA_INFO_MAX: highest possible station info attribute
*/
@@ -1858,6 +1867,9 @@ enum nl80211_sta_info {
NL80211_STA_INFO_STA_FLAGS,
NL80211_STA_INFO_BEACON_LOSS,
NL80211_STA_INFO_T_OFFSET,
+ NL80211_STA_INFO_LOCAL_PM,
+ NL80211_STA_INFO_PEER_PM,
+ NL80211_STA_INFO_NONPEER_PM,

/* keep last */
__NL80211_STA_INFO_AFTER_LAST,
@@ -2249,6 +2261,34 @@ enum nl80211_mntr_flags {
};

/**
+ * enum nl80211_mesh_power_mode - mesh power save modes
+ *
+ * @NL80211_MESH_POWER_UNKNOWN: The mesh power mode of the mesh STA is
+ * not known or has not been set yet.
+ * @NL80211_MESH_POWER_ACTIVE: Active mesh power mode. The mesh STA is
+ * in Awake state all the time.
+ * @NL80211_MESH_POWER_LIGHT_SLEEP: Light sleep mode. The mesh STA will
+ * alternate between Active and Doze states, but will wake up for
+ * neighbor's beacons.
+ * @NL80211_MESH_POWER_DEEP_SLEEP: Deep sleep mode. The mesh STA will
+ * alternate between Active and Doze states, but may not wake up
+ * for neighbor's beacons.
+ *
+ * @__NL80211_MESH_POWER_AFTER_LAST - internal use
+ * @NL80211_MESH_POWER_MAX - highest possible power save level
+ */
+
+enum nl80211_mesh_power_mode {
+ NL80211_MESH_POWER_UNKNOWN,
+ NL80211_MESH_POWER_ACTIVE,
+ NL80211_MESH_POWER_LIGHT_SLEEP,
+ NL80211_MESH_POWER_DEEP_SLEEP,
+
+ __NL80211_MESH_POWER_AFTER_LAST,
+ NL80211_MESH_POWER_MAX = __NL80211_MESH_POWER_AFTER_LAST - 1
+};
+
+/**
* enum nl80211_meshconf_params - mesh configuration parameters
*
* Mesh configuration parameters. These can be changed while the mesh is
@@ -2342,6 +2382,11 @@ enum nl80211_mntr_flags {
* (in TUs) during which a mesh STA can send only one Action frame
* containing a PREQ element for root path confirmation.
*
+ * @NL80211_MESHCONF_POWER_MODE: Default mesh power mode for new peer links.
+ * type &enum nl80211_mesh_power_mode (u32)
+ *
+ * @NL80211_MESHCONF_AWAKE_WINDOW: awake window duration (in TUs)
+ *
* @__NL80211_MESHCONF_ATTR_AFTER_LAST: internal use
*/
enum nl80211_meshconf_params {
@@ -2371,6 +2416,8 @@ enum nl80211_meshconf_params {
NL80211_MESHCONF_HWMP_PATH_TO_ROOT_TIMEOUT,
NL80211_MESHCONF_HWMP_ROOT_INTERVAL,
NL80211_MESHCONF_HWMP_CONFIRMATION_INTERVAL,
+ NL80211_MESHCONF_POWER_MODE,
+ NL80211_MESHCONF_AWAKE_WINDOW,

/* keep last */
__NL80211_MESHCONF_ATTR_AFTER_LAST,
diff --git a/net/wireless/mesh.c b/net/wireless/mesh.c
index 0fe8ceb..55957a2 100644
--- a/net/wireless/mesh.c
+++ b/net/wireless/mesh.c
@@ -46,6 +46,7 @@

#define MESH_DEFAULT_BEACON_INTERVAL 1000 /* in 1024 us units (=TUs) */
#define MESH_DEFAULT_DTIM_PERIOD 2
+#define MESH_DEFAULT_AWAKE_WINDOW 10 /* in 1024 us units (=TUs) */

const struct mesh_config default_mesh_config = {
.dot11MeshRetryTimeout = MESH_RET_T,
@@ -72,6 +73,8 @@ const struct mesh_config default_mesh_config = {
.dot11MeshHWMPactivePathToRootTimeout = MESH_PATH_TO_ROOT_TIMEOUT,
.dot11MeshHWMProotInterval = MESH_ROOT_INTERVAL,
.dot11MeshHWMPconfirmationInterval = MESH_ROOT_CONFIRMATION_INTERVAL,
+ .power_mode = NL80211_MESH_POWER_ACTIVE,
+ .dot11MeshAwakeWindowDuration = MESH_DEFAULT_AWAKE_WINDOW,
};

const struct mesh_setup default_mesh_setup = {
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index a32c7767..dfad6d7 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -3001,6 +3001,18 @@ static int nl80211_send_station(struct sk_buff *msg, u32 portid, u32 seq,
nla_put_u32(msg, NL80211_STA_INFO_BEACON_LOSS,
sinfo->beacon_loss_count))
goto nla_put_failure;
+ if ((sinfo->filled & STATION_INFO_LOCAL_PM) &&
+ nla_put_u32(msg, NL80211_STA_INFO_LOCAL_PM,
+ sinfo->local_pm))
+ goto nla_put_failure;
+ if ((sinfo->filled & STATION_INFO_PEER_PM) &&
+ nla_put_u32(msg, NL80211_STA_INFO_PEER_PM,
+ sinfo->peer_pm))
+ goto nla_put_failure;
+ if ((sinfo->filled & STATION_INFO_NONPEER_PM) &&
+ nla_put_u32(msg, NL80211_STA_INFO_NONPEER_PM,
+ sinfo->nonpeer_pm))
+ goto nla_put_failure;
if (sinfo->filled & STATION_INFO_BSS_PARAM) {
bss_param = nla_nest_start(msg, NL80211_STA_INFO_BSS_PARAM);
if (!bss_param)
@@ -3210,6 +3222,17 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
params.plink_state =
nla_get_u8(info->attrs[NL80211_ATTR_STA_PLINK_STATE]);

+ if (info->attrs[NL80211_ATTR_LOCAL_MESH_POWER_MODE]) {
+ enum nl80211_mesh_power_mode pm = nla_get_u32(
+ info->attrs[NL80211_ATTR_LOCAL_MESH_POWER_MODE]);
+
+ if (pm <= NL80211_MESH_POWER_UNKNOWN ||
+ pm > NL80211_MESH_POWER_MAX)
+ return -EINVAL;
+
+ params.local_pm = pm;
+ }
+
switch (dev->ieee80211_ptr->iftype) {
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_AP_VLAN:
@@ -3217,6 +3240,8 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
/* disallow mesh-specific things */
if (params.plink_action)
return -EINVAL;
+ if (params.local_pm)
+ err = -EINVAL;

/* TDLS can't be set, ... */
if (params.sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER))
@@ -3259,6 +3284,8 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
return -EINVAL;
if (params.listen_interval >= 0)
return -EINVAL;
+ if (params.local_pm)
+ err = -EINVAL;
/* reject any changes other than AUTHORIZED */
if (params.sta_flags_mask & ~BIT(NL80211_STA_FLAG_AUTHORIZED))
return -EINVAL;
@@ -3908,7 +3935,11 @@ static int nl80211_get_mesh_config(struct sk_buff *skb,
nla_put_u16(msg, NL80211_MESHCONF_HWMP_ROOT_INTERVAL,
cur_params.dot11MeshHWMProotInterval) ||
nla_put_u16(msg, NL80211_MESHCONF_HWMP_CONFIRMATION_INTERVAL,
- cur_params.dot11MeshHWMPconfirmationInterval))
+ cur_params.dot11MeshHWMPconfirmationInterval) ||
+ nla_put_u32(msg, NL80211_MESHCONF_POWER_MODE,
+ cur_params.power_mode) ||
+ nla_put_u16(msg, NL80211_MESHCONF_AWAKE_WINDOW,
+ cur_params.dot11MeshAwakeWindowDuration))
goto nla_put_failure;
nla_nest_end(msg, pinfoattr);
genlmsg_end(msg, hdr);
@@ -3947,6 +3978,8 @@ static const struct nla_policy nl80211_meshconf_params_policy[NL80211_MESHCONF_A
[NL80211_MESHCONF_HWMP_PATH_TO_ROOT_TIMEOUT] = { .type = NLA_U32 },
[NL80211_MESHCONF_HWMP_ROOT_INTERVAL] = { .type = NLA_U16 },
[NL80211_MESHCONF_HWMP_CONFIRMATION_INTERVAL] = { .type = NLA_U16 },
+ [NL80211_MESHCONF_POWER_MODE] = { .type = NLA_U32 },
+ [NL80211_MESHCONF_AWAKE_WINDOW] = { .type = NLA_U16 },
};

static const struct nla_policy
@@ -4074,6 +4107,14 @@ do { \
1, 65535, mask,
NL80211_MESHCONF_HWMP_CONFIRMATION_INTERVAL,
nla_get_u16);
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, power_mode,
+ NL80211_MESH_POWER_ACTIVE,
+ NL80211_MESH_POWER_MAX,
+ mask, NL80211_MESHCONF_POWER_MODE,
+ nla_get_u32);
+ FILL_IN_MESH_PARAM_IF_SET(tb, cfg, dot11MeshAwakeWindowDuration,
+ 0, 65535, mask,
+ NL80211_MESHCONF_AWAKE_WINDOW, nla_get_u16);
if (mask_out)
*mask_out = mask;

--
1.7.9.5


2013-01-18 11:52:17

by Johannes Berg

[permalink] [raw]
Subject: Re: [PATCHv2 6/6] mac80211: mesh power save basics

On Thu, 2013-01-17 at 17:34 +0100, Marco Porsch wrote:
> On 01/16/2013 11:14 PM, Johannes Berg wrote:
> > On Mon, 2013-01-07 at 16:04 +0100, Marco Porsch wrote:
> >
> >> mode determines when non-peer mesh STA may send Probe Requests and Mesh Peering
> >
> > Please break lines to less than 72 characters, I personally prefer
> > around 60 or so but I'll apply 72 too.
> >
> >> +static inline bool ieee80211_has_qos_mesh_ps(__le16 qc)
> >> +{
> >> + return (qc & cpu_to_le16(IEEE80211_QOS_CTL_MESH_PS_LEVEL)) != 0;
> >
> > bool means you don't need the !=0 and parentheses.
>
> Actually, then it pops the following warning:
> CHECK net/mac80211/mesh_ps.c
> include/linux/ieee80211.h:584:19: warning: incorrect type in return
> expression (different base types)
> include/linux/ieee80211.h:584:19: expected bool
> include/linux/ieee80211.h:584:19: got restricted __le16
>
>
> Other functions in that file have following style:
> static inline int ieee80211_has_pm(__le16 fc)
> {
> return (fc & cpu_to_le16(IEEE80211_FCTL_PM)) != 0;
> }
>
> What do you recommend?

I'm clearly not quick enough for you, sorry. I guess we should keep it
as is then, but I see you removed it entirely later? Anyway ... I just
wasn't aware sparse would then complain, that's a bit stupid but hey.

johannes


2013-01-18 11:55:17

by Marco Porsch

[permalink] [raw]
Subject: Re: [PATCHv2 6/6] mac80211: mesh power save basics

On 01/18/2013 12:52 PM, Johannes Berg wrote:
> On Thu, 2013-01-17 at 17:34 +0100, Marco Porsch wrote:
>> On 01/16/2013 11:14 PM, Johannes Berg wrote:
>>> On Mon, 2013-01-07 at 16:04 +0100, Marco Porsch wrote:
>>>
>>>> mode determines when non-peer mesh STA may send Probe Requests and Mesh Peering
>>>
>>> Please break lines to less than 72 characters, I personally prefer
>>> around 60 or so but I'll apply 72 too.
>>>
>>>> +static inline bool ieee80211_has_qos_mesh_ps(__le16 qc)
>>>> +{
>>>> + return (qc & cpu_to_le16(IEEE80211_QOS_CTL_MESH_PS_LEVEL)) != 0;
>>>
>>> bool means you don't need the !=0 and parentheses.
>>
>> Actually, then it pops the following warning:
>> CHECK net/mac80211/mesh_ps.c
>> include/linux/ieee80211.h:584:19: warning: incorrect type in return
>> expression (different base types)
>> include/linux/ieee80211.h:584:19: expected bool
>> include/linux/ieee80211.h:584:19: got restricted __le16
>>
>>
>> Other functions in that file have following style:
>> static inline int ieee80211_has_pm(__le16 fc)
>> {
>> return (fc & cpu_to_le16(IEEE80211_FCTL_PM)) != 0;
>> }
>>
>> What do you recommend?
>
> I'm clearly not quick enough for you, sorry. I guess we should keep it
> as is then, but I see you removed it entirely later? Anyway ... I just
> wasn't aware sparse would then complain, that's a bit stupid but hey.

Ah sorry. I just realized that
1) all the inlines in that file are about Frame Control and none copes
with QoS Control
2) I am the only one using QoS Mesh PM and in just a single occasion

So... SNIP =)

--Marco

2013-01-16 09:49:44

by Marco Porsch

[permalink] [raw]
Subject: Re: [PATCHv2 0/6] mesh power save - basics

Hi Johannes,

I just checked back on the status of my patches and see no further
comments while they are not applied yet. Is there anything odd about them?

Regards,
Marco


On 01/07/2013 04:04 PM, Marco Porsch wrote:
> Commits #5 and #6 cover the basic subset of functions, that each mesh node
> must support to communicate in the presence of power saving nodes.
> The current patchset does not implement any actual power savings yet (no
> hardware configuration, no doze state).
>
> Mesh powersave is different from client mode powersave in the sense that
> 1) there is not just a singular power mode towards the access point, but a
> link-specific power mode towards each peer instead
> 2) peers also maintain a power mode towards us (the local STA)
> 3) each node has to buffer frames towards power saving neighbors
>
> changes since PATCHv1:
> - explicitly clear QoS control field in mps_qos_null_get
> - beautify FILL_IN_MESH_PARAM_IF_SET macro
>
> Marco Porsch (6):
> nl80211: add range checks to mesh parameters
> mac80211: update mesh peer link counter during userspace peering
> mac80211: move add_tim to subfunction
> {cfg,nl,mac}80211: set beacon interval and DTIM period on mesh join
> {cfg,nl}80211: mesh power mode primitives and userspace access
> mac80211: mesh power save basics
>
> include/linux/ieee80211.h | 17 ++
> include/net/cfg80211.h | 25 ++
> include/uapi/linux/nl80211.h | 47 +++
> net/mac80211/Kconfig | 11 +
> net/mac80211/Makefile | 3 +-
> net/mac80211/cfg.c | 54 +++-
> net/mac80211/debug.h | 10 +
> net/mac80211/debugfs_netdev.c | 5 +
> net/mac80211/debugfs_sta.c | 5 +-
> net/mac80211/ieee80211_i.h | 7 +
> net/mac80211/mesh.c | 37 ++-
> net/mac80211/mesh.h | 35 ++-
> net/mac80211/mesh_hwmp.c | 7 +
> net/mac80211/mesh_pathtbl.c | 1 +
> net/mac80211/mesh_plink.c | 35 ++-
> net/mac80211/mesh_ps.c | 640 +++++++++++++++++++++++++++++++++++++++++
> net/mac80211/rx.c | 7 +
> net/mac80211/sta_info.c | 18 +-
> net/mac80211/sta_info.h | 17 ++
> net/mac80211/status.c | 5 +
> net/mac80211/tx.c | 81 ++++--
> net/mac80211/util.c | 4 +
> net/mac80211/wme.c | 13 +-
> net/wireless/mesh.c | 8 +
> net/wireless/nl80211.c | 137 ++++++---
> 25 files changed, 1137 insertions(+), 92 deletions(-)
> create mode 100644 net/mac80211/mesh_ps.c
>


2013-01-07 15:05:05

by Marco Porsch

[permalink] [raw]
Subject: [PATCHv2 3/6] mac80211: move add_tim to subfunction

This functions will be used for mesh beacons, too.

Signed-off-by: Marco Porsch <[email protected]>
---
net/mac80211/tx.c | 48 +++++++++++++++++++++++++++++-------------------
1 file changed, 29 insertions(+), 19 deletions(-)

diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index e9eadc4..1b04984 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -2261,9 +2261,9 @@ void ieee80211_tx_pending(unsigned long data)

/* functions for drivers to get certain frames */

-static void ieee80211_beacon_add_tim(struct ieee80211_sub_if_data *sdata,
- struct ps_data *ps,
- struct sk_buff *skb)
+static void __ieee80211_beacon_add_tim(struct ieee80211_sub_if_data *sdata,
+ struct ps_data *ps,
+ struct sk_buff *skb)
{
u8 *pos, *tim;
int aid0 = 0;
@@ -2325,6 +2325,31 @@ static void ieee80211_beacon_add_tim(struct ieee80211_sub_if_data *sdata,
}
}

+static int ieee80211_beacon_add_tim(struct ieee80211_sub_if_data *sdata,
+ struct ps_data *ps,
+ struct sk_buff *skb)
+{
+ struct ieee80211_local *local = sdata->local;
+
+ /* Not very nice, but we want to allow the driver to call
+ * ieee80211_beacon_get() as a response to the set_tim() callback.
+ * That, however, is already invoked under the sta_lock to guarantee
+ * consistent and race-free update of the tim bitmap in mac80211 and
+ * the driver.
+ */
+ if (local->tim_in_locked_section) {
+ __ieee80211_beacon_add_tim(sdata, ps, skb);
+ } else {
+ unsigned long flags;
+
+ spin_lock_irqsave(&local->tim_lock, flags);
+ __ieee80211_beacon_add_tim(sdata, ps, skb);
+ spin_unlock_irqrestore(&local->tim_lock, flags);
+ }
+
+ return 0;
+}
+
struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
u16 *tim_offset, u16 *tim_length)
@@ -2369,22 +2394,7 @@ struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw,
memcpy(skb_put(skb, beacon->head_len), beacon->head,
beacon->head_len);

- /*
- * Not very nice, but we want to allow the driver to call
- * ieee80211_beacon_get() as a response to the set_tim()
- * callback. That, however, is already invoked under the
- * sta_lock to guarantee consistent and race-free update
- * of the tim bitmap in mac80211 and the driver.
- */
- if (local->tim_in_locked_section) {
- ieee80211_beacon_add_tim(sdata, &ap->ps, skb);
- } else {
- unsigned long flags;
-
- spin_lock_irqsave(&local->tim_lock, flags);
- ieee80211_beacon_add_tim(sdata, &ap->ps, skb);
- spin_unlock_irqrestore(&local->tim_lock, flags);
- }
+ ieee80211_beacon_add_tim(sdata, &ap->ps, skb);

if (tim_offset)
*tim_offset = beacon->head_len;
--
1.7.9.5


2013-01-07 15:05:10

by Marco Porsch

[permalink] [raw]
Subject: [PATCHv2 6/6] mac80211: mesh power save basics

Add the local_pm field to the sta_info structure to represent
the link-specific power mode at this station towards the remote station.
When a peer link is established, the local link-specific power mode is
set up equal to the user-configurable default power mode.
To avoid frame losses when switching to a lower power state after peering
(and for possible use in a per-link dynamic powersave), the corresponding
function can be called with a delay parameter that sets a per-STA timer
accordingly.
Monitor the status of the local peer-specific power modes for mesh power mode
indication, and for checking if the radio may be set to doze state. Update
the PS status if the peering status of a neighbor changes.
Maintain a mesh power mode for non-peer mesh STAs. The non-peer mesh power
mode determines when non-peer mesh STA may send Probe Requests and Mesh Peering
Open frames to the mesh STA.
Based on the status of the peer-specific power modes also the non-peer power
mode is set: if there is at least one link in light or deep sleep mode, the
non-peer power mode will be deep sleep mode.
To allow successful peering, set the non-peer power mode to active mode during
peering.
Add a debug define for comfortable debugging output.

Indicate the current power mode in transmitted frames. This differs for
individually-addressed frames and group addressed frames as well as whether
the frames are addressed to peers or non-peers.
Especially indicate a peer-specific mesh power mode transition with an
individually-addressed QoS Null frame to the respective peer.
Indicate the non-peer mesh power mode in frames transmitted to non-peer STA
and in management frames in the Power Management field of the Frame Control
field.
In group addressed Mesh QoS Data frames the Mesh Power Save Level field
in the QoS Control field indicates whether the local STA has any deep
sleep peers.
In beacon (and probe response) frames indicate whether we have any deep sleep
peers, by setting the Mesh Power Save Level field of the Mesh Capability field.
For performance reasons, calls to the function setting the frame flags are
placed in HWMP routing routines, as there the STA pointer is already available.

Add the peer_pm field to the sta_info structure to represent the peer's
link-specific power mode towards the local station. Additionally, the
nonpeer_pm field represents the peer's power mode towards all non-peer
stations.
The peers' link-specific power modes are tracked from the Power Management
field in the Frame Control field and the Mesh Power Save Level field
in the QoS Control field. Peer and non-peer modes are tracked independently
from respective frames. This allows fast reconfiguration after a peering
change.

Add the ps_data structure to ieee80211_if_mesh. This structure contains the TIM
map, a counter how of PS neighbors and the group-addressed frame buffer.
Functions using this structure are modified for mesh mode.

In case of a neighbor changing its power mode or peering status, update the
setting of the WLAN_STA_PS flag. This flag triggers the frame buffering
routines for individually-addressed frames in the tx path used for client mode,
which are re-used here. Also the num_sta_ps counter is updated, which triggers
the buffering and release of group-addressed frames after DTIM beacons.

Until drivers are adapted for mesh power save, do not inform the driver about
the neighbor STA being in PS mode towards us (as sta_ps_start would do).

Announce the awake window value in an IE in beacons if the local STA is in
light or deep sleep mode towards any peer or non-peer. On receipt STA parse
the IE.

Create a TIM IE similarly to AP mode and add it to mesh beacons.

React to neighbor's Awake Window and TIM IE. When receiving a neighbor beacon,
check if the neighbor indicates buffered frames and if it announces an awake
window duration.

In case the neighbor is an established peer, frames may be released in a
mesh Peer Service Period. MPSPs start and end with a MPSP trigger frame. The
setting of RSPI and EOSP frame flags determine if a MPSP starts or ends and
which STA is the owner, recipient or both during the MPSP.
The indication of buffered frames at the peer and the status of the local
buffers determine which type of MPSP is started when a peer beacon is
received. The buffered frames are transmitted only after the MPSP trigger frame
has been acknowledged.
To end a MPSP set the EOSP flag in the last QoS Data frame. In case the last
frame is not a QoS data frame, append a QoS Null frame after the last frame.

Count the number of MPSPs the local STA is owner and/or recipient in. This will
be used to disallow doze state during ongoing MPSPs.

Currently, in HT channels MPSPs behave imperfectly and show large delay spikes
and frame losses.

Authenticated mesh peering is currently not working when either node is
configured to be in power save mode.

Signed-off-by: Marco Porsch <[email protected]>
Signed-off-by: Ivan Bezyazychnyy <[email protected]>
Signed-off-by: Mike Krinkin <[email protected]>
Signed-off-by: Max Filippov <[email protected]>
---
include/linux/ieee80211.h | 17 ++
net/mac80211/Kconfig | 11 +
net/mac80211/Makefile | 3 +-
net/mac80211/cfg.c | 29 +-
net/mac80211/debug.h | 10 +
net/mac80211/debugfs_netdev.c | 5 +
net/mac80211/debugfs_sta.c | 5 +-
net/mac80211/ieee80211_i.h | 7 +
net/mac80211/mesh.c | 36 +++
net/mac80211/mesh.h | 19 ++
net/mac80211/mesh_hwmp.c | 7 +
net/mac80211/mesh_pathtbl.c | 1 +
net/mac80211/mesh_plink.c | 21 ++
net/mac80211/mesh_ps.c | 640 +++++++++++++++++++++++++++++++++++++++++
net/mac80211/rx.c | 7 +
net/mac80211/sta_info.c | 18 +-
net/mac80211/sta_info.h | 17 ++
net/mac80211/status.c | 5 +
net/mac80211/tx.c | 33 ++-
net/mac80211/util.c | 4 +
net/mac80211/wme.c | 13 +-
21 files changed, 891 insertions(+), 17 deletions(-)
create mode 100644 net/mac80211/mesh_ps.c

diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h
index f0859cc..b2551c1 100644
--- a/include/linux/ieee80211.h
+++ b/include/linux/ieee80211.h
@@ -151,6 +151,11 @@
/* Mesh Control 802.11s */
#define IEEE80211_QOS_CTL_MESH_CONTROL_PRESENT 0x0100

+/* Mesh Power Save Level */
+#define IEEE80211_QOS_CTL_MESH_PS_LEVEL 0x0200
+/* Mesh Receiver Service Period Initiated */
+#define IEEE80211_QOS_CTL_RSPI 0x0400
+
/* U-APSD queue for WMM IEs sent by AP */
#define IEEE80211_WMM_IE_AP_QOSINFO_UAPSD (1<<7)
#define IEEE80211_WMM_IE_AP_QOSINFO_PARAM_SET_CNT_MASK 0x0f
@@ -570,6 +575,15 @@ static inline int ieee80211_is_first_frag(__le16 seq_ctrl)
return (seq_ctrl & cpu_to_le16(IEEE80211_SCTL_FRAG)) == 0;
}

+/**
+ * ieee80211_has_qos_pm - check Power Save Level in QoS control
+ * @qc - QoS control bytes in little-endian byteorder
+ */
+static inline bool ieee80211_has_qos_mesh_ps(__le16 qc)
+{
+ return (qc & cpu_to_le16(IEEE80211_QOS_CTL_MESH_PS_LEVEL)) != 0;
+}
+
struct ieee80211s_hdr {
u8 flags;
u8 ttl;
@@ -675,11 +689,14 @@ struct ieee80211_meshconf_ie {
* @IEEE80211_MESHCONF_CAPAB_FORWARDING: the STA forwards MSDUs
* @IEEE80211_MESHCONF_CAPAB_TBTT_ADJUSTING: TBTT adjustment procedure
* is ongoing
+ * @IEEE80211_MESHCONF_CAPAB_POWER_SAVE_LEVEL: STA is in deep sleep mode or has
+ * neighbors in deep sleep mode
*/
enum mesh_config_capab_flags {
IEEE80211_MESHCONF_CAPAB_ACCEPT_PLINKS = 0x01,
IEEE80211_MESHCONF_CAPAB_FORWARDING = 0x08,
IEEE80211_MESHCONF_CAPAB_TBTT_ADJUSTING = 0x20,
+ IEEE80211_MESHCONF_CAPAB_POWER_SAVE_LEVEL = 0x40,
};

/**
diff --git a/net/mac80211/Kconfig b/net/mac80211/Kconfig
index b4ecf26..0ecf947 100644
--- a/net/mac80211/Kconfig
+++ b/net/mac80211/Kconfig
@@ -258,6 +258,17 @@ config MAC80211_MESH_SYNC_DEBUG

Do not select this option.

+config MAC80211_MESH_PS_DEBUG
+ bool "Verbose mesh powersave debugging"
+ depends on MAC80211_DEBUG_MENU
+ depends on MAC80211_MESH
+ ---help---
+ Selecting this option causes mac80211 to print out very verbose mesh
+ powersave debugging messages (when mac80211 is taking part in a
+ mesh network).
+
+ Do not select this option.
+
config MAC80211_TDLS_DEBUG
bool "Verbose TDLS debugging"
depends on MAC80211_DEBUG_MENU
diff --git a/net/mac80211/Makefile b/net/mac80211/Makefile
index 4911202..9d7d840 100644
--- a/net/mac80211/Makefile
+++ b/net/mac80211/Makefile
@@ -39,7 +39,8 @@ mac80211-$(CONFIG_MAC80211_MESH) += \
mesh_pathtbl.o \
mesh_plink.o \
mesh_hwmp.o \
- mesh_sync.o
+ mesh_sync.o \
+ mesh_ps.o

mac80211-$(CONFIG_PM) += pm.o

diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index abfd5d3..09c3d2a 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -482,7 +482,10 @@ static void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo)
#ifdef CONFIG_MAC80211_MESH
sinfo->filled |= STATION_INFO_LLID |
STATION_INFO_PLID |
- STATION_INFO_PLINK_STATE;
+ STATION_INFO_PLINK_STATE |
+ STATION_INFO_LOCAL_PM |
+ STATION_INFO_PEER_PM |
+ STATION_INFO_NONPEER_PM;

sinfo->llid = le16_to_cpu(sta->llid);
sinfo->plid = le16_to_cpu(sta->plid);
@@ -491,6 +494,9 @@ static void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo)
sinfo->filled |= STATION_INFO_T_OFFSET;
sinfo->t_offset = sta->t_offset;
}
+ sinfo->local_pm = sta->local_pm;
+ sinfo->peer_pm = sta->peer_pm;
+ sinfo->nonpeer_pm = sta->nonpeer_pm;
#endif
}

@@ -1220,6 +1226,11 @@ static int sta_apply_parameters(struct ieee80211_local *local,
changed = mesh_plink_inc_estab_count(
sdata);
sta->plink_state = params->plink_state;
+
+ ieee80211_mps_sta_status_update(sta);
+ ieee80211_mps_set_sta_local_pm(sta,
+ sdata->u.mesh.mshcfg.power_mode,
+ 100);
break;
case NL80211_PLINK_LISTEN:
case NL80211_PLINK_BLOCKED:
@@ -1231,6 +1242,9 @@ static int sta_apply_parameters(struct ieee80211_local *local,
changed = mesh_plink_dec_estab_count(
sdata);
sta->plink_state = params->plink_state;
+
+ ieee80211_mps_sta_status_update(sta);
+ ieee80211_mps_local_status_update(sdata);
break;
default:
/* nothing */
@@ -1247,6 +1261,10 @@ static int sta_apply_parameters(struct ieee80211_local *local,
break;
}
}
+
+ if (params->local_pm)
+ ieee80211_mps_set_sta_local_pm(sta, params->local_pm,
+ 0);
#endif
}

@@ -1732,6 +1750,15 @@ static int ieee80211_update_mesh_config(struct wiphy *wiphy,
if (_chg_mesh_attr(NL80211_MESHCONF_HWMP_CONFIRMATION_INTERVAL, mask))
conf->dot11MeshHWMPconfirmationInterval =
nconf->dot11MeshHWMPconfirmationInterval;
+ if (_chg_mesh_attr(NL80211_MESHCONF_POWER_MODE, mask)) {
+ conf->power_mode = nconf->power_mode;
+ ieee80211_mps_local_status_update(sdata);
+ }
+ if (_chg_mesh_attr(NL80211_MESHCONF_AWAKE_WINDOW, mask)) {
+ conf->dot11MeshAwakeWindowDuration =
+ nconf->dot11MeshAwakeWindowDuration;
+ ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BEACON);
+ }
return 0;
}

diff --git a/net/mac80211/debug.h b/net/mac80211/debug.h
index 8f383a5..4ccc5ed 100644
--- a/net/mac80211/debug.h
+++ b/net/mac80211/debug.h
@@ -44,6 +44,12 @@
#define MAC80211_MESH_SYNC_DEBUG 0
#endif

+#ifdef CONFIG_MAC80211_MESH_PS_DEBUG
+#define MAC80211_MESH_PS_DEBUG 1
+#else
+#define MAC80211_MESH_PS_DEBUG 0
+#endif
+
#ifdef CONFIG_MAC80211_TDLS_DEBUG
#define MAC80211_TDLS_DEBUG 1
#else
@@ -151,6 +157,10 @@ do { \
_sdata_dbg(MAC80211_MESH_SYNC_DEBUG, \
sdata, fmt, ##__VA_ARGS__)

+#define mps_dbg(sdata, fmt, ...) \
+ _sdata_dbg(MAC80211_MESH_PS_DEBUG, \
+ sdata, fmt, ##__VA_ARGS__)
+
#define tdls_dbg(sdata, fmt, ...) \
_sdata_dbg(MAC80211_TDLS_DEBUG, \
sdata, fmt, ##__VA_ARGS__)
diff --git a/net/mac80211/debugfs_netdev.c b/net/mac80211/debugfs_netdev.c
index cbde5cc..059bbb8 100644
--- a/net/mac80211/debugfs_netdev.c
+++ b/net/mac80211/debugfs_netdev.c
@@ -515,6 +515,9 @@ IEEE80211_IF_FILE(dot11MeshHWMProotInterval,
u.mesh.mshcfg.dot11MeshHWMProotInterval, DEC);
IEEE80211_IF_FILE(dot11MeshHWMPconfirmationInterval,
u.mesh.mshcfg.dot11MeshHWMPconfirmationInterval, DEC);
+IEEE80211_IF_FILE(power_mode, u.mesh.mshcfg.power_mode, DEC);
+IEEE80211_IF_FILE(dot11MeshAwakeWindowDuration,
+ u.mesh.mshcfg.dot11MeshAwakeWindowDuration, DEC);
#endif

#define DEBUGFS_ADD_MODE(name, mode) \
@@ -620,6 +623,8 @@ static void add_mesh_config(struct ieee80211_sub_if_data *sdata)
MESHPARAMS_ADD(dot11MeshHWMPactivePathToRootTimeout);
MESHPARAMS_ADD(dot11MeshHWMProotInterval);
MESHPARAMS_ADD(dot11MeshHWMPconfirmationInterval);
+ MESHPARAMS_ADD(power_mode);
+ MESHPARAMS_ADD(dot11MeshAwakeWindowDuration);
#undef MESHPARAMS_ADD
}
#endif
diff --git a/net/mac80211/debugfs_sta.c b/net/mac80211/debugfs_sta.c
index 6fb1168..c7591f7 100644
--- a/net/mac80211/debugfs_sta.c
+++ b/net/mac80211/debugfs_sta.c
@@ -65,7 +65,7 @@ static ssize_t sta_flags_read(struct file *file, char __user *userbuf,
test_sta_flag(sta, WLAN_STA_##flg) ? #flg "\n" : ""

int res = scnprintf(buf, sizeof(buf),
- "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
+ "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
TEST(AUTH), TEST(ASSOC), TEST(PS_STA),
TEST(PS_DRIVER), TEST(AUTHORIZED),
TEST(SHORT_PREAMBLE),
@@ -74,7 +74,8 @@ static ssize_t sta_flags_read(struct file *file, char __user *userbuf,
TEST(UAPSD), TEST(SP), TEST(TDLS_PEER),
TEST(TDLS_PEER_AUTH), TEST(4ADDR_EVENT),
TEST(INSERTED), TEST(RATE_CONTROL),
- TEST(TOFFSET_KNOWN));
+ TEST(TOFFSET_KNOWN), TEST(MPSP_OWNER),
+ TEST(MPSP_RECIPIENT));
#undef TEST
return simple_read_from_buffer(userbuf, count, ppos, buf, res);
}
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 8563b9a..d40c39c 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -621,6 +621,12 @@ struct ieee80211_if_mesh {
s64 sync_offset_clockdrift_max;
spinlock_t sync_offset_lock;
bool adjusting_tbtt;
+ /* mesh power save */
+ enum nl80211_mesh_power_mode nonpeer_pm;
+ int ps_peers_light_sleep;
+ int ps_peers_deep_sleep;
+ struct ps_data ps;
+ atomic_t num_mpsp; /* counts both owner and recipient independently */
};

#ifdef CONFIG_MAC80211_MESH
@@ -1208,6 +1214,7 @@ struct ieee802_11_elems {
struct ieee80211_meshconf_ie *mesh_config;
u8 *mesh_id;
u8 *peering;
+ u8 *awake_window;
u8 *preq;
u8 *prep;
u8 *perr;
diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c
index 35013fa..59b7704 100644
--- a/net/mac80211/mesh.c
+++ b/net/mac80211/mesh.c
@@ -266,6 +266,9 @@ mesh_add_meshconf_ie(struct sk_buff *skb, struct ieee80211_sub_if_data *sdata)
*pos = IEEE80211_MESHCONF_CAPAB_FORWARDING;
*pos |= ifmsh->accepting_plinks ?
IEEE80211_MESHCONF_CAPAB_ACCEPT_PLINKS : 0x00;
+ /* Mesh PS mode. See IEEE802.11-2012 8.4.2.100.8 */
+ *pos |= ifmsh->ps_peers_deep_sleep ?
+ IEEE80211_MESHCONF_CAPAB_POWER_SAVE_LEVEL : 0x00;
*pos++ |= ifmsh->adjusting_tbtt ?
IEEE80211_MESHCONF_CAPAB_TBTT_ADJUSTING : 0x00;
*pos++ = 0x00;
@@ -291,6 +294,29 @@ mesh_add_meshid_ie(struct sk_buff *skb, struct ieee80211_sub_if_data *sdata)
return 0;
}

+int mesh_add_awake_window_ie(struct sk_buff *skb,
+ struct ieee80211_sub_if_data *sdata)
+{
+ struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
+ u8 *pos;
+
+ /* see IEEE802.11-2012 13.14.6 */
+ if (ifmsh->ps_peers_light_sleep == 0 &&
+ ifmsh->ps_peers_deep_sleep == 0 &&
+ ifmsh->nonpeer_pm == NL80211_MESH_POWER_ACTIVE)
+ return 0;
+
+ if (skb_tailroom(skb) < 4)
+ return -ENOMEM;
+
+ pos = skb_put(skb, 2 + 2);
+ *pos++ = WLAN_EID_MESH_AWAKE_WINDOW;
+ *pos++ = 2;
+ put_unaligned_le16(ifmsh->mshcfg.dot11MeshAwakeWindowDuration, pos);
+
+ return 0;
+}
+
int
mesh_add_vendor_ies(struct sk_buff *skb, struct ieee80211_sub_if_data *sdata)
{
@@ -627,6 +653,8 @@ void ieee80211_start_mesh(struct ieee80211_sub_if_data *sdata)
sdata->vif.bss_conf.basic_rates =
ieee80211_mandatory_rates(sdata->local,
ieee80211_get_sdata_band(sdata));
+ ieee80211_mps_local_status_update(sdata);
+
ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BEACON |
BSS_CHANGED_BEACON_ENABLED |
BSS_CHANGED_HT |
@@ -651,6 +679,13 @@ void ieee80211_stop_mesh(struct ieee80211_sub_if_data *sdata)
sta_info_flush(sdata->local, sdata);
mesh_path_flush_by_iface(sdata);

+ atomic_set(&ifmsh->ps.num_sta_ps, 0); /* not reset by sta_info_flush */
+ atomic_set(&ifmsh->num_mpsp, 0);
+
+ /* free all potentially still buffered group-addressed frames */
+ local->total_ps_buffered -= skb_queue_len(&ifmsh->ps.bc_buf);
+ skb_queue_purge(&ifmsh->ps.bc_buf);
+
del_timer_sync(&sdata->u.mesh.housekeeping_timer);
del_timer_sync(&sdata->u.mesh.mesh_path_root_timer);
del_timer_sync(&sdata->u.mesh.mesh_path_timer);
@@ -829,4 +864,5 @@ void ieee80211_mesh_init_sdata(struct ieee80211_sub_if_data *sdata)
INIT_LIST_HEAD(&ifmsh->preq_queue.list);
spin_lock_init(&ifmsh->mesh_preq_queue_lock);
spin_lock_init(&ifmsh->sync_offset_lock);
+ skb_queue_head_init(&ifmsh->ps.bc_buf);
}
diff --git a/net/mac80211/mesh.h b/net/mac80211/mesh.h
index aff3015..8e83e68 100644
--- a/net/mac80211/mesh.h
+++ b/net/mac80211/mesh.h
@@ -222,6 +222,8 @@ int mesh_add_meshid_ie(struct sk_buff *skb,
struct ieee80211_sub_if_data *sdata);
int mesh_add_rsn_ie(struct sk_buff *skb,
struct ieee80211_sub_if_data *sdata);
+int mesh_add_awake_window_ie(struct sk_buff *skb,
+ struct ieee80211_sub_if_data *sdata);
int mesh_add_vendor_ies(struct sk_buff *skb,
struct ieee80211_sub_if_data *sdata);
int mesh_add_ds_params_ie(struct sk_buff *skb,
@@ -242,6 +244,23 @@ void ieee80211_stop_mesh(struct ieee80211_sub_if_data *sdata);
void ieee80211_mesh_root_setup(struct ieee80211_if_mesh *ifmsh);
const struct ieee80211_mesh_sync_ops *ieee80211_mesh_sync_ops_get(u8 method);

+/* mesh power save */
+void ieee80211_mps_local_status_update(struct ieee80211_sub_if_data *sdata);
+void ieee80211_mps_set_sta_local_pm(struct sta_info *sta,
+ enum nl80211_mesh_power_mode pm,
+ u32 delay);
+void ieee80211_mps_sta_local_pm_timer(unsigned long data);
+void ieee80211_mps_set_frame_flags(struct ieee80211_sub_if_data *sdata,
+ struct sta_info *sta,
+ struct ieee80211_hdr *hdr);
+void ieee80211_mps_sta_status_update(struct sta_info *sta);
+void ieee80211_mps_rx_h_sta_process(struct sta_info *sta,
+ struct ieee80211_hdr *hdr);
+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);
+
/* Mesh paths */
int mesh_nexthop_lookup(struct sk_buff *skb,
struct ieee80211_sub_if_data *sdata);
diff --git a/net/mac80211/mesh_hwmp.c b/net/mac80211/mesh_hwmp.c
index 47aeee2..8c6bc11 100644
--- a/net/mac80211/mesh_hwmp.c
+++ b/net/mac80211/mesh_hwmp.c
@@ -205,6 +205,7 @@ static void prepare_frame_for_deferred_tx(struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb)
{
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;

skb_set_mac_header(skb, 0);
skb_set_network_header(skb, 0);
@@ -216,6 +217,7 @@ static void prepare_frame_for_deferred_tx(struct ieee80211_sub_if_data *sdata,

info->control.vif = &sdata->vif;
ieee80211_set_qos_hdr(sdata, skb);
+ ieee80211_mps_set_frame_flags(sdata, NULL, hdr);
}

/**
@@ -1074,6 +1076,10 @@ int mesh_nexthop_resolve(struct sk_buff *skb,
u8 *target_addr = hdr->addr3;
int err = 0;

+ /* Nulls are only sent to peers for PS and should be pre-addressed */
+ if (ieee80211_is_qos_nullfunc(hdr->frame_control))
+ return 0;
+
rcu_read_lock();
err = mesh_nexthop_lookup(skb, sdata);
if (!err)
@@ -1145,6 +1151,7 @@ int mesh_nexthop_lookup(struct sk_buff *skb,
if (next_hop) {
memcpy(hdr->addr1, next_hop->sta.addr, ETH_ALEN);
memcpy(hdr->addr2, sdata->vif.addr, ETH_ALEN);
+ ieee80211_mps_set_frame_flags(sdata, next_hop, hdr);
err = 0;
}

diff --git a/net/mac80211/mesh_pathtbl.c b/net/mac80211/mesh_pathtbl.c
index aa74981..d5786c3 100644
--- a/net/mac80211/mesh_pathtbl.c
+++ b/net/mac80211/mesh_pathtbl.c
@@ -212,6 +212,7 @@ void mesh_path_assign_nexthop(struct mesh_path *mpath, struct sta_info *sta)
hdr = (struct ieee80211_hdr *) skb->data;
memcpy(hdr->addr1, sta->sta.addr, ETH_ALEN);
memcpy(hdr->addr2, mpath->sdata->vif.addr, ETH_ALEN);
+ ieee80211_mps_set_frame_flags(sta->sdata, sta, hdr);
}

spin_unlock_irqrestore(&mpath->frame_queue.lock, flags);
diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c
index 9e04166..72d2e78 100644
--- a/net/mac80211/mesh_plink.c
+++ b/net/mac80211/mesh_plink.c
@@ -165,6 +165,9 @@ static u32 __mesh_plink_deactivate(struct sta_info *sta)
sta->plink_state = NL80211_PLINK_BLOCKED;
mesh_path_flush_by_nexthop(sta);

+ ieee80211_mps_sta_status_update(sta);
+ ieee80211_mps_local_status_update(sdata);
+
return changed;
}

@@ -405,6 +408,7 @@ void mesh_neighbour_update(struct ieee80211_sub_if_data *sdata,
rssi_threshold_check(sta, sdata))
mesh_plink_open(sta);

+ ieee80211_mps_frame_release(sta, elems);
out:
rcu_read_unlock();
}
@@ -535,6 +539,9 @@ int mesh_plink_open(struct sta_info *sta)
"Mesh plink: starting establishment with %pM\n",
sta->sta.addr);

+ /* set the non-peer mode to active during peering */
+ ieee80211_mps_local_status_update(sdata);
+
return mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_OPEN,
sta->sta.addr, llid, 0, 0);
}
@@ -766,6 +773,10 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m
sta->llid = llid;
mesh_plink_timer_set(sta,
mshcfg->dot11MeshRetryTimeout);
+
+ /* set the non-peer mode to active during peering */
+ ieee80211_mps_local_status_update(sdata);
+
spin_unlock_bh(&sta->lock);
mesh_plink_frame_tx(sdata,
WLAN_SP_MESH_PEERING_OPEN,
@@ -858,6 +869,10 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m
changed |= mesh_set_ht_prot_mode(sdata);
mpl_dbg(sdata, "Mesh plink with %pM ESTABLISHED\n",
sta->sta.addr);
+
+ ieee80211_mps_sta_status_update(sta);
+ ieee80211_mps_set_sta_local_pm(sta, mshcfg->power_mode,
+ 100);
break;
default:
spin_unlock_bh(&sta->lock);
@@ -896,6 +911,12 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m
mesh_plink_frame_tx(sdata,
WLAN_SP_MESH_PEERING_CONFIRM,
sta->sta.addr, llid, plid, 0);
+ ieee80211_mps_sta_status_update(sta);
+ /* we need some delay here, otherwise the announcement
+ * Null would arrive before the CONFIRM
+ */
+ ieee80211_mps_set_sta_local_pm(sta, mshcfg->power_mode,
+ 100);
break;
default:
spin_unlock_bh(&sta->lock);
diff --git a/net/mac80211/mesh_ps.c b/net/mac80211/mesh_ps.c
new file mode 100644
index 0000000..839a54d
--- /dev/null
+++ b/net/mac80211/mesh_ps.c
@@ -0,0 +1,640 @@
+/*
+ * Copyright 2012, Marco Porsch <[email protected]>
+ * Copyright 2012, cozybit Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include "mesh.h"
+#include "wme.h"
+
+
+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)) {
+ atomic_inc(&sta->sdata->u.mesh.num_mpsp);
+ return false;
+ }
+ return true;
+}
+
+static inline bool test_and_clear_mpsp_flag(struct sta_info *sta,
+ enum ieee80211_sta_info_flags flag)
+{
+ if (test_and_clear_sta_flag(sta, flag)) {
+ atomic_dec(&sta->sdata->u.mesh.num_mpsp);
+ return true;
+ }
+ return false;
+}
+
+
+/* mesh PS management */
+
+/**
+ * mps_null_get - create pre-addressed QoS Null frame for mesh powersave
+ *
+ * Returns the created sk_buff
+ *
+ * @sta: mesh STA
+ */
+static struct sk_buff *mps_qos_null_get(struct sta_info *sta)
+{
+ struct ieee80211_sub_if_data *sdata = sta->sdata;
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_hdr *nullfunc; /* use 4addr header */
+ struct sk_buff *skb;
+ int size = sizeof(*nullfunc);
+ __le16 fc;
+
+ skb = dev_alloc_skb(local->hw.extra_tx_headroom + size + 2);
+ if (!skb)
+ return NULL;
+ skb_reserve(skb, local->hw.extra_tx_headroom);
+
+ nullfunc = (struct ieee80211_hdr *) skb_put(skb, size);
+ fc = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_STYPE_QOS_NULLFUNC);
+ ieee80211_fill_mesh_addresses(nullfunc, &fc, sta->sta.addr,
+ sdata->vif.addr);
+ nullfunc->frame_control = fc;
+ nullfunc->duration_id = 0;
+ /* no address resolution for this frame -> set addr 1 immediately */
+ memcpy(nullfunc->addr1, sta->sta.addr, ETH_ALEN);
+ memset(skb_put(skb, 2), 0, 2); /* append QoS control field */
+ ieee80211_mps_set_frame_flags(sdata, sta, nullfunc);
+
+ return skb;
+}
+
+/**
+ * mps_null_tx - send a QoS Null to peer to indicate link-specific power mode
+ *
+ * @sta: mesh STA to inform
+ */
+static void mps_qos_null_tx(struct sta_info *sta)
+{
+ struct sk_buff *skb;
+
+ skb = mps_qos_null_get(sta);
+ if (!skb)
+ return;
+
+ mps_dbg(sta->sdata, "announcing peer-specific power mode to %pM\n",
+ sta->sta.addr);
+
+ /* don't unintentionally start a MPSP */
+ if (!test_sta_flag(sta, WLAN_STA_PS_STA)) {
+ __le16 *qc = (__le16 *) ieee80211_get_qos_ctl(
+ (struct ieee80211_hdr *) skb->data);
+
+ *qc |= cpu_to_le16(IEEE80211_QOS_CTL_EOSP);
+ }
+
+ ieee80211_tx_skb(sta->sdata, skb);
+}
+
+/**
+ * ieee80211_mps_local_status_update - track status of local link-specific PMs
+ *
+ * @sdata: local mesh subif
+ *
+ * sets the non-peer power mode and triggers the driver PS (re-)configuration
+ * called by cfg80211, on peer link changes and by a timer for delayed setting
+ */
+void ieee80211_mps_local_status_update(struct ieee80211_sub_if_data *sdata)
+{
+ struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
+ struct sta_info *sta;
+ bool peering = false;
+ int light_sleep_cnt = 0;
+ int deep_sleep_cnt = 0;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(sta, &sdata->local->sta_list, list) {
+ if (sdata != sta->sdata)
+ continue;
+
+ switch (sta->plink_state) {
+ case NL80211_PLINK_OPN_SNT:
+ case NL80211_PLINK_OPN_RCVD:
+ case NL80211_PLINK_CNF_RCVD:
+ peering = true;
+ break;
+ case NL80211_PLINK_ESTAB:
+ if (sta->local_pm == NL80211_MESH_POWER_LIGHT_SLEEP)
+ light_sleep_cnt++;
+ else if (sta->local_pm == NL80211_MESH_POWER_DEEP_SLEEP)
+ deep_sleep_cnt++;
+ break;
+ default:
+ break;
+ }
+ }
+ rcu_read_unlock();
+
+ /* set non-peer mode to active during peering/scanning/authentication
+ * (see IEEE802.11-2012 13.14.8.3)
+ * the non-peer mesh power mode is deep sleep if the local STA is in
+ * light or deep sleep towards at least one mesh peer (see 13.14.3.1)
+ * otherwise set it to the user-configured default value
+ */
+ if (peering) {
+ mps_dbg(sdata, "setting non-peer PM to active for peering\n");
+ ifmsh->nonpeer_pm = NL80211_MESH_POWER_ACTIVE;
+ } else if (light_sleep_cnt || deep_sleep_cnt) {
+ mps_dbg(sdata, "setting non-peer PM to deep sleep\n");
+ ifmsh->nonpeer_pm = NL80211_MESH_POWER_DEEP_SLEEP;
+ } else {
+ mps_dbg(sdata, "setting non-peer PM to user value\n");
+ ifmsh->nonpeer_pm = ifmsh->mshcfg.power_mode;
+ }
+
+ ifmsh->ps_peers_light_sleep = light_sleep_cnt;
+ ifmsh->ps_peers_deep_sleep = deep_sleep_cnt;
+}
+
+/**
+ * ieee80211_mps_set_sta_local_pm - set local PM towards a mesh STA
+ *
+ * @sta: mesh STA
+ * @pm: the power mode to set
+ * @delay: delay in msecs for committing and announcing the new value
+ *
+ * called by cfg80211 and on peer link establishment
+ */
+void ieee80211_mps_set_sta_local_pm(struct sta_info *sta,
+ enum nl80211_mesh_power_mode pm,
+ u32 delay)
+{
+ struct ieee80211_sub_if_data *sdata = sta->sdata;
+
+ if (delay) {
+ /* after peering/authentication/scanning it is useful to delay
+ * the transition to a lower power mode to avoid frame losses
+ * also intended for per-link dynamic powersave
+ */
+ sta->local_pm_delayed = pm;
+ mod_timer(&sta->local_pm_timer,
+ jiffies + msecs_to_jiffies(delay));
+ return;
+ }
+
+ mps_dbg(sdata, "local STA operates in mode %d with %pM\n",
+ pm, sta->sta.addr);
+
+ sta->local_pm = pm;
+
+ /* announce peer-specific power mode transition
+ * see IEEE802.11-2012 13.14.3.2 and 13.14.3.3
+ */
+ if (sta->plink_state == NL80211_PLINK_ESTAB)
+ mps_qos_null_tx(sta);
+
+ ieee80211_mps_local_status_update(sdata);
+}
+
+void ieee80211_mps_sta_local_pm_timer(unsigned long data)
+{
+ /* This STA is valid because free_sta_work() will
+ * del_timer_sync() this timer after having made sure
+ * it cannot be armed (by deleting the plink.)
+ */
+ struct sta_info *sta = (struct sta_info *) data;
+
+ ieee80211_mps_set_sta_local_pm(sta, sta->local_pm_delayed, 0);
+}
+
+/**
+ * ieee80211_mps_set_frame_flags - set mesh PS flags in FC (and QoS Control)
+ *
+ * @sdata: local mesh subif
+ * @sta: mesh STA
+ * @hdr: 802.11 frame header
+ *
+ * see IEEE802.11-2012 8.2.4.1.7 and 8.2.4.5.11
+ *
+ * NOTE: sta must be given when an individually-addressed QoS frame header
+ * is handled, for group-addressed and management frames it is not used
+ */
+void ieee80211_mps_set_frame_flags(struct ieee80211_sub_if_data *sdata,
+ struct sta_info *sta,
+ struct ieee80211_hdr *hdr)
+{
+ enum nl80211_mesh_power_mode pm;
+ __le16 *qc;
+
+ if (WARN_ON(is_unicast_ether_addr(hdr->addr1) &&
+ ieee80211_is_data_qos(hdr->frame_control) &&
+ !sta))
+ return;
+
+ if (is_unicast_ether_addr(hdr->addr1) &&
+ ieee80211_is_data_qos(hdr->frame_control) &&
+ sta->plink_state == NL80211_PLINK_ESTAB)
+ pm = sta->local_pm;
+ else
+ pm = sdata->u.mesh.nonpeer_pm;
+
+ if (pm == NL80211_MESH_POWER_ACTIVE)
+ hdr->frame_control &= cpu_to_le16(~IEEE80211_FCTL_PM);
+ else
+ hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_PM);
+
+ if (!ieee80211_is_data_qos(hdr->frame_control))
+ return;
+
+ qc = (__le16 *) ieee80211_get_qos_ctl(hdr);
+
+ if ((is_unicast_ether_addr(hdr->addr1) &&
+ pm == NL80211_MESH_POWER_DEEP_SLEEP) ||
+ (is_multicast_ether_addr(hdr->addr1) &&
+ sdata->u.mesh.ps_peers_deep_sleep > 0))
+ *qc |= cpu_to_le16(IEEE80211_QOS_CTL_MESH_PS_LEVEL);
+ else
+ *qc &= cpu_to_le16(~IEEE80211_QOS_CTL_MESH_PS_LEVEL);
+}
+
+/**
+ * ieee80211_mps_sta_status_update - update buffering status of neighbor STA
+ *
+ * @sta: mesh STA
+ *
+ * called after change of peering status or non-peer/peer-specific power mode
+ */
+void ieee80211_mps_sta_status_update(struct sta_info *sta)
+{
+ enum nl80211_mesh_power_mode pm;
+ bool do_buffer;
+
+ /* use peer-specific power mode if peering is established and
+ * the peer's power mode is known
+ */
+ if (sta->plink_state == NL80211_PLINK_ESTAB &&
+ sta->peer_pm != NL80211_MESH_POWER_UNKNOWN)
+ pm = sta->peer_pm;
+ else
+ pm = sta->nonpeer_pm;
+
+ do_buffer = (pm != NL80211_MESH_POWER_ACTIVE);
+
+ /* Don't let the same PS state be set twice */
+ if (test_sta_flag(sta, WLAN_STA_PS_STA) == do_buffer)
+ return;
+
+ if (do_buffer) {
+ set_sta_flag(sta, WLAN_STA_PS_STA);
+ atomic_inc(&sta->sdata->u.mesh.ps.num_sta_ps);
+ mps_dbg(sta->sdata, "start PS buffering frames towards %pM\n",
+ sta->sta.addr);
+ } else {
+ ieee80211_sta_ps_deliver_wakeup(sta);
+ }
+
+ /* clear the MPSP flags for non-peers or active STA */
+ if (sta->plink_state != NL80211_PLINK_ESTAB) {
+ test_and_clear_mpsp_flag(sta, WLAN_STA_MPSP_OWNER);
+ test_and_clear_mpsp_flag(sta, WLAN_STA_MPSP_RECIPIENT);
+ } else if (!do_buffer) {
+ test_and_clear_mpsp_flag(sta, WLAN_STA_MPSP_OWNER);
+ }
+}
+
+static void mps_set_sta_peer_pm(struct sta_info *sta,
+ struct ieee80211_hdr *hdr)
+{
+ enum nl80211_mesh_power_mode pm;
+ __le16 *qc = (__le16 *) ieee80211_get_qos_ctl(hdr);
+
+ /* Test Power Managment field of frame control (PW) and
+ * mesh power save level subfield of QoS control field (PSL)
+ *
+ * | PM | PSL| Mesh PM |
+ * +----+----+---------+
+ * | 0 |Rsrv| Active |
+ * | 1 | 0 | Light |
+ * | 1 | 1 | Deep |
+ */
+ if (ieee80211_has_pm(hdr->frame_control)) {
+ if (ieee80211_has_qos_mesh_ps(*qc))
+ pm = NL80211_MESH_POWER_DEEP_SLEEP;
+ else
+ pm = NL80211_MESH_POWER_LIGHT_SLEEP;
+ } else {
+ pm = NL80211_MESH_POWER_ACTIVE;
+ }
+
+ if (sta->peer_pm == pm)
+ return;
+
+ mps_dbg(sta->sdata, "STA %pM enters mode %d\n",
+ sta->sta.addr, pm);
+
+ sta->peer_pm = pm;
+
+ ieee80211_mps_sta_status_update(sta);
+}
+
+static void mps_set_sta_nonpeer_pm(struct sta_info *sta,
+ struct ieee80211_hdr *hdr)
+{
+ enum nl80211_mesh_power_mode pm;
+
+ if (ieee80211_has_pm(hdr->frame_control))
+ pm = NL80211_MESH_POWER_DEEP_SLEEP;
+ else
+ pm = NL80211_MESH_POWER_ACTIVE;
+
+ if (sta->nonpeer_pm == pm)
+ return;
+
+ mps_dbg(sta->sdata, "STA %pM sets non-peer mode to %d\n",
+ sta->sta.addr, pm);
+
+ sta->nonpeer_pm = pm;
+
+ ieee80211_mps_sta_status_update(sta);
+}
+
+/**
+ * ieee80211_mps_rx_h_sta_process - frame receive handler for mesh powersave
+ *
+ * @sta: STA info that transmitted the frame
+ * @hdr: IEEE 802.11 (QoS) Header
+ */
+void ieee80211_mps_rx_h_sta_process(struct sta_info *sta,
+ struct ieee80211_hdr *hdr)
+{
+ if (is_unicast_ether_addr(hdr->addr1) &&
+ ieee80211_is_data_qos(hdr->frame_control)) {
+ /* individually addressed QoS Data/Null frames contain
+ * peer link-specific PS mode towards the local STA
+ */
+ mps_set_sta_peer_pm(sta, hdr);
+
+ /* check for mesh Peer Service Period trigger frames */
+ ieee80211_mpsp_trigger_process(hdr, sta, false, false);
+ } else {
+ /* can only determine non-peer PS mode
+ * (see IEEE802.11-2012 8.2.4.1.7)
+ */
+ mps_set_sta_nonpeer_pm(sta, hdr);
+ }
+}
+
+
+/* mesh PS frame release */
+
+static void mpsp_trigger_send(struct sta_info *sta,
+ bool rspi, bool eosp)
+{
+ struct ieee80211_sub_if_data *sdata = sta->sdata;
+ struct sk_buff *skb;
+ struct ieee80211_hdr *nullfunc;
+ struct ieee80211_tx_info *info;
+ __le16 *qc;
+
+ skb = mps_qos_null_get(sta);
+ if (!skb)
+ return;
+
+ nullfunc = (struct ieee80211_hdr *) skb->data;
+ if (!eosp)
+ nullfunc->frame_control |=
+ cpu_to_le16(IEEE80211_FCTL_MOREDATA);
+ /* | RSPI | EOSP | MPSP triggering |
+ * +------+------+--------------------+
+ * | 0 | 0 | local STA is owner |
+ * | 0 | 1 | no MPSP (MPSP end) |
+ * | 1 | 0 | both STA are owner |
+ * | 1 | 1 | peer STA is owner | see IEEE802.11-2012 13.14.9.2
+ */
+ qc = (__le16 *) ieee80211_get_qos_ctl(nullfunc);
+ if (rspi)
+ *qc |= cpu_to_le16(IEEE80211_QOS_CTL_RSPI);
+ if (eosp)
+ *qc |= cpu_to_le16(IEEE80211_QOS_CTL_EOSP);
+
+ info = IEEE80211_SKB_CB(skb);
+
+ info->flags |= IEEE80211_TX_CTL_NO_PS_BUFFER |
+ IEEE80211_TX_CTL_REQ_TX_STATUS;
+
+ mps_dbg(sdata, "sending MPSP trigger%s%s to %pM\n",
+ rspi ? " RSPI" : "", eosp ? " EOSP" : "", sta->sta.addr);
+
+ ieee80211_tx_skb(sdata, skb);
+}
+
+/**
+ * mpsp_qos_null_append - append QoS Null frame to MPSP skb queue if needed
+ *
+ * @sta: peer mesh STA we are sending frames to
+ * @frames: skb queue to append to
+ *
+ * To properly end a mesh MPSP the last transmitted frame has to set the EOSP
+ * flag in the QoS Control field. In case the current tailing frame is not a
+ * QoS Data frame, append a QoS Null to carry the flag.
+ */
+static void mpsp_qos_null_append(struct sta_info *sta,
+ struct sk_buff_head *frames)
+{
+ struct ieee80211_sub_if_data *sdata = sta->sdata;
+ struct sk_buff *new_skb, *skb = skb_peek_tail(frames);
+ struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
+ struct ieee80211_tx_info *info;
+
+ if (ieee80211_is_data_qos(hdr->frame_control))
+ return;
+
+ new_skb = mps_qos_null_get(sta);
+ if (!new_skb)
+ return;
+
+ mps_dbg(sdata, "appending QoS Null in MPSP towards %pM\n",
+ sta->sta.addr);
+
+ /* should be transmitted last -> lowest priority */
+ new_skb->priority = 1;
+ skb_set_queue_mapping(new_skb, IEEE80211_AC_BK);
+ ieee80211_set_qos_hdr(sdata, new_skb);
+
+ info = IEEE80211_SKB_CB(new_skb);
+ info->control.vif = &sdata->vif;
+ info->flags |= IEEE80211_TX_INTFL_NEED_TXPROCESSING;
+
+ skb_queue_tail(frames, new_skb);
+}
+
+/**
+ * mps_frame_deliver - transmit frames during mesh powersave
+ *
+ * @sta: STA info to transmit to
+ * @n_frames: number of frames to transmit. -1 for all
+ */
+static void mps_frame_deliver(struct sta_info *sta, int n_frames)
+{
+ struct ieee80211_sub_if_data *sdata = sta->sdata;
+ struct ieee80211_local *local = sdata->local;
+ int ac;
+ struct sk_buff_head frames;
+ struct sk_buff *skb;
+ bool more_data = false;
+
+ skb_queue_head_init(&frames);
+
+ /* collect frame(s) from buffers */
+ for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
+ struct sk_buff *skb;
+
+ while (n_frames != 0) {
+ skb = skb_dequeue(&sta->tx_filtered[ac]);
+ if (!skb) {
+ skb = skb_dequeue(
+ &sta->ps_tx_buf[ac]);
+ if (skb)
+ local->total_ps_buffered--;
+ }
+ if (!skb)
+ break;
+ n_frames--;
+ skb_queue_tail(&frames, skb);
+ }
+
+ if (!skb_queue_empty(&sta->tx_filtered[ac]) ||
+ !skb_queue_empty(&sta->ps_tx_buf[ac])) {
+ more_data = true;
+ }
+ }
+
+ /* nothing to send? -> EOSP */
+ if (skb_queue_empty(&frames)) {
+ mpsp_trigger_send(sta, false, true);
+ return;
+ }
+
+ /* in a MPSP make sure the last skb is a QoS Data frame */
+ if (test_sta_flag(sta, WLAN_STA_MPSP_OWNER))
+ mpsp_qos_null_append(sta, &frames);
+
+ mps_dbg(sta->sdata, "sending %d frames to PS STA %pM\n",
+ skb_queue_len(&frames), sta->sta.addr);
+
+ /* prepare collected frames for transmission */
+ skb_queue_walk(&frames, skb) {
+ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ struct ieee80211_hdr *hdr = (void *) skb->data;
+
+ /* Tell TX path to send this frame even though the
+ * STA may still remain is PS mode after this frame
+ * exchange.
+ */
+ info->flags |= IEEE80211_TX_CTL_NO_PS_BUFFER;
+
+ if (more_data || !skb_queue_is_last(&frames, skb))
+ hdr->frame_control |=
+ cpu_to_le16(IEEE80211_FCTL_MOREDATA);
+ else
+ hdr->frame_control &=
+ cpu_to_le16(~IEEE80211_FCTL_MOREDATA);
+
+ if (skb_queue_is_last(&frames, skb) &&
+ ieee80211_is_data_qos(hdr->frame_control)) {
+ u8 *qoshdr = ieee80211_get_qos_ctl(hdr);
+
+ /* MPSP trigger frame ends service period */
+ *qoshdr |= IEEE80211_QOS_CTL_EOSP;
+ info->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS;
+ }
+ }
+
+ ieee80211_add_pending_skbs(local, &frames);
+ sta_info_recalc_tim(sta);
+}
+
+/**
+ * ieee80211_mpsp_trigger_process - track status of mesh Peer Service Periods
+ *
+ * @hdr: IEEE 802.11 QoS Header
+ * @sta: peer to start a MPSP with
+ * @tx: frame was transmitted by the local STA
+ * @acked: frame has been transmitted successfully
+ *
+ * NOTE: active mode STA may only serve as MPSP owner
+ */
+void ieee80211_mpsp_trigger_process(struct ieee80211_hdr *hdr,
+ struct sta_info *sta, bool tx, bool acked)
+{
+ __le16 *qc = (__le16 *) ieee80211_get_qos_ctl(hdr);
+ __le16 rspi = *qc & cpu_to_le16(IEEE80211_QOS_CTL_RSPI);
+ __le16 eosp = *qc & cpu_to_le16(IEEE80211_QOS_CTL_EOSP);
+
+ if (tx) {
+ if (rspi && acked)
+ test_and_set_mpsp_flag(sta, WLAN_STA_MPSP_RECIPIENT);
+
+ if (eosp)
+ test_and_clear_mpsp_flag(sta, WLAN_STA_MPSP_OWNER);
+ else if (acked &&
+ test_sta_flag(sta, WLAN_STA_PS_STA) &&
+ !test_and_set_mpsp_flag(sta, WLAN_STA_MPSP_OWNER))
+ mps_frame_deliver(sta, -1);
+ } else {
+ if (eosp)
+ test_and_clear_mpsp_flag(sta, WLAN_STA_MPSP_RECIPIENT);
+ else if (sta->local_pm != NL80211_MESH_POWER_ACTIVE)
+ test_and_set_mpsp_flag(sta, WLAN_STA_MPSP_RECIPIENT);
+
+ if (rspi && !test_and_set_mpsp_flag(sta, WLAN_STA_MPSP_OWNER))
+ mps_frame_deliver(sta, -1);
+ }
+}
+
+/**
+ * ieee80211_mps_frame_release - release buffered frames in response to beacon
+ *
+ * @sta: mesh STA
+ * @elems: beacon IEs
+ *
+ * For peers if we have individually-addressed frames buffered or the peer
+ * indicates buffered frames, send a corresponding MPSP trigger frame. Since
+ * we do not evaluate the awake window duration, QoS Nulls are used as MPSP
+ * trigger frames. If the neighbour STA is not a peer, only send single frames.
+ */
+void ieee80211_mps_frame_release(struct sta_info *sta,
+ struct ieee802_11_elems *elems)
+{
+ int ac, buffer_local = 0;
+ bool has_buffered = false;
+ u16 awake_window = 0;
+
+ /* TIM map only for LLID <= IEEE80211_MAX_AID */
+ if (sta->plink_state == NL80211_PLINK_ESTAB)
+ has_buffered = ieee80211_check_tim(elems->tim, elems->tim_len,
+ le16_to_cpu(sta->llid) % IEEE80211_MAX_AID);
+
+ if (has_buffered)
+ mps_dbg(sta->sdata, "%pM indicates buffered frames\n",
+ sta->sta.addr);
+
+ if (elems->awake_window)
+ awake_window = get_unaligned_le16(elems->awake_window);
+
+ /* only transmit towards PS STA with announced awake window */
+ if (test_sta_flag(sta, WLAN_STA_PS_STA) && !awake_window)
+ return;
+
+ for (ac = 0; ac < IEEE80211_NUM_ACS; ac++)
+ buffer_local += skb_queue_len(&sta->ps_tx_buf[ac]) +
+ skb_queue_len(&sta->tx_filtered[ac]);
+
+ if (!has_buffered && !buffer_local)
+ return;
+
+ if (sta->plink_state == NL80211_PLINK_ESTAB)
+ mpsp_trigger_send(sta, has_buffered, !buffer_local);
+ else
+ mps_frame_deliver(sta, 1);
+}
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 580704e..e5426c7 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -1452,6 +1452,10 @@ ieee80211_rx_h_sta_process(struct ieee80211_rx_data *rx)
}
}

+ /* mesh power save support */
+ if (ieee80211_vif_is_mesh(&rx->sdata->vif))
+ ieee80211_mps_rx_h_sta_process(sta, hdr);
+
/*
* Drop (qos-)data::nullfunc frames silently, since they
* are used only to control station power saving mode.
@@ -2090,7 +2094,10 @@ ieee80211_rx_h_mesh_fwding(struct ieee80211_rx_data *rx)
if (is_multicast_ether_addr(fwd_hdr->addr1)) {
IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, fwded_mcast);
memcpy(fwd_hdr->addr2, sdata->vif.addr, ETH_ALEN);
+ /* update power mode indication when forwarding */
+ ieee80211_mps_set_frame_flags(sdata, NULL, fwd_hdr);
} else if (!mesh_nexthop_lookup(fwd_skb, sdata)) {
+ /* mesh power mode flags updated in mesh_nexthop_lookup */
IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, fwded_unicast);
} else {
/* unable to resolve next hop */
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index ca9fde1..8a4cfcd 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -110,6 +110,8 @@ static void cleanup_single_sta(struct sta_info *sta)
if (sta->sdata->vif.type == NL80211_IFTYPE_AP ||
sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
ps = &sdata->bss->ps;
+ else if (ieee80211_vif_is_mesh(&sdata->vif))
+ ps = &sdata->u.mesh.ps;
else
return;

@@ -130,6 +132,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);
+ del_timer_sync(&sta->local_pm_timer);
}
#endif

@@ -373,6 +376,8 @@ 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);
+ setup_timer(&sta->local_pm_timer, ieee80211_mps_sta_local_pm_timer,
+ (unsigned long) sta);
#endif

return sta;
@@ -582,6 +587,12 @@ void sta_info_recalc_tim(struct sta_info *sta)

ps = &sta->sdata->bss->ps;
id = sta->sta.aid;
+#ifdef CONFIG_MAC80211_MESH
+ } else if (ieee80211_vif_is_mesh(&sta->sdata->vif)) {
+ ps = &sta->sdata->u.mesh.ps;
+ /* TIM map only for PLID <= IEEE80211_MAX_AID */
+ id = le16_to_cpu(sta->plid) % IEEE80211_MAX_AID;
+#endif
} else {
return;
}
@@ -740,8 +751,9 @@ static bool sta_info_cleanup_expire_buffered(struct ieee80211_local *local,
bool have_buffered = false;
int ac;

- /* This is only necessary for stations on BSS interfaces */
- if (!sta->sdata->bss)
+ /* This is only necessary for stations on BSS/MBSS interfaces */
+ if (!sta->sdata->bss &&
+ !ieee80211_vif_is_mesh(&sta->sdata->vif))
return false;

for (ac = 0; ac < IEEE80211_NUM_ACS; ac++)
@@ -1003,6 +1015,8 @@ static void clear_sta_ps_flags(void *_sta)
if (sdata->vif.type == NL80211_IFTYPE_AP ||
sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
ps = &sdata->bss->ps;
+ else if (ieee80211_vif_is_mesh(&sdata->vif))
+ ps = &sdata->u.mesh.ps;
else
return;

diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 37c1889..e29aacb 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -56,6 +56,10 @@
* @WLAN_STA_INSERTED: This station is inserted into the hash table.
* @WLAN_STA_RATE_CONTROL: rate control was initialized for this station.
* @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 -> stay awake
+ * @WLAN_STA_MPS_WAIT_FOR_CAB: STA multicast frames are imminent -> stay awake
*/
enum ieee80211_sta_info_flags {
WLAN_STA_AUTH,
@@ -78,6 +82,8 @@ enum ieee80211_sta_info_flags {
WLAN_STA_INSERTED,
WLAN_STA_RATE_CONTROL,
WLAN_STA_TOFFSET_KNOWN,
+ WLAN_STA_MPSP_OWNER,
+ WLAN_STA_MPSP_RECIPIENT,
};

#define ADDBA_RESP_INTERVAL HZ
@@ -275,6 +281,11 @@ struct sta_ampdu_mlme {
* @t_offset_setpoint: reference timing offset of this sta to be used when
* calculating clockdrift
* @ch_width: peer's channel width
+ * @local_pm: local link-specific power save mode
+ * @local_pm_delayed: temporary storage for delayed setting of local_pm
+ * @local_pm_timer: timer for delayed setting of local_pm
+ * @peer_pm: peer-specific power save mode towards local STA
+ * @nonpeer_pm: STA power save mode towards non-peer neighbors
* @debugfs: debug filesystem info
* @dead: set to true when sta is unlinked
* @uploaded: set to true when sta is uploaded to the driver
@@ -372,6 +383,12 @@ struct sta_info {
s64 t_offset;
s64 t_offset_setpoint;
enum nl80211_chan_width ch_width;
+ /* mesh power save */
+ enum nl80211_mesh_power_mode local_pm;
+ enum nl80211_mesh_power_mode local_pm_delayed;
+ struct timer_list local_pm_timer;
+ enum nl80211_mesh_power_mode peer_pm;
+ enum nl80211_mesh_power_mode nonpeer_pm;
#endif

#ifdef CONFIG_MAC80211_DEBUGFS
diff --git a/net/mac80211/status.c b/net/mac80211/status.c
index 07d9957..5e04201 100644
--- a/net/mac80211/status.c
+++ b/net/mac80211/status.c
@@ -468,6 +468,11 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb)
return;
}

+ /* mesh Peer Service Period support */
+ if (ieee80211_vif_is_mesh(&sta->sdata->vif) &&
+ ieee80211_is_data_qos(hdr->frame_control))
+ ieee80211_mpsp_trigger_process(hdr, sta, true, acked);
+
if ((local->hw.flags & IEEE80211_HW_HAS_RATE_CONTROL) &&
(rates_idx != -1))
sta->last_tx_rate = info->status.rates[rates_idx];
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 1b04984..5c23970 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -329,6 +329,8 @@ static void purge_old_ps_buffers(struct ieee80211_local *local)

if (sdata->vif.type == NL80211_IFTYPE_AP)
ps = &sdata->u.ap.ps;
+ else if (ieee80211_vif_is_mesh(&sdata->vif))
+ ps = &sdata->u.mesh.ps;
else
continue;

@@ -372,18 +374,20 @@ ieee80211_tx_h_multicast_ps_buf(struct ieee80211_tx_data *tx)
/*
* broadcast/multicast frame
*
- * If any of the associated stations is in power save mode,
+ * If any of the associated/peer stations is in power save mode,
* the frame is buffered to be sent after DTIM beacon frame.
* This is done either by the hardware or us.
*/

- /* powersaving STAs currently only in AP/VLAN mode */
+ /* powersaving STAs currently only in AP/VLAN/mesh mode */
if (tx->sdata->vif.type == NL80211_IFTYPE_AP ||
tx->sdata->vif.type == NL80211_IFTYPE_AP_VLAN) {
if (!tx->sdata->bss)
return TX_CONTINUE;

ps = &tx->sdata->bss->ps;
+ } else if (ieee80211_vif_is_mesh(&tx->sdata->vif)) {
+ ps = &tx->sdata->u.mesh.ps;
} else {
return TX_CONTINUE;
}
@@ -1472,12 +1476,16 @@ void ieee80211_xmit(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb,
hdr = (struct ieee80211_hdr *) skb->data;
info->control.vif = &sdata->vif;

- if (ieee80211_vif_is_mesh(&sdata->vif) &&
- ieee80211_is_data(hdr->frame_control) &&
- !is_multicast_ether_addr(hdr->addr1) &&
- mesh_nexthop_resolve(skb, sdata)) {
- /* skb queued: don't free */
- return;
+ if (ieee80211_vif_is_mesh(&sdata->vif)) {
+ if (ieee80211_is_data(hdr->frame_control) &&
+ is_unicast_ether_addr(hdr->addr1)) {
+ if (mesh_nexthop_resolve(skb, sdata)) {
+ /* skb queued: don't free */
+ return;
+ }
+ } else {
+ ieee80211_mps_set_frame_flags(sdata, NULL, hdr);
+ }
}

ieee80211_set_qos_hdr(sdata, skb);
@@ -2442,12 +2450,14 @@ struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw,
2 + /* NULL SSID */
2 + 8 + /* supported rates */
2 + 3 + /* DS params */
+ 256 + /* TIM IE */
2 + (IEEE80211_MAX_SUPP_RATES - 8) +
2 + sizeof(struct ieee80211_ht_cap) +
2 + sizeof(struct ieee80211_ht_operation) +
2 + sdata->u.mesh.mesh_id_len +
2 + sizeof(struct ieee80211_meshconf_ie) +
- sdata->u.mesh.ie_len);
+ sdata->u.mesh.ie_len +
+ 2 + sizeof(__le16)); /* awake window */
if (!skb)
goto out;

@@ -2459,6 +2469,7 @@ struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw,
eth_broadcast_addr(mgmt->da);
memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
memcpy(mgmt->bssid, sdata->vif.addr, ETH_ALEN);
+ ieee80211_mps_set_frame_flags(sdata, NULL, (void *) mgmt);
mgmt->u.beacon.beacon_int =
cpu_to_le16(sdata->vif.bss_conf.beacon_int);
mgmt->u.beacon.capab_info |= cpu_to_le16(
@@ -2472,12 +2483,14 @@ struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw,

if (ieee80211_add_srates_ie(sdata, skb, true, band) ||
mesh_add_ds_params_ie(skb, sdata) ||
+ ieee80211_beacon_add_tim(sdata, &ifmsh->ps, skb) ||
ieee80211_add_ext_srates_ie(sdata, skb, true, band) ||
mesh_add_rsn_ie(skb, sdata) ||
mesh_add_ht_cap_ie(skb, sdata) ||
mesh_add_ht_oper_ie(skb, sdata) ||
mesh_add_meshid_ie(skb, sdata) ||
mesh_add_meshconf_ie(skb, sdata) ||
+ mesh_add_awake_window_ie(skb, sdata) ||
mesh_add_vendor_ies(skb, sdata)) {
pr_err("o11s: couldn't add ies!\n");
goto out;
@@ -2731,6 +2744,8 @@ ieee80211_get_buffered_bc(struct ieee80211_hw *hw,
goto out;

ps = &sdata->u.ap.ps;
+ } else if (ieee80211_vif_is_mesh(&sdata->vif)) {
+ ps = &sdata->u.mesh.ps;
} else {
goto out;
}
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index f11e8c5..8b93352 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -805,6 +805,10 @@ u32 ieee802_11_parse_elems_crc(u8 *start, size_t len,
elems->peering = pos;
elems->peering_len = elen;
break;
+ case WLAN_EID_MESH_AWAKE_WINDOW:
+ if (elen >= 2)
+ elems->awake_window = pos;
+ break;
case WLAN_EID_PREQ:
elems->preq = pos;
elems->preq_len = elen;
diff --git a/net/mac80211/wme.c b/net/mac80211/wme.c
index 906f00c..afba19c 100644
--- a/net/mac80211/wme.c
+++ b/net/mac80211/wme.c
@@ -191,6 +191,15 @@ void ieee80211_set_qos_hdr(struct ieee80211_sub_if_data *sdata,

/* qos header is 2 bytes */
*p++ = ack_policy | tid;
- *p = ieee80211_vif_is_mesh(&sdata->vif) ?
- (IEEE80211_QOS_CTL_MESH_CONTROL_PRESENT >> 8) : 0;
+ if (ieee80211_vif_is_mesh(&sdata->vif)) {
+ /* preserve RSPI and Mesh PS Level bit */
+ *p &= ((IEEE80211_QOS_CTL_RSPI |
+ IEEE80211_QOS_CTL_MESH_PS_LEVEL) >> 8);
+
+ /* Nulls don't have a mesh header (frame body) */
+ if (!ieee80211_is_qos_nullfunc(hdr->frame_control))
+ *p |= (IEEE80211_QOS_CTL_MESH_CONTROL_PRESENT >> 8);
+ } else {
+ *p = 0;
+ }
}
--
1.7.9.5