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