1274078Sngie/*-
2274078Sngie * Copyright (c) 2009 Sam Leffler, Errno Consulting
3274078Sngie * All rights reserved.
4274078Sngie *
5274078Sngie * Redistribution and use in source and binary forms, with or without
6274078Sngie * modification, are permitted provided that the following conditions
7274078Sngie * are met:
8274078Sngie * 1. Redistributions of source code must retain the above copyright
9274078Sngie *    notice, this list of conditions and the following disclaimer.
10274078Sngie * 2. Redistributions in binary form must reproduce the above copyright
11274078Sngie *    notice, this list of conditions and the following disclaimer in the
12274078Sngie *    documentation and/or other materials provided with the distribution.
13274078Sngie *
14274078Sngie * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15274078Sngie * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16274078Sngie * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17274078Sngie * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18274078Sngie * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19274078Sngie * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20274078Sngie * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21274078Sngie * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22274078Sngie * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23274078Sngie * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24274078Sngie */
25274078Sngie
26274078Sngie#include <sys/cdefs.h>
27274078Sngie__FBSDID("$FreeBSD$");
28274078Sngie
29274078Sngie/*
30274078Sngie * IEEE 802.11 age queue support.
31274078Sngie */
32274078Sngie#include "opt_wlan.h"
33274078Sngie
34274078Sngie#include <sys/param.h>
35274078Sngie#include <sys/systm.h>
36274078Sngie#include <sys/kernel.h>
37274078Sngie#include <sys/malloc.h>
38274078Sngie
39274078Sngie#include <sys/socket.h>
40274078Sngie
41274078Sngie#include <net/if.h>
42274078Sngie#include <net/if_var.h>
43274078Sngie#include <net/if_media.h>
44274078Sngie#include <net/ethernet.h>
45274078Sngie
46274078Sngie#include <net80211/ieee80211_var.h>
47274078Sngie
48274078Sngie/*
49274078Sngie * Initialize an ageq.
50274078Sngie */
51274078Sngievoid
52274078Sngieieee80211_ageq_init(struct ieee80211_ageq *aq, int maxlen, const char *name)
53274078Sngie{
54274078Sngie	memset(aq, 0, sizeof(*aq));
55274078Sngie	aq->aq_maxlen = maxlen;
56274078Sngie	IEEE80211_AGEQ_INIT(aq, name);		/* OS-dependent setup */
57274078Sngie}
58274078Sngie
59274078Sngie/*
60274078Sngie * Cleanup an ageq initialized with ieee80211_ageq_init.  Note
61274078Sngie * the queue is assumed empty; this can be done with ieee80211_ageq_drain.
62274078Sngie */
63274078Sngievoid
64274078Sngieieee80211_ageq_cleanup(struct ieee80211_ageq *aq)
65274078Sngie{
66	KASSERT(aq->aq_len == 0, ("%d frames on ageq", aq->aq_len));
67	IEEE80211_AGEQ_DESTROY(aq);		/* OS-dependent cleanup */
68}
69
70/*
71 * Free an mbuf according to ageq rules: if marked as holding
72 * and 802.11 frame then also reclaim a node reference from
73 * the packet header; this handles packets q'd in the tx path.
74 */
75static void
76ageq_mfree(struct mbuf *m)
77{
78	if (m->m_flags & M_ENCAP) {
79		struct ieee80211_node *ni = (void *) m->m_pkthdr.rcvif;
80		ieee80211_free_node(ni);
81	}
82	m->m_nextpkt = NULL;
83	m_freem(m);
84}
85
86/*
87 * Free a list of mbufs using ageq rules (see above).
88 */
89void
90ieee80211_ageq_mfree(struct mbuf *m)
91{
92	struct mbuf *next;
93
94	for (; m != NULL; m = next) {
95		next = m->m_nextpkt;
96		ageq_mfree(m);
97	}
98}
99
100/*
101 * Append an mbuf to the ageq and mark it with the specified max age
102 * If the frame is not removed before the age (in seconds) expires
103 * then it is reclaimed (along with any node reference).
104 */
105int
106ieee80211_ageq_append(struct ieee80211_ageq *aq, struct mbuf *m, int age)
107{
108	IEEE80211_AGEQ_LOCK(aq);
109	if (__predict_true(aq->aq_len < aq->aq_maxlen)) {
110		if (aq->aq_tail == NULL) {
111			aq->aq_head = m;
112		} else {
113			aq->aq_tail->m_nextpkt = m;
114			age -= M_AGE_GET(aq->aq_head);
115		}
116		KASSERT(age >= 0, ("age %d", age));
117		M_AGE_SET(m, age);
118		m->m_nextpkt = NULL;
119		aq->aq_tail = m;
120		aq->aq_len++;
121		IEEE80211_AGEQ_UNLOCK(aq);
122		return 0;
123	} else {
124		/*
125		 * No space, drop and cleanup references.
126		 */
127		aq->aq_drops++;
128		IEEE80211_AGEQ_UNLOCK(aq);
129		/* XXX tail drop? */
130		ageq_mfree(m);
131		return ENOSPC;
132	}
133}
134
135/*
136 * Drain/reclaim all frames from an ageq.
137 */
138void
139ieee80211_ageq_drain(struct ieee80211_ageq *aq)
140{
141	ieee80211_ageq_mfree(ieee80211_ageq_remove(aq, NULL));
142}
143
144/*
145 * Drain/reclaim frames associated with a specific node from an ageq.
146 */
147void
148ieee80211_ageq_drain_node(struct ieee80211_ageq *aq,
149	struct ieee80211_node *ni)
150{
151	ieee80211_ageq_mfree(ieee80211_ageq_remove(aq, ni));
152}
153
154/*
155 * Age frames on the age queue.  Ages are stored as time
156 * deltas (in seconds) relative to the head so we can check
157 * and/or adjust only the head of the list.  If a frame's age
158 * exceeds the time quanta then remove it.  The list of removed
159 * frames is returned to the caller joined by m_nextpkt.
160 */
161struct mbuf *
162ieee80211_ageq_age(struct ieee80211_ageq *aq, int quanta)
163{
164	struct mbuf *head, **phead;
165	struct mbuf *m;
166
167	phead = &head;
168	if (aq->aq_len != 0) {
169		IEEE80211_AGEQ_LOCK(aq);
170		while ((m = aq->aq_head) != NULL && M_AGE_GET(m) < quanta) {
171			if ((aq->aq_head = m->m_nextpkt) == NULL)
172				aq->aq_tail = NULL;
173			KASSERT(aq->aq_len > 0, ("aq len %d", aq->aq_len));
174			aq->aq_len--;
175			/* add to private list for return */
176			*phead = m;
177			phead = &m->m_nextpkt;
178		}
179		if (m != NULL)
180			M_AGE_SUB(m, quanta);
181		IEEE80211_AGEQ_UNLOCK(aq);
182	}
183	*phead = NULL;
184	return head;
185}
186
187/*
188 * Remove all frames matching the specified node identifier
189 * (NULL matches all).  Frames are returned as a list joined
190 * by m_nextpkt.
191 */
192struct mbuf *
193ieee80211_ageq_remove(struct ieee80211_ageq *aq,
194	struct ieee80211_node *match)
195{
196	struct mbuf *m, **prev, *ohead;
197	struct mbuf *head, **phead;
198
199	IEEE80211_AGEQ_LOCK(aq);
200	ohead = aq->aq_head;
201	prev = &aq->aq_head;
202	phead = &head;
203	while ((m = *prev) != NULL) {
204		if (match != NULL && m->m_pkthdr.rcvif != (void *) match) {
205			prev = &m->m_nextpkt;
206			continue;
207		}
208		/*
209		 * Adjust q length.
210		 */
211		KASSERT(aq->aq_len > 0, ("aq len %d", aq->aq_len));
212		aq->aq_len--;
213		/*
214		 * Remove from forward list; tail pointer is harder.
215		 */
216		if (aq->aq_tail == m) {
217			KASSERT(m->m_nextpkt == NULL, ("not last"));
218			if (aq->aq_head == m) {		/* list empty */
219				KASSERT(aq->aq_len == 0,
220				    ("not empty, len %d", aq->aq_len));
221				aq->aq_tail = NULL;
222			} else {			/* must be one before */
223				aq->aq_tail = (struct mbuf *)((uintptr_t)prev -
224				    offsetof(struct mbuf, m_nextpkt));
225			}
226		}
227		*prev = m->m_nextpkt;
228
229		/* add to private list for return */
230		*phead = m;
231		phead = &m->m_nextpkt;
232	}
233	if (head == ohead && aq->aq_head != NULL)	/* correct age */
234		M_AGE_SET(aq->aq_head, M_AGE_GET(head));
235	IEEE80211_AGEQ_UNLOCK(aq);
236
237	*phead = NULL;
238	return head;
239}
240