1341172Sjhb/*-
2341172Sjhb * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3341172Sjhb *
4341172Sjhb * Copyright (c) 2012 Chelsio Communications, Inc.
5341172Sjhb * All rights reserved.
6341172Sjhb * Written by: Navdeep Parhar <np@FreeBSD.org>
7341172Sjhb *
8341172Sjhb * Redistribution and use in source and binary forms, with or without
9341172Sjhb * modification, are permitted provided that the following conditions
10341172Sjhb * are met:
11341172Sjhb * 1. Redistributions of source code must retain the above copyright
12341172Sjhb *    notice, this list of conditions and the following disclaimer.
13341172Sjhb * 2. Redistributions in binary form must reproduce the above copyright
14341172Sjhb *    notice, this list of conditions and the following disclaimer in the
15341172Sjhb *    documentation and/or other materials provided with the distribution.
16341172Sjhb *
17341172Sjhb * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18341172Sjhb * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19341172Sjhb * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20341172Sjhb * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21341172Sjhb * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22341172Sjhb * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23341172Sjhb * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24341172Sjhb * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25341172Sjhb * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26341172Sjhb * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27341172Sjhb * SUCH DAMAGE.
28341172Sjhb */
29341172Sjhb
30341172Sjhb#include <sys/cdefs.h>
31341172Sjhb__FBSDID("$FreeBSD: stable/11/sys/dev/cxgbe/t4_clip.c 346946 2019-04-30 07:34:34Z np $");
32341172Sjhb
33346934Snp#include "opt_inet.h"
34346934Snp#include "opt_inet6.h"
35346934Snp
36341172Sjhb#include <sys/types.h>
37341172Sjhb#include <sys/eventhandler.h>
38341172Sjhb#include <sys/malloc.h>
39341172Sjhb#include <sys/rmlock.h>
40341172Sjhb#include <sys/sbuf.h>
41341172Sjhb#include <sys/socket.h>
42341172Sjhb#include <sys/taskqueue.h>
43341172Sjhb#include <net/if.h>
44341172Sjhb#include <net/if_var.h>
45341172Sjhb#include <netinet/in.h>
46341172Sjhb#include <netinet6/in6_var.h>
47341172Sjhb#include <netinet6/scope6_var.h>
48341172Sjhb
49341172Sjhb#include "common/common.h"
50341172Sjhb#include "t4_clip.h"
51341172Sjhb
52346934Snp#if defined(INET6)
53341172Sjhbstatic int add_lip(struct adapter *, struct in6_addr *);
54341172Sjhbstatic int delete_lip(struct adapter *, struct in6_addr *);
55341172Sjhbstatic struct clip_entry *search_lip(struct adapter *, struct in6_addr *);
56341172Sjhbstatic void update_clip(struct adapter *, void *);
57341172Sjhbstatic void t4_clip_task(void *, int);
58341172Sjhbstatic void update_clip_table(struct adapter *);
59341172Sjhb
60341172Sjhbstatic int in6_ifaddr_gen;
61341172Sjhbstatic eventhandler_tag ifaddr_evhandler;
62341172Sjhbstatic struct timeout_task clip_task;
63341172Sjhb
64341172Sjhbstatic int
65341172Sjhbadd_lip(struct adapter *sc, struct in6_addr *lip)
66341172Sjhb{
67341172Sjhb        struct fw_clip_cmd c;
68341172Sjhb
69341172Sjhb	ASSERT_SYNCHRONIZED_OP(sc);
70341172Sjhb	mtx_assert(&sc->clip_table_lock, MA_OWNED);
71341172Sjhb
72341172Sjhb        memset(&c, 0, sizeof(c));
73341172Sjhb	c.op_to_write = htonl(V_FW_CMD_OP(FW_CLIP_CMD) | F_FW_CMD_REQUEST |
74341172Sjhb	    F_FW_CMD_WRITE);
75341172Sjhb        c.alloc_to_len16 = htonl(F_FW_CLIP_CMD_ALLOC | FW_LEN16(c));
76341172Sjhb        c.ip_hi = *(uint64_t *)&lip->s6_addr[0];
77341172Sjhb        c.ip_lo = *(uint64_t *)&lip->s6_addr[8];
78341172Sjhb
79341172Sjhb	return (-t4_wr_mbox_ns(sc, sc->mbox, &c, sizeof(c), &c));
80341172Sjhb}
81341172Sjhb
82341172Sjhbstatic int
83341172Sjhbdelete_lip(struct adapter *sc, struct in6_addr *lip)
84341172Sjhb{
85341172Sjhb	struct fw_clip_cmd c;
86341172Sjhb
87341172Sjhb	ASSERT_SYNCHRONIZED_OP(sc);
88341172Sjhb	mtx_assert(&sc->clip_table_lock, MA_OWNED);
89341172Sjhb
90341172Sjhb	memset(&c, 0, sizeof(c));
91341172Sjhb	c.op_to_write = htonl(V_FW_CMD_OP(FW_CLIP_CMD) | F_FW_CMD_REQUEST |
92341172Sjhb	    F_FW_CMD_READ);
93341172Sjhb        c.alloc_to_len16 = htonl(F_FW_CLIP_CMD_FREE | FW_LEN16(c));
94341172Sjhb        c.ip_hi = *(uint64_t *)&lip->s6_addr[0];
95341172Sjhb        c.ip_lo = *(uint64_t *)&lip->s6_addr[8];
96341172Sjhb
97341172Sjhb	return (-t4_wr_mbox_ns(sc, sc->mbox, &c, sizeof(c), &c));
98341172Sjhb}
99341172Sjhb
100341172Sjhbstatic struct clip_entry *
101341172Sjhbsearch_lip(struct adapter *sc, struct in6_addr *lip)
102341172Sjhb{
103341172Sjhb	struct clip_entry *ce;
104341172Sjhb
105341172Sjhb	mtx_assert(&sc->clip_table_lock, MA_OWNED);
106341172Sjhb
107341172Sjhb	TAILQ_FOREACH(ce, &sc->clip_table, link) {
108341172Sjhb		if (IN6_ARE_ADDR_EQUAL(&ce->lip, lip))
109341172Sjhb			return (ce);
110341172Sjhb	}
111341172Sjhb
112341172Sjhb	return (NULL);
113341172Sjhb}
114346934Snp#endif
115341172Sjhb
116341172Sjhbstruct clip_entry *
117341172Sjhbt4_hold_lip(struct adapter *sc, struct in6_addr *lip, struct clip_entry *ce)
118341172Sjhb{
119341172Sjhb
120346934Snp#ifdef INET6
121341172Sjhb	mtx_lock(&sc->clip_table_lock);
122341172Sjhb	if (ce == NULL)
123341172Sjhb		ce = search_lip(sc, lip);
124341172Sjhb	if (ce != NULL)
125341172Sjhb		ce->refcount++;
126341172Sjhb	mtx_unlock(&sc->clip_table_lock);
127341172Sjhb
128341172Sjhb	return (ce);
129346934Snp#else
130346934Snp	return (NULL);
131346934Snp#endif
132341172Sjhb}
133341172Sjhb
134341172Sjhbvoid
135341172Sjhbt4_release_lip(struct adapter *sc, struct clip_entry *ce)
136341172Sjhb{
137341172Sjhb
138346934Snp#ifdef INET6
139341172Sjhb	mtx_lock(&sc->clip_table_lock);
140341172Sjhb	KASSERT(search_lip(sc, &ce->lip) == ce,
141341172Sjhb	    ("%s: CLIP entry %p p not in CLIP table.", __func__, ce));
142341172Sjhb	KASSERT(ce->refcount > 0,
143341172Sjhb	    ("%s: CLIP entry %p has refcount 0", __func__, ce));
144341172Sjhb	--ce->refcount;
145341172Sjhb	mtx_unlock(&sc->clip_table_lock);
146346934Snp#endif
147341172Sjhb}
148341172Sjhb
149346934Snp#ifdef INET6
150341172Sjhbvoid
151341172Sjhbt4_init_clip_table(struct adapter *sc)
152341172Sjhb{
153341172Sjhb
154341172Sjhb	mtx_init(&sc->clip_table_lock, "CLIP table lock", NULL, MTX_DEF);
155341172Sjhb	TAILQ_INIT(&sc->clip_table);
156341172Sjhb	sc->clip_gen = -1;
157341172Sjhb
158341172Sjhb	/*
159341172Sjhb	 * Don't bother forcing an update of the clip table when the
160341172Sjhb	 * adapter is initialized.  Before an interface can be used it
161341172Sjhb	 * must be assigned an address which will trigger the event
162341172Sjhb	 * handler to update the table.
163341172Sjhb	 */
164341172Sjhb}
165341172Sjhb
166341172Sjhbstatic void
167341172Sjhbupdate_clip(struct adapter *sc, void *arg __unused)
168341172Sjhb{
169341172Sjhb
170341172Sjhb	if (begin_synchronized_op(sc, NULL, HOLD_LOCK, "t4clip"))
171341172Sjhb		return;
172341172Sjhb
173341172Sjhb	if (mtx_initialized(&sc->clip_table_lock))
174341172Sjhb		update_clip_table(sc);
175341172Sjhb
176341172Sjhb	end_synchronized_op(sc, LOCK_HELD);
177341172Sjhb}
178341172Sjhb
179341172Sjhbstatic void
180341172Sjhbt4_clip_task(void *arg, int count)
181341172Sjhb{
182341172Sjhb
183341172Sjhb	t4_iterate(update_clip, NULL);
184341172Sjhb}
185341172Sjhb
186341172Sjhbstatic void
187341172Sjhbupdate_clip_table(struct adapter *sc)
188341172Sjhb{
189341172Sjhb	struct rm_priotracker in6_ifa_tracker;
190341172Sjhb	struct in6_ifaddr *ia;
191341172Sjhb	struct in6_addr *lip, tlip;
192341172Sjhb	TAILQ_HEAD(, clip_entry) stale;
193341172Sjhb	struct clip_entry *ce, *ce_temp;
194341172Sjhb	struct vi_info *vi;
195341172Sjhb	int rc, gen, i, j;
196341172Sjhb	uintptr_t last_vnet;
197341172Sjhb
198341172Sjhb	ASSERT_SYNCHRONIZED_OP(sc);
199341172Sjhb
200341172Sjhb	IN6_IFADDR_RLOCK(&in6_ifa_tracker);
201341172Sjhb	mtx_lock(&sc->clip_table_lock);
202341172Sjhb
203341172Sjhb	gen = atomic_load_acq_int(&in6_ifaddr_gen);
204341172Sjhb	if (gen == sc->clip_gen)
205341172Sjhb		goto done;
206341172Sjhb
207341172Sjhb	TAILQ_INIT(&stale);
208341172Sjhb	TAILQ_CONCAT(&stale, &sc->clip_table, link);
209341172Sjhb
210341172Sjhb	/*
211341172Sjhb	 * last_vnet optimizes the common cases where all if_vnet = NULL (no
212341172Sjhb	 * VIMAGE) or all if_vnet = vnet0.
213341172Sjhb	 */
214341172Sjhb	last_vnet = (uintptr_t)(-1);
215341172Sjhb	for_each_port(sc, i)
216341172Sjhb	for_each_vi(sc->port[i], j, vi) {
217341172Sjhb		if (last_vnet == (uintptr_t)vi->ifp->if_vnet)
218341172Sjhb			continue;
219341172Sjhb
220341172Sjhb		/* XXX: races with if_vmove */
221341172Sjhb		CURVNET_SET(vi->ifp->if_vnet);
222346934Snp		TAILQ_FOREACH(ia, &V_in6_ifaddrhead, ia_link) {
223341172Sjhb			lip = &ia->ia_addr.sin6_addr;
224341172Sjhb
225341172Sjhb			KASSERT(!IN6_IS_ADDR_MULTICAST(lip),
226341172Sjhb			    ("%s: mcast address in in6_ifaddr list", __func__));
227341172Sjhb
228341172Sjhb			if (IN6_IS_ADDR_LOOPBACK(lip))
229341172Sjhb				continue;
230341172Sjhb			if (IN6_IS_SCOPE_EMBED(lip)) {
231341172Sjhb				/* Remove the embedded scope */
232341172Sjhb				tlip = *lip;
233341172Sjhb				lip = &tlip;
234341172Sjhb				in6_clearscope(lip);
235341172Sjhb			}
236341172Sjhb			/*
237341172Sjhb			 * XXX: how to weed out the link local address for the
238341172Sjhb			 * loopback interface?  It's fe80::1 usually (always?).
239341172Sjhb			 */
240341172Sjhb
241341172Sjhb			/*
242341172Sjhb			 * If it's in the main list then we already know it's
243341172Sjhb			 * not stale.
244341172Sjhb			 */
245341172Sjhb			TAILQ_FOREACH(ce, &sc->clip_table, link) {
246341172Sjhb				if (IN6_ARE_ADDR_EQUAL(&ce->lip, lip))
247341172Sjhb					goto next;
248341172Sjhb			}
249341172Sjhb
250341172Sjhb			/*
251341172Sjhb			 * If it's in the stale list we should move it to the
252341172Sjhb			 * main list.
253341172Sjhb			 */
254341172Sjhb			TAILQ_FOREACH(ce, &stale, link) {
255341172Sjhb				if (IN6_ARE_ADDR_EQUAL(&ce->lip, lip)) {
256341172Sjhb					TAILQ_REMOVE(&stale, ce, link);
257341172Sjhb					TAILQ_INSERT_TAIL(&sc->clip_table, ce,
258341172Sjhb					    link);
259341172Sjhb					goto next;
260341172Sjhb				}
261341172Sjhb			}
262341172Sjhb
263341172Sjhb			/* A new IP6 address; add it to the CLIP table */
264341172Sjhb			ce = malloc(sizeof(*ce), M_CXGBE, M_NOWAIT);
265341172Sjhb			memcpy(&ce->lip, lip, sizeof(ce->lip));
266341172Sjhb			ce->refcount = 0;
267341172Sjhb			rc = add_lip(sc, lip);
268341172Sjhb			if (rc == 0)
269341172Sjhb				TAILQ_INSERT_TAIL(&sc->clip_table, ce, link);
270341172Sjhb			else {
271341172Sjhb				char ip[INET6_ADDRSTRLEN];
272341172Sjhb
273341172Sjhb				inet_ntop(AF_INET6, &ce->lip, &ip[0],
274341172Sjhb				    sizeof(ip));
275341172Sjhb				log(LOG_ERR, "%s: could not add %s (%d)\n",
276341172Sjhb				    __func__, ip, rc);
277341172Sjhb				free(ce, M_CXGBE);
278341172Sjhb			}
279341172Sjhbnext:
280341172Sjhb			continue;
281341172Sjhb		}
282341172Sjhb		CURVNET_RESTORE();
283341172Sjhb		last_vnet = (uintptr_t)vi->ifp->if_vnet;
284341172Sjhb	}
285341172Sjhb
286341172Sjhb	/*
287341172Sjhb	 * Remove stale addresses (those no longer in V_in6_ifaddrhead) that are
288341172Sjhb	 * no longer referenced by the driver.
289341172Sjhb	 */
290341172Sjhb	TAILQ_FOREACH_SAFE(ce, &stale, link, ce_temp) {
291341172Sjhb		if (ce->refcount == 0) {
292341172Sjhb			rc = delete_lip(sc, &ce->lip);
293341172Sjhb			if (rc == 0) {
294341172Sjhb				TAILQ_REMOVE(&stale, ce, link);
295341172Sjhb				free(ce, M_CXGBE);
296341172Sjhb			} else {
297341172Sjhb				char ip[INET6_ADDRSTRLEN];
298341172Sjhb
299341172Sjhb				inet_ntop(AF_INET6, &ce->lip, &ip[0],
300341172Sjhb				    sizeof(ip));
301341172Sjhb				log(LOG_ERR, "%s: could not delete %s (%d)\n",
302341172Sjhb				    __func__, ip, rc);
303341172Sjhb			}
304341172Sjhb		}
305341172Sjhb	}
306341172Sjhb	/* The ones that are still referenced need to stay in the CLIP table */
307341172Sjhb	TAILQ_CONCAT(&sc->clip_table, &stale, link);
308341172Sjhb
309341172Sjhb	sc->clip_gen = gen;
310341172Sjhbdone:
311341172Sjhb	mtx_unlock(&sc->clip_table_lock);
312341172Sjhb	IN6_IFADDR_RUNLOCK(&in6_ifa_tracker);
313341172Sjhb}
314341172Sjhb
315341172Sjhbvoid
316341172Sjhbt4_destroy_clip_table(struct adapter *sc)
317341172Sjhb{
318341172Sjhb	struct clip_entry *ce, *ce_temp;
319341172Sjhb
320341172Sjhb	if (mtx_initialized(&sc->clip_table_lock)) {
321341172Sjhb		mtx_lock(&sc->clip_table_lock);
322341172Sjhb		TAILQ_FOREACH_SAFE(ce, &sc->clip_table, link, ce_temp) {
323341172Sjhb			KASSERT(ce->refcount == 0,
324341172Sjhb			    ("%s: CLIP entry %p still in use (%d)", __func__,
325341172Sjhb			    ce, ce->refcount));
326341172Sjhb			TAILQ_REMOVE(&sc->clip_table, ce, link);
327346946Snp#if 0
328341172Sjhb			delete_lip(sc, &ce->lip);
329346946Snp#endif
330341172Sjhb			free(ce, M_CXGBE);
331341172Sjhb		}
332341172Sjhb		mtx_unlock(&sc->clip_table_lock);
333341172Sjhb		mtx_destroy(&sc->clip_table_lock);
334341172Sjhb	}
335341172Sjhb}
336341172Sjhb
337341172Sjhbstatic void
338341172Sjhbt4_tom_ifaddr_event(void *arg __unused, struct ifnet *ifp)
339341172Sjhb{
340341172Sjhb
341341172Sjhb	atomic_add_rel_int(&in6_ifaddr_gen, 1);
342341172Sjhb	taskqueue_enqueue_timeout(taskqueue_thread, &clip_task, -hz / 4);
343341172Sjhb}
344341172Sjhb
345341172Sjhbint
346341172Sjhbsysctl_clip(SYSCTL_HANDLER_ARGS)
347341172Sjhb{
348341172Sjhb	struct adapter *sc = arg1;
349341172Sjhb	struct clip_entry *ce;
350341172Sjhb	struct sbuf *sb;
351341172Sjhb	int rc, header = 0;
352341172Sjhb	char ip[INET6_ADDRSTRLEN];
353341172Sjhb
354341172Sjhb	rc = sysctl_wire_old_buffer(req, 0);
355341172Sjhb	if (rc != 0)
356341172Sjhb		return (rc);
357341172Sjhb
358341172Sjhb	sb = sbuf_new_for_sysctl(NULL, NULL, 4096, req);
359341172Sjhb	if (sb == NULL)
360341172Sjhb		return (ENOMEM);
361341172Sjhb
362341172Sjhb	mtx_lock(&sc->clip_table_lock);
363341172Sjhb	TAILQ_FOREACH(ce, &sc->clip_table, link) {
364341172Sjhb		if (header == 0) {
365341172Sjhb			sbuf_printf(sb, "%-40s %-5s", "IP address", "Users");
366341172Sjhb			header = 1;
367341172Sjhb		}
368341172Sjhb		inet_ntop(AF_INET6, &ce->lip, &ip[0], sizeof(ip));
369341172Sjhb
370341172Sjhb		sbuf_printf(sb, "\n%-40s %5u", ip, ce->refcount);
371341172Sjhb	}
372341172Sjhb	mtx_unlock(&sc->clip_table_lock);
373341172Sjhb
374341172Sjhb	rc = sbuf_finish(sb);
375341172Sjhb	sbuf_delete(sb);
376341172Sjhb
377341172Sjhb	return (rc);
378341172Sjhb}
379341172Sjhb
380341172Sjhbvoid
381341172Sjhbt4_clip_modload(void)
382341172Sjhb{
383341172Sjhb
384341172Sjhb	TIMEOUT_TASK_INIT(taskqueue_thread, &clip_task, 0, t4_clip_task, NULL);
385341172Sjhb	ifaddr_evhandler = EVENTHANDLER_REGISTER(ifaddr_event,
386341172Sjhb	    t4_tom_ifaddr_event, NULL, EVENTHANDLER_PRI_ANY);
387341172Sjhb}
388341172Sjhb
389341172Sjhbvoid
390341172Sjhbt4_clip_modunload(void)
391341172Sjhb{
392341172Sjhb
393341172Sjhb	EVENTHANDLER_DEREGISTER(ifaddr_event, ifaddr_evhandler);
394341172Sjhb	taskqueue_cancel_timeout(taskqueue_thread, &clip_task, NULL);
395341172Sjhb}
396346934Snp#endif
397