Add an optional background scanning threshold triggered by low rssi
(useful for passing updated scan results to the supplicant ahead of
time, before losing connectivity entirely)

Signed-off-by: Felix Fietkau <nbd@openwrt.org>

--- a/net80211/ieee80211_ioctl.h
+++ b/net80211/ieee80211_ioctl.h
@@ -646,6 +646,7 @@
 	IEEE80211_PARAM_MINRATE			= 76,	/* Maximum rate (by table index) */
 	IEEE80211_PARAM_PROTMODE_RSSI		= 77,	/* RSSI Threshold for enabling protection mode */
 	IEEE80211_PARAM_PROTMODE_TIMEOUT	= 78,	/* Timeout for expiring protection mode */
+	IEEE80211_PARAM_BGSCAN_THRESH		= 79,	/* bg scan rssi threshold */
 };
 
 #define	SIOCG80211STATS			(SIOCDEVPRIVATE+2)
--- a/net80211/ieee80211_var.h
+++ b/net80211/ieee80211_var.h
@@ -92,6 +92,8 @@
 #define	IEEE80211_BGSCAN_IDLE_MIN	100	/* min idle time (ms) */
 #define	IEEE80211_BGSCAN_IDLE_DEFAULT	250	/* default idle time (ms) */
 
+#define IEEE80211_BGSCAN_TRIGGER_INTVL 20 /* min trigger interval for thresh based bgscan (secs) */
+
 #define IEEE80211_COVERAGE_CLASS_MAX	31	/* max coverage class */
 #define IEEE80211_REGCLASSIDS_MAX	10	/* max regclass id list */
 
@@ -219,6 +221,10 @@
 	u_int8_t iv_nickname[IEEE80211_NWID_LEN];
 	u_int iv_bgscanidle;				/* bg scan idle threshold */
 	u_int iv_bgscanintvl;				/* bg scan min interval */
+	u_int iv_bgscanthr;					/* bg scan rssi threshold */
+	u_int iv_bgscantrintvl;				/* bg scan trigger interval */
+	unsigned long iv_bgscanthr_next;		/* last trigger for bgscan */
+	unsigned long iv_lastconnect;	/* time of last connect attempt */
 	u_int iv_scanvalid;				/* scan cache valid threshold */
 	struct ieee80211_roam iv_roam;			/* sta-mode roaming state */
 
@@ -608,6 +614,7 @@
 #define IEEE80211_FEXT_SWBMISS		0x00000400	/* CONF: use software beacon timer */
 #define IEEE80211_FEXT_DROPUNENC_EAPOL	0x00000800	/* CONF: drop unencrypted eapol frames */
 #define IEEE80211_FEXT_APPIE_UPDATE	0x00001000	/* STATE: beacon APP IE updated */
+#define IEEE80211_FEXT_BGSCAN_THR	0x00002000	/* bgscan due to low rssi */
 
 #define IEEE80211_COM_UAPSD_ENABLE(_ic)		((_ic)->ic_flags_ext |= IEEE80211_FEXT_UAPSD)
 #define IEEE80211_COM_UAPSD_DISABLE(_ic)	((_ic)->ic_flags_ext &= ~IEEE80211_FEXT_UAPSD)
--- a/net80211/ieee80211_wireless.c
+++ b/net80211/ieee80211_wireless.c
@@ -2744,6 +2744,9 @@
 		else
 			retv = EINVAL;
 		break;
+	case IEEE80211_PARAM_BGSCAN_THRESH:
+		vap->iv_bgscanthr = value;
+		break;
 	case IEEE80211_PARAM_MCAST_RATE:
 		/* units are in KILObits per second */
 		if (value >= 256 && value <= 54000)
@@ -3144,6 +3147,9 @@
 	case IEEE80211_PARAM_BGSCAN_INTERVAL:
 		param[0] = vap->iv_bgscanintvl / HZ;	/* seconds */
 		break;
+	case IEEE80211_PARAM_BGSCAN_THRESH:
+		param[0] = vap->iv_bgscanthr;	/* rssi */
+		break;
 	case IEEE80211_PARAM_MCAST_RATE:
 		param[0] = vap->iv_mcast_rate;	/* seconds */
 		break;
