xref: /freebsd/sys/net80211/ieee80211_ageq.c (revision a0b9e2e854027e6ff61fb075a1309dbc71c42b54)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2009 Sam Leffler, Errno Consulting
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30 
31 /*
32  * IEEE 802.11 age queue support.
33  */
34 #include "opt_wlan.h"
35 
36 #include <sys/param.h>
37 #include <sys/systm.h>
38 #include <sys/kernel.h>
39 #include <sys/malloc.h>
40 
41 #include <sys/socket.h>
42 
43 #include <net/if.h>
44 #include <net/if_var.h>
45 #include <net/if_media.h>
46 #include <net/ethernet.h>
47 
48 #include <net80211/ieee80211_var.h>
49 
50 /*
51  * Initialize an ageq.
52  */
53 void
54 ieee80211_ageq_init(struct ieee80211_ageq *aq, int maxlen, const char *name)
55 {
56 	memset(aq, 0, sizeof(*aq));
57 	aq->aq_maxlen = maxlen;
58 	IEEE80211_AGEQ_INIT(aq, name);		/* OS-dependent setup */
59 }
60 
61 /*
62  * Cleanup an ageq initialized with ieee80211_ageq_init.  Note
63  * the queue is assumed empty; this can be done with ieee80211_ageq_drain.
64  */
65 void
66 ieee80211_ageq_cleanup(struct ieee80211_ageq *aq)
67 {
68 	KASSERT(aq->aq_len == 0, ("%d frames on ageq", aq->aq_len));
69 	IEEE80211_AGEQ_DESTROY(aq);		/* OS-dependent cleanup */
70 }
71 
72 /*
73  * Free an mbuf according to ageq rules: if marked as holding
74  * and 802.11 frame then also reclaim a node reference from
75  * the packet header; this handles packets q'd in the tx path.
76  */
77 static void
78 ageq_mfree(struct mbuf *m)
79 {
80 	if (m->m_flags & M_ENCAP) {
81 		struct ieee80211_node *ni = (void *) m->m_pkthdr.rcvif;
82 		ieee80211_free_node(ni);
83 	}
84 	m->m_nextpkt = NULL;
85 	m_freem(m);
86 }
87 
88 /*
89  * Free a list of mbufs using ageq rules (see above).
90  */
91 void
92 ieee80211_ageq_mfree(struct mbuf *m)
93 {
94 	struct mbuf *next;
95 
96 	for (; m != NULL; m = next) {
97 		next = m->m_nextpkt;
98 		ageq_mfree(m);
99 	}
100 }
101 
102 /*
103  * Append an mbuf to the ageq and mark it with the specified max age
104  * If the frame is not removed before the age (in seconds) expires
105  * then it is reclaimed (along with any node reference).
106  */
107 int
108 ieee80211_ageq_append(struct ieee80211_ageq *aq, struct mbuf *m, int age)
109 {
110 	IEEE80211_AGEQ_LOCK(aq);
111 	if (__predict_true(aq->aq_len < aq->aq_maxlen)) {
112 		if (aq->aq_tail == NULL) {
113 			aq->aq_head = m;
114 		} else {
115 			aq->aq_tail->m_nextpkt = m;
116 			age -= M_AGE_GET(aq->aq_head);
117 		}
118 		KASSERT(age >= 0, ("age %d", age));
119 		M_AGE_SET(m, age);
120 		m->m_nextpkt = NULL;
121 		aq->aq_tail = m;
122 		aq->aq_len++;
123 		IEEE80211_AGEQ_UNLOCK(aq);
124 		return 0;
125 	} else {
126 		/*
127 		 * No space, drop and cleanup references.
128 		 */
129 		aq->aq_drops++;
130 		IEEE80211_AGEQ_UNLOCK(aq);
131 		/* XXX tail drop? */
132 		ageq_mfree(m);
133 		return ENOSPC;
134 	}
135 }
136 
137 /*
138  * Drain/reclaim all frames from an ageq.
139  */
140 void
141 ieee80211_ageq_drain(struct ieee80211_ageq *aq)
142 {
143 	ieee80211_ageq_mfree(ieee80211_ageq_remove(aq, NULL));
144 }
145 
146 /*
147  * Drain/reclaim frames associated with a specific node from an ageq.
148  */
149 void
150 ieee80211_ageq_drain_node(struct ieee80211_ageq *aq,
151 	struct ieee80211_node *ni)
152 {
153 	ieee80211_ageq_mfree(ieee80211_ageq_remove(aq, ni));
154 }
155 
156 /*
157  * Age frames on the age queue.  Ages are stored as time
158  * deltas (in seconds) relative to the head so we can check
159  * and/or adjust only the head of the list.  If a frame's age
160  * exceeds the time quanta then remove it.  The list of removed
161  * frames is returned to the caller joined by m_nextpkt.
162  */
163 struct mbuf *
164 ieee80211_ageq_age(struct ieee80211_ageq *aq, int quanta)
165 {
166 	struct mbuf *head, **phead;
167 	struct mbuf *m;
168 
169 	phead = &head;
170 	if (aq->aq_len != 0) {
171 		IEEE80211_AGEQ_LOCK(aq);
172 		while ((m = aq->aq_head) != NULL && M_AGE_GET(m) < quanta) {
173 			if ((aq->aq_head = m->m_nextpkt) == NULL)
174 				aq->aq_tail = NULL;
175 			KASSERT(aq->aq_len > 0, ("aq len %d", aq->aq_len));
176 			aq->aq_len--;
177 			/* add to private list for return */
178 			*phead = m;
179 			phead = &m->m_nextpkt;
180 		}
181 		if (m != NULL)
182 			M_AGE_SUB(m, quanta);
183 		IEEE80211_AGEQ_UNLOCK(aq);
184 	}
185 	*phead = NULL;
186 	return head;
187 }
188 
189 /*
190  * Remove all frames matching the specified node identifier
191  * (NULL matches all).  Frames are returned as a list joined
192  * by m_nextpkt.
193  */
194 struct mbuf *
195 ieee80211_ageq_remove(struct ieee80211_ageq *aq,
196 	struct ieee80211_node *match)
197 {
198 	struct mbuf *m, **prev, *ohead;
199 	struct mbuf *head, **phead;
200 
201 	IEEE80211_AGEQ_LOCK(aq);
202 	ohead = aq->aq_head;
203 	prev = &aq->aq_head;
204 	phead = &head;
205 	while ((m = *prev) != NULL) {
206 		if (match != NULL && m->m_pkthdr.rcvif != (void *) match) {
207 			prev = &m->m_nextpkt;
208 			continue;
209 		}
210 		/*
211 		 * Adjust q length.
212 		 */
213 		KASSERT(aq->aq_len > 0, ("aq len %d", aq->aq_len));
214 		aq->aq_len--;
215 		/*
216 		 * Remove from forward list; tail pointer is harder.
217 		 */
218 		if (aq->aq_tail == m) {
219 			KASSERT(m->m_nextpkt == NULL, ("not last"));
220 			if (aq->aq_head == m) {		/* list empty */
221 				KASSERT(aq->aq_len == 0,
222 				    ("not empty, len %d", aq->aq_len));
223 				aq->aq_tail = NULL;
224 			} else {			/* must be one before */
225 				aq->aq_tail = (struct mbuf *)((uintptr_t)prev -
226 				    offsetof(struct mbuf, m_nextpkt));
227 			}
228 		}
229 		*prev = m->m_nextpkt;
230 
231 		/* add to private list for return */
232 		*phead = m;
233 		phead = &m->m_nextpkt;
234 	}
235 	if (head == ohead && aq->aq_head != NULL)	/* correct age */
236 		M_AGE_SET(aq->aq_head, M_AGE_GET(head));
237 	IEEE80211_AGEQ_UNLOCK(aq);
238 
239 	*phead = NULL;
240 	return head;
241 }
242