Commit 3f52b7e3 authored by Marco Porsch's avatar Marco Porsch Committed by Johannes Berg

mac80211: mesh power save basics

Add routines to
- maintain a PS mode for each peer and a non-peer PS mode
- indicate own PS mode in transmitted frames
- track neighbor STAs power modes
- buffer frames when neighbors are in PS mode
- add TIM and Awake Window IE to beacons
- release frames in Mesh Peer Service Periods

Add local_pm to sta_info to represent the link-specific power
mode at this station towards the remote station. When a peer
link is established, use the default power mode stored in mesh
config. Update the PS status if the peering status of a neighbor
changes.
Maintain a mesh power mode for non-peer mesh STAs. Set the
non-peer power mode to active mode during peering. Authenticated
mesh peering is currently not working when either node is
configured to be in power save mode.

Indicate the current power mode in transmitted frames. Use QoS
Nulls to indicate mesh power mode transitions.
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 peer_pm to sta_info to represent the peer's link-specific
power mode towards the local station. Add nonpeer_pm to
represent the peer's power mode towards all non-peer stations.
Track power modes based on received frames.

Add the ps_data structure to ieee80211_if_mesh (for TIM map, PS
neighbor counter and group-addressed frame buffer).

Set WLAN_STA_PS flag for STA in PS mode to use the unicast frame
buffering routines in the tx path. Update num_sta_ps to buffer
and release group-addressed frames after DTIM beacons.

Announce the awake window duration in beacons if in light or
deep sleep mode towards any peer or non-peer. Create a TIM IE
similarly to AP mode and add it to mesh beacons. Parse received
Awake Window IEs and check TIM IEs for buffered frames.

Release frames towards peers in mesh Peer Service Periods. Use
the corresponding trigger frames and monitor the MPSP status.
Append a QoS Null as trigger frame if neccessary to properly end
the MPSP. Currently, in HT channels MPSPs behave imperfectly and
show large delay spikes and frame losses.
Signed-off-by: default avatarMarco Porsch <marco@cozybit.com>
Signed-off-by: default avatarIvan Bezyazychnyy <ivan.bezyazychnyy@gmail.com>
Signed-off-by: default avatarMike Krinkin <krinkin.m.u@gmail.com>
Signed-off-by: default avatarMax Filippov <jcmvbkbc@gmail.com>
Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
parent 0532d4f1
......@@ -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
......@@ -675,11 +680,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,
};
/**
......
......@@ -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
......
......@@ -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
......
......@@ -492,7 +492,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);
......@@ -501,6 +504,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
}
......@@ -1262,6 +1268,10 @@ 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);
break;
case NL80211_PLINK_LISTEN:
case NL80211_PLINK_BLOCKED:
......@@ -1273,6 +1283,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 */
......@@ -1289,6 +1302,9 @@ static int sta_apply_parameters(struct ieee80211_local *local,
break;
}
}
if (params->local_pm)
ieee80211_mps_set_sta_local_pm(sta, params->local_pm);
#endif
}
......@@ -1777,6 +1793,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;
}
......
......@@ -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__)
......
......@@ -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
......
......@@ -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);
}
......
......@@ -590,6 +590,11 @@ 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;
};
#ifdef CONFIG_MAC80211_MESH
......@@ -1185,6 +1190,7 @@ struct ieee802_11_elems {
struct ieee80211_meshconf_ie *mesh_config;
u8 *mesh_id;
u8 *peering;
__le16 *awake_window;
u8 *preq;
u8 *prep;
u8 *perr;
......
......@@ -261,6 +261,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;
......@@ -286,6 +289,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)
{
......@@ -629,6 +655,8 @@ void ieee80211_start_mesh(struct ieee80211_sub_if_data *sdata)
sdata->vif.bss_conf.basic_rates =
ieee80211_mandatory_rates(local, band);
ieee80211_mps_local_status_update(sdata);
ieee80211_bss_info_change_notify(sdata, changed);
netif_carrier_on(sdata->dev);
......@@ -651,6 +679,10 @@ void ieee80211_stop_mesh(struct ieee80211_sub_if_data *sdata)
sta_info_flush(sdata);
mesh_path_flush_by_iface(sdata);
/* 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);
......@@ -828,6 +860,7 @@ void ieee80211_mesh_init_sdata(struct ieee80211_sub_if_data *sdata)
ieee80211_mesh_path_root_timer,
(unsigned long) sdata);
INIT_LIST_HEAD(&ifmsh->preq_queue.list);
skb_queue_head_init(&ifmsh->ps.bc_buf);
spin_lock_init(&ifmsh->mesh_preq_queue_lock);
spin_lock_init(&ifmsh->sync_offset_lock);
......
......@@ -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,21 @@ 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);
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(u8 *qc, 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);
......
......@@ -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);
......@@ -217,6 +218,7 @@ static void prepare_frame_for_deferred_tx(struct ieee80211_sub_if_data *sdata,
info->control.vif = &sdata->vif;
info->flags |= IEEE80211_TX_INTFL_NEED_TXPROCESSING;
ieee80211_set_qos_hdr(sdata, skb);
ieee80211_mps_set_frame_flags(sdata, NULL, hdr);
}
/**
......@@ -1080,6 +1082,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)
......@@ -1151,6 +1157,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;
}
......
......@@ -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);
......
......@@ -201,6 +201,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;
}
......@@ -503,6 +506,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();
}
......@@ -633,6 +637,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);
}
......@@ -866,6 +873,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,
......@@ -959,6 +970,9 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m
changed |= mesh_set_short_slot_time(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);
break;
default:
spin_unlock_bh(&sta->lock);
......@@ -998,6 +1012,9 @@ 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);
ieee80211_mps_set_sta_local_pm(sta,
mshcfg->power_mode);
break;
default:
spin_unlock_bh(&sta->lock);
......
/*
* Copyright 2012-2013, Marco Porsch <marco.porsch@s2005.tu-chemnitz.de>
* Copyright 2012-2013, 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"
/* mesh PS management */
/**
* mps_qos_null_get - create pre-addressed QoS Null frame for mesh powersave
*/
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_qos_null_tx - send a QoS Null to indicate link-specific power mode
*/
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)) {
u8 *qc = ieee80211_get_qos_ctl((void *) skb->data);
qc[0] |= 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
*/
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
*/
void ieee80211_mps_set_sta_local_pm(struct sta_info *sta,
enum nl80211_mesh_power_mode pm)
{
struct ieee80211_sub_if_data *sdata = sta->sdata;
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);
}
/**
* 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;
u8 *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 = 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[1] |= (IEEE80211_QOS_CTL_MESH_PS_LEVEL >> 8);
else
qc[1] &= ~(IEEE80211_QOS_CTL_MESH_PS_LEVEL >> 8);
}
/**
* 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) {