/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2009 Sam Leffler, Errno Consulting4* All rights reserved.5*6* Redistribution and use in source and binary forms, with or without7* modification, are permitted provided that the following conditions8* are met:9* 1. Redistributions of source code must retain the above copyright10* notice, this list of conditions and the following disclaimer.11* 2. Redistributions in binary form must reproduce the above copyright12* notice, this list of conditions and the following disclaimer in the13* documentation and/or other materials provided with the distribution.14*15* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR16* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES17* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.18* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,19* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT20* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,21* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY22* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT23* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF24* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.25*/2627#include <sys/cdefs.h>28/*29* IEEE 802.11 age queue support.30*/31#include "opt_wlan.h"3233#include <sys/param.h>34#include <sys/systm.h>35#include <sys/kernel.h>36#include <sys/malloc.h>3738#include <sys/socket.h>3940#include <net/if.h>41#include <net/if_var.h>42#include <net/if_media.h>43#include <net/ethernet.h>4445#include <net80211/ieee80211_var.h>4647/*48* Initialize an ageq.49*/50void51ieee80211_ageq_init(struct ieee80211_ageq *aq, int maxlen, const char *name)52{53memset(aq, 0, sizeof(*aq));54aq->aq_maxlen = maxlen;55IEEE80211_AGEQ_INIT(aq, name); /* OS-dependent setup */56}5758/*59* Cleanup an ageq initialized with ieee80211_ageq_init. Note60* the queue is assumed empty; this can be done with ieee80211_ageq_drain.61*/62void63ieee80211_ageq_cleanup(struct ieee80211_ageq *aq)64{65KASSERT(aq->aq_len == 0, ("%d frames on ageq", aq->aq_len));66IEEE80211_AGEQ_DESTROY(aq); /* OS-dependent cleanup */67}6869/*70* Free an mbuf according to ageq rules: if marked as holding71* and 802.11 frame then also reclaim a node reference from72* the packet header; this handles packets q'd in the tx path.73*/74static void75ageq_mfree(struct mbuf *m)76{77if (m->m_flags & M_ENCAP) {78struct ieee80211_node *ni = (void *) m->m_pkthdr.rcvif;79ieee80211_free_node(ni);80}81m->m_nextpkt = NULL;82m_freem(m);83}8485/*86* Free a list of mbufs using ageq rules (see above).87*/88void89ieee80211_ageq_mfree(struct mbuf *m)90{91struct mbuf *next;9293for (; m != NULL; m = next) {94next = m->m_nextpkt;95ageq_mfree(m);96}97}9899/*100* Append an mbuf to the ageq and mark it with the specified max age101* If the frame is not removed before the age (in seconds) expires102* then it is reclaimed (along with any node reference).103*/104int105ieee80211_ageq_append(struct ieee80211_ageq *aq, struct mbuf *m, int age)106{107IEEE80211_AGEQ_LOCK(aq);108if (__predict_true(aq->aq_len < aq->aq_maxlen)) {109if (aq->aq_tail == NULL) {110aq->aq_head = m;111} else {112aq->aq_tail->m_nextpkt = m;113age -= M_AGE_GET(aq->aq_head);114}115KASSERT(age >= 0, ("age %d", age));116M_AGE_SET(m, age);117m->m_nextpkt = NULL;118aq->aq_tail = m;119aq->aq_len++;120IEEE80211_AGEQ_UNLOCK(aq);121return 0;122} else {123/*124* No space, drop and cleanup references.125*/126aq->aq_drops++;127IEEE80211_AGEQ_UNLOCK(aq);128/* XXX tail drop? */129ageq_mfree(m);130return ENOSPC;131}132}133134/*135* Drain/reclaim all frames from an ageq.136*/137void138ieee80211_ageq_drain(struct ieee80211_ageq *aq)139{140ieee80211_ageq_mfree(ieee80211_ageq_remove(aq, NULL));141}142143/*144* Drain/reclaim frames associated with a specific node from an ageq.145*/146void147ieee80211_ageq_drain_node(struct ieee80211_ageq *aq,148struct ieee80211_node *ni)149{150ieee80211_ageq_mfree(ieee80211_ageq_remove(aq, ni));151}152153/*154* Age frames on the age queue. Ages are stored as time155* deltas (in seconds) relative to the head so we can check156* and/or adjust only the head of the list. If a frame's age157* exceeds the time quanta then remove it. The list of removed158* frames is returned to the caller joined by m_nextpkt.159*/160struct mbuf *161ieee80211_ageq_age(struct ieee80211_ageq *aq, int quanta)162{163struct mbuf *head, **phead;164struct mbuf *m;165166phead = &head;167if (aq->aq_len != 0) {168IEEE80211_AGEQ_LOCK(aq);169while ((m = aq->aq_head) != NULL && M_AGE_GET(m) < quanta) {170if ((aq->aq_head = m->m_nextpkt) == NULL)171aq->aq_tail = NULL;172KASSERT(aq->aq_len > 0, ("aq len %d", aq->aq_len));173aq->aq_len--;174/* add to private list for return */175*phead = m;176phead = &m->m_nextpkt;177}178if (m != NULL)179M_AGE_SUB(m, quanta);180IEEE80211_AGEQ_UNLOCK(aq);181}182*phead = NULL;183return head;184}185186/*187* Remove all frames matching the specified node identifier188* (NULL matches all). Frames are returned as a list joined189* by m_nextpkt.190*/191struct mbuf *192ieee80211_ageq_remove(struct ieee80211_ageq *aq,193struct ieee80211_node *match)194{195struct mbuf *m, **prev, *ohead;196struct mbuf *head, **phead;197198IEEE80211_AGEQ_LOCK(aq);199ohead = aq->aq_head;200prev = &aq->aq_head;201phead = &head;202while ((m = *prev) != NULL) {203if (match != NULL && m->m_pkthdr.rcvif != (void *) match) {204prev = &m->m_nextpkt;205continue;206}207/*208* Adjust q length.209*/210KASSERT(aq->aq_len > 0, ("aq len %d", aq->aq_len));211aq->aq_len--;212/*213* Remove from forward list; tail pointer is harder.214*/215if (aq->aq_tail == m) {216KASSERT(m->m_nextpkt == NULL, ("not last"));217if (aq->aq_head == m) { /* list empty */218KASSERT(aq->aq_len == 0,219("not empty, len %d", aq->aq_len));220aq->aq_tail = NULL;221} else { /* must be one before */222aq->aq_tail = (struct mbuf *)((uintptr_t)prev -223offsetof(struct mbuf, m_nextpkt));224}225}226*prev = m->m_nextpkt;227228/* add to private list for return */229*phead = m;230phead = &m->m_nextpkt;231}232if (head == ohead && aq->aq_head != NULL) /* correct age */233M_AGE_SET(aq->aq_head, M_AGE_GET(head));234IEEE80211_AGEQ_UNLOCK(aq);235236*phead = NULL;237return head;238}239240241