Search Linux Wireless

[RFCv2 03/13] mac80211: local link-specific mesh power mode logic

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



According to IEEE802.11-2012 a mesh peering is always associated between
two mesh STAs. Both mesh STAs have their own mesh power mode for the
mesh peering. This power mode is called the link-specific power mode.

Add the local_ps_mode 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-configured default power mode.
To avoid frame losses when switching to a lower power state after peering
and for use in a future per-link dynamic powersave, the set_local_ps_mode
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.

According to IEEE802.11-2012 a mesh STA maintains a mesh power mode
for non-peer mesh STAs. The non-peer mesh power mode determines when
non-peer mesh STAs may send Probe Request and Mesh Peering Open Request
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, 802.11s defines that the non-peer power mode is
temporarily set to active mode during peering.

A debug define allows comfortable debugging output.

Signed-off-by: Marco Porsch <marco.porsch@xxxxxxxxxxxxxxxxxxx>
Signed-off-by: Ivan Bezyazychnyy <ivan.bezyazychnyy@xxxxxxxxx>
Signed-off-by: Mike Krinkin <krinkin.m.u@xxxxxxxxx>
Signed-off-by: Max Filippov <jcmvbkbc@xxxxxxxxx>
---
 net/mac80211/Kconfig          |   11 ++++
 net/mac80211/Makefile         |    3 +-
 net/mac80211/cfg.c            |    4 ++
 net/mac80211/debug.h          |   10 ++++
 net/mac80211/debugfs_netdev.c |    3 +
 net/mac80211/ieee80211_i.h    |    4 ++
 net/mac80211/mesh.c           |    1 +
 net/mac80211/mesh.h           |    6 ++
 net/mac80211/mesh_plink.c     |   18 ++++++
 net/mac80211/mesh_ps.c        |  124 +++++++++++++++++++++++++++++++++++++++++
 net/mac80211/sta_info.c       |    4 ++
 net/mac80211/sta_info.h       |    6 ++
 12 files changed, 193 insertions(+), 1 deletion(-)
 create mode 100644 net/mac80211/mesh_ps.c

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 d0b7a7e..62564e9 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -1669,6 +1669,10 @@ 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_mesh_local_ps_update(sdata);
+	}
 	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 3393ad5..99bef96 100644
--- a/net/mac80211/debugfs_netdev.c
+++ b/net/mac80211/debugfs_netdev.c
@@ -516,6 +516,8 @@ 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);
 #endif
 
 #define DEBUGFS_ADD_MODE(name, mode) \
@@ -620,6 +622,7 @@ static void add_mesh_config(struct ieee80211_sub_if_data *sdata)
 	MESHPARAMS_ADD(dot11MeshHWMPactivePathToRootTimeout);
 	MESHPARAMS_ADD(dot11MeshHWMProotInterval);
 	MESHPARAMS_ADD(dot11MeshHWMPconfirmationInterval);
+	MESHPARAMS_ADD(power_mode);
 #undef MESHPARAMS_ADD
 }
 #endif
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 32e4785..0364844 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -618,6 +618,10 @@ 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_ps_mode;
+	u8 ps_peers_light_sleep;
+	u8 ps_peers_deep_sleep;
 };
 
 #ifdef CONFIG_MAC80211_MESH
diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c
index 5f3ef91..d4e90ba 100644
--- a/net/mac80211/mesh.c
+++ b/net/mac80211/mesh.c
@@ -633,6 +633,7 @@ 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_mesh_local_ps_update(sdata);
 	ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BEACON |
 						BSS_CHANGED_BEACON_ENABLED |
 						BSS_CHANGED_HT |
diff --git a/net/mac80211/mesh.h b/net/mac80211/mesh.h
index 7f3a78f..64781dd 100644
--- a/net/mac80211/mesh.h
+++ b/net/mac80211/mesh.h
@@ -242,6 +242,12 @@ 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_mesh_local_ps_update(struct ieee80211_sub_if_data *sdata);
+void ieee80211_set_sta_mesh_local_ps_mode(struct sta_info *sta,
+		enum nl80211_mesh_power_mode pm, u32 delay);
+void ieee80211_sta_mesh_local_ps_mode_timer(unsigned long data);
+
 /* Mesh paths */
 int mesh_nexthop_lookup(struct sk_buff *skb,
 		struct ieee80211_sub_if_data *sdata);
diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c
index 7a47f40..19994e7 100644
--- a/net/mac80211/mesh_plink.c
+++ b/net/mac80211/mesh_plink.c
@@ -179,6 +179,8 @@ static u32 __mesh_plink_deactivate(struct sta_info *sta)
 	sta->plink_state = NL80211_PLINK_BLOCKED;
 	mesh_path_flush_by_nexthop(sta);
 
+	ieee80211_mesh_local_ps_update(sdata);
+
 	return changed;
 }
 
@@ -545,6 +547,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_mesh_local_ps_update(sdata);
+
 	return mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_OPEN,
 				   sta->sta.addr, llid, 0, 0);
 }
