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$"); 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> 37295126Sglebius#include <sys/malloc.h> 38170530Ssam 39170530Ssam#include <sys/socket.h> 40170530Ssam 41170530Ssam#include <net/if.h> 42257176Sglebius#include <net/if_var.h> 43170530Ssam#include <net/if_media.h> 44170530Ssam#include <net/ethernet.h> 45170530Ssam 46170530Ssam#include <net80211/ieee80211_var.h> 47170530Ssam 48170530Ssam#include <net/bpf.h> 49170530Ssam 50178354Ssamstatic void ieee80211_update_ps(struct ieee80211vap *, int); 51178354Ssamstatic int ieee80211_set_tim(struct ieee80211_node *, int); 52170530Ssam 53227293Sedstatic MALLOC_DEFINE(M_80211_POWER, "80211power", "802.11 power save state"); 54178354Ssam 55170530Ssamvoid 56170530Ssamieee80211_power_attach(struct ieee80211com *ic) 57170530Ssam{ 58178354Ssam} 59178354Ssam 60178354Ssamvoid 61178354Ssamieee80211_power_detach(struct ieee80211com *ic) 62178354Ssam{ 63178354Ssam} 64178354Ssam 65178354Ssamvoid 66178354Ssamieee80211_power_vattach(struct ieee80211vap *vap) 67178354Ssam{ 68178354Ssam if (vap->iv_opmode == IEEE80211_M_HOSTAP || 69178354Ssam vap->iv_opmode == IEEE80211_M_IBSS) { 70170530Ssam /* NB: driver should override */ 71178354Ssam vap->iv_update_ps = ieee80211_update_ps; 72178354Ssam vap->iv_set_tim = ieee80211_set_tim; 73170530Ssam } 74241138Sadrian vap->iv_node_ps = ieee80211_node_pwrsave; 75241138Sadrian vap->iv_sta_ps = ieee80211_sta_pwrsave; 76170530Ssam} 77170530Ssam 78170530Ssamvoid 79178354Ssamieee80211_power_latevattach(struct ieee80211vap *vap) 80170530Ssam{ 81170530Ssam /* 82170530Ssam * Allocate these only if needed. Beware that we 83170530Ssam * know adhoc mode doesn't support ATIM yet... 84170530Ssam */ 85178354Ssam if (vap->iv_opmode == IEEE80211_M_HOSTAP) { 86178354Ssam vap->iv_tim_len = howmany(vap->iv_max_aid,8) * sizeof(uint8_t); 87283538Sadrian vap->iv_tim_bitmap = (uint8_t *) IEEE80211_MALLOC(vap->iv_tim_len, 88283538Sadrian M_80211_POWER, 89283538Sadrian IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); 90178354Ssam if (vap->iv_tim_bitmap == NULL) { 91170530Ssam printf("%s: no memory for TIM bitmap!\n", __func__); 92170530Ssam /* XXX good enough to keep from crashing? */ 93178354Ssam vap->iv_tim_len = 0; 94170530Ssam } 95170530Ssam } 96170530Ssam} 97170530Ssam 98170530Ssamvoid 99178354Ssamieee80211_power_vdetach(struct ieee80211vap *vap) 100170530Ssam{ 101178354Ssam if (vap->iv_tim_bitmap != NULL) { 102283538Sadrian IEEE80211_FREE(vap->iv_tim_bitmap, M_80211_POWER); 103178354Ssam vap->iv_tim_bitmap = NULL; 104170530Ssam } 105170530Ssam} 106170530Ssam 107184288Ssamvoid 108184288Ssamieee80211_psq_init(struct ieee80211_psq *psq, const char *name) 109184288Ssam{ 110223842Skevlo memset(psq, 0, sizeof(*psq)); 111184288Ssam psq->psq_maxlen = IEEE80211_PS_MAX_QUEUE; 112184288Ssam IEEE80211_PSQ_INIT(psq, name); /* OS-dependent setup */ 113184288Ssam} 114184288Ssam 115184288Ssamvoid 116184288Ssamieee80211_psq_cleanup(struct ieee80211_psq *psq) 117184288Ssam{ 118184288Ssam#if 0 119184288Ssam psq_drain(psq); /* XXX should not be needed? */ 120184288Ssam#else 121184288Ssam KASSERT(psq->psq_len == 0, ("%d frames on ps q", psq->psq_len)); 122184288Ssam#endif 123184288Ssam IEEE80211_PSQ_DESTROY(psq); /* OS-dependent cleanup */ 124184288Ssam} 125184288Ssam 126170530Ssam/* 127184288Ssam * Return the highest priority frame in the ps queue. 128184288Ssam */ 129184288Ssamstruct mbuf * 130184288Ssamieee80211_node_psq_dequeue(struct ieee80211_node *ni, int *qlen) 131184288Ssam{ 132184288Ssam struct ieee80211_psq *psq = &ni->ni_psq; 133184288Ssam struct ieee80211_psq_head *qhead; 134184288Ssam struct mbuf *m; 135184288Ssam 136184288Ssam IEEE80211_PSQ_LOCK(psq); 137184288Ssam qhead = &psq->psq_head[0]; 138184288Ssamagain: 139184288Ssam if ((m = qhead->head) != NULL) { 140184288Ssam if ((qhead->head = m->m_nextpkt) == NULL) 141184288Ssam qhead->tail = NULL; 142184288Ssam KASSERT(qhead->len > 0, ("qhead len %d", qhead->len)); 143184288Ssam qhead->len--; 144184288Ssam KASSERT(psq->psq_len > 0, ("psq len %d", psq->psq_len)); 145184288Ssam psq->psq_len--; 146184288Ssam m->m_nextpkt = NULL; 147184288Ssam } 148184288Ssam if (m == NULL && qhead == &psq->psq_head[0]) { 149184288Ssam /* Algol-68 style for loop */ 150184288Ssam qhead = &psq->psq_head[1]; 151184288Ssam goto again; 152184288Ssam } 153184288Ssam if (qlen != NULL) 154184288Ssam *qlen = psq->psq_len; 155184288Ssam IEEE80211_PSQ_UNLOCK(psq); 156184288Ssam return m; 157184288Ssam} 158184288Ssam 159184288Ssam/* 160184288Ssam * Reclaim an mbuf from the ps q. If marked with M_ENCAP 161184288Ssam * we assume there is a node reference that must be relcaimed. 162184288Ssam */ 163184288Ssamstatic void 164184288Ssampsq_mfree(struct mbuf *m) 165184288Ssam{ 166184288Ssam if (m->m_flags & M_ENCAP) { 167184288Ssam struct ieee80211_node *ni = (void *) m->m_pkthdr.rcvif; 168184288Ssam ieee80211_free_node(ni); 169184288Ssam } 170184288Ssam m->m_nextpkt = NULL; 171184288Ssam m_freem(m); 172184288Ssam} 173184288Ssam 174184288Ssam/* 175184288Ssam * Clear any frames queued in the power save queue. 176170530Ssam * The number of frames that were present is returned. 177170530Ssam */ 178184288Ssamstatic int 179184288Ssampsq_drain(struct ieee80211_psq *psq) 180170530Ssam{ 181184288Ssam struct ieee80211_psq_head *qhead; 182184288Ssam struct mbuf *m; 183170530Ssam int qlen; 184170530Ssam 185184288Ssam IEEE80211_PSQ_LOCK(psq); 186184288Ssam qlen = psq->psq_len; 187184288Ssam qhead = &psq->psq_head[0]; 188184288Ssamagain: 189184288Ssam while ((m = qhead->head) != NULL) { 190184288Ssam qhead->head = m->m_nextpkt; 191184288Ssam psq_mfree(m); 192184288Ssam } 193184288Ssam qhead->tail = NULL; 194184288Ssam qhead->len = 0; 195184288Ssam if (qhead == &psq->psq_head[0]) { /* Algol-68 style for loop */ 196184288Ssam qhead = &psq->psq_head[1]; 197184288Ssam goto again; 198184288Ssam } 199184288Ssam psq->psq_len = 0; 200184288Ssam IEEE80211_PSQ_UNLOCK(psq); 201170530Ssam 202170530Ssam return qlen; 203170530Ssam} 204170530Ssam 205170530Ssam/* 206184288Ssam * Clear any frames queued in the power save queue. 207184288Ssam * The number of frames that were present is returned. 208184288Ssam */ 209184288Ssamint 210184288Ssamieee80211_node_psq_drain(struct ieee80211_node *ni) 211184288Ssam{ 212184288Ssam return psq_drain(&ni->ni_psq); 213184288Ssam} 214184288Ssam 215184288Ssam/* 216170530Ssam * Age frames on the power save queue. The aging interval is 217170530Ssam * 4 times the listen interval specified by the station. This 218170530Ssam * number is factored into the age calculations when the frame 219170530Ssam * is placed on the queue. We store ages as time differences 220170530Ssam * so we can check and/or adjust only the head of the list. 221170530Ssam * If a frame's age exceeds the threshold then discard it. 222170530Ssam * The number of frames discarded is returned so the caller 223170530Ssam * can check if it needs to adjust the tim. 224170530Ssam */ 225170530Ssamint 226184288Ssamieee80211_node_psq_age(struct ieee80211_node *ni) 227170530Ssam{ 228184288Ssam struct ieee80211_psq *psq = &ni->ni_psq; 229170530Ssam int discard = 0; 230170530Ssam 231184288Ssam if (psq->psq_len != 0) { 232178354Ssam#ifdef IEEE80211_DEBUG 233178354Ssam struct ieee80211vap *vap = ni->ni_vap; 234178354Ssam#endif 235184288Ssam struct ieee80211_psq_head *qhead; 236170530Ssam struct mbuf *m; 237170530Ssam 238184288Ssam IEEE80211_PSQ_LOCK(psq); 239184288Ssam qhead = &psq->psq_head[0]; 240184288Ssam again: 241184288Ssam while ((m = qhead->head) != NULL && 242184288Ssam M_AGE_GET(m) < IEEE80211_INACT_WAIT) { 243178354Ssam IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, 244178354Ssam "discard frame, age %u", M_AGE_GET(m)); 245184288Ssam if ((qhead->head = m->m_nextpkt) == NULL) 246184288Ssam qhead->tail = NULL; 247184288Ssam KASSERT(qhead->len > 0, ("qhead len %d", qhead->len)); 248184288Ssam qhead->len--; 249184288Ssam KASSERT(psq->psq_len > 0, ("psq len %d", psq->psq_len)); 250184288Ssam psq->psq_len--; 251184288Ssam psq_mfree(m); 252170530Ssam discard++; 253170530Ssam } 254184288Ssam if (qhead == &psq->psq_head[0]) { /* Algol-68 style for loop */ 255184288Ssam qhead = &psq->psq_head[1]; 256184288Ssam goto again; 257184288Ssam } 258170530Ssam if (m != NULL) 259170530Ssam M_AGE_SUB(m, IEEE80211_INACT_WAIT); 260184288Ssam IEEE80211_PSQ_UNLOCK(psq); 261170530Ssam 262178354Ssam IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, 263170530Ssam "discard %u frames for age", discard); 264170530Ssam IEEE80211_NODE_STAT_ADD(ni, ps_discard, discard); 265170530Ssam } 266170530Ssam return discard; 267170530Ssam} 268170530Ssam 269170530Ssam/* 270178354Ssam * Handle a change in the PS station occupancy. 271178354Ssam */ 272178354Ssamstatic void 273178354Ssamieee80211_update_ps(struct ieee80211vap *vap, int nsta) 274178354Ssam{ 275178354Ssam 276178354Ssam KASSERT(vap->iv_opmode == IEEE80211_M_HOSTAP || 277178354Ssam vap->iv_opmode == IEEE80211_M_IBSS, 278178354Ssam ("operating mode %u", vap->iv_opmode)); 279178354Ssam} 280178354Ssam 281178354Ssam/* 282170530Ssam * Indicate whether there are frames queued for a station in power-save mode. 283170530Ssam */ 284178354Ssamstatic int 285170530Ssamieee80211_set_tim(struct ieee80211_node *ni, int set) 286170530Ssam{ 287178354Ssam struct ieee80211vap *vap = ni->ni_vap; 288170530Ssam struct ieee80211com *ic = ni->ni_ic; 289170530Ssam uint16_t aid; 290178354Ssam int changed; 291170530Ssam 292178354Ssam KASSERT(vap->iv_opmode == IEEE80211_M_HOSTAP || 293178354Ssam vap->iv_opmode == IEEE80211_M_IBSS, 294178354Ssam ("operating mode %u", vap->iv_opmode)); 295170530Ssam 296170530Ssam aid = IEEE80211_AID(ni->ni_associd); 297178354Ssam KASSERT(aid < vap->iv_max_aid, 298178354Ssam ("bogus aid %u, max %u", aid, vap->iv_max_aid)); 299170530Ssam 300178354Ssam IEEE80211_LOCK(ic); 301178354Ssam changed = (set != (isset(vap->iv_tim_bitmap, aid) != 0)); 302178354Ssam if (changed) { 303170530Ssam if (set) { 304178354Ssam setbit(vap->iv_tim_bitmap, aid); 305178354Ssam vap->iv_ps_pending++; 306170530Ssam } else { 307178354Ssam clrbit(vap->iv_tim_bitmap, aid); 308178354Ssam vap->iv_ps_pending--; 309170530Ssam } 310178354Ssam /* NB: we know vap is in RUN state so no need to check */ 311178354Ssam vap->iv_update_beacon(vap, IEEE80211_BEACON_TIM); 312170530Ssam } 313178354Ssam IEEE80211_UNLOCK(ic); 314178354Ssam 315178354Ssam return changed; 316170530Ssam} 317170530Ssam 318170530Ssam/* 319170530Ssam * Save an outbound packet for a node in power-save sleep state. 320170530Ssam * The new packet is placed on the node's saved queue, and the TIM 321170530Ssam * is changed, if necessary. 322170530Ssam */ 323184288Ssamint 324170530Ssamieee80211_pwrsave(struct ieee80211_node *ni, struct mbuf *m) 325170530Ssam{ 326184288Ssam struct ieee80211_psq *psq = &ni->ni_psq; 327178354Ssam struct ieee80211vap *vap = ni->ni_vap; 328170530Ssam struct ieee80211com *ic = ni->ni_ic; 329184288Ssam struct ieee80211_psq_head *qhead; 330170530Ssam int qlen, age; 331170530Ssam 332184288Ssam IEEE80211_PSQ_LOCK(psq); 333184288Ssam if (psq->psq_len >= psq->psq_maxlen) { 334184288Ssam psq->psq_drops++; 335184288Ssam IEEE80211_PSQ_UNLOCK(psq); 336178354Ssam IEEE80211_NOTE(vap, IEEE80211_MSG_ANY, ni, 337178354Ssam "pwr save q overflow, drops %d (size %d)", 338184288Ssam psq->psq_drops, psq->psq_len); 339170530Ssam#ifdef IEEE80211_DEBUG 340178354Ssam if (ieee80211_msg_dumppkts(vap)) 341178354Ssam ieee80211_dump_pkt(ni->ni_ic, mtod(m, caddr_t), 342178354Ssam m->m_len, -1, -1); 343170530Ssam#endif 344184288Ssam psq_mfree(m); 345184288Ssam return ENOSPC; 346170530Ssam } 347170530Ssam /* 348184288Ssam * Tag the frame with it's expiry time and insert it in 349184288Ssam * the appropriate queue. The aging interval is 4 times 350184288Ssam * the listen interval specified by the station. Frames 351184288Ssam * that sit around too long are reclaimed using this 352184288Ssam * information. 353170530Ssam */ 354170530Ssam /* TU -> secs. XXX handle overflow? */ 355170530Ssam age = IEEE80211_TU_TO_MS((ni->ni_intval * ic->ic_bintval) << 2) / 1000; 356184288Ssam /* 357184288Ssam * Encapsulated frames go on the high priority queue, 358184288Ssam * other stuff goes on the low priority queue. We use 359184288Ssam * this to order frames returned out of the driver 360184288Ssam * ahead of frames we collect in ieee80211_start. 361184288Ssam */ 362184288Ssam if (m->m_flags & M_ENCAP) 363184288Ssam qhead = &psq->psq_head[0]; 364184288Ssam else 365184288Ssam qhead = &psq->psq_head[1]; 366184288Ssam if (qhead->tail == NULL) { 367184288Ssam struct mbuf *mh; 368170530Ssam 369184288Ssam qhead->head = m; 370184288Ssam /* 371184288Ssam * Take care to adjust age when inserting the first 372184288Ssam * frame of a queue and the other queue already has 373184288Ssam * frames. We need to preserve the age difference 374184288Ssam * relationship so ieee80211_node_psq_age works. 375184288Ssam */ 376184288Ssam if (qhead == &psq->psq_head[1]) { 377184288Ssam mh = psq->psq_head[0].head; 378184288Ssam if (mh != NULL) 379184288Ssam age-= M_AGE_GET(mh); 380184288Ssam } else { 381184288Ssam mh = psq->psq_head[1].head; 382184288Ssam if (mh != NULL) { 383184288Ssam int nage = M_AGE_GET(mh) - age; 384184288Ssam /* XXX is clamping to zero good 'nuf? */ 385184288Ssam M_AGE_SET(mh, nage < 0 ? 0 : nage); 386184288Ssam } 387184288Ssam } 388184288Ssam } else { 389184288Ssam qhead->tail->m_nextpkt = m; 390184288Ssam age -= M_AGE_GET(qhead->head); 391184288Ssam } 392184288Ssam KASSERT(age >= 0, ("age %d", age)); 393184288Ssam M_AGE_SET(m, age); 394184288Ssam m->m_nextpkt = NULL; 395184288Ssam qhead->tail = m; 396184288Ssam qhead->len++; 397184288Ssam qlen = ++(psq->psq_len); 398184288Ssam IEEE80211_PSQ_UNLOCK(psq); 399184288Ssam 400178354Ssam IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, 401178354Ssam "save frame with age %d, %u now queued", age, qlen); 402170530Ssam 403178354Ssam if (qlen == 1 && vap->iv_set_tim != NULL) 404178354Ssam vap->iv_set_tim(ni, 1); 405184288Ssam 406184288Ssam return 0; 407170530Ssam} 408170530Ssam 409170530Ssam/* 410184288Ssam * Move frames from the ps q to the vap's send queue 411184288Ssam * and/or the driver's send queue; and kick the start 412184288Ssam * method for each, as appropriate. Note we're careful 413184288Ssam * to preserve packet ordering here. 414170530Ssam */ 415178354Ssamstatic void 416178354Ssampwrsave_flushq(struct ieee80211_node *ni) 417170530Ssam{ 418184288Ssam struct ieee80211_psq *psq = &ni->ni_psq; 419248069Sadrian struct ieee80211com *ic = ni->ni_ic; 420184288Ssam struct ieee80211vap *vap = ni->ni_vap; 421184288Ssam struct ieee80211_psq_head *qhead; 422245464Sadrian struct mbuf *parent_q = NULL, *ifp_q = NULL; 423245464Sadrian struct mbuf *m; 424170530Ssam 425184288Ssam IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, 426184288Ssam "flush ps queue, %u packets queued", psq->psq_len); 427184288Ssam 428184288Ssam IEEE80211_PSQ_LOCK(psq); 429184288Ssam qhead = &psq->psq_head[0]; /* 802.11 frames */ 430184288Ssam if (qhead->head != NULL) { 431184288Ssam /* XXX could dispatch through vap and check M_ENCAP */ 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; 437287197Sglebius } 438184288Ssam 439184288Ssam qhead = &psq->psq_head[1]; /* 802.3 frames */ 440184288Ssam if (qhead->head != NULL) { 441184288Ssam /* XXX need different driver interface */ 442184288Ssam /* XXX bypasses q max and OACTIVE */ 443245464Sadrian ifp_q = qhead->head; 444184288Ssam qhead->head = qhead->tail = NULL; 445184288Ssam qhead->len = 0; 446287197Sglebius } 447184288Ssam psq->psq_len = 0; 448184288Ssam IEEE80211_PSQ_UNLOCK(psq); 449184288Ssam 450184288Ssam /* NB: do this outside the psq lock */ 451184288Ssam /* XXX packets might get reordered if parent is OACTIVE */ 452245464Sadrian /* parent frames, should be encapsulated */ 453287197Sglebius while (parent_q != NULL) { 454287197Sglebius m = parent_q; 455287197Sglebius parent_q = m->m_nextpkt; 456287197Sglebius m->m_nextpkt = NULL; 457287197Sglebius /* must be encapsulated */ 458287197Sglebius KASSERT((m->m_flags & M_ENCAP), 459287197Sglebius ("%s: parentq with non-M_ENCAP frame!\n", 460287197Sglebius __func__)); 461289164Sadrian (void) ieee80211_parent_xmitpkt(ic, m); 462245464Sadrian } 463245464Sadrian 464245464Sadrian /* VAP frames, aren't encapsulated */ 465287197Sglebius while (ifp_q != NULL) { 466287197Sglebius m = ifp_q; 467287197Sglebius ifp_q = m->m_nextpkt; 468287197Sglebius m->m_nextpkt = NULL; 469287197Sglebius KASSERT((!(m->m_flags & M_ENCAP)), 470287197Sglebius ("%s: vapq with M_ENCAP frame!\n", __func__)); 471287197Sglebius (void) ieee80211_vap_xmitpkt(vap, m); 472245464Sadrian } 473170530Ssam} 474170530Ssam 475170530Ssam/* 476178354Ssam * Handle station power-save state change. 477178354Ssam */ 478178354Ssamvoid 479178354Ssamieee80211_node_pwrsave(struct ieee80211_node *ni, int enable) 480178354Ssam{ 481178354Ssam struct ieee80211vap *vap = ni->ni_vap; 482178354Ssam int update; 483178354Ssam 484178354Ssam update = 0; 485178354Ssam if (enable) { 486178354Ssam if ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) == 0) { 487178354Ssam vap->iv_ps_sta++; 488178354Ssam update = 1; 489178354Ssam } 490178354Ssam ni->ni_flags |= IEEE80211_NODE_PWR_MGT; 491178354Ssam IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, 492178354Ssam "power save mode on, %u sta's in ps mode", vap->iv_ps_sta); 493178354Ssam 494178354Ssam if (update) 495178354Ssam vap->iv_update_ps(vap, vap->iv_ps_sta); 496178354Ssam } else { 497178354Ssam if (ni->ni_flags & IEEE80211_NODE_PWR_MGT) { 498178354Ssam vap->iv_ps_sta--; 499178354Ssam update = 1; 500178354Ssam } 501178354Ssam ni->ni_flags &= ~IEEE80211_NODE_PWR_MGT; 502178354Ssam IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, 503178354Ssam "power save mode off, %u sta's in ps mode", vap->iv_ps_sta); 504178354Ssam 505178354Ssam /* NB: order here is intentional so TIM is clear before flush */ 506178354Ssam if (vap->iv_set_tim != NULL) 507178354Ssam vap->iv_set_tim(ni, 0); 508178354Ssam if (update) { 509178354Ssam /* NB if no sta's in ps, driver should flush mc q */ 510178354Ssam vap->iv_update_ps(vap, vap->iv_ps_sta); 511178354Ssam } 512184288Ssam if (ni->ni_psq.psq_len != 0) 513184288Ssam pwrsave_flushq(ni); 514178354Ssam } 515178354Ssam} 516178354Ssam 517178354Ssam/* 518170530Ssam * Handle power-save state change in station mode. 519170530Ssam */ 520170530Ssamvoid 521178354Ssamieee80211_sta_pwrsave(struct ieee80211vap *vap, int enable) 522170530Ssam{ 523178354Ssam struct ieee80211_node *ni = vap->iv_bss; 524170530Ssam 525170530Ssam if (!((enable != 0) ^ ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) != 0))) 526170530Ssam return; 527170530Ssam 528178354Ssam IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, 529170530Ssam "sta power save mode %s", enable ? "on" : "off"); 530170530Ssam if (!enable) { 531170530Ssam ni->ni_flags &= ~IEEE80211_NODE_PWR_MGT; 532170530Ssam ieee80211_send_nulldata(ieee80211_ref_node(ni)); 533170530Ssam /* 534170530Ssam * Flush any queued frames; we can do this immediately 535170530Ssam * because we know they'll be queued behind the null 536170530Ssam * data frame we send the ap. 537170530Ssam * XXX can we use a data frame to take us out of ps? 538170530Ssam */ 539184288Ssam if (ni->ni_psq.psq_len != 0) 540178354Ssam pwrsave_flushq(ni); 541170530Ssam } else { 542170530Ssam ni->ni_flags |= IEEE80211_NODE_PWR_MGT; 543170530Ssam ieee80211_send_nulldata(ieee80211_ref_node(ni)); 544170530Ssam } 545170530Ssam} 546264855Sadrian 547264855Sadrian/* 548264855Sadrian * Handle being notified that we have data available for us in a TIM/ATIM. 549264855Sadrian * 550264855Sadrian * This may schedule a transition from _SLEEP -> _RUN if it's appropriate. 551275984Sadrian * 552275984Sadrian * In STA mode, we may have put to sleep during scan and need to be dragged 553275984Sadrian * back out of powersave mode. 554264855Sadrian */ 555264855Sadrianvoid 556264855Sadrianieee80211_sta_tim_notify(struct ieee80211vap *vap, int set) 557264855Sadrian{ 558275984Sadrian struct ieee80211com *ic = vap->iv_ic; 559275984Sadrian 560264855Sadrian /* 561264855Sadrian * Schedule the driver state change. It'll happen at some point soon. 562264855Sadrian * Since the hardware shouldn't know that we're running just yet 563264855Sadrian * (and thus tell the peer that we're awake before we actually wake 564264855Sadrian * up said hardware), we leave the actual node state transition 565264855Sadrian * up to the transition to RUN. 566264855Sadrian * 567264855Sadrian * XXX TODO: verify that the transition to RUN will wake up the 568264855Sadrian * BSS node! 569264855Sadrian */ 570264855Sadrian IEEE80211_LOCK(vap->iv_ic); 571264855Sadrian if (set == 1 && vap->iv_state == IEEE80211_S_SLEEP) { 572264855Sadrian ieee80211_new_state_locked(vap, IEEE80211_S_RUN, 0); 573275984Sadrian IEEE80211_DPRINTF(vap, IEEE80211_MSG_POWER, 574275984Sadrian "%s: TIM=%d; wakeup\n", __func__, set); 575275984Sadrian } else if ((set == 1) && (ic->ic_flags_ext & IEEE80211_FEXT_BGSCAN)) { 576275984Sadrian /* 577275984Sadrian * XXX only do this if we're in RUN state? 578275984Sadrian */ 579275984Sadrian IEEE80211_DPRINTF(vap, IEEE80211_MSG_POWER, 580275984Sadrian "%s: wake up from bgscan vap sleep\n", 581275984Sadrian __func__); 582275984Sadrian /* 583275984Sadrian * We may be in BGSCAN mode - this means the VAP is is in STA 584275984Sadrian * mode powersave. If it is, we need to wake it up so we 585275984Sadrian * can process outbound traffic. 586275984Sadrian */ 587275984Sadrian vap->iv_sta_ps(vap, 0); 588264855Sadrian } 589264855Sadrian IEEE80211_UNLOCK(vap->iv_ic); 590264855Sadrian} 591264855Sadrian 592264855Sadrian/* 593264855Sadrian * Timer check on whether the VAP has had any transmit activity. 594264855Sadrian * 595264855Sadrian * This may schedule a transition from _RUN -> _SLEEP if it's appropriate. 596264855Sadrian */ 597264855Sadrianvoid 598264855Sadrianieee80211_sta_ps_timer_check(struct ieee80211vap *vap) 599264855Sadrian{ 600264855Sadrian struct ieee80211com *ic = vap->iv_ic; 601264855Sadrian 602264855Sadrian /* XXX lock assert */ 603264855Sadrian 604264855Sadrian /* For no, only do this in STA mode */ 605264855Sadrian if (! (vap->iv_caps & IEEE80211_C_SWSLEEP)) 606264855Sadrian goto out; 607264855Sadrian 608264855Sadrian if (vap->iv_opmode != IEEE80211_M_STA) 609264855Sadrian goto out; 610264855Sadrian 611264855Sadrian /* If we're not at run state, bail */ 612264855Sadrian if (vap->iv_state != IEEE80211_S_RUN) 613264855Sadrian goto out; 614264855Sadrian 615264855Sadrian IEEE80211_DPRINTF(vap, IEEE80211_MSG_POWER, 616264855Sadrian "%s: lastdata=%llu, ticks=%llu\n", 617264855Sadrian __func__, (unsigned long long) ic->ic_lastdata, 618264855Sadrian (unsigned long long) ticks); 619264855Sadrian 620264855Sadrian /* If powersave is disabled on the VAP, don't bother */ 621264855Sadrian if (! (vap->iv_flags & IEEE80211_F_PMGTON)) 622264855Sadrian goto out; 623264855Sadrian 624264855Sadrian /* If we've done any data within our idle interval, bail */ 625264855Sadrian /* XXX hard-coded to one second for now, ew! */ 626297405Sadrian if (ieee80211_time_after(ic->ic_lastdata + 500, ticks)) 627264855Sadrian goto out; 628264855Sadrian 629264855Sadrian /* 630264855Sadrian * Signify we're going into power save and transition the 631264855Sadrian * node to powersave. 632264855Sadrian */ 633264855Sadrian if ((vap->iv_bss->ni_flags & IEEE80211_NODE_PWR_MGT) == 0) 634264855Sadrian vap->iv_sta_ps(vap, 1); 635264855Sadrian 636264855Sadrian /* 637264855Sadrian * XXX The driver has to handle the fact that we're going 638264855Sadrian * to sleep but frames may still be transmitted; 639264855Sadrian * hopefully it and/or us will do the right thing and mark any 640264855Sadrian * transmitted frames with PWRMGT set to 1. 641264855Sadrian */ 642264855Sadrian ieee80211_new_state_locked(vap, IEEE80211_S_SLEEP, 0); 643264855Sadrian 644264855Sadrian IEEE80211_DPRINTF(vap, IEEE80211_MSG_POWER, 645264855Sadrian "%s: time delta=%d msec\n", __func__, 646264855Sadrian (int) ticks_to_msecs(ticks - ic->ic_lastdata)); 647264855Sadrian 648264855Sadrianout: 649264855Sadrian return; 650264855Sadrian} 651