xref: /freebsd/sys/dev/cxgbe/t4_clip.c (revision f6a3b357e9be4c6423c85eff9a847163a0d307c8)
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 				log(LOG_ERR, "%s: could not add %s (%d)\n",
277 				    __func__, ip, rc);
278 				free(ce, M_CXGBE);
279 			}
280 next:
281 			continue;
282 		}
283 		CURVNET_RESTORE();
284 		last_vnet = (uintptr_t)vi->ifp->if_vnet;
285 	}
286 
287 	/*
288 	 * Remove stale addresses (those no longer in V_in6_ifaddrhead) that are
289 	 * no longer referenced by the driver.
290 	 */
291 	TAILQ_FOREACH_SAFE(ce, &stale, link, ce_temp) {
292 		if (ce->refcount == 0) {
293 			rc = delete_lip(sc, &ce->lip);
294 			if (rc == 0) {
295 				TAILQ_REMOVE(&stale, ce, link);
296 				free(ce, M_CXGBE);
297 			} else {
298 				char ip[INET6_ADDRSTRLEN];
299 
300 				inet_ntop(AF_INET6, &ce->lip, &ip[0],
301 				    sizeof(ip));
302 				log(LOG_ERR, "%s: could not delete %s (%d)\n",
303 				    __func__, ip, rc);
304 			}
305 		}
306 	}
307 	/* The ones that are still referenced need to stay in the CLIP table */
308 	TAILQ_CONCAT(&sc->clip_table, &stale, link);
309 
310 	sc->clip_gen = gen;
311 done:
312 	mtx_unlock(&sc->clip_table_lock);
313 	IN6_IFADDR_RUNLOCK(&in6_ifa_tracker);
314 }
315 
316 void
317 t4_destroy_clip_table(struct adapter *sc)
318 {
319 	struct clip_entry *ce, *ce_temp;
320 
321 	if (mtx_initialized(&sc->clip_table_lock)) {
322 		mtx_lock(&sc->clip_table_lock);
323 		TAILQ_FOREACH_SAFE(ce, &sc->clip_table, link, ce_temp) {
324 			KASSERT(ce->refcount == 0,
325 			    ("%s: CLIP entry %p still in use (%d)", __func__,
326 			    ce, ce->refcount));
327 			TAILQ_REMOVE(&sc->clip_table, ce, link);
328 #if 0
329 			delete_lip(sc, &ce->lip);
330 #endif
331 			free(ce, M_CXGBE);
332 		}
333 		mtx_unlock(&sc->clip_table_lock);
334 		mtx_destroy(&sc->clip_table_lock);
335 	}
336 }
337 
338 static void
339 t4_tom_ifaddr_event(void *arg __unused, struct ifnet *ifp)
340 {
341 
342 	atomic_add_rel_int(&in6_ifaddr_gen, 1);
343 	taskqueue_enqueue_timeout(taskqueue_thread, &clip_task, -hz / 4);
344 }
345 
346 int
347 sysctl_clip(SYSCTL_HANDLER_ARGS)
348 {
349 	struct adapter *sc = arg1;
350 	struct clip_entry *ce;
351 	struct sbuf *sb;
352 	int rc, header = 0;
353 	char ip[INET6_ADDRSTRLEN];
354 
355 	rc = sysctl_wire_old_buffer(req, 0);
356 	if (rc != 0)
357 		return (rc);
358 
359 	sb = sbuf_new_for_sysctl(NULL, NULL, 4096, req);
360 	if (sb == NULL)
361 		return (ENOMEM);
362 
363 	mtx_lock(&sc->clip_table_lock);
364 	TAILQ_FOREACH(ce, &sc->clip_table, link) {
365 		if (header == 0) {
366 			sbuf_printf(sb, "%-40s %-5s", "IP address", "Users");
367 			header = 1;
368 		}
369 		inet_ntop(AF_INET6, &ce->lip, &ip[0], sizeof(ip));
370 
371 		sbuf_printf(sb, "\n%-40s %5u", ip, ce->refcount);
372 	}
373 	mtx_unlock(&sc->clip_table_lock);
374 
375 	rc = sbuf_finish(sb);
376 	sbuf_delete(sb);
377 
378 	return (rc);
379 }
380 
381 void
382 t4_clip_modload(void)
383 {
384 
385 	TIMEOUT_TASK_INIT(taskqueue_thread, &clip_task, 0, t4_clip_task, NULL);
386 	ifaddr_evhandler = EVENTHANDLER_REGISTER(ifaddr_event,
387 	    t4_tom_ifaddr_event, NULL, EVENTHANDLER_PRI_ANY);
388 }
389 
390 void
391 t4_clip_modunload(void)
392 {
393 
394 	EVENTHANDLER_DEREGISTER(ifaddr_event, ifaddr_evhandler);
395 	taskqueue_cancel_timeout(taskqueue_thread, &clip_task, NULL);
396 }
397 #endif
398