@@ -776,6 +781,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_mesh_local_ps_update(sdata);
+
 			spin_unlock_bh(&sta->lock);
 			mesh_plink_frame_tx(sdata,
 					    WLAN_SP_MESH_PEERING_OPEN,
@@ -868,6 +877,9 @@ 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_set_sta_mesh_local_ps_mode(sta,
+					mshcfg->power_mode, 100);
 			break;
 		default:
 			spin_unlock_bh(&sta->lock);
@@ -906,6 +918,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);
+			/*
+			 * we need some delay here, otherwise
+			 * the announcement Null would arrive before the CONFIRM
+			 */
+			ieee80211_set_sta_mesh_local_ps_mode(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..14bf65b
--- /dev/null
+++ b/net/mac80211/mesh_ps.c
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2012, Marco Porsch <marco.porsch@xxxxxxxxxxxxxxxxxxxx>
+ * 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"
+
+/**
+ * ieee80211_mesh_local_ps_update - keep track of link-specific PS modes
+ *
+ * @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_mesh_local_ps_update(struct ieee80211_sub_if_data *sdata)
+{
+	struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
+	struct sta_info *sta;
+	bool peering = false;
+	u8 light_sleep_cnt = 0;
+	u8 deep_sleep_cnt = 0;
+
+	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_ps_mode ==
+					NL80211_MESH_POWER_LIGHT_SLEEP)
+				light_sleep_cnt++;
+			if (sta->local_ps_mode ==
+					NL80211_MESH_POWER_DEEP_SLEEP)
+				deep_sleep_cnt++;
+			break;
+		default:
+			break;
+		}
+	}
+
+	/*
+	 * 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 PS mode to active until "
+			"peering is complete\n");
+		ifmsh->nonpeer_ps_mode = NL80211_MESH_POWER_ACTIVE;
+	} else if (light_sleep_cnt || deep_sleep_cnt) {
+		mps_dbg(sdata, "setting non-peer PS mode to deep sleep\n");
+		ifmsh->nonpeer_ps_mode = NL80211_MESH_POWER_DEEP_SLEEP;
+	} else {
+		mps_dbg(sdata, "setting non-peer PS mode to user value\n");
+		ifmsh->nonpeer_ps_mode = ifmsh->mshcfg.power_mode;
+	}
+
+	ifmsh->ps_peers_light_sleep = light_sleep_cnt;
+	ifmsh->ps_peers_deep_sleep = deep_sleep_cnt;
+}
+
+/**
+ * ieee80211_set_sta_mesh_local_ps_mode - set local PS mode 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_set_sta_mesh_local_ps_mode(struct sta_info *sta,
+		enum nl80211_mesh_power_mode pm, u32 delay)
+{
+	struct ieee80211_sub_if_data *sdata = sta->sdata;
+	static const char *modes[] = {
+		[NL80211_MESH_POWER_ACTIVE] = "active",
+		[NL80211_MESH_POWER_LIGHT_SLEEP] = "light sleep",
+		[NL80211_MESH_POWER_DEEP_SLEEP] = "deep sleep",
+	};
+
+	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_ps_mode_delayed = pm;
+		mod_timer(&sta->local_ps_mode_timer,
+			  jiffies + msecs_to_jiffies(delay));
+		return;
+	}
+
+	mps_dbg(sdata, "local STA operates in %s mode with %pM\n",
+		modes[pm], sta->sta.addr);
+
+	sta->local_ps_mode = pm;
+
+	ieee80211_mesh_local_ps_update(sdata);
+}
+
+void ieee80211_sta_mesh_local_ps_mode_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_set_sta_mesh_local_ps_mode(sta, sta->local_ps_mode_delayed,
+					     0);
+}
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index e9d5768..09e350a 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -131,6 +131,7 @@ static void free_sta_work(struct work_struct *wk)
 		mesh_accept_plinks_update(sdata);
 		mesh_plink_deactivate(sta);
 		del_timer_sync(&sta->plink_timer);
+		del_timer_sync(&sta->local_ps_mode_timer);
 	}
 #endif
 
@@ -351,6 +352,9 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
 #ifdef CONFIG_MAC80211_MESH
 	sta->plink_state = NL80211_PLINK_LISTEN;
 	init_timer(&sta->plink_timer);
+	setup_timer(&sta->local_ps_mode_timer,
+			ieee80211_sta_mesh_local_ps_mode_timer,
+			(unsigned long) sta);
 #endif
 
 	return sta;
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index c88f161f..046ca70 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -274,6 +274,9 @@ struct sta_ampdu_mlme {
  * @t_offset_setpoint: reference timing offset of this sta to be used when
  * 	calculating clockdrift
  * @ch_type: peer's channel type
+ * @local_ps_mode: local link-specific power save mode
+ * @local_ps_mode_delayed: temp. storage for delayed setting of local_ps_mode
+ * @local_ps_mode_timer: timer for delayed setting of local_ps_mode
  * @debugfs: debug filesystem info
  * @dead: set to true when sta is unlinked
  * @uploaded: set to true when sta is uploaded to the driver
@@ -370,6 +373,9 @@ struct sta_info {
 	s64 t_offset;
 	s64 t_offset_setpoint;
 	enum nl80211_channel_type ch_type;
+	enum nl80211_mesh_power_mode local_ps_mode;
+	enum nl80211_mesh_power_mode local_ps_mode_delayed;
+	struct timer_list local_ps_mode_timer;
 #endif
 
 #ifdef CONFIG_MAC80211_DEBUGFS
-- 
1.7.9.5

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


[Index of Archives]     [Linux Host AP]     [ATH6KL]     [Linux Wireless Personal Area Network]     [Linux Bluetooth]     [Linux Netdev]     [Kernel Newbies]     [Linux Kernel]     [IDE]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite Hiking]     [MIPS Linux]     [ARM Linux]     [Linux RAID]

  Powered by Linux