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