xref: /freebsd/sys/dev/cxgbe/t4_clip.c (revision 2ed3236082a4473c1da8f72c1ebc071a7b54321f)
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 "opt_inet.h"
34 #include "opt_inet6.h"
35 
36 #include <sys/types.h>
37 #include <sys/ck.h>
38 #include <sys/eventhandler.h>
39 #include <sys/malloc.h>
40 #include <sys/rmlock.h>
41 #include <sys/sbuf.h>
42 #include <sys/socket.h>
43 #include <sys/taskqueue.h>
44 #include <net/if.h>
45 #include <net/if_var.h>
46 #include <netinet/in.h>
47 #include <netinet6/in6_var.h>
48 #include <netinet6/scope6_var.h>
49 
50 #include "common/common.h"
51 #include "t4_clip.h"
52 
53 #if defined(INET6)
54 static int add_lip(struct adapter *, struct in6_addr *);
55 static int delete_lip(struct adapter *, struct in6_addr *);
56 static struct clip_entry *search_lip(struct adapter *, struct in6_addr *);
57 static void update_clip(struct adapter *, void *);
58 static void t4_clip_task(void *, int);
59 static void update_clip_table(struct adapter *);
60 
61 static int in6_ifaddr_gen;
62 static eventhandler_tag ifaddr_evhandler;
63 static struct timeout_task clip_task;
64 
65 static int
66 add_lip(struct adapter *sc, struct in6_addr *lip)
67 {
68         struct fw_clip_cmd c;
69 
70 	ASSERT_SYNCHRONIZED_OP(sc);
71 	mtx_assert(&sc->clip_table_lock, MA_OWNED);
72 
73         memset(&c, 0, sizeof(c));
74 	c.op_to_write = htonl(V_FW_CMD_OP(FW_CLIP_CMD) | F_FW_CMD_REQUEST |
75 	    F_FW_CMD_WRITE);
76         c.alloc_to_len16 = htonl(F_FW_CLIP_CMD_ALLOC | FW_LEN16(c));
77         c.ip_hi = *(uint64_t *)&lip->s6_addr[0];
78         c.ip_lo = *(uint64_t *)&lip->s6_addr[8];
79 
80 	return (-t4_wr_mbox_ns(sc, sc->mbox, &c, sizeof(c), &c));
81 }
82 
83 static int
84 delete_lip(struct adapter *sc, struct in6_addr *lip)
85 {
86 	struct fw_clip_cmd c;
87 
88 	ASSERT_SYNCHRONIZED_OP(sc);
89 	mtx_assert(&sc->clip_table_lock, MA_OWNED);
90 
91 	memset(&c, 0, sizeof(c));
92 	c.op_to_write = htonl(V_FW_CMD_OP(FW_CLIP_CMD) | F_FW_CMD_REQUEST |
93 	    F_FW_CMD_READ);
94         c.alloc_to_len16 = htonl(F_FW_CLIP_CMD_FREE | FW_LEN16(c));
95         c.ip_hi = *(uint64_t *)&lip->s6_addr[0];
96         c.ip_lo = *(uint64_t *)&lip->s6_addr[8];
97 
98 	return (-t4_wr_mbox_ns(sc, sc->mbox, &c, sizeof(c), &c));
99 }
100 
101 static struct clip_entry *
102 search_lip(struct adapter *sc, struct in6_addr *lip)
103 {
104 	struct clip_entry *ce;
105 
106 	mtx_assert(&sc->clip_table_lock, MA_OWNED);
107 
108 	TAILQ_FOREACH(ce, &sc->clip_table, link) {
109 		if (IN6_ARE_ADDR_EQUAL(&ce->lip, lip))
110 			return (ce);
111 	}
112 
113 	return (NULL);
114 }
115 #endif
116 
117 struct clip_entry *
118 t4_hold_lip(struct adapter *sc, struct in6_addr *lip, struct clip_entry *ce)
119 {
120 
121 #ifdef INET6
122 	mtx_lock(&sc->clip_table_lock);
123 	if (ce == NULL)
124 		ce = search_lip(sc, lip);
125 	if (ce != NULL)
126 		ce->refcount++;
127 	mtx_unlock(&sc->clip_table_lock);
128 
129 	return (ce);
130 #else
131 	return (NULL);
132 #endif
133 }
134 
135 void
136 t4_release_lip(struct adapter *sc, struct clip_entry *ce)
137 {
138 
139 #ifdef INET6
140 	mtx_lock(&sc->clip_table_lock);
141 	KASSERT(search_lip(sc, &ce->lip) == ce,
142 	    ("%s: CLIP entry %p p not in CLIP table.", __func__, ce));
143 	KASSERT(ce->refcount > 0,
144 	    ("%s: CLIP entry %p has refcount 0", __func__, ce));
145 	--ce->refcount;
146 	mtx_unlock(&sc->clip_table_lock);
147 #endif
148 }
149 
150 #ifdef INET6
151 void
152 t4_init_clip_table(struct adapter *sc)
153 {
154 
155 	mtx_init(&sc->clip_table_lock, "CLIP table lock", NULL, MTX_DEF);
156 	TAILQ_INIT(&sc->clip_table);
157 	sc->clip_gen = -1;
158 
159 	/*
160 	 * Don't bother forcing an update of the clip table when the
161 	 * adapter is initialized.  Before an interface can be used it
162 	 * must be assigned an address which will trigger the event
163 	 * handler to update the table.
164 	 */
165 }
166 
167 static void
168 update_clip(struct adapter *sc, void *arg __unused)
169 {
170 
171 	if (begin_synchronized_op(sc, NULL, HOLD_LOCK, "t4clip"))
172 		return;
173 
174 	if (mtx_initialized(&sc->clip_table_lock))
175 		update_clip_table(sc);
176 
177 	end_synchronized_op(sc, LOCK_HELD);
178 }
179 
180 static void
181 t4_clip_task(void *arg, int count)
182 {
183 
184 	t4_iterate(update_clip, NULL);
185 }
186 
187 static void
188 update_clip_table(struct adapter *sc)
189 {
190 	struct rm_priotracker in6_ifa_tracker;
191 	struct in6_ifaddr *ia;
192 	struct in6_addr *lip, tlip;
193 	TAILQ_HEAD(, clip_entry) stale;
194 	struct clip_entry *ce, *ce_temp;
195 	struct vi_info *vi;
196 	int rc, gen, i, j;
197 	uintptr_t last_vnet;
198 
199 	ASSERT_SYNCHRONIZED_OP(sc);
200 
201 	IN6_IFADDR_RLOCK(&in6_ifa_tracker);
202 	mtx_lock(&sc->clip_table_lock);
203 
204 	gen = atomic_load_acq_int(&in6_ifaddr_gen);
205 	if (gen == sc->clip_gen)
206 		goto done;
207 
208 	TAILQ_INIT(&stale);
209 	TAILQ_CONCAT(&stale, &sc->clip_table, link);
210 
211 	/*
212 	 * last_vnet optimizes the common cases where all if_vnet = NULL (no
213 	 * VIMAGE) or all if_vnet = vnet0.
214 	 */
215 	last_vnet = (uintptr_t)(-1);
216 	for_each_port(sc, i)
217 	for_each_vi(sc->port[i], j, vi) {
218 		if (last_vnet == (uintptr_t)vi->ifp->if_vnet)
219 			continue;
220 
221 		/* XXX: races with if_vmove */
222 		CURVNET_SET(vi->ifp->if_vnet);
223 		CK_STAILQ_FOREACH(ia, &V_in6_ifaddrhead, ia_link) {
224 			lip = &ia->ia_addr.sin6_addr;
225 
226 			KASSERT(!IN6_IS_ADDR_MULTICAST(lip),
227 			    ("%s: mcast address in in6_ifaddr list", __func__));
228 
229 			if (IN6_IS_ADDR_LOOPBACK(lip))
230 				continue;
231 			if (IN6_IS_SCOPE_EMBED(lip)) {
232 				/* Remove the embedded scope */
233 				tlip = *lip;
234 				lip = &tlip;
235 				in6_clearscope(lip);
236 			}
237 			/*
238 			 * XXX: how to weed out the link local address for the
239 			 * loopback interface?  It's fe80::1 usually (always?).
240 			 */
241 
242 			/*
243 			 * If it's in the main list then we already know it's
244 			 * not stale.
245 			 */
246 			TAILQ_FOREACH(ce, &sc->clip_table, link) {
247 				if (IN6_ARE_ADDR_EQUAL(&ce->lip, lip))
248 					goto next;
249 			}
250 
251 			/*
252 			 * If it's in the stale list we should move it to the
253 			 * main list.
254 			 */
255 			TAILQ_FOREACH(ce, &stale, link) {
256 				if (IN6_ARE_ADDR_EQUAL(&ce->lip, lip)) {
257 					TAILQ_REMOVE(&stale, ce, link);
258 					TAILQ_INSERT_TAIL(&sc->clip_table, ce,
259 					    link);
260 					goto next;
261 				}
262 			}
263 
264 			/* A new IP6 address; add it to the CLIP table */
265 			ce = malloc(sizeof(*ce), M_CXGBE, M_NOWAIT);
266 			memcpy(&ce->lip, lip, sizeof(ce->lip));
267 			ce->refcount = 0;
268 			rc = add_lip(sc, lip);
269 			if (rc == 0)
270 				TAILQ_INSERT_TAIL(&sc->clip_table, ce, link);
271 			else {
272 				char ip[INET6_ADDRSTRLEN];
273 
274 				inet_ntop(AF_INET6, &ce->lip, &ip[0],
275 				    sizeof(ip));
276 				if (sc->flags & KERN_TLS_OK ||
277 				    sc->active_ulds != 0) {
278 					log(LOG_ERR,
279 					    "%s: could not add %s (%d)\n",
280 					    __func__, ip, rc);
281 				}
282 				free(ce, M_CXGBE);
283 			}
284 next:
285 			continue;
286 		}
287 		CURVNET_RESTORE();
288 		last_vnet = (uintptr_t)vi->ifp->if_vnet;
289 	}
290 
291 	/*
292 	 * Remove stale addresses (those no longer in V_in6_ifaddrhead) that are
293 	 * no longer referenced by the driver.
294 	 */
295 	TAILQ_FOREACH_SAFE(ce, &stale, link, ce_temp) {
296 		if (ce->refcount == 0) {
297 			rc = delete_lip(sc, &ce->lip);
298 			if (rc == 0) {
299 				TAILQ_REMOVE(&stale, ce, link);
300 				free(ce, M_CXGBE);
301 			} else {
302 				char ip[INET6_ADDRSTRLEN];
303 
304 				inet_ntop(AF_INET6, &ce->lip, &ip[0],
305 				    sizeof(ip));
306 				log(LOG_ERR, "%s: could not delete %s (%d)\n",
307 				    __func__, ip, rc);
308 			}
309 		}
310 	}
311 	/* The ones that are still referenced need to stay in the CLIP table */
312 	TAILQ_CONCAT(&sc->clip_table, &stale, link);
313 
314 	sc->clip_gen = gen;
315 done:
316 	mtx_unlock(&sc->clip_table_lock);
317 	IN6_IFADDR_RUNLOCK(&in6_ifa_tracker);
318 }
319 
320 void
321 t4_destroy_clip_table(struct adapter *sc)
322 {
323 	struct clip_entry *ce, *ce_temp;
324 
325 	if (mtx_initialized(&sc->clip_table_lock)) {
326 		mtx_lock(&sc->clip_table_lock);
327 		TAILQ_FOREACH_SAFE(ce, &sc->clip_table, link, ce_temp) {
328 			KASSERT(ce->refcount == 0,
329 			    ("%s: CLIP entry %p still in use (%d)", __func__,
330 			    ce, ce->refcount));
331 			TAILQ_REMOVE(&sc->clip_table, ce, link);
332 #if 0
333 			delete_lip(sc, &ce->lip);
334 #endif
335 			free(ce, M_CXGBE);
336 		}
337 		mtx_unlock(&sc->clip_table_lock);
338 		mtx_destroy(&sc->clip_table_lock);
339 	}
340 }
341 
342 static void
343 t4_tom_ifaddr_event(void *arg __unused, struct ifnet *ifp)
344 {
345 
346 	atomic_add_rel_int(&in6_ifaddr_gen, 1);
347 	taskqueue_enqueue_timeout(taskqueue_thread, &clip_task, -hz / 4);
348 }
349 
350 int
351 sysctl_clip(SYSCTL_HANDLER_ARGS)
352 {
353 	struct adapter *sc = arg1;
354 	struct clip_entry *ce;
355 	struct sbuf *sb;
356 	int rc, header = 0;
357 	char ip[INET6_ADDRSTRLEN];
358 
359 	rc = sysctl_wire_old_buffer(req, 0);
360 	if (rc != 0)
361 		return (rc);
362 
363 	sb = sbuf_new_for_sysctl(NULL, NULL, 4096, req);
364 	if (sb == NULL)
365 		return (ENOMEM);
366 
367 	mtx_lock(&sc->clip_table_lock);
368 	TAILQ_FOREACH(ce, &sc->clip_table, link) {
369 		if (header == 0) {
370 			sbuf_printf(sb, "%-40s %-5s", "IP address", "Users");
371 			header = 1;
372 		}
373 		inet_ntop(AF_INET6, &ce->lip, &ip[0], sizeof(ip));
374 
375 		sbuf_printf(sb, "\n%-40s %5u", ip, ce->refcount);
376 	}
377 	mtx_unlock(&sc->clip_table_lock);
378 
379 	rc = sbuf_finish(sb);
380 	sbuf_delete(sb);
381 
382 	return (rc);
383 }
384 
385 void
386 t4_clip_modload(void)
387 {
388 
389 	TIMEOUT_TASK_INIT(taskqueue_thread, &clip_task, 0, t4_clip_task, NULL);
390 	ifaddr_evhandler = EVENTHANDLER_REGISTER(ifaddr_event,
391 	    t4_tom_ifaddr_event, NULL, EVENTHANDLER_PRI_ANY);
392 }
393 
394 void
395 t4_clip_modunload(void)
396 {
397 
398 	EVENTHANDLER_DEREGISTER(ifaddr_event, ifaddr_evhandler);
399 	taskqueue_cancel_timeout(taskqueue_thread, &clip_task, NULL);
400 }
401 #endif
402