xref: /freebsd/sys/dev/cxgbe/t4_clip.c (revision 78afed13)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2012 Chelsio Communications, Inc.
5  * All rights reserved.
6  * Written by: Navdeep Parhar <np@FreeBSD.org>
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29 
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
32 
33 #include <sys/types.h>
34 #include <sys/ck.h>
35 #include <sys/eventhandler.h>
36 #include <sys/malloc.h>
37 #include <sys/rmlock.h>
38 #include <sys/sbuf.h>
39 #include <sys/socket.h>
40 #include <sys/taskqueue.h>
41 #include <net/if.h>
42 #include <net/if_var.h>
43 #include <netinet/in.h>
44 #include <netinet6/in6_var.h>
45 #include <netinet6/scope6_var.h>
46 
47 #include "common/common.h"
48 #include "t4_clip.h"
49 
50 static int add_lip(struct adapter *, struct in6_addr *);
51 static int delete_lip(struct adapter *, struct in6_addr *);
52 static struct clip_entry *search_lip(struct adapter *, struct in6_addr *);
53 static void update_clip(struct adapter *, void *);
54 static void t4_clip_task(void *, int);
55 static void update_clip_table(struct adapter *);
56 
57 static int in6_ifaddr_gen;
58 static eventhandler_tag ifaddr_evhandler;
59 static struct timeout_task clip_task;
60 
61 static int
62 add_lip(struct adapter *sc, struct in6_addr *lip)
63 {
64         struct fw_clip_cmd c;
65 
66 	ASSERT_SYNCHRONIZED_OP(sc);
67 	mtx_assert(&sc->clip_table_lock, MA_OWNED);
68 
69         memset(&c, 0, sizeof(c));
70 	c.op_to_write = htonl(V_FW_CMD_OP(FW_CLIP_CMD) | F_FW_CMD_REQUEST |
71 	    F_FW_CMD_WRITE);
72         c.alloc_to_len16 = htonl(F_FW_CLIP_CMD_ALLOC | FW_LEN16(c));
73         c.ip_hi = *(uint64_t *)&lip->s6_addr[0];
74         c.ip_lo = *(uint64_t *)&lip->s6_addr[8];
75 
76 	return (-t4_wr_mbox_ns(sc, sc->mbox, &c, sizeof(c), &c));
77 }
78 
79 static int
80 delete_lip(struct adapter *sc, struct in6_addr *lip)
81 {
82 	struct fw_clip_cmd c;
83 
84 	ASSERT_SYNCHRONIZED_OP(sc);
85 	mtx_assert(&sc->clip_table_lock, MA_OWNED);
86 
87 	memset(&c, 0, sizeof(c));
88 	c.op_to_write = htonl(V_FW_CMD_OP(FW_CLIP_CMD) | F_FW_CMD_REQUEST |
89 	    F_FW_CMD_READ);
90         c.alloc_to_len16 = htonl(F_FW_CLIP_CMD_FREE | FW_LEN16(c));
91         c.ip_hi = *(uint64_t *)&lip->s6_addr[0];
92         c.ip_lo = *(uint64_t *)&lip->s6_addr[8];
93 
94 	return (-t4_wr_mbox_ns(sc, sc->mbox, &c, sizeof(c), &c));
95 }
96 
97 static struct clip_entry *
98 search_lip(struct adapter *sc, struct in6_addr *lip)
99 {
100 	struct clip_entry *ce;
101 
102 	mtx_assert(&sc->clip_table_lock, MA_OWNED);
103 
104 	TAILQ_FOREACH(ce, &sc->clip_table, link) {
105 		if (IN6_ARE_ADDR_EQUAL(&ce->lip, lip))
106 			return (ce);
107 	}
108 
109 	return (NULL);
110 }
111 
112 struct clip_entry *
113 t4_hold_lip(struct adapter *sc, struct in6_addr *lip, struct clip_entry *ce)
114 {
115 
116 	mtx_lock(&sc->clip_table_lock);
117 	if (ce == NULL)
118 		ce = search_lip(sc, lip);
119 	if (ce != NULL)
120 		ce->refcount++;
121 	mtx_unlock(&sc->clip_table_lock);
122 
123 	return (ce);
124 }
125 
126 void
127 t4_release_lip(struct adapter *sc, struct clip_entry *ce)
128 {
129 
130 	mtx_lock(&sc->clip_table_lock);
131 	KASSERT(search_lip(sc, &ce->lip) == ce,
132 	    ("%s: CLIP entry %p p not in CLIP table.", __func__, ce));
133 	KASSERT(ce->refcount > 0,
134 	    ("%s: CLIP entry %p has refcount 0", __func__, ce));
135 	--ce->refcount;
136 	mtx_unlock(&sc->clip_table_lock);
137 }
138 
139 void
140 t4_init_clip_table(struct adapter *sc)
141 {
142 
143 	mtx_init(&sc->clip_table_lock, "CLIP table lock", NULL, MTX_DEF);
144 	TAILQ_INIT(&sc->clip_table);
145 	sc->clip_gen = -1;
146 
147 	/*
148 	 * Don't bother forcing an update of the clip table when the
149 	 * adapter is initialized.  Before an interface can be used it
150 	 * must be assigned an address which will trigger the event
151 	 * handler to update the table.
152 	 */
153 }
154 
155 static void
156 update_clip(struct adapter *sc, void *arg __unused)
157 {
158 
159 	if (begin_synchronized_op(sc, NULL, HOLD_LOCK, "t4clip"))
160 		return;
161 
162 	if (mtx_initialized(&sc->clip_table_lock))
163 		update_clip_table(sc);
164 
165 	end_synchronized_op(sc, LOCK_HELD);
166 }
167 
168 static void
169 t4_clip_task(void *arg, int count)
170 {
171 
172 	t4_iterate(update_clip, NULL);
173 }
174 
175 static void
176 update_clip_table(struct adapter *sc)
177 {
178 	struct rm_priotracker in6_ifa_tracker;
179 	struct in6_ifaddr *ia;
180 	struct in6_addr *lip, tlip;
181 	TAILQ_HEAD(, clip_entry) stale;
182 	struct clip_entry *ce, *ce_temp;
183 	struct vi_info *vi;
184 	int rc, gen, i, j;
185 	uintptr_t last_vnet;
186 
187 	ASSERT_SYNCHRONIZED_OP(sc);
188 
189 	IN6_IFADDR_RLOCK(&in6_ifa_tracker);
190 	mtx_lock(&sc->clip_table_lock);
191 
192 	gen = atomic_load_acq_int(&in6_ifaddr_gen);
193 	if (gen == sc->clip_gen)
194 		goto done;
195 
196 	TAILQ_INIT(&stale);
197 	TAILQ_CONCAT(&stale, &sc->clip_table, link);
198 
199 	/*
200 	 * last_vnet optimizes the common cases where all if_vnet = NULL (no
201 	 * VIMAGE) or all if_vnet = vnet0.
202 	 */
203 	last_vnet = (uintptr_t)(-1);
204 	for_each_port(sc, i)
205 	for_each_vi(sc->port[i], j, vi) {
206 		if (last_vnet == (uintptr_t)vi->ifp->if_vnet)
207 			continue;
208 
209 		/* XXX: races with if_vmove */
210 		CURVNET_SET(vi->ifp->if_vnet);
211 		CK_STAILQ_FOREACH(ia, &V_in6_ifaddrhead, ia_link) {
212 			lip = &ia->ia_addr.sin6_addr;
213 
214 			KASSERT(!IN6_IS_ADDR_MULTICAST(lip),
215 			    ("%s: mcast address in in6_ifaddr list", __func__));
216 
217 			if (IN6_IS_ADDR_LOOPBACK(lip))
218 				continue;
219 			if (IN6_IS_SCOPE_EMBED(lip)) {
220 				/* Remove the embedded scope */
221 				tlip = *lip;
222 				lip = &tlip;
223 				in6_clearscope(lip);
224 			}
225 			/*
226 			 * XXX: how to weed out the link local address for the
227 			 * loopback interface?  It's fe80::1 usually (always?).
228 			 */
229 
230 			/*
231 			 * If it's in the main list then we already know it's
232 			 * not stale.
233 			 */
234 			TAILQ_FOREACH(ce, &sc->clip_table, link) {
235 				if (IN6_ARE_ADDR_EQUAL(&ce->lip, lip))
236 					goto next;
237 			}
238 
239 			/*
240 			 * If it's in the stale list we should move it to the
241 			 * main list.
242 			 */
243 			TAILQ_FOREACH(ce, &stale, link) {
244 				if (IN6_ARE_ADDR_EQUAL(&ce->lip, lip)) {
245 					TAILQ_REMOVE(&stale, ce, link);
246 					TAILQ_INSERT_TAIL(&sc->clip_table, ce,
247 					    link);
248 					goto next;
249 				}
250 			}
251 
252 			/* A new IP6 address; add it to the CLIP table */
253 			ce = malloc(sizeof(*ce), M_CXGBE, M_NOWAIT);
254 			memcpy(&ce->lip, lip, sizeof(ce->lip));
255 			ce->refcount = 0;
256 			rc = add_lip(sc, lip);
257 			if (rc == 0)
258 				TAILQ_INSERT_TAIL(&sc->clip_table, ce, link);
259 			else {
260 				char ip[INET6_ADDRSTRLEN];
261 
262 				inet_ntop(AF_INET6, &ce->lip, &ip[0],
263 				    sizeof(ip));
264 				log(LOG_ERR, "%s: could not add %s (%d)\n",
265 				    __func__, ip, rc);
266 				free(ce, M_CXGBE);
267 			}
268 next:
269 			continue;
270 		}
271 		CURVNET_RESTORE();
272 		last_vnet = (uintptr_t)vi->ifp->if_vnet;
273 	}
274 
275 	/*
276 	 * Remove stale addresses (those no longer in V_in6_ifaddrhead) that are
277 	 * no longer referenced by the driver.
278 	 */
279 	TAILQ_FOREACH_SAFE(ce, &stale, link, ce_temp) {
280 		if (ce->refcount == 0) {
281 			rc = delete_lip(sc, &ce->lip);
282 			if (rc == 0) {
283 				TAILQ_REMOVE(&stale, ce, link);
284 				free(ce, M_CXGBE);
285 			} else {
286 				char ip[INET6_ADDRSTRLEN];
287 
288 				inet_ntop(AF_INET6, &ce->lip, &ip[0],
289 				    sizeof(ip));
290 				log(LOG_ERR, "%s: could not delete %s (%d)\n",
291 				    __func__, ip, rc);
292 			}
293 		}
294 	}
295 	/* The ones that are still referenced need to stay in the CLIP table */
296 	TAILQ_CONCAT(&sc->clip_table, &stale, link);
297 
298 	sc->clip_gen = gen;
299 done:
300 	mtx_unlock(&sc->clip_table_lock);
301 	IN6_IFADDR_RUNLOCK(&in6_ifa_tracker);
302 }
303 
304 void
305 t4_destroy_clip_table(struct adapter *sc)
306 {
307 	struct clip_entry *ce, *ce_temp;
308 
309 	if (mtx_initialized(&sc->clip_table_lock)) {
310 		mtx_lock(&sc->clip_table_lock);
311 		TAILQ_FOREACH_SAFE(ce, &sc->clip_table, link, ce_temp) {
312 			KASSERT(ce->refcount == 0,
313 			    ("%s: CLIP entry %p still in use (%d)", __func__,
314 			    ce, ce->refcount));
315 			TAILQ_REMOVE(&sc->clip_table, ce, link);
316 			delete_lip(sc, &ce->lip);
317 			free(ce, M_CXGBE);
318 		}
319 		mtx_unlock(&sc->clip_table_lock);
320 		mtx_destroy(&sc->clip_table_lock);
321 	}
322 }
323 
324 static void
325 t4_tom_ifaddr_event(void *arg __unused, struct ifnet *ifp)
326 {
327 
328 	atomic_add_rel_int(&in6_ifaddr_gen, 1);
329 	taskqueue_enqueue_timeout(taskqueue_thread, &clip_task, -hz / 4);
330 }
331 
332 int
333 sysctl_clip(SYSCTL_HANDLER_ARGS)
334 {
335 	struct adapter *sc = arg1;
336 	struct clip_entry *ce;
337 	struct sbuf *sb;
338 	int rc, header = 0;
339 	char ip[INET6_ADDRSTRLEN];
340 
341 	rc = sysctl_wire_old_buffer(req, 0);
342 	if (rc != 0)
343 		return (rc);
344 
345 	sb = sbuf_new_for_sysctl(NULL, NULL, 4096, req);
346 	if (sb == NULL)
347 		return (ENOMEM);
348 
349 	mtx_lock(&sc->clip_table_lock);
350 	TAILQ_FOREACH(ce, &sc->clip_table, link) {
351 		if (header == 0) {
352 			sbuf_printf(sb, "%-40s %-5s", "IP address", "Users");
353 			header = 1;
354 		}
355 		inet_ntop(AF_INET6, &ce->lip, &ip[0], sizeof(ip));
356 
357 		sbuf_printf(sb, "\n%-40s %5u", ip, ce->refcount);
358 	}
359 	mtx_unlock(&sc->clip_table_lock);
360 
361 	rc = sbuf_finish(sb);
362 	sbuf_delete(sb);
363 
364 	return (rc);
365 }
366 
367 void
368 t4_clip_modload(void)
369 {
370 
371 	TIMEOUT_TASK_INIT(taskqueue_thread, &clip_task, 0, t4_clip_task, NULL);
372 	ifaddr_evhandler = EVENTHANDLER_REGISTER(ifaddr_event,
373 	    t4_tom_ifaddr_event, NULL, EVENTHANDLER_PRI_ANY);
374 }
375 
376 void
377 t4_clip_modunload(void)
378 {
379 
380 	EVENTHANDLER_DEREGISTER(ifaddr_event, ifaddr_evhandler);
381 	taskqueue_cancel_timeout(taskqueue_thread, &clip_task, NULL);
382 }
383