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