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: releng/12.0/sys/net80211/ieee80211_ageq.c 326272 2017-11-27 15:23:17Z pfg $");
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 */
53void
54ieee80211_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 */
65void
66ieee80211_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 */
77static void
78ageq_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 */
91void
92ieee80211_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 */
107int
108ieee80211_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 */
140void
141ieee80211_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 */
149void
150ieee80211_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 */
163struct mbuf *
164ieee80211_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 */
194struct mbuf *
195ieee80211_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