ieee80211_power.c revision 178354
1170530Ssam/*-
2178354Ssam * Copyright (c) 2002-2008 Sam Leffler, Errno Consulting
3170530Ssam * All rights reserved.
4170530Ssam *
5170530Ssam * Redistribution and use in source and binary forms, with or without
6170530Ssam * modification, are permitted provided that the following conditions
7170530Ssam * are met:
8170530Ssam * 1. Redistributions of source code must retain the above copyright
9170530Ssam *    notice, this list of conditions and the following disclaimer.
10170530Ssam * 2. Redistributions in binary form must reproduce the above copyright
11170530Ssam *    notice, this list of conditions and the following disclaimer in the
12170530Ssam *    documentation and/or other materials provided with the distribution.
13170530Ssam *
14170530Ssam * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15170530Ssam * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16170530Ssam * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17170530Ssam * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18170530Ssam * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19170530Ssam * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20170530Ssam * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21170530Ssam * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22170530Ssam * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23170530Ssam * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24170530Ssam */
25170530Ssam
26170530Ssam#include <sys/cdefs.h>
27170530Ssam__FBSDID("$FreeBSD: head/sys/net80211/ieee80211_power.c 178354 2008-04-20 20:35:46Z sam $");
28170530Ssam
29170530Ssam/*
30170530Ssam * IEEE 802.11 power save support.
31170530Ssam */
32178354Ssam#include "opt_wlan.h"
33178354Ssam
34170530Ssam#include <sys/param.h>
35170530Ssam#include <sys/systm.h>
36170530Ssam#include <sys/kernel.h>
37170530Ssam
38170530Ssam#include <sys/socket.h>
39170530Ssam
40170530Ssam#include <net/if.h>
41170530Ssam#include <net/if_media.h>
42170530Ssam#include <net/ethernet.h>
43170530Ssam
44170530Ssam#include <net80211/ieee80211_var.h>
45170530Ssam
46170530Ssam#include <net/bpf.h>
47170530Ssam
48178354Ssamstatic void ieee80211_update_ps(struct ieee80211vap *, int);
49178354Ssamstatic int ieee80211_set_tim(struct ieee80211_node *, int);
50170530Ssam
51178354SsamMALLOC_DEFINE(M_80211_POWER, "80211power", "802.11 power save state");
52178354Ssam
53170530Ssamvoid
54170530Ssamieee80211_power_attach(struct ieee80211com *ic)
55170530Ssam{
56178354Ssam}
57178354Ssam
58178354Ssamvoid
59178354Ssamieee80211_power_detach(struct ieee80211com *ic)
60178354Ssam{
61178354Ssam}
62178354Ssam
63178354Ssamvoid
64178354Ssamieee80211_power_vattach(struct ieee80211vap *vap)
65178354Ssam{
66178354Ssam	if (vap->iv_opmode == IEEE80211_M_HOSTAP ||
67178354Ssam	    vap->iv_opmode == IEEE80211_M_IBSS) {
68170530Ssam		/* NB: driver should override */
69178354Ssam		vap->iv_update_ps = ieee80211_update_ps;
70178354Ssam		vap->iv_set_tim = ieee80211_set_tim;
71170530Ssam	}
72170530Ssam}
73170530Ssam
74170530Ssamvoid
75178354Ssamieee80211_power_latevattach(struct ieee80211vap *vap)
76170530Ssam{
77170530Ssam	/*
78170530Ssam	 * Allocate these only if needed.  Beware that we
79170530Ssam	 * know adhoc mode doesn't support ATIM yet...
80170530Ssam	 */
81178354Ssam	if (vap->iv_opmode == IEEE80211_M_HOSTAP) {
82178354Ssam		vap->iv_tim_len = howmany(vap->iv_max_aid,8) * sizeof(uint8_t);
83178354Ssam		MALLOC(vap->iv_tim_bitmap, uint8_t *, vap->iv_tim_len,
84178354Ssam			M_80211_POWER, M_NOWAIT | M_ZERO);
85178354Ssam		if (vap->iv_tim_bitmap == NULL) {
86170530Ssam			printf("%s: no memory for TIM bitmap!\n", __func__);
87170530Ssam			/* XXX good enough to keep from crashing? */
88178354Ssam			vap->iv_tim_len = 0;
89170530Ssam		}
90170530Ssam	}
91170530Ssam}
92170530Ssam
93170530Ssamvoid
94178354Ssamieee80211_power_vdetach(struct ieee80211vap *vap)
95170530Ssam{
96178354Ssam	if (vap->iv_tim_bitmap != NULL) {
97178354Ssam		FREE(vap->iv_tim_bitmap, M_80211_POWER);
98178354Ssam		vap->iv_tim_bitmap = NULL;
99170530Ssam	}
100170530Ssam}
101170530Ssam
102170530Ssam/*
103170530Ssam * Clear any frames queued on a node's power save queue.
104170530Ssam * The number of frames that were present is returned.
105170530Ssam */
106170530Ssamint
107170530Ssamieee80211_node_saveq_drain(struct ieee80211_node *ni)
108170530Ssam{
109170530Ssam	int qlen;
110170530Ssam
111170530Ssam	IEEE80211_NODE_SAVEQ_LOCK(ni);
112170530Ssam	qlen = IEEE80211_NODE_SAVEQ_QLEN(ni);
113170530Ssam	_IF_DRAIN(&ni->ni_savedq);
114170530Ssam	IEEE80211_NODE_SAVEQ_UNLOCK(ni);
115170530Ssam
116170530Ssam	return qlen;
117170530Ssam}
118170530Ssam
119170530Ssam/*
120170530Ssam * Age frames on the power save queue. The aging interval is
121170530Ssam * 4 times the listen interval specified by the station.  This
122170530Ssam * number is factored into the age calculations when the frame
123170530Ssam * is placed on the queue.  We store ages as time differences
124170530Ssam * so we can check and/or adjust only the head of the list.
125170530Ssam * If a frame's age exceeds the threshold then discard it.
126170530Ssam * The number of frames discarded is returned so the caller
127170530Ssam * can check if it needs to adjust the tim.
128170530Ssam */
129170530Ssamint
130170530Ssamieee80211_node_saveq_age(struct ieee80211_node *ni)
131170530Ssam{
132170530Ssam	int discard = 0;
133170530Ssam
134170530Ssam	if (IEEE80211_NODE_SAVEQ_QLEN(ni) != 0) {
135178354Ssam#ifdef IEEE80211_DEBUG
136178354Ssam		struct ieee80211vap *vap = ni->ni_vap;
137178354Ssam#endif
138170530Ssam		struct mbuf *m;
139170530Ssam
140170530Ssam		IEEE80211_NODE_SAVEQ_LOCK(ni);
141170530Ssam		while (IF_POLL(&ni->ni_savedq, m) != NULL &&
142170530Ssam		     M_AGE_GET(m) < IEEE80211_INACT_WAIT) {
143178354Ssam			IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni,
144178354Ssam			     "discard frame, age %u", M_AGE_GET(m));
145170530Ssam			_IEEE80211_NODE_SAVEQ_DEQUEUE_HEAD(ni, m);
146170530Ssam			m_freem(m);
147170530Ssam			discard++;
148170530Ssam		}
149170530Ssam		if (m != NULL)
150170530Ssam			M_AGE_SUB(m, IEEE80211_INACT_WAIT);
151170530Ssam		IEEE80211_NODE_SAVEQ_UNLOCK(ni);
152170530Ssam
153178354Ssam		IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni,
154170530Ssam		    "discard %u frames for age", discard);
155170530Ssam		IEEE80211_NODE_STAT_ADD(ni, ps_discard, discard);
156170530Ssam	}
157170530Ssam	return discard;
158170530Ssam}
159170530Ssam
160170530Ssam/*
161178354Ssam * Handle a change in the PS station occupancy.
162178354Ssam */
163178354Ssamstatic void
164178354Ssamieee80211_update_ps(struct ieee80211vap *vap, int nsta)
165178354Ssam{
166178354Ssam
167178354Ssam	KASSERT(vap->iv_opmode == IEEE80211_M_HOSTAP ||
168178354Ssam		vap->iv_opmode == IEEE80211_M_IBSS,
169178354Ssam		("operating mode %u", vap->iv_opmode));
170178354Ssam}
171178354Ssam
172178354Ssam/*
173170530Ssam * Indicate whether there are frames queued for a station in power-save mode.
174170530Ssam */
175178354Ssamstatic int
176170530Ssamieee80211_set_tim(struct ieee80211_node *ni, int set)
177170530Ssam{
178178354Ssam	struct ieee80211vap *vap = ni->ni_vap;
179170530Ssam	struct ieee80211com *ic = ni->ni_ic;
180170530Ssam	uint16_t aid;
181178354Ssam	int changed;
182170530Ssam
183178354Ssam	KASSERT(vap->iv_opmode == IEEE80211_M_HOSTAP ||
184178354Ssam		vap->iv_opmode == IEEE80211_M_IBSS,
185178354Ssam		("operating mode %u", vap->iv_opmode));
186170530Ssam
187170530Ssam	aid = IEEE80211_AID(ni->ni_associd);
188178354Ssam	KASSERT(aid < vap->iv_max_aid,
189178354Ssam		("bogus aid %u, max %u", aid, vap->iv_max_aid));
190170530Ssam
191178354Ssam	IEEE80211_LOCK(ic);
192178354Ssam	changed = (set != (isset(vap->iv_tim_bitmap, aid) != 0));
193178354Ssam	if (changed) {
194170530Ssam		if (set) {
195178354Ssam			setbit(vap->iv_tim_bitmap, aid);
196178354Ssam			vap->iv_ps_pending++;
197170530Ssam		} else {
198178354Ssam			clrbit(vap->iv_tim_bitmap, aid);
199178354Ssam			vap->iv_ps_pending--;
200170530Ssam		}
201178354Ssam		/* NB: we know vap is in RUN state so no need to check */
202178354Ssam		vap->iv_update_beacon(vap, IEEE80211_BEACON_TIM);
203170530Ssam	}
204178354Ssam	IEEE80211_UNLOCK(ic);
205178354Ssam
206178354Ssam	return changed;
207170530Ssam}
208170530Ssam
209170530Ssam/*
210170530Ssam * Save an outbound packet for a node in power-save sleep state.
211170530Ssam * The new packet is placed on the node's saved queue, and the TIM
212170530Ssam * is changed, if necessary.
213170530Ssam */
214170530Ssamvoid
215170530Ssamieee80211_pwrsave(struct ieee80211_node *ni, struct mbuf *m)
216170530Ssam{
217178354Ssam	struct ieee80211vap *vap = ni->ni_vap;
218170530Ssam	struct ieee80211com *ic = ni->ni_ic;
219170530Ssam	int qlen, age;
220170530Ssam
221170530Ssam	IEEE80211_NODE_SAVEQ_LOCK(ni);
222170530Ssam	if (_IF_QFULL(&ni->ni_savedq)) {
223170530Ssam		_IF_DROP(&ni->ni_savedq);
224170530Ssam		IEEE80211_NODE_SAVEQ_UNLOCK(ni);
225178354Ssam		IEEE80211_NOTE(vap, IEEE80211_MSG_ANY, ni,
226178354Ssam		    "pwr save q overflow, drops %d (size %d)",
227178354Ssam		    ni->ni_savedq.ifq_drops, IEEE80211_PS_MAX_QUEUE);
228170530Ssam#ifdef IEEE80211_DEBUG
229178354Ssam		if (ieee80211_msg_dumppkts(vap))
230178354Ssam			ieee80211_dump_pkt(ni->ni_ic, mtod(m, caddr_t),
231178354Ssam			    m->m_len, -1, -1);
232170530Ssam#endif
233170530Ssam		m_freem(m);
234170530Ssam		return;
235170530Ssam	}
236170530Ssam	/*
237170530Ssam	 * Tag the frame with it's expiry time and insert
238170530Ssam	 * it in the queue.  The aging interval is 4 times
239170530Ssam	 * the listen interval specified by the station.
240170530Ssam	 * Frames that sit around too long are reclaimed
241170530Ssam	 * using this information.
242170530Ssam	 */
243170530Ssam	/* TU -> secs.  XXX handle overflow? */
244170530Ssam	age = IEEE80211_TU_TO_MS((ni->ni_intval * ic->ic_bintval) << 2) / 1000;
245170530Ssam	_IEEE80211_NODE_SAVEQ_ENQUEUE(ni, m, qlen, age);
246170530Ssam	IEEE80211_NODE_SAVEQ_UNLOCK(ni);
247170530Ssam
248178354Ssam	IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni,
249178354Ssam	    "save frame with age %d, %u now queued", age, qlen);
250170530Ssam
251178354Ssam	if (qlen == 1 && vap->iv_set_tim != NULL)
252178354Ssam		vap->iv_set_tim(ni, 1);
253170530Ssam}
254170530Ssam
255170530Ssam/*
256178354Ssam * Unload the frames from the ps q but don't send them
257178354Ssam * to the driver yet.  We do this in two stages to minimize
258178354Ssam * locking but also because there's no easy way to preserve
259178354Ssam * ordering given the existing ifnet access mechanisms.
260178354Ssam * XXX could be optimized
261170530Ssam */
262178354Ssamstatic void
263178354Ssampwrsave_flushq(struct ieee80211_node *ni)
264170530Ssam{
265170530Ssam	struct mbuf *m, *mhead, *mtail;
266170530Ssam	int mcount;
267170530Ssam
268170530Ssam	IEEE80211_NODE_SAVEQ_LOCK(ni);
269170530Ssam	mcount = IEEE80211_NODE_SAVEQ_QLEN(ni);
270170530Ssam	mhead = mtail = NULL;
271170530Ssam	for (;;) {
272170530Ssam		_IEEE80211_NODE_SAVEQ_DEQUEUE_HEAD(ni, m);
273170530Ssam		if (m == NULL)
274170530Ssam			break;
275170530Ssam		if (mhead == NULL) {
276170530Ssam			mhead = m;
277170530Ssam			m->m_nextpkt = NULL;
278170530Ssam		} else
279170530Ssam			mtail->m_nextpkt = m;
280170530Ssam		mtail = m;
281170530Ssam	}
282170530Ssam	IEEE80211_NODE_SAVEQ_UNLOCK(ni);
283170530Ssam	if (mhead != NULL) {
284170530Ssam		/* XXX need different driver interface */
285178354Ssam		/* XXX bypasses q max and OACTIVE */
286178354Ssam		struct ifnet *ifp = ni->ni_vap->iv_ifp;
287178354Ssam		IF_PREPEND_LIST(&ifp->if_snd, mhead, mtail, mcount);
288178354Ssam		if_start(ifp);
289170530Ssam	}
290170530Ssam}
291170530Ssam
292170530Ssam/*
293178354Ssam * Handle station power-save state change.
294178354Ssam */
295178354Ssamvoid
296178354Ssamieee80211_node_pwrsave(struct ieee80211_node *ni, int enable)
297178354Ssam{
298178354Ssam	struct ieee80211vap *vap = ni->ni_vap;
299178354Ssam	int update;
300178354Ssam
301178354Ssam	update = 0;
302178354Ssam	if (enable) {
303178354Ssam		if ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) == 0) {
304178354Ssam			vap->iv_ps_sta++;
305178354Ssam			update = 1;
306178354Ssam		}
307178354Ssam		ni->ni_flags |= IEEE80211_NODE_PWR_MGT;
308178354Ssam		IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni,
309178354Ssam		    "power save mode on, %u sta's in ps mode", vap->iv_ps_sta);
310178354Ssam
311178354Ssam		if (update)
312178354Ssam			vap->iv_update_ps(vap, vap->iv_ps_sta);
313178354Ssam	} else {
314178354Ssam		if (ni->ni_flags & IEEE80211_NODE_PWR_MGT) {
315178354Ssam			vap->iv_ps_sta--;
316178354Ssam			update = 1;
317178354Ssam		}
318178354Ssam		ni->ni_flags &= ~IEEE80211_NODE_PWR_MGT;
319178354Ssam		IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni,
320178354Ssam		    "power save mode off, %u sta's in ps mode", vap->iv_ps_sta);
321178354Ssam
322178354Ssam		/* NB: order here is intentional so TIM is clear before flush */
323178354Ssam		if (vap->iv_set_tim != NULL)
324178354Ssam			vap->iv_set_tim(ni, 0);
325178354Ssam		if (update) {
326178354Ssam			/* NB if no sta's in ps, driver should flush mc q */
327178354Ssam			vap->iv_update_ps(vap, vap->iv_ps_sta);
328178354Ssam		}
329178354Ssam		pwrsave_flushq(ni);
330178354Ssam	}
331178354Ssam}
332178354Ssam
333178354Ssam/*
334170530Ssam * Handle power-save state change in station mode.
335170530Ssam */
336170530Ssamvoid
337178354Ssamieee80211_sta_pwrsave(struct ieee80211vap *vap, int enable)
338170530Ssam{
339178354Ssam	struct ieee80211_node *ni = vap->iv_bss;
340170530Ssam	int qlen;
341170530Ssam
342170530Ssam	if (!((enable != 0) ^ ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) != 0)))
343170530Ssam		return;
344170530Ssam
345178354Ssam	IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni,
346170530Ssam	    "sta power save mode %s", enable ? "on" : "off");
347170530Ssam	if (!enable) {
348170530Ssam		ni->ni_flags &= ~IEEE80211_NODE_PWR_MGT;
349170530Ssam		ieee80211_send_nulldata(ieee80211_ref_node(ni));
350170530Ssam		/*
351170530Ssam		 * Flush any queued frames; we can do this immediately
352170530Ssam		 * because we know they'll be queued behind the null
353170530Ssam		 * data frame we send the ap.
354170530Ssam		 * XXX can we use a data frame to take us out of ps?
355170530Ssam		 */
356170530Ssam		qlen = IEEE80211_NODE_SAVEQ_QLEN(ni);
357170530Ssam		if (qlen != 0) {
358178354Ssam			IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni,
359170530Ssam			    "flush ps queue, %u packets queued", qlen);
360178354Ssam			pwrsave_flushq(ni);
361170530Ssam		}
362170530Ssam	} else {
363170530Ssam		ni->ni_flags |= IEEE80211_NODE_PWR_MGT;
364170530Ssam		ieee80211_send_nulldata(ieee80211_ref_node(ni));
365170530Ssam	}
366170530Ssam}
367