| Commit | Line | Data |
|---|---|---|
| 32176cfd RP |
1 | /*- |
| 2 | * Copyright (c) 2002-2008 Sam Leffler, Errno Consulting | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions | |
| 7 | * are met: | |
| 8 | * 1. Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * 2. Redistributions in binary form must reproduce the above copyright | |
| 11 | * notice, this list of conditions and the following disclaimer in the | |
| 12 | * documentation and/or other materials provided with the distribution. | |
| 13 | * | |
| 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | |
| 15 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
| 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
| 17 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | |
| 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |
| 19 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 20 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 21 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
| 23 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 24 | * | |
| 25 | * $FreeBSD: head/sys/net80211/ieee80211_power.c 186302 2008-12-18 23:00:09Z sam $ | |
| 26 | * $DragonFly$ | |
| 27 | */ | |
| 28 | ||
| 29 | /* | |
| 30 | * IEEE 802.11 power save support. | |
| 31 | */ | |
| 32 | #include "opt_wlan.h" | |
| 33 | ||
| 34 | #include <sys/param.h> | |
| 35 | #include <sys/systm.h> | |
| 36 | #include <sys/kernel.h> | |
| 37 | ||
| 38 | #include <sys/socket.h> | |
| 39 | ||
| 40 | #include <net/if.h> | |
| 41 | #include <net/if_media.h> | |
| 42 | #include <net/ethernet.h> | |
| 43 | #include <net/route.h> | |
| 44 | ||
| 45 | #include <netproto/802_11/ieee80211_var.h> | |
| 46 | ||
| 47 | #include <net/bpf.h> | |
| 48 | ||
| 49 | static void ieee80211_update_ps(struct ieee80211vap *, int); | |
| 50 | static int ieee80211_set_tim(struct ieee80211_node *, int); | |
| 51 | ||
| 52 | MALLOC_DEFINE(M_80211_POWER, "80211power", "802.11 power save state"); | |
| 53 | ||
| 54 | void | |
| 55 | ieee80211_power_attach(struct ieee80211com *ic) | |
| 56 | { | |
| 57 | } | |
| 58 | ||
| 59 | void | |
| 60 | ieee80211_power_detach(struct ieee80211com *ic) | |
| 61 | { | |
| 62 | } | |
| 63 | ||
| 64 | void | |
| 65 | ieee80211_power_vattach(struct ieee80211vap *vap) | |
| 66 | { | |
| 67 | if (vap->iv_opmode == IEEE80211_M_HOSTAP || | |
| 68 | vap->iv_opmode == IEEE80211_M_IBSS) { | |
| 69 | /* NB: driver should override */ | |
| 70 | vap->iv_update_ps = ieee80211_update_ps; | |
| 71 | vap->iv_set_tim = ieee80211_set_tim; | |
| 72 | } | |
| 73 | } | |
| 74 | ||
| 75 | void | |
| 76 | ieee80211_power_latevattach(struct ieee80211vap *vap) | |
| 77 | { | |
| 78 | /* | |
| 79 | * Allocate these only if needed. Beware that we | |
| 80 | * know adhoc mode doesn't support ATIM yet... | |
| 81 | */ | |
| 82 | if (vap->iv_opmode == IEEE80211_M_HOSTAP) { | |
| 83 | vap->iv_tim_len = howmany(vap->iv_max_aid,8) * sizeof(uint8_t); | |
| 84 | vap->iv_tim_bitmap = (uint8_t *) kmalloc(vap->iv_tim_len, | |
| fcaa651d | 85 | M_80211_POWER, M_INTWAIT | M_ZERO); |
| 32176cfd RP |
86 | if (vap->iv_tim_bitmap == NULL) { |
| 87 | kprintf("%s: no memory for TIM bitmap!\n", __func__); | |
| 88 | /* XXX good enough to keep from crashing? */ | |
| 89 | vap->iv_tim_len = 0; | |
| 90 | } | |
| 91 | } | |
| 92 | } | |
| 93 | ||
| 94 | void | |
| 95 | ieee80211_power_vdetach(struct ieee80211vap *vap) | |
| 96 | { | |
| 97 | if (vap->iv_tim_bitmap != NULL) { | |
| 98 | kfree(vap->iv_tim_bitmap, M_80211_POWER); | |
| 99 | vap->iv_tim_bitmap = NULL; | |
| 100 | } | |
| 101 | } | |
| 102 | ||
| 103 | void | |
| 104 | ieee80211_psq_init(struct ieee80211_psq *psq, const char *name) | |
| 105 | { | |
| 106 | memset(psq, 0, sizeof(psq)); | |
| 107 | psq->psq_maxlen = IEEE80211_PS_MAX_QUEUE; | |
| 32176cfd RP |
108 | } |
| 109 | ||
| 110 | void | |
| 111 | ieee80211_psq_cleanup(struct ieee80211_psq *psq) | |
| 112 | { | |
| 113 | #if 0 | |
| 114 | psq_drain(psq); /* XXX should not be needed? */ | |
| 115 | #else | |
| 116 | KASSERT(psq->psq_len == 0, ("%d frames on ps q", psq->psq_len)); | |
| 117 | #endif | |
| 32176cfd RP |
118 | } |
| 119 | ||
| 120 | /* | |
| 121 | * Return the highest priority frame in the ps queue. | |
| 122 | */ | |
| 123 | struct mbuf * | |
| 124 | ieee80211_node_psq_dequeue(struct ieee80211_node *ni, int *qlen) | |
| 125 | { | |
| 126 | struct ieee80211_psq *psq = &ni->ni_psq; | |
| 127 | struct ieee80211_psq_head *qhead; | |
| 128 | struct mbuf *m; | |
| 129 | ||
| 32176cfd RP |
130 | qhead = &psq->psq_head[0]; |
| 131 | again: | |
| 132 | if ((m = qhead->head) != NULL) { | |
| 133 | if ((qhead->head = m->m_nextpkt) == NULL) | |
| 134 | qhead->tail = NULL; | |
| 135 | KASSERT(qhead->len > 0, ("qhead len %d", qhead->len)); | |
| 136 | qhead->len--; | |
| 137 | KASSERT(psq->psq_len > 0, ("psq len %d", psq->psq_len)); | |
| 138 | psq->psq_len--; | |
| 139 | m->m_nextpkt = NULL; | |
| 140 | } | |
| 141 | if (m == NULL && qhead == &psq->psq_head[0]) { | |
| 142 | /* Algol-68 style for loop */ | |
| 143 | qhead = &psq->psq_head[1]; | |
| 144 | goto again; | |
| 145 | } | |
| 146 | if (qlen != NULL) | |
| 147 | *qlen = psq->psq_len; | |
| 32176cfd RP |
148 | return m; |
| 149 | } | |
| 150 | ||
| 151 | /* | |
| 152 | * Reclaim an mbuf from the ps q. If marked with M_ENCAP | |
| 153 | * we assume there is a node reference that must be relcaimed. | |
| 154 | */ | |
| 155 | static void | |
| 156 | psq_mfree(struct mbuf *m) | |
| 157 | { | |
| 158 | if (m->m_flags & M_ENCAP) { | |
| 159 | struct ieee80211_node *ni = (void *) m->m_pkthdr.rcvif; | |
| 160 | ieee80211_free_node(ni); | |
| 161 | } | |
| 162 | m->m_nextpkt = NULL; | |
| 163 | m_freem(m); | |
| 164 | } | |
| 165 | ||
| 166 | /* | |
| 167 | * Clear any frames queued in the power save queue. | |
| 168 | * The number of frames that were present is returned. | |
| 169 | */ | |
| 170 | static int | |
| 171 | psq_drain(struct ieee80211_psq *psq) | |
| 172 | { | |
| 173 | struct ieee80211_psq_head *qhead; | |
| 174 | struct mbuf *m; | |
| 175 | int qlen; | |
| 176 | ||
| 32176cfd RP |
177 | qlen = psq->psq_len; |
| 178 | qhead = &psq->psq_head[0]; | |
| 179 | again: | |
| 180 | while ((m = qhead->head) != NULL) { | |
| 181 | qhead->head = m->m_nextpkt; | |
| 182 | psq_mfree(m); | |
| 183 | } | |
| 184 | qhead->tail = NULL; | |
| 185 | qhead->len = 0; | |
| 186 | if (qhead == &psq->psq_head[0]) { /* Algol-68 style for loop */ | |
| 187 | qhead = &psq->psq_head[1]; | |
| 188 | goto again; | |
| 189 | } | |
| 190 | psq->psq_len = 0; | |
| 32176cfd RP |
191 | |
| 192 | return qlen; | |
| 193 | } | |
| 194 | ||
| 195 | /* | |
| 196 | * Clear any frames queued in the power save queue. | |
| 197 | * The number of frames that were present is returned. | |
| 198 | */ | |
| 199 | int | |
| 200 | ieee80211_node_psq_drain(struct ieee80211_node *ni) | |
| 201 | { | |
| 202 | return psq_drain(&ni->ni_psq); | |
| 203 | } | |
| 204 | ||
| 205 | /* | |
| 206 | * Age frames on the power save queue. The aging interval is | |
| 207 | * 4 times the listen interval specified by the station. This | |
| 208 | * number is factored into the age calculations when the frame | |
| 209 | * is placed on the queue. We store ages as time differences | |
| 210 | * so we can check and/or adjust only the head of the list. | |
| 211 | * If a frame's age exceeds the threshold then discard it. | |
| 212 | * The number of frames discarded is returned so the caller | |
| 213 | * can check if it needs to adjust the tim. | |
| 214 | */ | |
| 215 | int | |
| 216 | ieee80211_node_psq_age(struct ieee80211_node *ni) | |
| 217 | { | |
| 218 | struct ieee80211_psq *psq = &ni->ni_psq; | |
| 219 | int discard = 0; | |
| 220 | ||
| 221 | if (psq->psq_len != 0) { | |
| 222 | #ifdef IEEE80211_DEBUG | |
| 223 | struct ieee80211vap *vap = ni->ni_vap; | |
| 224 | #endif | |
| 225 | struct ieee80211_psq_head *qhead; | |
| 226 | struct mbuf *m; | |
| 227 | ||
| 32176cfd RP |
228 | qhead = &psq->psq_head[0]; |
| 229 | again: | |
| 230 | while ((m = qhead->head) != NULL && | |
| 231 | M_AGE_GET(m) < IEEE80211_INACT_WAIT) { | |
| 232 | IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, | |
| 233 | "discard frame, age %u", M_AGE_GET(m)); | |
| 234 | if ((qhead->head = m->m_nextpkt) == NULL) | |
| 235 | qhead->tail = NULL; | |
| 236 | KASSERT(qhead->len > 0, ("qhead len %d", qhead->len)); | |
| 237 | qhead->len--; | |
| 238 | KASSERT(psq->psq_len > 0, ("psq len %d", psq->psq_len)); | |
| 239 | psq->psq_len--; | |
| 240 | psq_mfree(m); | |
| 241 | discard++; | |
| 242 | } | |
| 243 | if (qhead == &psq->psq_head[0]) { /* Algol-68 style for loop */ | |
| 244 | qhead = &psq->psq_head[1]; | |
| 245 | goto again; | |
| 246 | } | |
| 247 | if (m != NULL) | |
| 248 | M_AGE_SUB(m, IEEE80211_INACT_WAIT); | |
| 32176cfd RP |
249 | |
| 250 | IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, | |
| 251 | "discard %u frames for age", discard); | |
| 252 | IEEE80211_NODE_STAT_ADD(ni, ps_discard, discard); | |
| 253 | } | |
| 254 | return discard; | |
| 255 | } | |
| 256 | ||
| 257 | /* | |
| 258 | * Handle a change in the PS station occupancy. | |
| 259 | */ | |
| 260 | static void | |
| 261 | ieee80211_update_ps(struct ieee80211vap *vap, int nsta) | |
| 262 | { | |
| 263 | ||
| 264 | KASSERT(vap->iv_opmode == IEEE80211_M_HOSTAP || | |
| 265 | vap->iv_opmode == IEEE80211_M_IBSS, | |
| 266 | ("operating mode %u", vap->iv_opmode)); | |
| 267 | } | |
| 268 | ||
| 269 | /* | |
| 270 | * Indicate whether there are frames queued for a station in power-save mode. | |
| 271 | */ | |
| 272 | static int | |
| 273 | ieee80211_set_tim(struct ieee80211_node *ni, int set) | |
| 274 | { | |
| 275 | struct ieee80211vap *vap = ni->ni_vap; | |
| 276 | struct ieee80211com *ic = ni->ni_ic; | |
| 277 | uint16_t aid; | |
| 278 | int changed; | |
| 279 | ||
| e8361ca0 | 280 | ic = ni->ni_ic; |
| 32176cfd RP |
281 | KASSERT(vap->iv_opmode == IEEE80211_M_HOSTAP || |
| 282 | vap->iv_opmode == IEEE80211_M_IBSS, | |
| 283 | ("operating mode %u", vap->iv_opmode)); | |
| 284 | ||
| 285 | aid = IEEE80211_AID(ni->ni_associd); | |
| 286 | KASSERT(aid < vap->iv_max_aid, | |
| 287 | ("bogus aid %u, max %u", aid, vap->iv_max_aid)); | |
| 288 | ||
| 32176cfd RP |
289 | changed = (set != (isset(vap->iv_tim_bitmap, aid) != 0)); |
| 290 | if (changed) { | |
| 291 | if (set) { | |
| 292 | setbit(vap->iv_tim_bitmap, aid); | |
| 293 | vap->iv_ps_pending++; | |
| 294 | } else { | |
| 295 | clrbit(vap->iv_tim_bitmap, aid); | |
| 296 | vap->iv_ps_pending--; | |
| 297 | } | |
| 298 | /* NB: we know vap is in RUN state so no need to check */ | |
| 299 | vap->iv_update_beacon(vap, IEEE80211_BEACON_TIM); | |
| 300 | } | |
| 32176cfd RP |
301 | |
| 302 | return changed; | |
| 303 | } | |
| 304 | ||
| 305 | /* | |
| 306 | * Save an outbound packet for a node in power-save sleep state. | |
| 307 | * The new packet is placed on the node's saved queue, and the TIM | |
| 308 | * is changed, if necessary. | |
| 309 | */ | |
| 310 | int | |
| 311 | ieee80211_pwrsave(struct ieee80211_node *ni, struct mbuf *m) | |
| 312 | { | |
| 313 | struct ieee80211_psq *psq = &ni->ni_psq; | |
| 314 | struct ieee80211vap *vap = ni->ni_vap; | |
| 315 | struct ieee80211com *ic = ni->ni_ic; | |
| 316 | struct ieee80211_psq_head *qhead; | |
| 317 | int qlen, age; | |
| 318 | ||
| 32176cfd RP |
319 | if (psq->psq_len >= psq->psq_maxlen) { |
| 320 | psq->psq_drops++; | |
| 32176cfd RP |
321 | IEEE80211_NOTE(vap, IEEE80211_MSG_ANY, ni, |
| 322 | "pwr save q overflow, drops %d (size %d)", | |
| 323 | psq->psq_drops, psq->psq_len); | |
| 324 | #ifdef IEEE80211_DEBUG | |
| 325 | if (ieee80211_msg_dumppkts(vap)) | |
| 326 | ieee80211_dump_pkt(ni->ni_ic, mtod(m, caddr_t), | |
| 327 | m->m_len, -1, -1); | |
| 328 | #endif | |
| 329 | psq_mfree(m); | |
| 330 | return ENOSPC; | |
| 331 | } | |
| 332 | /* | |
| 333 | * Tag the frame with it's expiry time and insert it in | |
| 334 | * the appropriate queue. The aging interval is 4 times | |
| 335 | * the listen interval specified by the station. Frames | |
| 336 | * that sit around too long are reclaimed using this | |
| 337 | * information. | |
| 338 | */ | |
| 339 | /* TU -> secs. XXX handle overflow? */ | |
| 340 | age = IEEE80211_TU_TO_MS((ni->ni_intval * ic->ic_bintval) << 2) / 1000; | |
| 341 | /* | |
| 342 | * Encapsulated frames go on the high priority queue, | |
| 343 | * other stuff goes on the low priority queue. We use | |
| 344 | * this to order frames returned out of the driver | |
| 345 | * ahead of frames we collect in ieee80211_start. | |
| 346 | */ | |
| 347 | if (m->m_flags & M_ENCAP) | |
| 348 | qhead = &psq->psq_head[0]; | |
| 349 | else | |
| 350 | qhead = &psq->psq_head[1]; | |
| 351 | if (qhead->tail == NULL) { | |
| 352 | struct mbuf *mh; | |
| 353 | ||
| 354 | qhead->head = m; | |
| 355 | /* | |
| 356 | * Take care to adjust age when inserting the first | |
| 357 | * frame of a queue and the other queue already has | |
| 358 | * frames. We need to preserve the age difference | |
| 359 | * relationship so ieee80211_node_psq_age works. | |
| 360 | */ | |
| 361 | if (qhead == &psq->psq_head[1]) { | |
| 362 | mh = psq->psq_head[0].head; | |
| 363 | if (mh != NULL) | |
| 364 | age-= M_AGE_GET(mh); | |
| 365 | } else { | |
| 366 | mh = psq->psq_head[1].head; | |
| 367 | if (mh != NULL) { | |
| 368 | int nage = M_AGE_GET(mh) - age; | |
| 369 | /* XXX is clamping to zero good 'nuf? */ | |
| 370 | M_AGE_SET(mh, nage < 0 ? 0 : nage); | |
| 371 | } | |
| 372 | } | |
| 373 | } else { | |
| 374 | qhead->tail->m_nextpkt = m; | |
| 375 | age -= M_AGE_GET(qhead->head); | |
| 376 | } | |
| 377 | KASSERT(age >= 0, ("age %d", age)); | |
| 378 | M_AGE_SET(m, age); | |
| 379 | m->m_nextpkt = NULL; | |
| 380 | qhead->tail = m; | |
| 381 | qhead->len++; | |
| 382 | qlen = ++(psq->psq_len); | |
| 32176cfd RP |
383 | |
| 384 | IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, | |
| 385 | "save frame with age %d, %u now queued", age, qlen); | |
| 386 | ||
| 387 | if (qlen == 1 && vap->iv_set_tim != NULL) | |
| 388 | vap->iv_set_tim(ni, 1); | |
| 389 | ||
| 390 | return 0; | |
| 391 | } | |
| 392 | ||
| 393 | /* | |
| 394 | * Move frames from the ps q to the vap's send queue | |
| 395 | * and/or the driver's send queue; and kick the start | |
| 396 | * method for each, as appropriate. Note we're careful | |
| 397 | * to preserve packet ordering here. | |
| 398 | */ | |
| 399 | static void | |
| 400 | pwrsave_flushq(struct ieee80211_node *ni) | |
| 401 | { | |
| 402 | struct ieee80211_psq *psq = &ni->ni_psq; | |
| 403 | struct ieee80211vap *vap = ni->ni_vap; | |
| 404 | struct ieee80211_psq_head *qhead; | |
| 405 | struct ifnet *parent, *ifp; | |
| 406 | ||
| 407 | IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, | |
| 408 | "flush ps queue, %u packets queued", psq->psq_len); | |
| 409 | ||
| 32176cfd RP |
410 | qhead = &psq->psq_head[0]; /* 802.11 frames */ |
| 411 | if (qhead->head != NULL) { | |
| 412 | /* XXX could dispatch through vap and check M_ENCAP */ | |
| 413 | parent = vap->iv_ic->ic_ifp; | |
| 414 | /* XXX need different driver interface */ | |
| 415 | /* XXX bypasses q max and OACTIVE */ | |
| 416 | IF_PREPEND_LIST(&parent->if_snd, qhead->head, qhead->tail, | |
| 417 | qhead->len); | |
| 418 | qhead->head = qhead->tail = NULL; | |
| 419 | qhead->len = 0; | |
| 420 | } else | |
| 421 | parent = NULL; | |
| 422 | ||
| 423 | qhead = &psq->psq_head[1]; /* 802.3 frames */ | |
| 424 | if (qhead->head != NULL) { | |
| 425 | ifp = vap->iv_ifp; | |
| 426 | /* XXX need different driver interface */ | |
| 427 | /* XXX bypasses q max and OACTIVE */ | |
| 428 | IF_PREPEND_LIST(&ifp->if_snd, qhead->head, qhead->tail, | |
| 429 | qhead->len); | |
| 430 | qhead->head = qhead->tail = NULL; | |
| 431 | qhead->len = 0; | |
| 432 | } else | |
| 433 | ifp = NULL; | |
| 434 | psq->psq_len = 0; | |
| 32176cfd RP |
435 | |
| 436 | /* NB: do this outside the psq lock */ | |
| 437 | /* XXX packets might get reordered if parent is OACTIVE */ | |
| 438 | if (parent != NULL) | |
| 34a60cf6 | 439 | parent->if_start(parent); |
| 32176cfd | 440 | if (ifp != NULL) |
| 34a60cf6 | 441 | ifp->if_start(ifp); |
| 32176cfd RP |
442 | } |
| 443 | ||
| 444 | /* | |
| 445 | * Handle station power-save state change. | |
| 446 | */ | |
| 447 | void | |
| 448 | ieee80211_node_pwrsave(struct ieee80211_node *ni, int enable) | |
| 449 | { | |
| 450 | struct ieee80211vap *vap = ni->ni_vap; | |
| 451 | int update; | |
| 452 | ||
| 453 | update = 0; | |
| 454 | if (enable) { | |
| 455 | if ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) == 0) { | |
| 456 | vap->iv_ps_sta++; | |
| 457 | update = 1; | |
| 458 | } | |
| 459 | ni->ni_flags |= IEEE80211_NODE_PWR_MGT; | |
| 460 | IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, | |
| 461 | "power save mode on, %u sta's in ps mode", vap->iv_ps_sta); | |
| 462 | ||
| 463 | if (update) | |
| 464 | vap->iv_update_ps(vap, vap->iv_ps_sta); | |
| 465 | } else { | |
| 466 | if (ni->ni_flags & IEEE80211_NODE_PWR_MGT) { | |
| 467 | vap->iv_ps_sta--; | |
| 468 | update = 1; | |
| 469 | } | |
| 470 | ni->ni_flags &= ~IEEE80211_NODE_PWR_MGT; | |
| 471 | IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, | |
| 472 | "power save mode off, %u sta's in ps mode", vap->iv_ps_sta); | |
| 473 | ||
| 474 | /* NB: order here is intentional so TIM is clear before flush */ | |
| 475 | if (vap->iv_set_tim != NULL) | |
| 476 | vap->iv_set_tim(ni, 0); | |
| 477 | if (update) { | |
| 478 | /* NB if no sta's in ps, driver should flush mc q */ | |
| 479 | vap->iv_update_ps(vap, vap->iv_ps_sta); | |
| 480 | } | |
| 481 | if (ni->ni_psq.psq_len != 0) | |
| 482 | pwrsave_flushq(ni); | |
| 483 | } | |
| 484 | } | |
| 485 | ||
| 486 | /* | |
| 487 | * Handle power-save state change in station mode. | |
| 488 | */ | |
| 489 | void | |
| 490 | ieee80211_sta_pwrsave(struct ieee80211vap *vap, int enable) | |
| 491 | { | |
| 492 | struct ieee80211_node *ni = vap->iv_bss; | |
| 493 | ||
| 494 | if (!((enable != 0) ^ ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) != 0))) | |
| 495 | return; | |
| 496 | ||
| 497 | IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, | |
| 498 | "sta power save mode %s", enable ? "on" : "off"); | |
| 499 | if (!enable) { | |
| 500 | ni->ni_flags &= ~IEEE80211_NODE_PWR_MGT; | |
| 501 | ieee80211_send_nulldata(ieee80211_ref_node(ni)); | |
| 502 | /* | |
| 503 | * Flush any queued frames; we can do this immediately | |
| 504 | * because we know they'll be queued behind the null | |
| 505 | * data frame we send the ap. | |
| 506 | * XXX can we use a data frame to take us out of ps? | |
| 507 | */ | |
| 508 | if (ni->ni_psq.psq_len != 0) | |
| 509 | pwrsave_flushq(ni); | |
| 510 | } else { | |
| 511 | ni->ni_flags |= IEEE80211_NODE_PWR_MGT; | |
| 512 | ieee80211_send_nulldata(ieee80211_ref_node(ni)); | |
| 513 | } | |
| 514 | } |