@@ -5666,6 +5672,10 @@
 	  IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "bgscanintvl" },
 	{ IEEE80211_PARAM_BGSCAN_INTERVAL,
 	  0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_bgscanintvl" },
+	{ IEEE80211_PARAM_BGSCAN_THRESH,
+	  IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "bgscanthr" },
+	{ IEEE80211_PARAM_BGSCAN_THRESH,
+	  0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_bgscanthr" },
 	{ IEEE80211_PARAM_MCAST_RATE,
 	  IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "mcast_rate" },
 	{ IEEE80211_PARAM_MCAST_RATE,
--- a/net80211/ieee80211_input.c
+++ b/net80211/ieee80211_input.c
@@ -3013,8 +3013,10 @@
 {
 	struct ieee80211com *ic = vap->iv_ic;
 
+	vap->iv_bgscantrintvl = (vap->iv_bgscantrintvl + 1) % 4;
 	return ((ic->ic_flags_ext & IEEE80211_FEXT_BGSCAN) &&
-		time_after(jiffies, ic->ic_lastdata + vap->iv_bgscanidle));
+		(((ic->ic_flags_ext & IEEE80211_FEXT_BGSCAN_THR) && !vap->iv_bgscantrintvl) ||
+			time_after(jiffies, ic->ic_lastdata + vap->iv_bgscanidle)));
 }
 
 static __inline int
@@ -3258,6 +3260,25 @@
 			/* record tsf of last beacon */
 			memcpy(ni->ni_tstamp.data, scan.tstamp,
 				sizeof(ni->ni_tstamp));
+
+			/* When rssi is low, start doing bgscans more frequently to allow
+			 * the supplicant to make a better switching decision */
+			if (!(ic->ic_flags & IEEE80211_F_SCAN) && (rssi < vap->iv_bgscanthr) &&
+					(!vap->iv_bgscanthr_next ||
+						!time_before(jiffies, vap->iv_bgscanthr_next)) &&
+					(vap->iv_state == IEEE80211_S_RUN) &&
+					time_after(jiffies, vap->iv_lastconnect +
+						msecs_to_jiffies(IEEE80211_BGSCAN_INTVAL_MIN * 1000))) {
+				int ret;
+
+				ic->ic_lastdata = 0;
+				ic->ic_lastscan = 0;
+				ic->ic_flags_ext |= IEEE80211_FEXT_BGSCAN_THR;
+				ret = ieee80211_bg_scan(vap);
+				if (ret)
+					vap->iv_bgscanthr_next = jiffies + msecs_to_jiffies(IEEE80211_BGSCAN_TRIGGER_INTVL * 1000);
+			}
+
 			if (ni->ni_intval != scan.bintval) {
 				IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni,
 						"beacon interval divergence: "
--- a/net80211/ieee80211_scan.c
+++ b/net80211/ieee80211_scan.c
@@ -616,6 +616,7 @@
 
 		/* clear bg scan NOPICK and mark cancel request */
 		ss->ss_flags &= ~IEEE80211_SCAN_NOPICK;
+		ic->ic_flags_ext &= ~IEEE80211_FEXT_BGSCAN_THR;
 		SCAN_PRIVATE(ss)->ss_iflags |= ISCAN_CANCEL;
 		ss->ss_ops->scan_cancel(ss, vap);
 		/* force it to fire asap */
@@ -782,7 +783,7 @@
 				ieee80211_sta_pwrsave(vap, 0);
 				if (ss->ss_next >= ss->ss_last) {
 					ieee80211_notify_scan_done(vap);
-					ic->ic_flags_ext &= ~IEEE80211_FEXT_BGSCAN;
+					ic->ic_flags_ext &= ~(IEEE80211_FEXT_BGSCAN|IEEE80211_FEXT_BGSCAN_THR);
 				}
 			}
 			SCAN_PRIVATE(ss)->ss_iflags &= ~ISCAN_CANCEL;
--- a/net80211/ieee80211_proto.c
+++ b/net80211/ieee80211_proto.c
@@ -1450,6 +1450,7 @@
 		}
 		break;
 	case IEEE80211_S_AUTH:
+		vap->iv_lastconnect = jiffies;
 		/* auth frames are possible between IBSS nodes, 
 		 * see 802.11-1999, chapter 5.7.6 */
 		KASSERT(vap->iv_opmode == IEEE80211_M_STA || 
--- a/net80211/ieee80211_output.c
+++ b/net80211/ieee80211_output.c
@@ -238,7 +238,8 @@
 	}
 	
 	/* Cancel any running BG scan */
-	ieee80211_cancel_scan(vap);
+	if (!(ic->ic_flags_ext & IEEE80211_FEXT_BGSCAN_THR) && (vap->iv_state == IEEE80211_S_RUN))
+		ieee80211_cancel_scan(vap);
 
 	/* 
 	 * Find the node for the destination so we can do
