ieee80211_power.c revision 275984
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 275984 2014-12-21 04:58:45Z adrian $"); 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> 41257176Sglebius#include <net/if_var.h> 42170530Ssam#include <net/if_media.h> 43170530Ssam#include <net/ethernet.h> 44170530Ssam 45170530Ssam#include <net80211/ieee80211_var.h> 46170530Ssam 47170530Ssam#include <net/bpf.h> 48170530Ssam 49178354Ssamstatic void ieee80211_update_ps(struct ieee80211vap *, int); 50178354Ssamstatic int ieee80211_set_tim(struct ieee80211_node *, int); 51170530Ssam 52227293Sedstatic MALLOC_DEFINE(M_80211_POWER, "80211power", "802.11 power save state"); 53178354Ssam 54170530Ssamvoid 55170530Ssamieee80211_power_attach(struct ieee80211com *ic) 56170530Ssam{ 57178354Ssam} 58178354Ssam 59178354Ssamvoid 60178354Ssamieee80211_power_detach(struct ieee80211com *ic) 61178354Ssam{ 62178354Ssam} 63178354Ssam 64178354Ssamvoid 65178354Ssamieee80211_power_vattach(struct ieee80211vap *vap) 66178354Ssam{ 67178354Ssam if (vap->iv_opmode == IEEE80211_M_HOSTAP || 68178354Ssam vap->iv_opmode == IEEE80211_M_IBSS) { 69170530Ssam /* NB: driver should override */ 70178354Ssam vap->iv_update_ps = ieee80211_update_ps; 71178354Ssam vap->iv_set_tim = ieee80211_set_tim; 72170530Ssam } 73241138Sadrian vap->iv_node_ps = ieee80211_node_pwrsave; 74241138Sadrian vap->iv_sta_ps = ieee80211_sta_pwrsave; 75170530Ssam} 76170530Ssam 77170530Ssamvoid 78178354Ssamieee80211_power_latevattach(struct ieee80211vap *vap) 79170530Ssam{ 80170530Ssam /* 81170530Ssam * Allocate these only if needed. Beware that we 82170530Ssam * know adhoc mode doesn't support ATIM yet... 83170530Ssam */ 84178354Ssam if (vap->iv_opmode == IEEE80211_M_HOSTAP) { 85178354Ssam vap->iv_tim_len = howmany(vap->iv_max_aid,8) * sizeof(uint8_t); 86186302Ssam vap->iv_tim_bitmap = (uint8_t *) malloc(vap->iv_tim_len, 87178354Ssam M_80211_POWER, M_NOWAIT | M_ZERO); 88178354Ssam if (vap->iv_tim_bitmap == NULL) { 89170530Ssam printf("%s: no memory for TIM bitmap!\n", __func__); 90170530Ssam /* XXX good enough to keep from crashing? */ 91178354Ssam vap->iv_tim_len = 0; 92170530Ssam } 93170530Ssam } 94170530Ssam} 95170530Ssam 96170530Ssamvoid 97178354Ssamieee80211_power_vdetach(struct ieee80211vap *vap) 98170530Ssam{ 99178354Ssam if (vap->iv_tim_bitmap != NULL) { 100186302Ssam free(vap->iv_tim_bitmap, M_80211_POWER); 101178354Ssam vap->iv_tim_bitmap = NULL; 102170530Ssam } 103170530Ssam} 104170530Ssam 105184288Ssamvoid 106184288Ssamieee80211_psq_init(struct ieee80211_psq *psq, const char *name) 107184288Ssam{ 108223842Skevlo memset(psq, 0, sizeof(*psq)); 109184288Ssam psq->psq_maxlen = IEEE80211_PS_MAX_QUEUE; 110184288Ssam IEEE80211_PSQ_INIT(psq, name); /* OS-dependent setup */ 111184288Ssam} 112184288Ssam 113184288Ssamvoid 114184288Ssamieee80211_psq_cleanup(struct ieee80211_psq *psq) 115184288Ssam{ 116184288Ssam#if 0 117184288Ssam psq_drain(psq); /* XXX should not be needed? */ 118184288Ssam#else 119184288Ssam KASSERT(psq->psq_len == 0, ("%d frames on ps q", psq->psq_len)); 120184288Ssam#endif 121184288Ssam IEEE80211_PSQ_DESTROY(psq); /* OS-dependent cleanup */ 122184288Ssam} 123184288Ssam 124170530Ssam/* 125184288Ssam * Return the highest priority frame in the ps queue. 126184288Ssam */ 127184288Ssamstruct mbuf * 128184288Ssamieee80211_node_psq_dequeue(struct ieee80211_node *ni, int *qlen) 129184288Ssam{ 130184288Ssam struct ieee80211_psq *psq = &ni->ni_psq; 131184288Ssam struct ieee80211_psq_head *qhead; 132184288Ssam struct mbuf *m; 133184288Ssam 134184288Ssam IEEE80211_PSQ_LOCK(psq); 135184288Ssam qhead = &psq->psq_head[0]; 136184288Ssamagain: 137184288Ssam if ((m = qhead->head) != NULL) { 138184288Ssam if ((qhead->head = m->m_nextpkt) == NULL) 139184288Ssam qhead->tail = NULL; 140184288Ssam KASSERT(qhead->len > 0, ("qhead len %d", qhead->len)); 141184288Ssam qhead->len--; 142184288Ssam KASSERT(psq->psq_len > 0, ("psq len %d", psq->psq_len)); 143184288Ssam psq->psq_len--; 144184288Ssam m->m_nextpkt = NULL; 145184288Ssam } 146184288Ssam if (m == NULL && qhead == &psq->psq_head[0]) { 147184288Ssam /* Algol-68 style for loop */ 148184288Ssam qhead = &psq->psq_head[1]; 149184288Ssam goto again; 150184288Ssam } 151184288Ssam if (qlen != NULL) 152184288Ssam *qlen = psq->psq_len; 153184288Ssam IEEE80211_PSQ_UNLOCK(psq); 154184288Ssam return m; 155184288Ssam} 156184288Ssam 157184288Ssam/* 158184288Ssam * Reclaim an mbuf from the ps q. If marked with M_ENCAP 159184288Ssam * we assume there is a node reference that must be relcaimed. 160184288Ssam */ 161184288Ssamstatic void 162184288Ssampsq_mfree(struct mbuf *m) 163184288Ssam{ 164184288Ssam if (m->m_flags & M_ENCAP) { 165184288Ssam struct ieee80211_node *ni = (void *) m->m_pkthdr.rcvif; 166184288Ssam ieee80211_free_node(ni); 167184288Ssam } 168184288Ssam m->m_nextpkt = NULL; 169184288Ssam m_freem(m); 170184288Ssam} 171184288Ssam 172184288Ssam/* 173184288Ssam * Clear any frames queued in the power save queue. 174170530Ssam * The number of frames that were present is returned. 175170530Ssam */ 176184288Ssamstatic int 177184288Ssampsq_drain(struct ieee80211_psq *psq) 178170530Ssam{ 179184288Ssam struct ieee80211_psq_head *qhead; 180184288Ssam struct mbuf *m; 181170530Ssam int qlen; 182170530Ssam 183184288Ssam IEEE80211_PSQ_LOCK(psq); 184184288Ssam qlen = psq->psq_len; 185184288Ssam qhead = &psq->psq_head[0]; 186184288Ssamagain: 187184288Ssam while ((m = qhead->head) != NULL) { 188184288Ssam qhead->head = m->m_nextpkt; 189184288Ssam psq_mfree(m); 190184288Ssam } 191184288Ssam qhead->tail = NULL; 192184288Ssam qhead->len = 0; 193184288Ssam if (qhead == &psq->psq_head[0]) { /* Algol-68 style for loop */ 194184288Ssam qhead = &psq->psq_head[1]; 195184288Ssam goto again; 196184288Ssam } 197184288Ssam psq->psq_len = 0; 198184288Ssam IEEE80211_PSQ_UNLOCK(psq); 199170530Ssam 200170530Ssam return qlen; 201170530Ssam} 202170530Ssam 203170530Ssam/* 204184288Ssam * Clear any frames queued in the power save queue. 205184288Ssam * The number of frames that were present is returned. 206184288Ssam */ 207184288Ssamint 208184288Ssamieee80211_node_psq_drain(struct ieee80211_node *ni) 209184288Ssam{ 210184288Ssam return psq_drain(&ni->ni_psq); 211184288Ssam} 212184288Ssam 213184288Ssam/* 214170530Ssam * Age frames on the power save queue. The aging interval is 215170530Ssam * 4 times the listen interval specified by the station. This 216170530Ssam * number is factored into the age calculations when the frame 217170530Ssam * is placed on the queue. We store ages as time differences 218170530Ssam * so we can check and/or adjust only the head of the list. 219170530Ssam * If a frame's age exceeds the threshold then discard it. 220170530Ssam * The number of frames discarded is returned so the caller 221170530Ssam * can check if it needs to adjust the tim. 222170530Ssam */ 223170530Ssamint 224184288Ssamieee80211_node_psq_age(struct ieee80211_node *ni) 225170530Ssam{ 226184288Ssam struct ieee80211_psq *psq = &ni->ni_psq; 227170530Ssam int discard = 0; 228170530Ssam 229184288Ssam if (psq->psq_len != 0) { 230178354Ssam#ifdef IEEE80211_DEBUG 231178354Ssam struct ieee80211vap *vap = ni->ni_vap; 232178354Ssam#endif 233184288Ssam struct ieee80211_psq_head *qhead; 234170530Ssam struct mbuf *m; 235170530Ssam 236184288Ssam IEEE80211_PSQ_LOCK(psq); 237184288Ssam qhead = &psq->psq_head[0]; 238184288Ssam again: 239184288Ssam while ((m = qhead->head) != NULL && 240184288Ssam M_AGE_GET(m) < IEEE80211_INACT_WAIT) { 241178354Ssam IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, 242178354Ssam "discard frame, age %u", M_AGE_GET(m)); 243184288Ssam if ((qhead->head = m->m_nextpkt) == NULL) 244184288Ssam qhead->tail = NULL; 245184288Ssam KASSERT(qhead->len > 0, ("qhead len %d", qhead->len)); 246184288Ssam qhead->len--; 247184288Ssam KASSERT(psq->psq_len > 0, ("psq len %d", psq->psq_len)); 248184288Ssam psq->psq_len--; 249184288Ssam psq_mfree(m); 250170530Ssam discard++; 251170530Ssam } 252184288Ssam if (qhead == &psq->psq_head[0]) { /* Algol-68 style for loop */ 253184288Ssam qhead = &psq->psq_head[1]; 254184288Ssam goto again; 255184288Ssam } 256170530Ssam if (m != NULL) 257170530Ssam M_AGE_SUB(m, IEEE80211_INACT_WAIT); 258184288Ssam IEEE80211_PSQ_UNLOCK(psq); 259170530Ssam 260178354Ssam IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, 261170530Ssam "discard %u frames for age", discard); 262170530Ssam IEEE80211_NODE_STAT_ADD(ni, ps_discard, discard); 263170530Ssam } 264170530Ssam return discard; 265170530Ssam} 266170530Ssam 267170530Ssam/* 268178354Ssam * Handle a change in the PS station occupancy. 269178354Ssam */ 270178354Ssamstatic void 271178354Ssamieee80211_update_ps(struct ieee80211vap *vap, int nsta) 272178354Ssam{ 273178354Ssam 274178354Ssam KASSERT(vap->iv_opmode == IEEE80211_M_HOSTAP || 275178354Ssam vap->iv_opmode == IEEE80211_M_IBSS, 276178354Ssam ("operating mode %u", vap->iv_opmode)); 277178354Ssam} 278178354Ssam 279178354Ssam/* 280170530Ssam * Indicate whether there are frames queued for a station in power-save mode. 281170530Ssam */ 282178354Ssamstatic int 283170530Ssamieee80211_set_tim(struct ieee80211_node *ni, int set) 284170530Ssam{ 285178354Ssam struct ieee80211vap *vap = ni->ni_vap; 286170530Ssam struct ieee80211com *ic = ni->ni_ic; 287170530Ssam uint16_t aid; 288178354Ssam int changed; 289170530Ssam 290178354Ssam KASSERT(vap->iv_opmode == IEEE80211_M_HOSTAP || 291178354Ssam vap->iv_opmode == IEEE80211_M_IBSS, 292178354Ssam ("operating mode %u", vap->iv_opmode)); 293170530Ssam 294170530Ssam aid = IEEE80211_AID(ni->ni_associd); 295178354Ssam KASSERT(aid < vap->iv_max_aid, 296178354Ssam ("bogus aid %u, max %u", aid, vap->iv_max_aid)); 297170530Ssam 298178354Ssam IEEE80211_LOCK(ic); 299178354Ssam changed = (set != (isset(vap->iv_tim_bitmap, aid) != 0)); 300178354Ssam if (changed) { 301170530Ssam if (set) { 302178354Ssam setbit(vap->iv_tim_bitmap, aid); 303178354Ssam vap->iv_ps_pending++; 304170530Ssam } else { 305178354Ssam clrbit(vap->iv_tim_bitmap, aid); 306178354Ssam vap->iv_ps_pending--; 307170530Ssam } 308178354Ssam /* NB: we know vap is in RUN state so no need to check */ 309178354Ssam vap->iv_update_beacon(vap, IEEE80211_BEACON_TIM); 310170530Ssam } 311178354Ssam IEEE80211_UNLOCK(ic); 312178354Ssam 313178354Ssam return changed; 314170530Ssam} 315170530Ssam 316170530Ssam/* 317170530Ssam * Save an outbound packet for a node in power-save sleep state. 318170530Ssam * The new packet is placed on the node's saved queue, and the TIM 319170530Ssam * is changed, if necessary. 320170530Ssam */ 321184288Ssamint 322170530Ssamieee80211_pwrsave(struct ieee80211_node *ni, struct mbuf *m) 323170530Ssam{ 324184288Ssam struct ieee80211_psq *psq = &ni->ni_psq; 325178354Ssam struct ieee80211vap *vap = ni->ni_vap; 326170530Ssam struct ieee80211com *ic = ni->ni_ic; 327184288Ssam struct ieee80211_psq_head *qhead; 328170530Ssam int qlen, age; 329170530Ssam 330184288Ssam IEEE80211_PSQ_LOCK(psq); 331184288Ssam if (psq->psq_len >= psq->psq_maxlen) { 332184288Ssam psq->psq_drops++; 333184288Ssam IEEE80211_PSQ_UNLOCK(psq); 334178354Ssam IEEE80211_NOTE(vap, IEEE80211_MSG_ANY, ni, 335178354Ssam "pwr save q overflow, drops %d (size %d)", 336184288Ssam psq->psq_drops, psq->psq_len); 337170530Ssam#ifdef IEEE80211_DEBUG 338178354Ssam if (ieee80211_msg_dumppkts(vap)) 339178354Ssam ieee80211_dump_pkt(ni->ni_ic, mtod(m, caddr_t), 340178354Ssam m->m_len, -1, -1); 341170530Ssam#endif 342184288Ssam psq_mfree(m); 343184288Ssam return ENOSPC; 344170530Ssam } 345170530Ssam /* 346184288Ssam * Tag the frame with it's expiry time and insert it in 347184288Ssam * the appropriate queue. The aging interval is 4 times 348184288Ssam * the listen interval specified by the station. Frames 349184288Ssam * that sit around too long are reclaimed using this 350184288Ssam * information. 351170530Ssam */ 352170530Ssam /* TU -> secs. XXX handle overflow? */ 353170530Ssam age = IEEE80211_TU_TO_MS((ni->ni_intval * ic->ic_bintval) << 2) / 1000; 354184288Ssam /* 355184288Ssam * Encapsulated frames go on the high priority queue, 356184288Ssam * other stuff goes on the low priority queue. We use 357184288Ssam * this to order frames returned out of the driver 358184288Ssam * ahead of frames we collect in ieee80211_start. 359184288Ssam */ 360184288Ssam if (m->m_flags & M_ENCAP) 361184288Ssam qhead = &psq->psq_head[0]; 362184288Ssam else 363184288Ssam qhead = &psq->psq_head[1]; 364184288Ssam if (qhead->tail == NULL) { 365184288Ssam struct mbuf *mh; 366170530Ssam 367184288Ssam qhead->head = m; 368184288Ssam /* 369184288Ssam * Take care to adjust age when inserting the first 370184288Ssam * frame of a queue and the other queue already has 371184288Ssam * frames. We need to preserve the age difference 372184288Ssam * relationship so ieee80211_node_psq_age works. 373184288Ssam */ 374184288Ssam if (qhead == &psq->psq_head[1]) { 375184288Ssam mh = psq->psq_head[0].head; 376184288Ssam if (mh != NULL) 377184288Ssam age-= M_AGE_GET(mh); 378184288Ssam } else { 379184288Ssam mh = psq->psq_head[1].head; 380184288Ssam if (mh != NULL) { 381184288Ssam int nage = M_AGE_GET(mh) - age; 382184288Ssam /* XXX is clamping to zero good 'nuf? */ 383184288Ssam M_AGE_SET(mh, nage < 0 ? 0 : nage); 384184288Ssam } 385184288Ssam } 386184288Ssam } else { 387184288Ssam qhead->tail->m_nextpkt = m; 388184288Ssam age -= M_AGE_GET(qhead->head); 389184288Ssam } 390184288Ssam KASSERT(age >= 0, ("age %d", age)); 391184288Ssam M_AGE_SET(m, age); 392184288Ssam m->m_nextpkt = NULL; 393184288Ssam qhead->tail = m; 394184288Ssam qhead->len++; 395184288Ssam qlen = ++(psq->psq_len); 396184288Ssam IEEE80211_PSQ_UNLOCK(psq); 397184288Ssam 398178354Ssam IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, 399178354Ssam "save frame with age %d, %u now queued", age, qlen); 400170530Ssam 401178354Ssam if (qlen == 1 && vap->iv_set_tim != NULL) 402178354Ssam vap->iv_set_tim(ni, 1); 403184288Ssam 404184288Ssam return 0; 405170530Ssam} 406170530Ssam 407170530Ssam/* 408184288Ssam * Move frames from the ps q to the vap's send queue 409184288Ssam * and/or the driver's send queue; and kick the start 410184288Ssam * method for each, as appropriate. Note we're careful 411184288Ssam * to preserve packet ordering here. 412170530Ssam */ 413178354Ssamstatic void 414178354Ssampwrsave_flushq(struct ieee80211_node *ni) 415170530Ssam{ 416184288Ssam struct ieee80211_psq *psq = &ni->ni_psq; 417248069Sadrian struct ieee80211com *ic = ni->ni_ic; 418184288Ssam struct ieee80211vap *vap = ni->ni_vap; 419184288Ssam struct ieee80211_psq_head *qhead; 420184288Ssam struct ifnet *parent, *ifp; 421245464Sadrian struct mbuf *parent_q = NULL, *ifp_q = NULL; 422245464Sadrian struct mbuf *m; 423170530Ssam 424184288Ssam IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, 425184288Ssam "flush ps queue, %u packets queued", psq->psq_len); 426184288Ssam 427184288Ssam IEEE80211_PSQ_LOCK(psq); 428184288Ssam qhead = &psq->psq_head[0]; /* 802.11 frames */ 429184288Ssam if (qhead->head != NULL) { 430184288Ssam /* XXX could dispatch through vap and check M_ENCAP */ 431184288Ssam parent = vap->iv_ic->ic_ifp; 432170530Ssam /* XXX need different driver interface */ 433178354Ssam /* XXX bypasses q max and OACTIVE */ 434245464Sadrian parent_q = qhead->head; 435184288Ssam qhead->head = qhead->tail = NULL; 436184288Ssam qhead->len = 0; 437184288Ssam } else 438184288Ssam parent = NULL; 439184288Ssam 440184288Ssam qhead = &psq->psq_head[1]; /* 802.3 frames */ 441184288Ssam if (qhead->head != NULL) { 442184288Ssam ifp = vap->iv_ifp; 443184288Ssam /* XXX need different driver interface */ 444184288Ssam /* XXX bypasses q max and OACTIVE */ 445245464Sadrian ifp_q = qhead->head; 446184288Ssam qhead->head = qhead->tail = NULL; 447184288Ssam qhead->len = 0; 448184288Ssam } else 449184288Ssam ifp = NULL; 450184288Ssam psq->psq_len = 0; 451184288Ssam IEEE80211_PSQ_UNLOCK(psq); 452184288Ssam 453184288Ssam /* NB: do this outside the psq lock */ 454184288Ssam /* XXX packets might get reordered if parent is OACTIVE */ 455245464Sadrian /* parent frames, should be encapsulated */ 456245464Sadrian if (parent != NULL) { 457245464Sadrian while (parent_q != NULL) { 458245464Sadrian m = parent_q; 459245464Sadrian parent_q = m->m_nextpkt; 460254236Sadrian m->m_nextpkt = NULL; 461245464Sadrian /* must be encapsulated */ 462245464Sadrian KASSERT((m->m_flags & M_ENCAP), 463245464Sadrian ("%s: parentq with non-M_ENCAP frame!\n", 464245464Sadrian __func__)); 465245464Sadrian /* 466245464Sadrian * For encaped frames, we need to free the node 467245464Sadrian * reference upon failure. 468245464Sadrian */ 469254082Sadrian if (ieee80211_parent_xmitpkt(ic, m) != 0) 470245464Sadrian ieee80211_free_node(ni); 471245464Sadrian } 472245464Sadrian } 473245464Sadrian 474245464Sadrian /* VAP frames, aren't encapsulated */ 475245464Sadrian if (ifp != NULL) { 476245464Sadrian while (ifp_q != NULL) { 477245464Sadrian m = ifp_q; 478245464Sadrian ifp_q = m->m_nextpkt; 479254261Sadrian m->m_nextpkt = NULL; 480245464Sadrian KASSERT((!(m->m_flags & M_ENCAP)), 481245464Sadrian ("%s: vapq with M_ENCAP frame!\n", __func__)); 482254082Sadrian (void) ieee80211_vap_xmitpkt(vap, m); 483245464Sadrian } 484245464Sadrian } 485170530Ssam} 486170530Ssam 487170530Ssam/* 488178354Ssam * Handle station power-save state change. 489178354Ssam */ 490178354Ssamvoid 491178354Ssamieee80211_node_pwrsave(struct ieee80211_node *ni, int enable) 492178354Ssam{ 493178354Ssam struct ieee80211vap *vap = ni->ni_vap; 494178354Ssam int update; 495178354Ssam 496178354Ssam update = 0; 497178354Ssam if (enable) { 498178354Ssam if ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) == 0) { 499178354Ssam vap->iv_ps_sta++; 500178354Ssam update = 1; 501178354Ssam } 502178354Ssam ni->ni_flags |= IEEE80211_NODE_PWR_MGT; 503178354Ssam IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, 504178354Ssam "power save mode on, %u sta's in ps mode", vap->iv_ps_sta); 505178354Ssam 506178354Ssam if (update) 507178354Ssam vap->iv_update_ps(vap, vap->iv_ps_sta); 508178354Ssam } else { 509178354Ssam if (ni->ni_flags & IEEE80211_NODE_PWR_MGT) { 510178354Ssam vap->iv_ps_sta--; 511178354Ssam update = 1; 512178354Ssam } 513178354Ssam ni->ni_flags &= ~IEEE80211_NODE_PWR_MGT; 514178354Ssam IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, 515178354Ssam "power save mode off, %u sta's in ps mode", vap->iv_ps_sta); 516178354Ssam 517178354Ssam /* NB: order here is intentional so TIM is clear before flush */ 518178354Ssam if (vap->iv_set_tim != NULL) 519178354Ssam vap->iv_set_tim(ni, 0); 520178354Ssam if (update) { 521178354Ssam /* NB if no sta's in ps, driver should flush mc q */ 522178354Ssam vap->iv_update_ps(vap, vap->iv_ps_sta); 523178354Ssam } 524184288Ssam if (ni->ni_psq.psq_len != 0) 525184288Ssam pwrsave_flushq(ni); 526178354Ssam } 527178354Ssam} 528178354Ssam 529178354Ssam/* 530170530Ssam * Handle power-save state change in station mode. 531170530Ssam */ 532170530Ssamvoid 533178354Ssamieee80211_sta_pwrsave(struct ieee80211vap *vap, int enable) 534170530Ssam{ 535178354Ssam struct ieee80211_node *ni = vap->iv_bss; 536170530Ssam 537170530Ssam if (!((enable != 0) ^ ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) != 0))) 538170530Ssam return; 539170530Ssam 540178354Ssam IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, 541170530Ssam "sta power save mode %s", enable ? "on" : "off"); 542170530Ssam if (!enable) { 543170530Ssam ni->ni_flags &= ~IEEE80211_NODE_PWR_MGT; 544170530Ssam ieee80211_send_nulldata(ieee80211_ref_node(ni)); 545170530Ssam /* 546170530Ssam * Flush any queued frames; we can do this immediately 547170530Ssam * because we know they'll be queued behind the null 548170530Ssam * data frame we send the ap. 549170530Ssam * XXX can we use a data frame to take us out of ps? 550170530Ssam */ 551184288Ssam if (ni->ni_psq.psq_len != 0) 552178354Ssam pwrsave_flushq(ni); 553170530Ssam } else { 554170530Ssam ni->ni_flags |= IEEE80211_NODE_PWR_MGT; 555170530Ssam ieee80211_send_nulldata(ieee80211_ref_node(ni)); 556170530Ssam } 557170530Ssam} 558264855Sadrian 559264855Sadrian/* 560264855Sadrian * Handle being notified that we have data available for us in a TIM/ATIM. 561264855Sadrian * 562264855Sadrian * This may schedule a transition from _SLEEP -> _RUN if it's appropriate. 563275984Sadrian * 564275984Sadrian * In STA mode, we may have put to sleep during scan and need to be dragged 565275984Sadrian * back out of powersave mode. 566264855Sadrian */ 567264855Sadrianvoid 568264855Sadrianieee80211_sta_tim_notify(struct ieee80211vap *vap, int set) 569264855Sadrian{ 570275984Sadrian struct ieee80211com *ic = vap->iv_ic; 571275984Sadrian 572264855Sadrian /* 573264855Sadrian * Schedule the driver state change. It'll happen at some point soon. 574264855Sadrian * Since the hardware shouldn't know that we're running just yet 575264855Sadrian * (and thus tell the peer that we're awake before we actually wake 576264855Sadrian * up said hardware), we leave the actual node state transition 577264855Sadrian * up to the transition to RUN. 578264855Sadrian * 579264855Sadrian * XXX TODO: verify that the transition to RUN will wake up the 580264855Sadrian * BSS node! 581264855Sadrian */ 582264855Sadrian IEEE80211_LOCK(vap->iv_ic); 583264855Sadrian if (set == 1 && vap->iv_state == IEEE80211_S_SLEEP) { 584264855Sadrian ieee80211_new_state_locked(vap, IEEE80211_S_RUN, 0); 585275984Sadrian IEEE80211_DPRINTF(vap, IEEE80211_MSG_POWER, 586275984Sadrian "%s: TIM=%d; wakeup\n", __func__, set); 587275984Sadrian } else if ((set == 1) && (ic->ic_flags_ext & IEEE80211_FEXT_BGSCAN)) { 588275984Sadrian /* 589275984Sadrian * XXX only do this if we're in RUN state? 590275984Sadrian */ 591275984Sadrian IEEE80211_DPRINTF(vap, IEEE80211_MSG_POWER, 592275984Sadrian "%s: wake up from bgscan vap sleep\n", 593275984Sadrian __func__); 594275984Sadrian /* 595275984Sadrian * We may be in BGSCAN mode - this means the VAP is is in STA 596275984Sadrian * mode powersave. If it is, we need to wake it up so we 597275984Sadrian * can process outbound traffic. 598275984Sadrian */ 599275984Sadrian vap->iv_sta_ps(vap, 0); 600264855Sadrian } 601264855Sadrian IEEE80211_UNLOCK(vap->iv_ic); 602264855Sadrian} 603264855Sadrian 604264855Sadrian/* 605264855Sadrian * Timer check on whether the VAP has had any transmit activity. 606264855Sadrian * 607264855Sadrian * This may schedule a transition from _RUN -> _SLEEP if it's appropriate. 608264855Sadrian */ 609264855Sadrianvoid 610264855Sadrianieee80211_sta_ps_timer_check(struct ieee80211vap *vap) 611264855Sadrian{ 612264855Sadrian struct ieee80211com *ic = vap->iv_ic; 613264855Sadrian 614264855Sadrian /* XXX lock assert */ 615264855Sadrian 616264855Sadrian /* For no, only do this in STA mode */ 617264855Sadrian if (! (vap->iv_caps & IEEE80211_C_SWSLEEP)) 618264855Sadrian goto out; 619264855Sadrian 620264855Sadrian if (vap->iv_opmode != IEEE80211_M_STA) 621264855Sadrian goto out; 622264855Sadrian 623264855Sadrian /* If we're not at run state, bail */ 624264855Sadrian if (vap->iv_state != IEEE80211_S_RUN) 625264855Sadrian goto out; 626264855Sadrian 627264855Sadrian IEEE80211_DPRINTF(vap, IEEE80211_MSG_POWER, 628264855Sadrian "%s: lastdata=%llu, ticks=%llu\n", 629264855Sadrian __func__, (unsigned long long) ic->ic_lastdata, 630264855Sadrian (unsigned long long) ticks); 631264855Sadrian 632264855Sadrian /* If powersave is disabled on the VAP, don't bother */ 633264855Sadrian if (! (vap->iv_flags & IEEE80211_F_PMGTON)) 634264855Sadrian goto out; 635264855Sadrian 636264855Sadrian /* If we've done any data within our idle interval, bail */ 637264855Sadrian /* XXX hard-coded to one second for now, ew! */ 638264855Sadrian if (time_after(ic->ic_lastdata + 500, ticks)) 639264855Sadrian goto out; 640264855Sadrian 641264855Sadrian /* 642264855Sadrian * Signify we're going into power save and transition the 643264855Sadrian * node to powersave. 644264855Sadrian */ 645264855Sadrian if ((vap->iv_bss->ni_flags & IEEE80211_NODE_PWR_MGT) == 0) 646264855Sadrian vap->iv_sta_ps(vap, 1); 647264855Sadrian 648264855Sadrian /* 649264855Sadrian * XXX The driver has to handle the fact that we're going 650264855Sadrian * to sleep but frames may still be transmitted; 651264855Sadrian * hopefully it and/or us will do the right thing and mark any 652264855Sadrian * transmitted frames with PWRMGT set to 1. 653264855Sadrian */ 654264855Sadrian ieee80211_new_state_locked(vap, IEEE80211_S_SLEEP, 0); 655264855Sadrian 656264855Sadrian IEEE80211_DPRINTF(vap, IEEE80211_MSG_POWER, 657264855Sadrian "%s: time delta=%d msec\n", __func__, 658264855Sadrian (int) ticks_to_msecs(ticks - ic->ic_lastdata)); 659264855Sadrian 660264855Sadrianout: 661264855Sadrian return; 662264855Sadrian} 663