1178354Ssam/*	$FreeBSD: stable/11/sys/net80211/ieee80211_rssadapt.c 343035 2019-01-15 02:16:23Z avos $	*/
2178354Ssam/* $NetBSD: ieee80211_rssadapt.c,v 1.9 2005/02/26 22:45:09 perry Exp $ */
3178354Ssam/*-
4206358Srpaulo * Copyright (c) 2010 Rui Paulo <rpaulo@FreeBSD.org>
5178354Ssam * Copyright (c) 2003, 2004 David Young.  All rights reserved.
6178354Ssam *
7178354Ssam * Redistribution and use in source and binary forms, with or
8178354Ssam * without modification, are permitted provided that the following
9178354Ssam * conditions are met:
10178354Ssam * 1. Redistributions of source code must retain the above copyright
11178354Ssam *    notice, this list of conditions and the following disclaimer.
12178354Ssam * 2. Redistributions in binary form must reproduce the above
13178354Ssam *    copyright notice, this list of conditions and the following
14178354Ssam *    disclaimer in the documentation and/or other materials provided
15178354Ssam *    with the distribution.
16178354Ssam * 3. The name of David Young may not be used to endorse or promote
17178354Ssam *    products derived from this software without specific prior
18178354Ssam *    written permission.
19178354Ssam *
20178354Ssam * THIS SOFTWARE IS PROVIDED BY David Young ``AS IS'' AND ANY
21178354Ssam * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
22178354Ssam * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23178354Ssam * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL David
24178354Ssam * Young BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25178354Ssam * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
26178354Ssam * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27178354Ssam * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
28178354Ssam * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29178354Ssam * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30178354Ssam * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
31178354Ssam * OF SUCH DAMAGE.
32178354Ssam */
33178354Ssam#include "opt_wlan.h"
34178354Ssam
35178354Ssam#include <sys/param.h>
36257241Sglebius#include <sys/systm.h>
37178354Ssam#include <sys/kernel.h>
38257241Sglebius#include <sys/malloc.h>
39178354Ssam#include <sys/module.h>
40178354Ssam#include <sys/socket.h>
41178354Ssam#include <sys/sysctl.h>
42178354Ssam
43178354Ssam#include <net/if.h>
44257241Sglebius#include <net/if_var.h>
45178354Ssam#include <net/if_media.h>
46257241Sglebius#include <net/ethernet.h>
47178354Ssam
48178354Ssam#include <net80211/ieee80211_var.h>
49178354Ssam#include <net80211/ieee80211_rssadapt.h>
50206358Srpaulo#include <net80211/ieee80211_ratectl.h>
51178354Ssam
52178354Ssamstruct rssadapt_expavgctl {
53178354Ssam	/* RSS threshold decay. */
54178354Ssam	u_int rc_decay_denom;
55178354Ssam	u_int rc_decay_old;
56178354Ssam	/* RSS threshold update. */
57178354Ssam	u_int rc_thresh_denom;
58178354Ssam	u_int rc_thresh_old;
59178354Ssam	/* RSS average update. */
60178354Ssam	u_int rc_avgrssi_denom;
61178354Ssam	u_int rc_avgrssi_old;
62178354Ssam};
63178354Ssam
64178354Ssamstatic struct rssadapt_expavgctl master_expavgctl = {
65209092Sed	.rc_decay_denom = 16,
66209092Sed	.rc_decay_old = 15,
67209092Sed	.rc_thresh_denom = 8,
68209092Sed	.rc_thresh_old = 4,
69209092Sed	.rc_avgrssi_denom = 8,
70209092Sed	.rc_avgrssi_old = 4
71178354Ssam};
72178354Ssam
73178354Ssam#ifdef interpolate
74178354Ssam#undef interpolate
75178354Ssam#endif
76178354Ssam#define interpolate(parm, old, new) ((parm##_old * (old) + \
77178354Ssam                                     (parm##_denom - parm##_old) * (new)) / \
78178354Ssam				    parm##_denom)
79178354Ssam
80206358Srpaulostatic void	rssadapt_setinterval(const struct ieee80211vap *, int);
81206358Srpaulostatic void	rssadapt_init(struct ieee80211vap *);
82206358Srpaulostatic void	rssadapt_deinit(struct ieee80211vap *);
83206358Srpaulostatic void	rssadapt_updatestats(struct ieee80211_rssadapt_node *);
84206358Srpaulostatic void	rssadapt_node_init(struct ieee80211_node *);
85206358Srpaulostatic void	rssadapt_node_deinit(struct ieee80211_node *);
86206358Srpaulostatic int	rssadapt_rate(struct ieee80211_node *, void *, uint32_t);
87206358Srpaulostatic void	rssadapt_lower_rate(struct ieee80211_rssadapt_node *, int, int);
88206358Srpaulostatic void	rssadapt_raise_rate(struct ieee80211_rssadapt_node *,
89206358Srpaulo			int, int);
90206358Srpaulostatic void	rssadapt_tx_complete(const struct ieee80211vap *,
91206358Srpaulo    			const struct ieee80211_node *, int,
92206358Srpaulo			void *, void *);
93206358Srpaulostatic void	rssadapt_sysctlattach(struct ieee80211vap *,
94206358Srpaulo			struct sysctl_ctx_list *, struct sysctl_oid *);
95178354Ssam
96178354Ssam/* number of references from net80211 layer */
97178354Ssamstatic	int nrefs = 0;
98178354Ssam
99206358Srpaulostatic const struct ieee80211_ratectl rssadapt = {
100206358Srpaulo	.ir_name	= "rssadapt",
101206358Srpaulo	.ir_attach	= NULL,
102206358Srpaulo	.ir_detach	= NULL,
103206358Srpaulo	.ir_init	= rssadapt_init,
104206358Srpaulo	.ir_deinit	= rssadapt_deinit,
105206358Srpaulo	.ir_node_init	= rssadapt_node_init,
106206358Srpaulo	.ir_node_deinit	= rssadapt_node_deinit,
107206358Srpaulo	.ir_rate	= rssadapt_rate,
108206358Srpaulo	.ir_tx_complete	= rssadapt_tx_complete,
109206358Srpaulo	.ir_tx_update	= NULL,
110206358Srpaulo	.ir_setinterval	= rssadapt_setinterval,
111206358Srpaulo};
112206358SrpauloIEEE80211_RATECTL_MODULE(rssadapt, 1);
113206358SrpauloIEEE80211_RATECTL_ALG(rssadapt, IEEE80211_RATECTL_RSSADAPT, rssadapt);
114206358Srpaulo
115206358Srpaulostatic void
116206358Srpaulorssadapt_setinterval(const struct ieee80211vap *vap, int msecs)
117178354Ssam{
118206358Srpaulo	struct ieee80211_rssadapt *rs = vap->iv_rs;
119178354Ssam	int t;
120178354Ssam
121343035Savos	if (!rs)
122343035Savos		return;
123343035Savos
124178354Ssam	if (msecs < 100)
125178354Ssam		msecs = 100;
126178354Ssam	t = msecs_to_ticks(msecs);
127178354Ssam	rs->interval = (t < 1) ? 1 : t;
128178354Ssam}
129178354Ssam
130206358Srpaulostatic void
131206358Srpaulorssadapt_init(struct ieee80211vap *vap)
132178354Ssam{
133206358Srpaulo	struct ieee80211_rssadapt *rs;
134206358Srpaulo
135206358Srpaulo	KASSERT(vap->iv_rs == NULL, ("%s: iv_rs already initialized",
136206358Srpaulo	    __func__));
137321724Savos
138321724Savos	nrefs++;		/* XXX locking */
139283538Sadrian	vap->iv_rs = rs = IEEE80211_MALLOC(sizeof(struct ieee80211_rssadapt),
140283538Sadrian	    M_80211_RATECTL, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO);
141206419Srpaulo	if (rs == NULL) {
142206419Srpaulo		if_printf(vap->iv_ifp, "couldn't alloc ratectl structure\n");
143206419Srpaulo		return;
144206419Srpaulo	}
145178354Ssam	rs->vap = vap;
146206358Srpaulo	rssadapt_setinterval(vap, 500 /* msecs */);
147206358Srpaulo	rssadapt_sysctlattach(vap, vap->iv_sysctl, vap->iv_oid);
148178354Ssam}
149178354Ssam
150206358Srpaulostatic void
151206358Srpaulorssadapt_deinit(struct ieee80211vap *vap)
152178354Ssam{
153283538Sadrian	IEEE80211_FREE(vap->iv_rs, M_80211_RATECTL);
154321724Savos	KASSERT(nrefs > 0, ("imbalanced attach/detach"));
155321724Savos	nrefs--;		/* XXX locking */
156178354Ssam}
157178354Ssam
158178354Ssamstatic void
159178354Ssamrssadapt_updatestats(struct ieee80211_rssadapt_node *ra)
160178354Ssam{
161178354Ssam	long interval;
162178354Ssam
163178354Ssam	ra->ra_pktrate = (ra->ra_pktrate + 10*(ra->ra_nfail + ra->ra_nok))/2;
164178354Ssam	ra->ra_nfail = ra->ra_nok = 0;
165178354Ssam
166178354Ssam	/*
167178354Ssam	 * A node is eligible for its rate to be raised every 1/10 to 10
168178354Ssam	 * seconds, more eligible in proportion to recent packet rates.
169178354Ssam	 */
170178354Ssam	interval = MAX(10*1000, 10*1000 / MAX(1, 10 * ra->ra_pktrate));
171178354Ssam	ra->ra_raise_interval = msecs_to_ticks(interval);
172178354Ssam}
173178354Ssam
174206358Srpaulostatic void
175206358Srpaulorssadapt_node_init(struct ieee80211_node *ni)
176178354Ssam{
177206358Srpaulo	struct ieee80211_rssadapt_node *ra;
178206419Srpaulo	struct ieee80211vap *vap = ni->ni_vap;
179206419Srpaulo	struct ieee80211_rssadapt *rsa = vap->iv_rs;
180178354Ssam	const struct ieee80211_rateset *rs = &ni->ni_rates;
181178354Ssam
182343035Savos	if (!rsa) {
183343035Savos		if_printf(vap->iv_ifp, "ratectl structure was not allocated, "
184343035Savos		    "per-node structure allocation skipped\n");
185343035Savos		return;
186343035Savos	}
187343035Savos
188207323Srpaulo	if (ni->ni_rctls == NULL) {
189207323Srpaulo		ni->ni_rctls = ra =
190283538Sadrian		    IEEE80211_MALLOC(sizeof(struct ieee80211_rssadapt_node),
191283538Sadrian		        M_80211_RATECTL, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO);
192207323Srpaulo		if (ra == NULL) {
193207323Srpaulo			if_printf(vap->iv_ifp, "couldn't alloc per-node ratectl "
194207323Srpaulo			    "structure\n");
195207323Srpaulo			return;
196207323Srpaulo		}
197207323Srpaulo	} else
198207323Srpaulo		ra = ni->ni_rctls;
199178354Ssam	ra->ra_rs = rsa;
200178354Ssam	ra->ra_rates = *rs;
201178354Ssam	rssadapt_updatestats(ra);
202178354Ssam
203178354Ssam	/* pick initial rate */
204178354Ssam	for (ra->ra_rix = rs->rs_nrates - 1;
205178354Ssam	     ra->ra_rix > 0 && (rs->rs_rates[ra->ra_rix] & IEEE80211_RATE_VAL) > 72;
206178354Ssam	     ra->ra_rix--)
207178354Ssam		;
208178354Ssam	ni->ni_txrate = rs->rs_rates[ra->ra_rix] & IEEE80211_RATE_VAL;
209178354Ssam	ra->ra_ticks = ticks;
210178354Ssam
211178354Ssam	IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_RATECTL, ni,
212178354Ssam	    "RSSADAPT initial rate %d", ni->ni_txrate);
213178354Ssam}
214178354Ssam
215206358Srpaulostatic void
216206358Srpaulorssadapt_node_deinit(struct ieee80211_node *ni)
217206358Srpaulo{
218206358Srpaulo
219283538Sadrian	IEEE80211_FREE(ni->ni_rctls, M_80211_RATECTL);
220206358Srpaulo}
221206358Srpaulo
222178354Ssamstatic __inline int
223178354Ssambucket(int pktlen)
224178354Ssam{
225178354Ssam	int i, top, thridx;
226178354Ssam
227178354Ssam	for (i = 0, top = IEEE80211_RSSADAPT_BKT0;
228178354Ssam	     i < IEEE80211_RSSADAPT_BKTS;
229178354Ssam	     i++, top <<= IEEE80211_RSSADAPT_BKTPOWER) {
230178354Ssam		thridx = i;
231178354Ssam		if (pktlen <= top)
232178354Ssam			break;
233178354Ssam	}
234178354Ssam	return thridx;
235178354Ssam}
236178354Ssam
237206358Srpaulostatic int
238206358Srpaulorssadapt_rate(struct ieee80211_node *ni, void *arg __unused, uint32_t iarg)
239178354Ssam{
240206358Srpaulo	struct ieee80211_rssadapt_node *ra = ni->ni_rctls;
241206358Srpaulo	u_int pktlen = iarg;
242343035Savos	const struct ieee80211_rateset *rs;
243178354Ssam	uint16_t (*thrs)[IEEE80211_RATE_SIZE];
244178354Ssam	int rix, rssi;
245178354Ssam
246343035Savos	/* XXX should return -1 here, but drivers may not expect this... */
247343035Savos	if (!ra)
248343035Savos	{
249343035Savos		ni->ni_txrate = ni->ni_rates.rs_rates[0];
250343035Savos		return 0;
251343035Savos	}
252343035Savos
253343035Savos	rs = &ra->ra_rates;
254178354Ssam	if ((ticks - ra->ra_ticks) > ra->ra_rs->interval) {
255178354Ssam		rssadapt_updatestats(ra);
256178354Ssam		ra->ra_ticks = ticks;
257178354Ssam	}
258178354Ssam
259178354Ssam	thrs = &ra->ra_rate_thresh[bucket(pktlen)];
260178354Ssam
261178354Ssam	/* XXX this is average rssi, should be using last value */
262178354Ssam	rssi = ni->ni_ic->ic_node_getrssi(ni);
263178354Ssam	for (rix = rs->rs_nrates-1; rix >= 0; rix--)
264178354Ssam		if ((*thrs)[rix] < (rssi << 8))
265178354Ssam			break;
266178354Ssam	if (rix != ra->ra_rix) {
267178354Ssam		/* update public rate */
268178354Ssam		ni->ni_txrate = ni->ni_rates.rs_rates[rix] & IEEE80211_RATE_VAL;
269178354Ssam		ra->ra_rix = rix;
270178354Ssam
271178354Ssam		IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_RATECTL, ni,
272178354Ssam		    "RSSADAPT new rate %d (pktlen %d rssi %d)",
273178354Ssam		    ni->ni_txrate, pktlen, rssi);
274178354Ssam	}
275178354Ssam	return rix;
276178354Ssam}
277178354Ssam
278178354Ssam/*
279178354Ssam * Adapt the data rate to suit the conditions.  When a transmitted
280178354Ssam * packet is dropped after RAL_RSSADAPT_RETRY_LIMIT retransmissions,
281178354Ssam * raise the RSS threshold for transmitting packets of similar length at
282178354Ssam * the same data rate.
283178354Ssam */
284206358Srpaulostatic void
285206358Srpaulorssadapt_lower_rate(struct ieee80211_rssadapt_node *ra, int pktlen, int rssi)
286178354Ssam{
287178354Ssam	uint16_t last_thr;
288178354Ssam	uint16_t (*thrs)[IEEE80211_RATE_SIZE];
289178354Ssam	u_int rix;
290178354Ssam
291178354Ssam	thrs = &ra->ra_rate_thresh[bucket(pktlen)];
292178354Ssam
293178354Ssam	rix = ra->ra_rix;
294178354Ssam	last_thr = (*thrs)[rix];
295178354Ssam	(*thrs)[rix] = interpolate(master_expavgctl.rc_thresh,
296178354Ssam	    last_thr, (rssi << 8));
297178354Ssam
298178354Ssam	IEEE80211_DPRINTF(ra->ra_rs->vap, IEEE80211_MSG_RATECTL,
299178354Ssam	    "RSSADAPT lower threshold for rate %d (last_thr %d new thr %d rssi %d)\n",
300178354Ssam	    ra->ra_rates.rs_rates[rix + 1] & IEEE80211_RATE_VAL,
301178354Ssam	    last_thr, (*thrs)[rix], rssi);
302178354Ssam}
303178354Ssam
304206358Srpaulostatic void
305206358Srpaulorssadapt_raise_rate(struct ieee80211_rssadapt_node *ra, int pktlen, int rssi)
306178354Ssam{
307178354Ssam	uint16_t (*thrs)[IEEE80211_RATE_SIZE];
308178354Ssam	uint16_t newthr, oldthr;
309178354Ssam	int rix;
310178354Ssam
311178354Ssam	thrs = &ra->ra_rate_thresh[bucket(pktlen)];
312178354Ssam
313178354Ssam	rix = ra->ra_rix;
314178354Ssam	if ((*thrs)[rix + 1] > (*thrs)[rix]) {
315178354Ssam		oldthr = (*thrs)[rix + 1];
316178354Ssam		if ((*thrs)[rix] == 0)
317178354Ssam			newthr = (rssi << 8);
318178354Ssam		else
319178354Ssam			newthr = (*thrs)[rix];
320178354Ssam		(*thrs)[rix + 1] = interpolate(master_expavgctl.rc_decay,
321178354Ssam		    oldthr, newthr);
322178354Ssam
323178354Ssam		IEEE80211_DPRINTF(ra->ra_rs->vap, IEEE80211_MSG_RATECTL,
324178354Ssam		    "RSSADAPT raise threshold for rate %d (oldthr %d newthr %d rssi %d)\n",
325178354Ssam		    ra->ra_rates.rs_rates[rix + 1] & IEEE80211_RATE_VAL,
326178354Ssam		    oldthr, newthr, rssi);
327178354Ssam
328178354Ssam		ra->ra_last_raise = ticks;
329178354Ssam	}
330178354Ssam}
331178354Ssam
332206358Srpaulostatic void
333206358Srpaulorssadapt_tx_complete(const struct ieee80211vap *vap,
334206358Srpaulo    const struct ieee80211_node *ni, int success, void *arg1, void *arg2)
335206358Srpaulo{
336206358Srpaulo	struct ieee80211_rssadapt_node *ra = ni->ni_rctls;
337206358Srpaulo	int pktlen = *(int *)arg1, rssi = *(int *)arg2;
338206358Srpaulo
339343035Savos	if (!ra)
340343035Savos		return;
341343035Savos
342206358Srpaulo	if (success) {
343206358Srpaulo		ra->ra_nok++;
344206358Srpaulo		if ((ra->ra_rix + 1) < ra->ra_rates.rs_nrates &&
345206358Srpaulo		    (ticks - ra->ra_last_raise) >= ra->ra_raise_interval)
346206358Srpaulo			rssadapt_raise_rate(ra, pktlen, rssi);
347206358Srpaulo	} else {
348206358Srpaulo		ra->ra_nfail++;
349206358Srpaulo		rssadapt_lower_rate(ra, pktlen, rssi);
350206358Srpaulo	}
351206358Srpaulo}
352206358Srpaulo
353178354Ssamstatic int
354178354Ssamrssadapt_sysctl_interval(SYSCTL_HANDLER_ARGS)
355178354Ssam{
356206358Srpaulo	struct ieee80211vap *vap = arg1;
357206358Srpaulo	struct ieee80211_rssadapt *rs = vap->iv_rs;
358343035Savos	int msecs, error;
359178354Ssam
360343035Savos	if (!rs)
361343035Savos		return ENOMEM;
362343035Savos
363343035Savos	msecs = ticks_to_msecs(rs->interval);
364178354Ssam	error = sysctl_handle_int(oidp, &msecs, 0, req);
365178354Ssam	if (error || !req->newptr)
366178354Ssam		return error;
367206358Srpaulo	rssadapt_setinterval(vap, msecs);
368178354Ssam	return 0;
369178354Ssam}
370178354Ssam
371178354Ssamstatic void
372206358Srpaulorssadapt_sysctlattach(struct ieee80211vap *vap,
373178354Ssam    struct sysctl_ctx_list *ctx, struct sysctl_oid *tree)
374178354Ssam{
375178354Ssam
376178354Ssam	SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
377206358Srpaulo	    "rssadapt_rate_interval", CTLTYPE_INT | CTLFLAG_RW, vap,
378178354Ssam	    0, rssadapt_sysctl_interval, "I", "rssadapt operation interval (ms)");
379178354Ssam}
380