spectrum_router.c 58.5 KB
Newer Older
1 2 3 4 5
/*
 * drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
 * Copyright (c) 2016 Mellanox Technologies. All rights reserved.
 * Copyright (c) 2016 Jiri Pirko <jiri@mellanox.com>
 * Copyright (c) 2016 Ido Schimmel <idosch@mellanox.com>
6
 * Copyright (c) 2016 Yotam Gigi <yotamg@mellanox.com>
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the names of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * Alternatively, this software may be distributed under the terms of the
 * GNU General Public License ("GPL") version 2 as published by the Free
 * Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <linux/kernel.h>
#include <linux/types.h>
39 40 41
#include <linux/rhashtable.h>
#include <linux/bitops.h>
#include <linux/in6.h>
42 43
#include <linux/notifier.h>
#include <net/netevent.h>
44 45
#include <net/neighbour.h>
#include <net/arp.h>
46
#include <net/ip_fib.h>
47 48 49 50 51

#include "spectrum.h"
#include "core.h"
#include "reg.h"

52 53 54
#define mlxsw_sp_prefix_usage_for_each(prefix, prefix_usage) \
	for_each_set_bit(prefix, (prefix_usage)->b, MLXSW_SP_PREFIX_COUNT)

55 56 57 58 59 60 61 62 63 64 65 66 67
static bool
mlxsw_sp_prefix_usage_subset(struct mlxsw_sp_prefix_usage *prefix_usage1,
			     struct mlxsw_sp_prefix_usage *prefix_usage2)
{
	unsigned char prefix;

	mlxsw_sp_prefix_usage_for_each(prefix, prefix_usage1) {
		if (!test_bit(prefix, prefix_usage2->b))
			return false;
	}
	return true;
}

68 69 70 71 72 73 74
static bool
mlxsw_sp_prefix_usage_eq(struct mlxsw_sp_prefix_usage *prefix_usage1,
			 struct mlxsw_sp_prefix_usage *prefix_usage2)
{
	return !memcmp(prefix_usage1, prefix_usage2, sizeof(*prefix_usage1));
}

75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
static bool
mlxsw_sp_prefix_usage_none(struct mlxsw_sp_prefix_usage *prefix_usage)
{
	struct mlxsw_sp_prefix_usage prefix_usage_none = {{ 0 } };

	return mlxsw_sp_prefix_usage_eq(prefix_usage, &prefix_usage_none);
}

static void
mlxsw_sp_prefix_usage_cpy(struct mlxsw_sp_prefix_usage *prefix_usage1,
			  struct mlxsw_sp_prefix_usage *prefix_usage2)
{
	memcpy(prefix_usage1, prefix_usage2, sizeof(*prefix_usage1));
}

static void
mlxsw_sp_prefix_usage_zero(struct mlxsw_sp_prefix_usage *prefix_usage)
{
	memset(prefix_usage, 0, sizeof(*prefix_usage));
}

96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
static void
mlxsw_sp_prefix_usage_set(struct mlxsw_sp_prefix_usage *prefix_usage,
			  unsigned char prefix_len)
{
	set_bit(prefix_len, prefix_usage->b);
}

static void
mlxsw_sp_prefix_usage_clear(struct mlxsw_sp_prefix_usage *prefix_usage,
			    unsigned char prefix_len)
{
	clear_bit(prefix_len, prefix_usage->b);
}

struct mlxsw_sp_fib_key {
111
	struct net_device *dev;
112 113 114 115
	unsigned char addr[sizeof(struct in6_addr)];
	unsigned char prefix_len;
};

116 117 118 119 120 121
enum mlxsw_sp_fib_entry_type {
	MLXSW_SP_FIB_ENTRY_TYPE_REMOTE,
	MLXSW_SP_FIB_ENTRY_TYPE_LOCAL,
	MLXSW_SP_FIB_ENTRY_TYPE_TRAP,
};

122 123
struct mlxsw_sp_nexthop_group;

124 125
struct mlxsw_sp_fib_entry {
	struct rhash_head ht_node;
126
	struct list_head list;
127
	struct mlxsw_sp_fib_key key;
128
	enum mlxsw_sp_fib_entry_type type;
129
	unsigned int ref_count;
130
	struct mlxsw_sp_vr *vr;
131 132
	struct list_head nexthop_group_node;
	struct mlxsw_sp_nexthop_group *nh_group;
133 134 135 136
};

struct mlxsw_sp_fib {
	struct rhashtable ht;
137
	struct list_head entry_list;
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
	unsigned long prefix_ref_count[MLXSW_SP_PREFIX_COUNT];
	struct mlxsw_sp_prefix_usage prefix_usage;
};

static const struct rhashtable_params mlxsw_sp_fib_ht_params = {
	.key_offset = offsetof(struct mlxsw_sp_fib_entry, key),
	.head_offset = offsetof(struct mlxsw_sp_fib_entry, ht_node),
	.key_len = sizeof(struct mlxsw_sp_fib_key),
	.automatic_shrinking = true,
};

static int mlxsw_sp_fib_entry_insert(struct mlxsw_sp_fib *fib,
				     struct mlxsw_sp_fib_entry *fib_entry)
{
	unsigned char prefix_len = fib_entry->key.prefix_len;
	int err;

	err = rhashtable_insert_fast(&fib->ht, &fib_entry->ht_node,
				     mlxsw_sp_fib_ht_params);
	if (err)
		return err;
159
	list_add_tail(&fib_entry->list, &fib->entry_list);
160 161 162 163 164 165 166 167 168 169 170 171
	if (fib->prefix_ref_count[prefix_len]++ == 0)
		mlxsw_sp_prefix_usage_set(&fib->prefix_usage, prefix_len);
	return 0;
}

static void mlxsw_sp_fib_entry_remove(struct mlxsw_sp_fib *fib,
				      struct mlxsw_sp_fib_entry *fib_entry)
{
	unsigned char prefix_len = fib_entry->key.prefix_len;

	if (--fib->prefix_ref_count[prefix_len] == 0)
		mlxsw_sp_prefix_usage_clear(&fib->prefix_usage, prefix_len);
172
	list_del(&fib_entry->list);
173 174 175 176 177 178
	rhashtable_remove_fast(&fib->ht, &fib_entry->ht_node,
			       mlxsw_sp_fib_ht_params);
}

static struct mlxsw_sp_fib_entry *
mlxsw_sp_fib_entry_create(struct mlxsw_sp_fib *fib, const void *addr,
179 180
			  size_t addr_len, unsigned char prefix_len,
			  struct net_device *dev)
181 182 183 184 185 186
{
	struct mlxsw_sp_fib_entry *fib_entry;

	fib_entry = kzalloc(sizeof(*fib_entry), GFP_KERNEL);
	if (!fib_entry)
		return NULL;
187
	fib_entry->key.dev = dev;
188 189 190 191 192 193 194 195 196 197 198 199
	memcpy(fib_entry->key.addr, addr, addr_len);
	fib_entry->key.prefix_len = prefix_len;
	return fib_entry;
}

static void mlxsw_sp_fib_entry_destroy(struct mlxsw_sp_fib_entry *fib_entry)
{
	kfree(fib_entry);
}

static struct mlxsw_sp_fib_entry *
mlxsw_sp_fib_entry_lookup(struct mlxsw_sp_fib *fib, const void *addr,
200 201
			  size_t addr_len, unsigned char prefix_len,
			  struct net_device *dev)
202
{
203
	struct mlxsw_sp_fib_key key;
204

205 206
	memset(&key, 0, sizeof(key));
	key.dev = dev;
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
	memcpy(key.addr, addr, addr_len);
	key.prefix_len = prefix_len;
	return rhashtable_lookup_fast(&fib->ht, &key, mlxsw_sp_fib_ht_params);
}

static struct mlxsw_sp_fib *mlxsw_sp_fib_create(void)
{
	struct mlxsw_sp_fib *fib;
	int err;

	fib = kzalloc(sizeof(*fib), GFP_KERNEL);
	if (!fib)
		return ERR_PTR(-ENOMEM);
	err = rhashtable_init(&fib->ht, &mlxsw_sp_fib_ht_params);
	if (err)
		goto err_rhashtable_init;
223
	INIT_LIST_HEAD(&fib->entry_list);
224 225 226 227 228 229 230 231 232 233 234 235 236
	return fib;

err_rhashtable_init:
	kfree(fib);
	return ERR_PTR(err);
}

static void mlxsw_sp_fib_destroy(struct mlxsw_sp_fib *fib)
{
	rhashtable_destroy(&fib->ht);
	kfree(fib);
}

237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
static struct mlxsw_sp_lpm_tree *
mlxsw_sp_lpm_tree_find_unused(struct mlxsw_sp *mlxsw_sp, bool one_reserved)
{
	static struct mlxsw_sp_lpm_tree *lpm_tree;
	int i;

	for (i = 0; i < MLXSW_SP_LPM_TREE_COUNT; i++) {
		lpm_tree = &mlxsw_sp->router.lpm_trees[i];
		if (lpm_tree->ref_count == 0) {
			if (one_reserved)
				one_reserved = false;
			else
				return lpm_tree;
		}
	}
	return NULL;
}

static int mlxsw_sp_lpm_tree_alloc(struct mlxsw_sp *mlxsw_sp,
				   struct mlxsw_sp_lpm_tree *lpm_tree)
{
	char ralta_pl[MLXSW_REG_RALTA_LEN];

260 261 262
	mlxsw_reg_ralta_pack(ralta_pl, true,
			     (enum mlxsw_reg_ralxx_protocol) lpm_tree->proto,
			     lpm_tree->id);
263 264 265 266 267 268 269 270
	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ralta), ralta_pl);
}

static int mlxsw_sp_lpm_tree_free(struct mlxsw_sp *mlxsw_sp,
				  struct mlxsw_sp_lpm_tree *lpm_tree)
{
	char ralta_pl[MLXSW_REG_RALTA_LEN];

271 272 273
	mlxsw_reg_ralta_pack(ralta_pl, false,
			     (enum mlxsw_reg_ralxx_protocol) lpm_tree->proto,
			     lpm_tree->id);
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ralta), ralta_pl);
}

static int
mlxsw_sp_lpm_tree_left_struct_set(struct mlxsw_sp *mlxsw_sp,
				  struct mlxsw_sp_prefix_usage *prefix_usage,
				  struct mlxsw_sp_lpm_tree *lpm_tree)
{
	char ralst_pl[MLXSW_REG_RALST_LEN];
	u8 root_bin = 0;
	u8 prefix;
	u8 last_prefix = MLXSW_REG_RALST_BIN_NO_CHILD;

	mlxsw_sp_prefix_usage_for_each(prefix, prefix_usage)
		root_bin = prefix;

	mlxsw_reg_ralst_pack(ralst_pl, root_bin, lpm_tree->id);
	mlxsw_sp_prefix_usage_for_each(prefix, prefix_usage) {
		if (prefix == 0)
			continue;
		mlxsw_reg_ralst_bin_pack(ralst_pl, prefix, last_prefix,
					 MLXSW_REG_RALST_BIN_NO_CHILD);
		last_prefix = prefix;
	}
	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ralst), ralst_pl);
}

static struct mlxsw_sp_lpm_tree *
mlxsw_sp_lpm_tree_create(struct mlxsw_sp *mlxsw_sp,
			 struct mlxsw_sp_prefix_usage *prefix_usage,
			 enum mlxsw_sp_l3proto proto, bool one_reserved)
{
	struct mlxsw_sp_lpm_tree *lpm_tree;
	int err;

	lpm_tree = mlxsw_sp_lpm_tree_find_unused(mlxsw_sp, one_reserved);
	if (!lpm_tree)
		return ERR_PTR(-EBUSY);
	lpm_tree->proto = proto;
	err = mlxsw_sp_lpm_tree_alloc(mlxsw_sp, lpm_tree);
	if (err)
		return ERR_PTR(err);

	err = mlxsw_sp_lpm_tree_left_struct_set(mlxsw_sp, prefix_usage,
						lpm_tree);
	if (err)
		goto err_left_struct_set;
321 322
	memcpy(&lpm_tree->prefix_usage, prefix_usage,
	       sizeof(lpm_tree->prefix_usage));
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
	return lpm_tree;

err_left_struct_set:
	mlxsw_sp_lpm_tree_free(mlxsw_sp, lpm_tree);
	return ERR_PTR(err);
}

static int mlxsw_sp_lpm_tree_destroy(struct mlxsw_sp *mlxsw_sp,
				     struct mlxsw_sp_lpm_tree *lpm_tree)
{
	return mlxsw_sp_lpm_tree_free(mlxsw_sp, lpm_tree);
}

static struct mlxsw_sp_lpm_tree *
mlxsw_sp_lpm_tree_get(struct mlxsw_sp *mlxsw_sp,
		      struct mlxsw_sp_prefix_usage *prefix_usage,
		      enum mlxsw_sp_l3proto proto, bool one_reserved)
{
	struct mlxsw_sp_lpm_tree *lpm_tree;
	int i;

	for (i = 0; i < MLXSW_SP_LPM_TREE_COUNT; i++) {
		lpm_tree = &mlxsw_sp->router.lpm_trees[i];
346 347
		if (lpm_tree->ref_count != 0 &&
		    lpm_tree->proto == proto &&
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
		    mlxsw_sp_prefix_usage_eq(&lpm_tree->prefix_usage,
					     prefix_usage))
			goto inc_ref_count;
	}
	lpm_tree = mlxsw_sp_lpm_tree_create(mlxsw_sp, prefix_usage,
					    proto, one_reserved);
	if (IS_ERR(lpm_tree))
		return lpm_tree;

inc_ref_count:
	lpm_tree->ref_count++;
	return lpm_tree;
}

static int mlxsw_sp_lpm_tree_put(struct mlxsw_sp *mlxsw_sp,
				 struct mlxsw_sp_lpm_tree *lpm_tree)
{
	if (--lpm_tree->ref_count == 0)
		return mlxsw_sp_lpm_tree_destroy(mlxsw_sp, lpm_tree);
	return 0;
}

static void mlxsw_sp_lpm_init(struct mlxsw_sp *mlxsw_sp)
{
	struct mlxsw_sp_lpm_tree *lpm_tree;
	int i;

	for (i = 0; i < MLXSW_SP_LPM_TREE_COUNT; i++) {
		lpm_tree = &mlxsw_sp->router.lpm_trees[i];
		lpm_tree->id = i + MLXSW_SP_LPM_TREE_MIN;
	}
}

381 382 383 384 385
static struct mlxsw_sp_vr *mlxsw_sp_vr_find_unused(struct mlxsw_sp *mlxsw_sp)
{
	struct mlxsw_sp_vr *vr;
	int i;

J
Jiri Pirko 已提交
386
	for (i = 0; i < MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_VRS); i++) {
387 388 389 390 391 392 393 394 395 396 397 398
		vr = &mlxsw_sp->router.vrs[i];
		if (!vr->used)
			return vr;
	}
	return NULL;
}

static int mlxsw_sp_vr_lpm_tree_bind(struct mlxsw_sp *mlxsw_sp,
				     struct mlxsw_sp_vr *vr)
{
	char raltb_pl[MLXSW_REG_RALTB_LEN];

399 400 401
	mlxsw_reg_raltb_pack(raltb_pl, vr->id,
			     (enum mlxsw_reg_ralxx_protocol) vr->proto,
			     vr->lpm_tree->id);
402 403 404 405 406 407 408 409 410
	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(raltb), raltb_pl);
}

static int mlxsw_sp_vr_lpm_tree_unbind(struct mlxsw_sp *mlxsw_sp,
				       struct mlxsw_sp_vr *vr)
{
	char raltb_pl[MLXSW_REG_RALTB_LEN];

	/* Bind to tree 0 which is default */
411 412
	mlxsw_reg_raltb_pack(raltb_pl, vr->id,
			     (enum mlxsw_reg_ralxx_protocol) vr->proto, 0);
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431
	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(raltb), raltb_pl);
}

static u32 mlxsw_sp_fix_tb_id(u32 tb_id)
{
	/* For our purpose, squash main and local table into one */
	if (tb_id == RT_TABLE_LOCAL)
		tb_id = RT_TABLE_MAIN;
	return tb_id;
}

static struct mlxsw_sp_vr *mlxsw_sp_vr_find(struct mlxsw_sp *mlxsw_sp,
					    u32 tb_id,
					    enum mlxsw_sp_l3proto proto)
{
	struct mlxsw_sp_vr *vr;
	int i;

	tb_id = mlxsw_sp_fix_tb_id(tb_id);
432

J
Jiri Pirko 已提交
433
	for (i = 0; i < MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_VRS); i++) {
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
		vr = &mlxsw_sp->router.vrs[i];
		if (vr->used && vr->proto == proto && vr->tb_id == tb_id)
			return vr;
	}
	return NULL;
}

static struct mlxsw_sp_vr *mlxsw_sp_vr_create(struct mlxsw_sp *mlxsw_sp,
					      unsigned char prefix_len,
					      u32 tb_id,
					      enum mlxsw_sp_l3proto proto)
{
	struct mlxsw_sp_prefix_usage req_prefix_usage;
	struct mlxsw_sp_lpm_tree *lpm_tree;
	struct mlxsw_sp_vr *vr;
	int err;

	vr = mlxsw_sp_vr_find_unused(mlxsw_sp);
	if (!vr)
		return ERR_PTR(-EBUSY);
	vr->fib = mlxsw_sp_fib_create();
	if (IS_ERR(vr->fib))
		return ERR_CAST(vr->fib);

	vr->proto = proto;
	vr->tb_id = tb_id;
	mlxsw_sp_prefix_usage_zero(&req_prefix_usage);
	mlxsw_sp_prefix_usage_set(&req_prefix_usage, prefix_len);
	lpm_tree = mlxsw_sp_lpm_tree_get(mlxsw_sp, &req_prefix_usage,
					 proto, true);
	if (IS_ERR(lpm_tree)) {
		err = PTR_ERR(lpm_tree);
		goto err_tree_get;
	}
	vr->lpm_tree = lpm_tree;
	err = mlxsw_sp_vr_lpm_tree_bind(mlxsw_sp, vr);
	if (err)
		goto err_tree_bind;

	vr->used = true;
	return vr;

err_tree_bind:
	mlxsw_sp_lpm_tree_put(mlxsw_sp, vr->lpm_tree);
err_tree_get:
	mlxsw_sp_fib_destroy(vr->fib);

	return ERR_PTR(err);
}

static void mlxsw_sp_vr_destroy(struct mlxsw_sp *mlxsw_sp,
				struct mlxsw_sp_vr *vr)
{
	mlxsw_sp_vr_lpm_tree_unbind(mlxsw_sp, vr);
	mlxsw_sp_lpm_tree_put(mlxsw_sp, vr->lpm_tree);
	mlxsw_sp_fib_destroy(vr->fib);
	vr->used = false;
}

static int
mlxsw_sp_vr_lpm_tree_check(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_vr *vr,
			   struct mlxsw_sp_prefix_usage *req_prefix_usage)
{
	struct mlxsw_sp_lpm_tree *lpm_tree;

	if (mlxsw_sp_prefix_usage_eq(req_prefix_usage,
				     &vr->lpm_tree->prefix_usage))
		return 0;

	lpm_tree = mlxsw_sp_lpm_tree_get(mlxsw_sp, req_prefix_usage,
					 vr->proto, false);
	if (IS_ERR(lpm_tree)) {
		/* We failed to get a tree according to the required
		 * prefix usage. However, the current tree might be still good
		 * for us if our requirement is subset of the prefixes used
		 * in the tree.
		 */
		if (mlxsw_sp_prefix_usage_subset(req_prefix_usage,
						 &vr->lpm_tree->prefix_usage))
			return 0;
		return PTR_ERR(lpm_tree);
	}

	mlxsw_sp_vr_lpm_tree_unbind(mlxsw_sp, vr);
	mlxsw_sp_lpm_tree_put(mlxsw_sp, vr->lpm_tree);
	vr->lpm_tree = lpm_tree;
	return mlxsw_sp_vr_lpm_tree_bind(mlxsw_sp, vr);
}

static struct mlxsw_sp_vr *mlxsw_sp_vr_get(struct mlxsw_sp *mlxsw_sp,
					   unsigned char prefix_len,
					   u32 tb_id,
					   enum mlxsw_sp_l3proto proto)
{
	struct mlxsw_sp_vr *vr;
	int err;

	tb_id = mlxsw_sp_fix_tb_id(tb_id);
	vr = mlxsw_sp_vr_find(mlxsw_sp, tb_id, proto);
	if (!vr) {
		vr = mlxsw_sp_vr_create(mlxsw_sp, prefix_len, tb_id, proto);
		if (IS_ERR(vr))
			return vr;
	} else {
		struct mlxsw_sp_prefix_usage req_prefix_usage;

		mlxsw_sp_prefix_usage_cpy(&req_prefix_usage,
					  &vr->fib->prefix_usage);
		mlxsw_sp_prefix_usage_set(&req_prefix_usage, prefix_len);
		/* Need to replace LPM tree in case new prefix is required. */
		err = mlxsw_sp_vr_lpm_tree_check(mlxsw_sp, vr,
						 &req_prefix_usage);
		if (err)
			return ERR_PTR(err);
	}
	return vr;
}

static void mlxsw_sp_vr_put(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_vr *vr)
{
	/* Destroy virtual router entity in case the associated FIB is empty
	 * and allow it to be used for other tables in future. Otherwise,
	 * check if some prefix usage did not disappear and change tree if
	 * that is the case. Note that in case new, smaller tree cannot be
	 * allocated, the original one will be kept being used.
	 */
	if (mlxsw_sp_prefix_usage_none(&vr->fib->prefix_usage))
		mlxsw_sp_vr_destroy(mlxsw_sp, vr);
	else
		mlxsw_sp_vr_lpm_tree_check(mlxsw_sp, vr,
					   &vr->fib->prefix_usage);
}

567
static int mlxsw_sp_vrs_init(struct mlxsw_sp *mlxsw_sp)
568 569
{
	struct mlxsw_sp_vr *vr;
J
Jiri Pirko 已提交
570
	u64 max_vrs;
571 572
	int i;

J
Jiri Pirko 已提交
573
	if (!MLXSW_CORE_RES_VALID(mlxsw_sp->core, MAX_VRS))
574 575
		return -EIO;

J
Jiri Pirko 已提交
576 577
	max_vrs = MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_VRS);
	mlxsw_sp->router.vrs = kcalloc(max_vrs, sizeof(struct mlxsw_sp_vr),
578 579 580 581
				       GFP_KERNEL);
	if (!mlxsw_sp->router.vrs)
		return -ENOMEM;

J
Jiri Pirko 已提交
582
	for (i = 0; i < max_vrs; i++) {
583 584 585
		vr = &mlxsw_sp->router.vrs[i];
		vr->id = i;
	}
586 587 588 589

	return 0;
}

590 591
static void mlxsw_sp_router_fib_flush(struct mlxsw_sp *mlxsw_sp);

592 593
static void mlxsw_sp_vrs_fini(struct mlxsw_sp *mlxsw_sp)
{
594 595 596 597 598 599 600 601
	/* At this stage we're guaranteed not to have new incoming
	 * FIB notifications and the work queue is free from FIBs
	 * sitting on top of mlxsw netdevs. However, we can still
	 * have other FIBs queued. Flush the queue before flushing
	 * the device's tables. No need for locks, as we're the only
	 * writer.
	 */
	mlxsw_core_flush_owq();
602
	mlxsw_sp_router_fib_flush(mlxsw_sp);
603
	kfree(mlxsw_sp->router.vrs);
604 605
}

606
struct mlxsw_sp_neigh_key {
607
	struct neighbour *n;
608 609 610 611 612 613
};

struct mlxsw_sp_neigh_entry {
	struct rhash_head ht_node;
	struct mlxsw_sp_neigh_key key;
	u16 rif;
614
	bool connected;
615
	unsigned char ha[ETH_ALEN];
616 617 618
	struct list_head nexthop_list; /* list of nexthops using
					* this neigh entry
					*/
619
	struct list_head nexthop_neighs_list_node;
620 621 622 623 624 625 626 627 628
};

static const struct rhashtable_params mlxsw_sp_neigh_ht_params = {
	.key_offset = offsetof(struct mlxsw_sp_neigh_entry, key),
	.head_offset = offsetof(struct mlxsw_sp_neigh_entry, ht_node),
	.key_len = sizeof(struct mlxsw_sp_neigh_key),
};

static struct mlxsw_sp_neigh_entry *
629 630
mlxsw_sp_neigh_entry_alloc(struct mlxsw_sp *mlxsw_sp, struct neighbour *n,
			   u16 rif)
631 632 633
{
	struct mlxsw_sp_neigh_entry *neigh_entry;

634
	neigh_entry = kzalloc(sizeof(*neigh_entry), GFP_KERNEL);
635 636
	if (!neigh_entry)
		return NULL;
637

638
	neigh_entry->key.n = n;
639
	neigh_entry->rif = rif;
640
	INIT_LIST_HEAD(&neigh_entry->nexthop_list);
641

642 643 644
	return neigh_entry;
}

645
static void mlxsw_sp_neigh_entry_free(struct mlxsw_sp_neigh_entry *neigh_entry)
646 647 648 649
{
	kfree(neigh_entry);
}

650 651 652
static int
mlxsw_sp_neigh_entry_insert(struct mlxsw_sp *mlxsw_sp,
			    struct mlxsw_sp_neigh_entry *neigh_entry)
653
{
654 655 656 657
	return rhashtable_insert_fast(&mlxsw_sp->router.neigh_ht,
				      &neigh_entry->ht_node,
				      mlxsw_sp_neigh_ht_params);
}
658

659 660 661 662 663 664 665
static void
mlxsw_sp_neigh_entry_remove(struct mlxsw_sp *mlxsw_sp,
			    struct mlxsw_sp_neigh_entry *neigh_entry)
{
	rhashtable_remove_fast(&mlxsw_sp->router.neigh_ht,
			       &neigh_entry->ht_node,
			       mlxsw_sp_neigh_ht_params);
666 667
}

668 669
static struct mlxsw_sp_neigh_entry *
mlxsw_sp_neigh_entry_create(struct mlxsw_sp *mlxsw_sp, struct neighbour *n)
670 671 672 673 674
{
	struct mlxsw_sp_neigh_entry *neigh_entry;
	struct mlxsw_sp_rif *r;
	int err;

675
	r = mlxsw_sp_rif_find_by_dev(mlxsw_sp, n->dev);
676 677
	if (!r)
		return ERR_PTR(-EINVAL);
678

679
	neigh_entry = mlxsw_sp_neigh_entry_alloc(mlxsw_sp, n, r->rif);
680
	if (!neigh_entry)
681 682
		return ERR_PTR(-ENOMEM);

683 684 685
	err = mlxsw_sp_neigh_entry_insert(mlxsw_sp, neigh_entry);
	if (err)
		goto err_neigh_entry_insert;
686 687

	return neigh_entry;
688 689

err_neigh_entry_insert:
690 691
	mlxsw_sp_neigh_entry_free(neigh_entry);
	return ERR_PTR(err);
692 693
}

694 695 696
static void
mlxsw_sp_neigh_entry_destroy(struct mlxsw_sp *mlxsw_sp,
			     struct mlxsw_sp_neigh_entry *neigh_entry)
697
{
698 699 700
	mlxsw_sp_neigh_entry_remove(mlxsw_sp, neigh_entry);
	mlxsw_sp_neigh_entry_free(neigh_entry);
}
701

702 703 704 705
static struct mlxsw_sp_neigh_entry *
mlxsw_sp_neigh_entry_lookup(struct mlxsw_sp *mlxsw_sp, struct neighbour *n)
{
	struct mlxsw_sp_neigh_key key;
706

707 708 709
	key.n = n;
	return rhashtable_lookup_fast(&mlxsw_sp->router.neigh_ht,
				      &key, mlxsw_sp_neigh_ht_params);
710 711
}

712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787
static void
mlxsw_sp_router_neighs_update_interval_init(struct mlxsw_sp *mlxsw_sp)
{
	unsigned long interval = NEIGH_VAR(&arp_tbl.parms, DELAY_PROBE_TIME);

	mlxsw_sp->router.neighs_update.interval = jiffies_to_msecs(interval);
}

static void mlxsw_sp_router_neigh_ent_ipv4_process(struct mlxsw_sp *mlxsw_sp,
						   char *rauhtd_pl,
						   int ent_index)
{
	struct net_device *dev;
	struct neighbour *n;
	__be32 dipn;
	u32 dip;
	u16 rif;

	mlxsw_reg_rauhtd_ent_ipv4_unpack(rauhtd_pl, ent_index, &rif, &dip);

	if (!mlxsw_sp->rifs[rif]) {
		dev_err_ratelimited(mlxsw_sp->bus_info->dev, "Incorrect RIF in neighbour entry\n");
		return;
	}

	dipn = htonl(dip);
	dev = mlxsw_sp->rifs[rif]->dev;
	n = neigh_lookup(&arp_tbl, &dipn, dev);
	if (!n) {
		netdev_err(dev, "Failed to find matching neighbour for IP=%pI4h\n",
			   &dip);
		return;
	}

	netdev_dbg(dev, "Updating neighbour with IP=%pI4h\n", &dip);
	neigh_event_send(n, NULL);
	neigh_release(n);
}

static void mlxsw_sp_router_neigh_rec_ipv4_process(struct mlxsw_sp *mlxsw_sp,
						   char *rauhtd_pl,
						   int rec_index)
{
	u8 num_entries;
	int i;

	num_entries = mlxsw_reg_rauhtd_ipv4_rec_num_entries_get(rauhtd_pl,
								rec_index);
	/* Hardware starts counting at 0, so add 1. */
	num_entries++;

	/* Each record consists of several neighbour entries. */
	for (i = 0; i < num_entries; i++) {
		int ent_index;

		ent_index = rec_index * MLXSW_REG_RAUHTD_IPV4_ENT_PER_REC + i;
		mlxsw_sp_router_neigh_ent_ipv4_process(mlxsw_sp, rauhtd_pl,
						       ent_index);
	}

}

static void mlxsw_sp_router_neigh_rec_process(struct mlxsw_sp *mlxsw_sp,
					      char *rauhtd_pl, int rec_index)
{
	switch (mlxsw_reg_rauhtd_rec_type_get(rauhtd_pl, rec_index)) {
	case MLXSW_REG_RAUHTD_TYPE_IPV4:
		mlxsw_sp_router_neigh_rec_ipv4_process(mlxsw_sp, rauhtd_pl,
						       rec_index);
		break;
	case MLXSW_REG_RAUHTD_TYPE_IPV6:
		WARN_ON_ONCE(1);
		break;
	}
}

788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807
static bool mlxsw_sp_router_rauhtd_is_full(char *rauhtd_pl)
{
	u8 num_rec, last_rec_index, num_entries;

	num_rec = mlxsw_reg_rauhtd_num_rec_get(rauhtd_pl);
	last_rec_index = num_rec - 1;

	if (num_rec < MLXSW_REG_RAUHTD_REC_MAX_NUM)
		return false;
	if (mlxsw_reg_rauhtd_rec_type_get(rauhtd_pl, last_rec_index) ==
	    MLXSW_REG_RAUHTD_TYPE_IPV6)
		return true;

	num_entries = mlxsw_reg_rauhtd_ipv4_rec_num_entries_get(rauhtd_pl,
								last_rec_index);
	if (++num_entries == MLXSW_REG_RAUHTD_IPV4_ENT_PER_REC)
		return true;
	return false;
}

808
static int mlxsw_sp_router_neighs_update_rauhtd(struct mlxsw_sp *mlxsw_sp)
809 810 811 812 813 814 815
{
	char *rauhtd_pl;
	u8 num_rec;
	int i, err;

	rauhtd_pl = kmalloc(MLXSW_REG_RAUHTD_LEN, GFP_KERNEL);
	if (!rauhtd_pl)
816
		return -ENOMEM;
817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833

	/* Make sure the neighbour's netdev isn't removed in the
	 * process.
	 */
	rtnl_lock();
	do {
		mlxsw_reg_rauhtd_pack(rauhtd_pl, MLXSW_REG_RAUHTD_TYPE_IPV4);
		err = mlxsw_reg_query(mlxsw_sp->core, MLXSW_REG(rauhtd),
				      rauhtd_pl);
		if (err) {
			dev_err_ratelimited(mlxsw_sp->bus_info->dev, "Failed to dump neighbour talbe\n");
			break;
		}
		num_rec = mlxsw_reg_rauhtd_num_rec_get(rauhtd_pl);
		for (i = 0; i < num_rec; i++)
			mlxsw_sp_router_neigh_rec_process(mlxsw_sp, rauhtd_pl,
							  i);
834
	} while (mlxsw_sp_router_rauhtd_is_full(rauhtd_pl));
835 836 837
	rtnl_unlock();

	kfree(rauhtd_pl);
838 839 840 841 842 843 844 845 846 847
	return err;
}

static void mlxsw_sp_router_neighs_update_nh(struct mlxsw_sp *mlxsw_sp)
{
	struct mlxsw_sp_neigh_entry *neigh_entry;

	/* Take RTNL mutex here to prevent lists from changes */
	rtnl_lock();
	list_for_each_entry(neigh_entry, &mlxsw_sp->router.nexthop_neighs_list,
848
			    nexthop_neighs_list_node)
849 850 851
		/* If this neigh have nexthops, make the kernel think this neigh
		 * is active regardless of the traffic.
		 */
852
		neigh_event_send(neigh_entry->key.n, NULL);
853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876
	rtnl_unlock();
}

static void
mlxsw_sp_router_neighs_update_work_schedule(struct mlxsw_sp *mlxsw_sp)
{
	unsigned long interval = mlxsw_sp->router.neighs_update.interval;

	mlxsw_core_schedule_dw(&mlxsw_sp->router.neighs_update.dw,
			       msecs_to_jiffies(interval));
}

static void mlxsw_sp_router_neighs_update_work(struct work_struct *work)
{
	struct mlxsw_sp *mlxsw_sp = container_of(work, struct mlxsw_sp,
						 router.neighs_update.dw.work);
	int err;

	err = mlxsw_sp_router_neighs_update_rauhtd(mlxsw_sp);
	if (err)
		dev_err(mlxsw_sp->bus_info->dev, "Could not update kernel for neigh activity");

	mlxsw_sp_router_neighs_update_nh(mlxsw_sp);

877 878 879
	mlxsw_sp_router_neighs_update_work_schedule(mlxsw_sp);
}

880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895
static void mlxsw_sp_router_probe_unresolved_nexthops(struct work_struct *work)
{
	struct mlxsw_sp_neigh_entry *neigh_entry;
	struct mlxsw_sp *mlxsw_sp = container_of(work, struct mlxsw_sp,
						 router.nexthop_probe_dw.work);

	/* Iterate over nexthop neighbours, find those who are unresolved and
	 * send arp on them. This solves the chicken-egg problem when
	 * the nexthop wouldn't get offloaded until the neighbor is resolved
	 * but it wouldn't get resolved ever in case traffic is flowing in HW
	 * using different nexthop.
	 *
	 * Take RTNL mutex here to prevent lists from changes.
	 */
	rtnl_lock();
	list_for_each_entry(neigh_entry, &mlxsw_sp->router.nexthop_neighs_list,
896
			    nexthop_neighs_list_node)
897
		if (!neigh_entry->connected)
898
			neigh_event_send(neigh_entry->key.n, NULL);
899 900 901 902 903 904
	rtnl_unlock();

	mlxsw_core_schedule_dw(&mlxsw_sp->router.nexthop_probe_dw,
			       MLXSW_SP_UNRESOLVED_NH_PROBE_INTERVAL);
}

905 906 907 908 909
static void
mlxsw_sp_nexthop_neigh_update(struct mlxsw_sp *mlxsw_sp,
			      struct mlxsw_sp_neigh_entry *neigh_entry,
			      bool removing);

910 911 912 913 914 915 916 917 918 919
static enum mlxsw_reg_rauht_op mlxsw_sp_rauht_op(bool adding)
{
	return adding ? MLXSW_REG_RAUHT_OP_WRITE_ADD :
			MLXSW_REG_RAUHT_OP_WRITE_DELETE;
}

static void
mlxsw_sp_router_neigh_entry_op4(struct mlxsw_sp *mlxsw_sp,
				struct mlxsw_sp_neigh_entry *neigh_entry,
				enum mlxsw_reg_rauht_op op)
920
{
921
	struct neighbour *n = neigh_entry->key.n;
922
	u32 dip = ntohl(*((__be32 *) n->primary_key));
923
	char rauht_pl[MLXSW_REG_RAUHT_LEN];
924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958

	mlxsw_reg_rauht_pack4(rauht_pl, op, neigh_entry->rif, neigh_entry->ha,
			      dip);
	mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(rauht), rauht_pl);
}

static void
mlxsw_sp_neigh_entry_update(struct mlxsw_sp *mlxsw_sp,
			    struct mlxsw_sp_neigh_entry *neigh_entry,
			    bool adding)
{
	if (!adding && !neigh_entry->connected)
		return;
	neigh_entry->connected = adding;
	if (neigh_entry->key.n->tbl == &arp_tbl)
		mlxsw_sp_router_neigh_entry_op4(mlxsw_sp, neigh_entry,
						mlxsw_sp_rauht_op(adding));
	else
		WARN_ON_ONCE(1);
}

struct mlxsw_sp_neigh_event_work {
	struct work_struct work;
	struct mlxsw_sp *mlxsw_sp;
	struct neighbour *n;
};

static void mlxsw_sp_router_neigh_event_work(struct work_struct *work)
{
	struct mlxsw_sp_neigh_event_work *neigh_work =
		container_of(work, struct mlxsw_sp_neigh_event_work, work);
	struct mlxsw_sp *mlxsw_sp = neigh_work->mlxsw_sp;
	struct mlxsw_sp_neigh_entry *neigh_entry;
	struct neighbour *n = neigh_work->n;
	unsigned char ha[ETH_ALEN];
959
	bool entry_connected;
960
	u8 nud_state, dead;
961

962 963 964 965
	/* If these parameters are changed after we release the lock,
	 * then we are guaranteed to receive another event letting us
	 * know about it.
	 */
966
	read_lock_bh(&n->lock);
967
	memcpy(ha, n->ha, ETH_ALEN);
968
	nud_state = n->nud_state;
969
	dead = n->dead;
970 971
	read_unlock_bh(&n->lock);

972
	rtnl_lock();
973
	entry_connected = nud_state & NUD_VALID && !dead;
974 975 976 977 978 979 980
	neigh_entry = mlxsw_sp_neigh_entry_lookup(mlxsw_sp, n);
	if (!entry_connected && !neigh_entry)
		goto out;
	if (!neigh_entry) {
		neigh_entry = mlxsw_sp_neigh_entry_create(mlxsw_sp, n);
		if (IS_ERR(neigh_entry))
			goto out;
981 982
	}

983 984 985 986 987 988 989 990 991
	memcpy(neigh_entry->ha, ha, ETH_ALEN);
	mlxsw_sp_neigh_entry_update(mlxsw_sp, neigh_entry, entry_connected);
	mlxsw_sp_nexthop_neigh_update(mlxsw_sp, neigh_entry, !entry_connected);

	if (!neigh_entry->connected && list_empty(&neigh_entry->nexthop_list))
		mlxsw_sp_neigh_entry_destroy(mlxsw_sp, neigh_entry);

out:
	rtnl_unlock();
992
	neigh_release(n);
993
	kfree(neigh_work);
994 995
}

996 997
int mlxsw_sp_router_netevent_event(struct notifier_block *unused,
				   unsigned long event, void *ptr)
998
{
999
	struct mlxsw_sp_neigh_event_work *neigh_work;
1000 1001 1002 1003
	struct mlxsw_sp_port *mlxsw_sp_port;
	struct mlxsw_sp *mlxsw_sp;
	unsigned long interval;
	struct neigh_parms *p;
1004
	struct neighbour *n;
1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026

	switch (event) {
	case NETEVENT_DELAY_PROBE_TIME_UPDATE:
		p = ptr;

		/* We don't care about changes in the default table. */
		if (!p->dev || p->tbl != &arp_tbl)
			return NOTIFY_DONE;

		/* We are in atomic context and can't take RTNL mutex,
		 * so use RCU variant to walk the device chain.
		 */
		mlxsw_sp_port = mlxsw_sp_port_lower_dev_hold(p->dev);
		if (!mlxsw_sp_port)
			return NOTIFY_DONE;

		mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
		interval = jiffies_to_msecs(NEIGH_VAR(p, DELAY_PROBE_TIME));
		mlxsw_sp->router.neighs_update.interval = interval;

		mlxsw_sp_port_dev_put(mlxsw_sp_port);
		break;
1027 1028 1029 1030 1031 1032
	case NETEVENT_NEIGH_UPDATE:
		n = ptr;

		if (n->tbl != &arp_tbl)
			return NOTIFY_DONE;

1033
		mlxsw_sp_port = mlxsw_sp_port_lower_dev_hold(n->dev);
1034 1035 1036
		if (!mlxsw_sp_port)
			return NOTIFY_DONE;

1037 1038
		neigh_work = kzalloc(sizeof(*neigh_work), GFP_ATOMIC);
		if (!neigh_work) {
1039
			mlxsw_sp_port_dev_put(mlxsw_sp_port);
1040
			return NOTIFY_BAD;
1041
		}
1042 1043 1044 1045

		INIT_WORK(&neigh_work->work, mlxsw_sp_router_neigh_event_work);
		neigh_work->mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
		neigh_work->n = n;
1046 1047 1048 1049 1050 1051

		/* Take a reference to ensure the neighbour won't be
		 * destructed until we drop the reference in delayed
		 * work.
		 */
		neigh_clone(n);
1052 1053
		mlxsw_core_schedule_work(&neigh_work->work);
		mlxsw_sp_port_dev_put(mlxsw_sp_port);
1054
		break;
1055 1056 1057 1058 1059
	}

	return NOTIFY_DONE;
}

1060 1061
static int mlxsw_sp_neigh_init(struct mlxsw_sp *mlxsw_sp)
{
1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073
	int err;

	err = rhashtable_init(&mlxsw_sp->router.neigh_ht,
			      &mlxsw_sp_neigh_ht_params);
	if (err)
		return err;

	/* Initialize the polling interval according to the default
	 * table.
	 */
	mlxsw_sp_router_neighs_update_interval_init(mlxsw_sp);

1074
	/* Create the delayed works for the activity_update */
1075 1076
	INIT_DELAYED_WORK(&mlxsw_sp->router.neighs_update.dw,
			  mlxsw_sp_router_neighs_update_work);
1077 1078
	INIT_DELAYED_WORK(&mlxsw_sp->router.nexthop_probe_dw,
			  mlxsw_sp_router_probe_unresolved_nexthops);
1079
	mlxsw_core_schedule_dw(&mlxsw_sp->router.neighs_update.dw, 0);
1080
	mlxsw_core_schedule_dw(&mlxsw_sp->router.nexthop_probe_dw, 0);
1081
	return 0;
1082 1083 1084 1085
}

static void mlxsw_sp_neigh_fini(struct mlxsw_sp *mlxsw_sp)
{
1086
	cancel_delayed_work_sync(&mlxsw_sp->router.neighs_update.dw);
1087
	cancel_delayed_work_sync(&mlxsw_sp->router.nexthop_probe_dw);
1088 1089 1090
	rhashtable_destroy(&mlxsw_sp->router.neigh_ht);
}

1091 1092 1093 1094
struct mlxsw_sp_nexthop_key {
	struct fib_nh *fib_nh;
};

1095 1096 1097 1098 1099
struct mlxsw_sp_nexthop {
	struct list_head neigh_list_node; /* member of neigh entry list */
	struct mlxsw_sp_nexthop_group *nh_grp; /* pointer back to the group
						* this belongs to
						*/
1100 1101
	struct rhash_head ht_node;
	struct mlxsw_sp_nexthop_key key;
1102
	struct mlxsw_sp_rif *r;
1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114
	u8 should_offload:1, /* set indicates this neigh is connected and
			      * should be put to KVD linear area of this group.
			      */
	   offloaded:1, /* set in case the neigh is actually put into
			 * KVD linear area of this group.
			 */
	   update:1; /* set indicates that MAC of this neigh should be
		      * updated in HW
		      */
	struct mlxsw_sp_neigh_entry *neigh_entry;
};

1115 1116 1117 1118
struct mlxsw_sp_nexthop_group_key {
	struct fib_info *fi;
};

1119
struct mlxsw_sp_nexthop_group {
1120
	struct rhash_head ht_node;
1121
	struct list_head fib_list; /* list of fib entries that use this group */
1122
	struct mlxsw_sp_nexthop_group_key key;
1123 1124
	u8 adj_index_valid:1,
	   gateway:1; /* routes using the group use a gateway */
1125 1126 1127 1128
	u32 adj_index;
	u16 ecmp_size;
	u16 count;
	struct mlxsw_sp_nexthop nexthops[0];
1129
#define nh_rif	nexthops[0].r
1130 1131
};

1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161
static const struct rhashtable_params mlxsw_sp_nexthop_group_ht_params = {
	.key_offset = offsetof(struct mlxsw_sp_nexthop_group, key),
	.head_offset = offsetof(struct mlxsw_sp_nexthop_group, ht_node),
	.key_len = sizeof(struct mlxsw_sp_nexthop_group_key),
};

static int mlxsw_sp_nexthop_group_insert(struct mlxsw_sp *mlxsw_sp,
					 struct mlxsw_sp_nexthop_group *nh_grp)
{
	return rhashtable_insert_fast(&mlxsw_sp->router.nexthop_group_ht,
				      &nh_grp->ht_node,
				      mlxsw_sp_nexthop_group_ht_params);
}

static void mlxsw_sp_nexthop_group_remove(struct mlxsw_sp *mlxsw_sp,
					  struct mlxsw_sp_nexthop_group *nh_grp)
{
	rhashtable_remove_fast(&mlxsw_sp->router.nexthop_group_ht,
			       &nh_grp->ht_node,
			       mlxsw_sp_nexthop_group_ht_params);
}

static struct mlxsw_sp_nexthop_group *
mlxsw_sp_nexthop_group_lookup(struct mlxsw_sp *mlxsw_sp,
			      struct mlxsw_sp_nexthop_group_key key)
{
	return rhashtable_lookup_fast(&mlxsw_sp->router.nexthop_group_ht, &key,
				      mlxsw_sp_nexthop_group_ht_params);
}

1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181
static const struct rhashtable_params mlxsw_sp_nexthop_ht_params = {
	.key_offset = offsetof(struct mlxsw_sp_nexthop, key),
	.head_offset = offsetof(struct mlxsw_sp_nexthop, ht_node),
	.key_len = sizeof(struct mlxsw_sp_nexthop_key),
};

static int mlxsw_sp_nexthop_insert(struct mlxsw_sp *mlxsw_sp,
				   struct mlxsw_sp_nexthop *nh)
{
	return rhashtable_insert_fast(&mlxsw_sp->router.nexthop_ht,
				      &nh->ht_node, mlxsw_sp_nexthop_ht_params);
}

static void mlxsw_sp_nexthop_remove(struct mlxsw_sp *mlxsw_sp,
				    struct mlxsw_sp_nexthop *nh)
{
	rhashtable_remove_fast(&mlxsw_sp->router.nexthop_ht, &nh->ht_node,
			       mlxsw_sp_nexthop_ht_params);
}

1182 1183 1184 1185 1186 1187 1188 1189
static int mlxsw_sp_adj_index_mass_update_vr(struct mlxsw_sp *mlxsw_sp,
					     struct mlxsw_sp_vr *vr,
					     u32 adj_index, u16 ecmp_size,
					     u32 new_adj_index,
					     u16 new_ecmp_size)
{
	char raleu_pl[MLXSW_REG_RALEU_LEN];

1190 1191 1192 1193
	mlxsw_reg_raleu_pack(raleu_pl,
			     (enum mlxsw_reg_ralxx_protocol) vr->proto, vr->id,
			     adj_index, ecmp_size, new_adj_index,
			     new_ecmp_size);
1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233
	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(raleu), raleu_pl);
}

static int mlxsw_sp_adj_index_mass_update(struct mlxsw_sp *mlxsw_sp,
					  struct mlxsw_sp_nexthop_group *nh_grp,
					  u32 old_adj_index, u16 old_ecmp_size)
{
	struct mlxsw_sp_fib_entry *fib_entry;
	struct mlxsw_sp_vr *vr = NULL;
	int err;

	list_for_each_entry(fib_entry, &nh_grp->fib_list, nexthop_group_node) {
		if (vr == fib_entry->vr)
			continue;
		vr = fib_entry->vr;
		err = mlxsw_sp_adj_index_mass_update_vr(mlxsw_sp, vr,
							old_adj_index,
							old_ecmp_size,
							nh_grp->adj_index,
							nh_grp->ecmp_size);
		if (err)
			return err;
	}
	return 0;
}

static int mlxsw_sp_nexthop_mac_update(struct mlxsw_sp *mlxsw_sp, u32 adj_index,
				       struct mlxsw_sp_nexthop *nh)
{
	struct mlxsw_sp_neigh_entry *neigh_entry = nh->neigh_entry;
	char ratr_pl[MLXSW_REG_RATR_LEN];

	mlxsw_reg_ratr_pack(ratr_pl, MLXSW_REG_RATR_OP_WRITE_WRITE_ENTRY,
			    true, adj_index, neigh_entry->rif);
	mlxsw_reg_ratr_eth_entry_pack(ratr_pl, neigh_entry->ha);
	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ratr), ratr_pl);
}

static int
mlxsw_sp_nexthop_group_mac_update(struct mlxsw_sp *mlxsw_sp,
1234 1235
				  struct mlxsw_sp_nexthop_group *nh_grp,
				  bool reallocate)
1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249
{
	u32 adj_index = nh_grp->adj_index; /* base */
	struct mlxsw_sp_nexthop *nh;
	int i;
	int err;

	for (i = 0; i < nh_grp->count; i++) {
		nh = &nh_grp->nexthops[i];

		if (!nh->should_offload) {
			nh->offloaded = 0;
			continue;
		}

1250
		if (nh->update || reallocate) {
1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295
			err = mlxsw_sp_nexthop_mac_update(mlxsw_sp,
							  adj_index, nh);
			if (err)
				return err;
			nh->update = 0;
			nh->offloaded = 1;
		}
		adj_index++;
	}
	return 0;
}

static int mlxsw_sp_fib_entry_update(struct mlxsw_sp *mlxsw_sp,
				     struct mlxsw_sp_fib_entry *fib_entry);

static int
mlxsw_sp_nexthop_fib_entries_update(struct mlxsw_sp *mlxsw_sp,
				    struct mlxsw_sp_nexthop_group *nh_grp)
{
	struct mlxsw_sp_fib_entry *fib_entry;
	int err;

	list_for_each_entry(fib_entry, &nh_grp->fib_list, nexthop_group_node) {
		err = mlxsw_sp_fib_entry_update(mlxsw_sp, fib_entry);
		if (err)
			return err;
	}
	return 0;
}

static void
mlxsw_sp_nexthop_group_refresh(struct mlxsw_sp *mlxsw_sp,
			       struct mlxsw_sp_nexthop_group *nh_grp)
{
	struct mlxsw_sp_nexthop *nh;
	bool offload_change = false;
	u32 adj_index;
	u16 ecmp_size = 0;
	bool old_adj_index_valid;
	u32 old_adj_index;
	u16 old_ecmp_size;
	int ret;
	int i;
	int err;

1296 1297 1298 1299 1300
	if (!nh_grp->gateway) {
		mlxsw_sp_nexthop_fib_entries_update(mlxsw_sp, nh_grp);
		return;
	}

1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315
	for (i = 0; i < nh_grp->count; i++) {
		nh = &nh_grp->nexthops[i];

		if (nh->should_offload ^ nh->offloaded) {
			offload_change = true;
			if (nh->should_offload)
				nh->update = 1;
		}
		if (nh->should_offload)
			ecmp_size++;
	}
	if (!offload_change) {
		/* Nothing was added or removed, so no need to reallocate. Just
		 * update MAC on existing adjacency indexes.
		 */
1316 1317
		err = mlxsw_sp_nexthop_group_mac_update(mlxsw_sp, nh_grp,
							false);
1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344
		if (err) {
			dev_warn(mlxsw_sp->bus_info->dev, "Failed to update neigh MAC in adjacency table.\n");
			goto set_trap;
		}
		return;
	}
	if (!ecmp_size)
		/* No neigh of this group is connected so we just set
		 * the trap and let everthing flow through kernel.
		 */
		goto set_trap;

	ret = mlxsw_sp_kvdl_alloc(mlxsw_sp, ecmp_size);
	if (ret < 0) {
		/* We ran out of KVD linear space, just set the
		 * trap and let everything flow through kernel.
		 */
		dev_warn(mlxsw_sp->bus_info->dev, "Failed to allocate KVD linear area for nexthop group.\n");
		goto set_trap;
	}
	adj_index = ret;
	old_adj_index_valid = nh_grp->adj_index_valid;
	old_adj_index = nh_grp->adj_index;
	old_ecmp_size = nh_grp->ecmp_size;
	nh_grp->adj_index_valid = 1;
	nh_grp->adj_index = adj_index;
	nh_grp->ecmp_size = ecmp_size;
1345
	err = mlxsw_sp_nexthop_group_mac_update(mlxsw_sp, nh_grp, true);
1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416
	if (err) {
		dev_warn(mlxsw_sp->bus_info->dev, "Failed to update neigh MAC in adjacency table.\n");
		goto set_trap;
	}

	if (!old_adj_index_valid) {
		/* The trap was set for fib entries, so we have to call
		 * fib entry update to unset it and use adjacency index.
		 */
		err = mlxsw_sp_nexthop_fib_entries_update(mlxsw_sp, nh_grp);
		if (err) {
			dev_warn(mlxsw_sp->bus_info->dev, "Failed to add adjacency index to fib entries.\n");
			goto set_trap;
		}
		return;
	}

	err = mlxsw_sp_adj_index_mass_update(mlxsw_sp, nh_grp,
					     old_adj_index, old_ecmp_size);
	mlxsw_sp_kvdl_free(mlxsw_sp, old_adj_index);
	if (err) {
		dev_warn(mlxsw_sp->bus_info->dev, "Failed to mass-update adjacency index for nexthop group.\n");
		goto set_trap;
	}
	return;

set_trap:
	old_adj_index_valid = nh_grp->adj_index_valid;
	nh_grp->adj_index_valid = 0;
	for (i = 0; i < nh_grp->count; i++) {
		nh = &nh_grp->nexthops[i];
		nh->offloaded = 0;
	}
	err = mlxsw_sp_nexthop_fib_entries_update(mlxsw_sp, nh_grp);
	if (err)
		dev_warn(mlxsw_sp->bus_info->dev, "Failed to set traps for fib entries.\n");
	if (old_adj_index_valid)
		mlxsw_sp_kvdl_free(mlxsw_sp, nh_grp->adj_index);
}

static void __mlxsw_sp_nexthop_neigh_update(struct mlxsw_sp_nexthop *nh,
					    bool removing)
{
	if (!removing && !nh->should_offload)
		nh->should_offload = 1;
	else if (removing && nh->offloaded)
		nh->should_offload = 0;
	nh->update = 1;
}

static void
mlxsw_sp_nexthop_neigh_update(struct mlxsw_sp *mlxsw_sp,
			      struct mlxsw_sp_neigh_entry *neigh_entry,
			      bool removing)
{
	struct mlxsw_sp_nexthop *nh;

	list_for_each_entry(nh, &neigh_entry->nexthop_list,
			    neigh_list_node) {
		__mlxsw_sp_nexthop_neigh_update(nh, removing);
		mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh->nh_grp);
	}
}

static int mlxsw_sp_nexthop_init(struct mlxsw_sp *mlxsw_sp,
				 struct mlxsw_sp_nexthop_group *nh_grp,
				 struct mlxsw_sp_nexthop *nh,
				 struct fib_nh *fib_nh)
{
	struct mlxsw_sp_neigh_entry *neigh_entry;
	struct net_device *dev = fib_nh->nh_dev;
1417
	struct mlxsw_sp_rif *r;
1418
	struct neighbour *n;
1419
	u8 nud_state, dead;
1420 1421
	int err;

1422
	nh->nh_grp = nh_grp;
1423 1424 1425 1426
	nh->key.fib_nh = fib_nh;
	err = mlxsw_sp_nexthop_insert(mlxsw_sp, nh);
	if (err)
		return err;
1427

1428 1429 1430 1431 1432 1433 1434 1435
	r = mlxsw_sp_rif_find_by_dev(mlxsw_sp, dev);
	if (!r)
		return 0;
	nh->r = r;

	if (!nh_grp->gateway)
		return 0;

1436 1437 1438
	/* Take a reference of neigh here ensuring that neigh would
	 * not be detructed before the nexthop entry is finished.
	 * The reference is taken either in neigh_lookup() or
1439
	 * in neigh_create() in case n is not found.
1440 1441 1442 1443
	 */
	n = neigh_lookup(&arp_tbl, &fib_nh->nh_gw, dev);
	if (!n) {
		n = neigh_create(&arp_tbl, &fib_nh->nh_gw, dev);
1444 1445 1446 1447
		if (IS_ERR(n)) {
			err = PTR_ERR(n);
			goto err_neigh_create;
		}
1448
		neigh_event_send(n, NULL);
1449 1450 1451
	}
	neigh_entry = mlxsw_sp_neigh_entry_lookup(mlxsw_sp, n);
	if (!neigh_entry) {
1452 1453
		neigh_entry = mlxsw_sp_neigh_entry_create(mlxsw_sp, n);
		if (IS_ERR(neigh_entry)) {
1454 1455
			err = -EINVAL;
			goto err_neigh_entry_create;
1456
		}
1457
	}
1458 1459 1460 1461 1462 1463 1464 1465

	/* If that is the first nexthop connected to that neigh, add to
	 * nexthop_neighs_list
	 */
	if (list_empty(&neigh_entry->nexthop_list))
		list_add_tail(&neigh_entry->nexthop_neighs_list_node,
			      &mlxsw_sp->router.nexthop_neighs_list);

1466 1467 1468 1469
	nh->neigh_entry = neigh_entry;
	list_add_tail(&nh->neigh_list_node, &neigh_entry->nexthop_list);
	read_lock_bh(&n->lock);
	nud_state = n->nud_state;
1470
	dead = n->dead;
1471
	read_unlock_bh(&n->lock);
1472
	__mlxsw_sp_nexthop_neigh_update(nh, !(nud_state & NUD_VALID && !dead));
1473 1474

	return 0;
1475 1476 1477 1478 1479 1480

err_neigh_entry_create:
	neigh_release(n);
err_neigh_create:
	mlxsw_sp_nexthop_remove(mlxsw_sp, nh);
	return err;
1481 1482 1483 1484 1485 1486
}

static void mlxsw_sp_nexthop_fini(struct mlxsw_sp *mlxsw_sp,
				  struct mlxsw_sp_nexthop *nh)
{
	struct mlxsw_sp_neigh_entry *neigh_entry = nh->neigh_entry;
1487
	struct neighbour *n = neigh_entry->key.n;
1488

1489 1490 1491
	if (!neigh_entry)
		goto out;

1492
	__mlxsw_sp_nexthop_neigh_update(nh, true);
1493
	list_del(&nh->neigh_list_node);
1494
	nh->neigh_entry = NULL;
1495 1496 1497 1498

	/* If that is the last nexthop connected to that neigh, remove from
	 * nexthop_neighs_list
	 */
1499 1500
	if (list_empty(&neigh_entry->nexthop_list))
		list_del(&neigh_entry->nexthop_neighs_list_node);
1501

1502 1503 1504 1505
	if (!neigh_entry->connected && list_empty(&neigh_entry->nexthop_list))
		mlxsw_sp_neigh_entry_destroy(mlxsw_sp, neigh_entry);

	neigh_release(n);
1506

1507
out:
1508
	mlxsw_sp_nexthop_remove(mlxsw_sp, nh);
1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526
}

static struct mlxsw_sp_nexthop_group *
mlxsw_sp_nexthop_group_create(struct mlxsw_sp *mlxsw_sp, struct fib_info *fi)
{
	struct mlxsw_sp_nexthop_group *nh_grp;
	struct mlxsw_sp_nexthop *nh;
	struct fib_nh *fib_nh;
	size_t alloc_size;
	int i;
	int err;

	alloc_size = sizeof(*nh_grp) +
		     fi->fib_nhs * sizeof(struct mlxsw_sp_nexthop);
	nh_grp = kzalloc(alloc_size, GFP_KERNEL);
	if (!nh_grp)
		return ERR_PTR(-ENOMEM);
	INIT_LIST_HEAD(&nh_grp->fib_list);
1527
	nh_grp->gateway = fi->fib_nh->nh_scope == RT_SCOPE_LINK;
1528
	nh_grp->count = fi->fib_nhs;
1529
	nh_grp->key.fi = fi;
1530 1531 1532 1533 1534 1535 1536
	for (i = 0; i < nh_grp->count; i++) {
		nh = &nh_grp->nexthops[i];
		fib_nh = &fi->fib_nh[i];
		err = mlxsw_sp_nexthop_init(mlxsw_sp, nh_grp, nh, fib_nh);
		if (err)
			goto err_nexthop_init;
	}
1537 1538 1539
	err = mlxsw_sp_nexthop_group_insert(mlxsw_sp, nh_grp);
	if (err)
		goto err_nexthop_group_insert;
1540 1541 1542
	mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh_grp);
	return nh_grp;

1543
err_nexthop_group_insert:
1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557
err_nexthop_init:
	for (i--; i >= 0; i--)
		mlxsw_sp_nexthop_fini(mlxsw_sp, nh);
	kfree(nh_grp);
	return ERR_PTR(err);
}

static void
mlxsw_sp_nexthop_group_destroy(struct mlxsw_sp *mlxsw_sp,
			       struct mlxsw_sp_nexthop_group *nh_grp)
{
	struct mlxsw_sp_nexthop *nh;
	int i;

1558
	mlxsw_sp_nexthop_group_remove(mlxsw_sp, nh_grp);
1559 1560 1561 1562
	for (i = 0; i < nh_grp->count; i++) {
		nh = &nh_grp->nexthops[i];
		mlxsw_sp_nexthop_fini(mlxsw_sp, nh);
	}
1563 1564
	mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh_grp);
	WARN_ON_ONCE(nh_grp->adj_index_valid);
1565 1566 1567 1568 1569 1570 1571
	kfree(nh_grp);
}

static int mlxsw_sp_nexthop_group_get(struct mlxsw_sp *mlxsw_sp,
				      struct mlxsw_sp_fib_entry *fib_entry,
				      struct fib_info *fi)
{
1572
	struct mlxsw_sp_nexthop_group_key key;
1573 1574
	struct mlxsw_sp_nexthop_group *nh_grp;

1575 1576
	key.fi = fi;
	nh_grp = mlxsw_sp_nexthop_group_lookup(mlxsw_sp, key);
1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622
	if (!nh_grp) {
		nh_grp = mlxsw_sp_nexthop_group_create(mlxsw_sp, fi);
		if (IS_ERR(nh_grp))
			return PTR_ERR(nh_grp);
	}
	list_add_tail(&fib_entry->nexthop_group_node, &nh_grp->fib_list);
	fib_entry->nh_group = nh_grp;
	return 0;
}

static void mlxsw_sp_nexthop_group_put(struct mlxsw_sp *mlxsw_sp,
				       struct mlxsw_sp_fib_entry *fib_entry)
{
	struct mlxsw_sp_nexthop_group *nh_grp = fib_entry->nh_group;

	list_del(&fib_entry->nexthop_group_node);
	if (!list_empty(&nh_grp->fib_list))
		return;
	mlxsw_sp_nexthop_group_destroy(mlxsw_sp, nh_grp);
}

static int mlxsw_sp_fib_entry_op4_remote(struct mlxsw_sp *mlxsw_sp,
					 struct mlxsw_sp_fib_entry *fib_entry,
					 enum mlxsw_reg_ralue_op op)
{
	char ralue_pl[MLXSW_REG_RALUE_LEN];
	u32 *p_dip = (u32 *) fib_entry->key.addr;
	struct mlxsw_sp_vr *vr = fib_entry->vr;
	enum mlxsw_reg_ralue_trap_action trap_action;
	u16 trap_id = 0;
	u32 adjacency_index = 0;
	u16 ecmp_size = 0;

	/* In case the nexthop group adjacency index is valid, use it
	 * with provided ECMP size. Otherwise, setup trap and pass
	 * traffic to kernel.
	 */
	if (fib_entry->nh_group->adj_index_valid) {
		trap_action = MLXSW_REG_RALUE_TRAP_ACTION_NOP;
		adjacency_index = fib_entry->nh_group->adj_index;
		ecmp_size = fib_entry->nh_group->ecmp_size;
	} else {
		trap_action = MLXSW_REG_RALUE_TRAP_ACTION_TRAP;
		trap_id = MLXSW_TRAP_ID_RTR_INGRESS0;
	}

1623 1624 1625
	mlxsw_reg_ralue_pack4(ralue_pl,
			      (enum mlxsw_reg_ralxx_protocol) vr->proto, op,
			      vr->id, fib_entry->key.prefix_len, *p_dip);
1626 1627 1628 1629 1630
	mlxsw_reg_ralue_act_remote_pack(ralue_pl, trap_action, trap_id,
					adjacency_index, ecmp_size);
	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ralue), ralue_pl);
}

1631 1632 1633 1634
static int mlxsw_sp_fib_entry_op4_local(struct mlxsw_sp *mlxsw_sp,
					struct mlxsw_sp_fib_entry *fib_entry,
					enum mlxsw_reg_ralue_op op)
{
1635
	struct mlxsw_sp_rif *r = fib_entry->nh_group->nh_rif;
1636 1637 1638 1639
	char ralue_pl[MLXSW_REG_RALUE_LEN];
	u32 *p_dip = (u32 *) fib_entry->key.addr;
	struct mlxsw_sp_vr *vr = fib_entry->vr;

1640 1641 1642
	mlxsw_reg_ralue_pack4(ralue_pl,
			      (enum mlxsw_reg_ralxx_protocol) vr->proto, op,
			      vr->id, fib_entry->key.prefix_len, *p_dip);
1643 1644
	mlxsw_reg_ralue_act_local_pack(ralue_pl,
				       MLXSW_REG_RALUE_TRAP_ACTION_NOP, 0,
1645
				       r->rif);
1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656
	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ralue), ralue_pl);
}

static int mlxsw_sp_fib_entry_op4_trap(struct mlxsw_sp *mlxsw_sp,
				       struct mlxsw_sp_fib_entry *fib_entry,
				       enum mlxsw_reg_ralue_op op)
{
	char ralue_pl[MLXSW_REG_RALUE_LEN];
	u32 *p_dip = (u32 *) fib_entry->key.addr;
	struct mlxsw_sp_vr *vr = fib_entry->vr;

1657 1658 1659
	mlxsw_reg_ralue_pack4(ralue_pl,
			      (enum mlxsw_reg_ralxx_protocol) vr->proto, op,
			      vr->id, fib_entry->key.prefix_len, *p_dip);
1660 1661 1662 1663 1664 1665 1666 1667 1668 1669
	mlxsw_reg_ralue_act_ip2me_pack(ralue_pl);
	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ralue), ralue_pl);
}

static int mlxsw_sp_fib_entry_op4(struct mlxsw_sp *mlxsw_sp,
				  struct mlxsw_sp_fib_entry *fib_entry,
				  enum mlxsw_reg_ralue_op op)
{
	switch (fib_entry->type) {
	case MLXSW_SP_FIB_ENTRY_TYPE_REMOTE:
1670
		return mlxsw_sp_fib_entry_op4_remote(mlxsw_sp, fib_entry, op);
1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694
	case MLXSW_SP_FIB_ENTRY_TYPE_LOCAL:
		return mlxsw_sp_fib_entry_op4_local(mlxsw_sp, fib_entry, op);
	case MLXSW_SP_FIB_ENTRY_TYPE_TRAP:
		return mlxsw_sp_fib_entry_op4_trap(mlxsw_sp, fib_entry, op);
	}
	return -EINVAL;
}

static int mlxsw_sp_fib_entry_op(struct mlxsw_sp *mlxsw_sp,
				 struct mlxsw_sp_fib_entry *fib_entry,
				 enum mlxsw_reg_ralue_op op)
{
	switch (fib_entry->vr->proto) {
	case MLXSW_SP_L3_PROTO_IPV4:
		return mlxsw_sp_fib_entry_op4(mlxsw_sp, fib_entry, op);
	case MLXSW_SP_L3_PROTO_IPV6:
		return -EINVAL;
	}
	return -EINVAL;
}

static int mlxsw_sp_fib_entry_update(struct mlxsw_sp *mlxsw_sp,
				     struct mlxsw_sp_fib_entry *fib_entry)
{
1695 1696
	return mlxsw_sp_fib_entry_op(mlxsw_sp, fib_entry,
				     MLXSW_REG_RALUE_OP_WRITE_WRITE);
1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707
}

static int mlxsw_sp_fib_entry_del(struct mlxsw_sp *mlxsw_sp,
				  struct mlxsw_sp_fib_entry *fib_entry)
{
	return mlxsw_sp_fib_entry_op(mlxsw_sp, fib_entry,
				     MLXSW_REG_RALUE_OP_WRITE_DELETE);
}

static int
mlxsw_sp_router_fib4_entry_init(struct mlxsw_sp *mlxsw_sp,
1708
				const struct fib_entry_notifier_info *fen_info,
1709 1710
				struct mlxsw_sp_fib_entry *fib_entry)
{
1711
	struct fib_info *fi = fen_info->fi;
1712
	struct mlxsw_sp_rif *r = NULL;
1713
	int nhsel;
1714

1715
	if (fen_info->type == RTN_LOCAL || fen_info->type == RTN_BROADCAST) {
1716 1717 1718
		fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_TRAP;
		return 0;
	}
1719
	if (fen_info->type != RTN_UNICAST)
1720 1721
		return -EINVAL;

1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734
	for (nhsel = 0; nhsel < fi->fib_nhs; nhsel++) {
		const struct fib_nh *nh = &fi->fib_nh[nhsel];

		if (!nh->nh_dev)
			continue;
		r = mlxsw_sp_rif_find_by_dev(mlxsw_sp, nh->nh_dev);
		if (!r) {
			/* In case router interface is not found for
			 * at least one of the nexthops, that means
			 * the nexthop points to some device unrelated
			 * to us. Set trap and pass the packets for
			 * this prefix to kernel.
			 */
1735
			break;
1736 1737
		}
	}
1738

1739 1740 1741 1742 1743
	if (!r) {
		fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_TRAP;
		return 0;
	}

1744
	if (fi->fib_nh->nh_scope != RT_SCOPE_LINK)
1745
		fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_LOCAL;
1746
	else
1747 1748 1749
		fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_REMOTE;
	fib_info_offload_inc(fen_info->fi);
	return 0;
1750 1751 1752 1753 1754 1755
}

static void
mlxsw_sp_router_fib4_entry_fini(struct mlxsw_sp *mlxsw_sp,
				struct mlxsw_sp_fib_entry *fib_entry)
{
1756
	if (fib_entry->type != MLXSW_SP_FIB_ENTRY_TYPE_TRAP)
1757
		fib_info_offload_dec(fib_entry->nh_group->key.fi);
1758 1759
}

1760 1761
static struct mlxsw_sp_fib_entry *
mlxsw_sp_fib_entry_get(struct mlxsw_sp *mlxsw_sp,
1762
		       const struct fib_entry_notifier_info *fen_info)
1763 1764
{
	struct mlxsw_sp_fib_entry *fib_entry;
1765
	struct fib_info *fi = fen_info->fi;
1766 1767 1768
	struct mlxsw_sp_vr *vr;
	int err;

1769
	vr = mlxsw_sp_vr_get(mlxsw_sp, fen_info->dst_len, fen_info->tb_id,
1770 1771
			     MLXSW_SP_L3_PROTO_IPV4);
	if (IS_ERR(vr))
1772
		return ERR_CAST(vr);
1773

1774 1775 1776
	fib_entry = mlxsw_sp_fib_entry_lookup(vr->fib, &fen_info->dst,
					      sizeof(fen_info->dst),
					      fen_info->dst_len, fi->fib_dev);
1777 1778 1779 1780 1781
	if (fib_entry) {
		/* Already exists, just take a reference */
		fib_entry->ref_count++;
		return fib_entry;
	}
1782 1783 1784
	fib_entry = mlxsw_sp_fib_entry_create(vr->fib, &fen_info->dst,
					      sizeof(fen_info->dst),
					      fen_info->dst_len, fi->fib_dev);
1785 1786 1787 1788 1789
	if (!fib_entry) {
		err = -ENOMEM;
		goto err_fib_entry_create;
	}
	fib_entry->vr = vr;
1790
	fib_entry->ref_count = 1;
1791

1792
	err = mlxsw_sp_router_fib4_entry_init(mlxsw_sp, fen_info, fib_entry);
1793 1794 1795
	if (err)
		goto err_fib4_entry_init;

1796 1797 1798 1799
	err = mlxsw_sp_nexthop_group_get(mlxsw_sp, fib_entry, fi);
	if (err)
		goto err_nexthop_group_get;

1800 1801
	return fib_entry;

1802 1803
err_nexthop_group_get:
	mlxsw_sp_router_fib4_entry_fini(mlxsw_sp, fib_entry);
1804 1805 1806 1807 1808 1809 1810 1811 1812 1813
err_fib4_entry_init:
	mlxsw_sp_fib_entry_destroy(fib_entry);
err_fib_entry_create:
	mlxsw_sp_vr_put(mlxsw_sp, vr);

	return ERR_PTR(err);
}

static struct mlxsw_sp_fib_entry *
mlxsw_sp_fib_entry_find(struct mlxsw_sp *mlxsw_sp,
1814
			const struct fib_entry_notifier_info *fen_info)
1815 1816 1817
{
	struct mlxsw_sp_vr *vr;

1818 1819
	vr = mlxsw_sp_vr_find(mlxsw_sp, fen_info->tb_id,
			      MLXSW_SP_L3_PROTO_IPV4);
1820 1821 1822
	if (!vr)
		return NULL;

1823 1824 1825 1826
	return mlxsw_sp_fib_entry_lookup(vr->fib, &fen_info->dst,
					 sizeof(fen_info->dst),
					 fen_info->dst_len,
					 fen_info->fi->fib_dev);
1827 1828
}

1829 1830
static void mlxsw_sp_fib_entry_put(struct mlxsw_sp *mlxsw_sp,
				   struct mlxsw_sp_fib_entry *fib_entry)
1831 1832 1833 1834
{
	struct mlxsw_sp_vr *vr = fib_entry->vr;

	if (--fib_entry->ref_count == 0) {
1835
		mlxsw_sp_nexthop_group_put(mlxsw_sp, fib_entry);
1836 1837 1838 1839 1840 1841
		mlxsw_sp_router_fib4_entry_fini(mlxsw_sp, fib_entry);
		mlxsw_sp_fib_entry_destroy(fib_entry);
	}
	mlxsw_sp_vr_put(mlxsw_sp, vr);
}

1842 1843
static void mlxsw_sp_fib_entry_put_all(struct mlxsw_sp *mlxsw_sp,
				       struct mlxsw_sp_fib_entry *fib_entry)
1844
{
1845
	unsigned int last_ref_count;
1846

1847 1848 1849 1850
	do {
		last_ref_count = fib_entry->ref_count;
		mlxsw_sp_fib_entry_put(mlxsw_sp, fib_entry);
	} while (last_ref_count != 1);
1851 1852
}

1853 1854
static int mlxsw_sp_router_fib4_add(struct mlxsw_sp *mlxsw_sp,
				    struct fib_entry_notifier_info *fen_info)
1855 1856 1857 1858 1859
{
	struct mlxsw_sp_fib_entry *fib_entry;
	struct mlxsw_sp_vr *vr;
	int err;

1860 1861 1862 1863 1864 1865 1866 1867
	if (mlxsw_sp->router.aborted)
		return 0;

	fib_entry = mlxsw_sp_fib_entry_get(mlxsw_sp, fen_info);
	if (IS_ERR(fib_entry)) {
		dev_warn(mlxsw_sp->bus_info->dev, "Failed to get FIB4 entry being added.\n");
		return PTR_ERR(fib_entry);
	}
1868

1869 1870 1871
	if (fib_entry->ref_count != 1)
		return 0;

1872
	vr = fib_entry->vr;
1873
	err = mlxsw_sp_fib_entry_insert(vr->fib, fib_entry);
1874 1875
	if (err) {
		dev_warn(mlxsw_sp->bus_info->dev, "Failed to insert FIB4 entry being added.\n");
1876
		goto err_fib_entry_insert;
1877 1878
	}
	err = mlxsw_sp_fib_entry_update(mlxsw_sp, fib_entry);
1879 1880 1881 1882 1883 1884 1885
	if (err)
		goto err_fib_entry_add;
	return 0;

err_fib_entry_add:
	mlxsw_sp_fib_entry_remove(vr->fib, fib_entry);
err_fib_entry_insert:
1886
	mlxsw_sp_fib_entry_put(mlxsw_sp, fib_entry);
1887 1888 1889
	return err;
}

1890 1891
static void mlxsw_sp_router_fib4_del(struct mlxsw_sp *mlxsw_sp,
				     struct fib_entry_notifier_info *fen_info)
1892 1893 1894
{
	struct mlxsw_sp_fib_entry *fib_entry;

1895
	if (mlxsw_sp->router.aborted)
1896
		return;
1897 1898

	fib_entry = mlxsw_sp_fib_entry_find(mlxsw_sp, fen_info);
1899 1900
	if (!fib_entry)
		return;
1901 1902 1903 1904 1905 1906 1907

	if (fib_entry->ref_count == 1) {
		mlxsw_sp_fib_entry_del(mlxsw_sp, fib_entry);
		mlxsw_sp_fib_entry_remove(fib_entry->vr->fib, fib_entry);
	}

	mlxsw_sp_fib_entry_put(mlxsw_sp, fib_entry);
1908
}
1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928

static int mlxsw_sp_router_set_abort_trap(struct mlxsw_sp *mlxsw_sp)
{
	char ralta_pl[MLXSW_REG_RALTA_LEN];
	char ralst_pl[MLXSW_REG_RALST_LEN];
	char raltb_pl[MLXSW_REG_RALTB_LEN];
	char ralue_pl[MLXSW_REG_RALUE_LEN];
	int err;

	mlxsw_reg_ralta_pack(ralta_pl, true, MLXSW_REG_RALXX_PROTOCOL_IPV4,
			     MLXSW_SP_LPM_TREE_MIN);
	err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ralta), ralta_pl);
	if (err)
		return err;

	mlxsw_reg_ralst_pack(ralst_pl, 0xff, MLXSW_SP_LPM_TREE_MIN);
	err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ralst), ralst_pl);
	if (err)
		return err;

1929 1930
	mlxsw_reg_raltb_pack(raltb_pl, 0, MLXSW_REG_RALXX_PROTOCOL_IPV4,
			     MLXSW_SP_LPM_TREE_MIN);
1931 1932 1933 1934 1935 1936 1937 1938 1939 1940
	err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(raltb), raltb_pl);
	if (err)
		return err;

	mlxsw_reg_ralue_pack4(ralue_pl, MLXSW_SP_L3_PROTO_IPV4,
			      MLXSW_REG_RALUE_OP_WRITE_WRITE, 0, 0, 0);
	mlxsw_reg_ralue_act_ip2me_pack(ralue_pl);
	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ralue), ralue_pl);
}

1941
static void mlxsw_sp_router_fib_flush(struct mlxsw_sp *mlxsw_sp)
1942 1943 1944 1945 1946 1947
{
	struct mlxsw_sp_fib_entry *fib_entry;
	struct mlxsw_sp_fib_entry *tmp;
	struct mlxsw_sp_vr *vr;
	int i;

J
Jiri Pirko 已提交
1948
	for (i = 0; i < MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_VRS); i++) {
1949
		vr = &mlxsw_sp->router.vrs[i];
1950

1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965
		if (!vr->used)
			continue;

		list_for_each_entry_safe(fib_entry, tmp,
					 &vr->fib->entry_list, list) {
			bool do_break = &tmp->list == &vr->fib->entry_list;

			mlxsw_sp_fib_entry_del(mlxsw_sp, fib_entry);
			mlxsw_sp_fib_entry_remove(fib_entry->vr->fib,
						  fib_entry);
			mlxsw_sp_fib_entry_put_all(mlxsw_sp, fib_entry);
			if (do_break)
				break;
		}
	}
1966 1967 1968 1969 1970 1971
}

static void mlxsw_sp_router_fib4_abort(struct mlxsw_sp *mlxsw_sp)
{
	int err;

1972 1973 1974
	if (mlxsw_sp->router.aborted)
		return;
	dev_warn(mlxsw_sp->bus_info->dev, "FIB abort triggered. Note that FIB entries are no longer being offloaded to this device.\n");
1975
	mlxsw_sp_router_fib_flush(mlxsw_sp);
1976 1977 1978 1979 1980 1981 1982 1983 1984
	mlxsw_sp->router.aborted = true;
	err = mlxsw_sp_router_set_abort_trap(mlxsw_sp);
	if (err)
		dev_warn(mlxsw_sp->bus_info->dev, "Failed to set abort trap.\n");
}

static int __mlxsw_sp_router_init(struct mlxsw_sp *mlxsw_sp)
{
	char rgcr_pl[MLXSW_REG_RGCR_LEN];
J
Jiri Pirko 已提交
1985
	u64 max_rifs;
1986 1987
	int err;

J
Jiri Pirko 已提交
1988
	if (!MLXSW_CORE_RES_VALID(mlxsw_sp->core, MAX_RIFS))
1989 1990
		return -EIO;

J
Jiri Pirko 已提交
1991 1992 1993
	max_rifs = MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_RIFS);
	mlxsw_sp->rifs = kcalloc(max_rifs, sizeof(struct mlxsw_sp_rif *),
				 GFP_KERNEL);
1994 1995 1996 1997
	if (!mlxsw_sp->rifs)
		return -ENOMEM;

	mlxsw_reg_rgcr_pack(rgcr_pl, true);
J
Jiri Pirko 已提交
1998
	mlxsw_reg_rgcr_max_router_interfaces_set(rgcr_pl, max_rifs);
1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017
	err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(rgcr), rgcr_pl);
	if (err)
		goto err_rgcr_fail;

	return 0;

err_rgcr_fail:
	kfree(mlxsw_sp->rifs);
	return err;
}

static void __mlxsw_sp_router_fini(struct mlxsw_sp *mlxsw_sp)
{
	char rgcr_pl[MLXSW_REG_RGCR_LEN];
	int i;

	mlxsw_reg_rgcr_pack(rgcr_pl, false);
	mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(rgcr), rgcr_pl);

J
Jiri Pirko 已提交
2018
	for (i = 0; i < MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_RIFS); i++)
2019 2020 2021 2022 2023
		WARN_ON_ONCE(mlxsw_sp->rifs[i]);

	kfree(mlxsw_sp->rifs);
}

2024
struct mlxsw_sp_fib_event_work {
2025
	struct work_struct work;
2026 2027 2028 2029 2030 2031
	struct fib_entry_notifier_info fen_info;
	struct mlxsw_sp *mlxsw_sp;
	unsigned long event;
};

static void mlxsw_sp_router_fib_event_work(struct work_struct *work)
2032
{
2033
	struct mlxsw_sp_fib_event_work *fib_work =
2034
		container_of(work, struct mlxsw_sp_fib_event_work, work);
2035
	struct mlxsw_sp *mlxsw_sp = fib_work->mlxsw_sp;
2036 2037
	int err;

2038 2039 2040
	/* Protect internal structures from changes */
	rtnl_lock();
	switch (fib_work->event) {
2041
	case FIB_EVENT_ENTRY_ADD:
2042
		err = mlxsw_sp_router_fib4_add(mlxsw_sp, &fib_work->fen_info);
2043 2044
		if (err)
			mlxsw_sp_router_fib4_abort(mlxsw_sp);
2045
		fib_info_put(fib_work->fen_info.fi);
2046 2047
		break;
	case FIB_EVENT_ENTRY_DEL:
2048 2049
		mlxsw_sp_router_fib4_del(mlxsw_sp, &fib_work->fen_info);
		fib_info_put(fib_work->fen_info.fi);
2050 2051 2052 2053 2054 2055
		break;
	case FIB_EVENT_RULE_ADD: /* fall through */
	case FIB_EVENT_RULE_DEL:
		mlxsw_sp_router_fib4_abort(mlxsw_sp);
		break;
	}
2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074
	rtnl_unlock();
	kfree(fib_work);
}

/* Called with rcu_read_lock() */
static int mlxsw_sp_router_fib_event(struct notifier_block *nb,
				     unsigned long event, void *ptr)
{
	struct mlxsw_sp *mlxsw_sp = container_of(nb, struct mlxsw_sp, fib_nb);
	struct mlxsw_sp_fib_event_work *fib_work;
	struct fib_notifier_info *info = ptr;

	if (!net_eq(info->net, &init_net))
		return NOTIFY_DONE;

	fib_work = kzalloc(sizeof(*fib_work), GFP_ATOMIC);
	if (WARN_ON(!fib_work))
		return NOTIFY_BAD;

2075
	INIT_WORK(&fib_work->work, mlxsw_sp_router_fib_event_work);
2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089
	fib_work->mlxsw_sp = mlxsw_sp;
	fib_work->event = event;

	switch (event) {
	case FIB_EVENT_ENTRY_ADD: /* fall through */
	case FIB_EVENT_ENTRY_DEL:
		memcpy(&fib_work->fen_info, ptr, sizeof(fib_work->fen_info));
		/* Take referece on fib_info to prevent it from being
		 * freed while work is queued. Release it afterwards.
		 */
		fib_info_hold(fib_work->fen_info.fi);
		break;
	}

2090
	mlxsw_core_schedule_work(&fib_work->work);
2091

2092 2093 2094
	return NOTIFY_DONE;
}

2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106
static void mlxsw_sp_router_fib_dump_flush(struct notifier_block *nb)
{
	struct mlxsw_sp *mlxsw_sp = container_of(nb, struct mlxsw_sp, fib_nb);

	/* Flush pending FIB notifications and then flush the device's
	 * table before requesting another dump. The FIB notification
	 * block is unregistered, so no need to take RTNL.
	 */
	mlxsw_core_flush_owq();
	mlxsw_sp_router_fib_flush(mlxsw_sp);
}

2107 2108 2109 2110 2111 2112 2113 2114 2115
int mlxsw_sp_router_init(struct mlxsw_sp *mlxsw_sp)
{
	int err;

	INIT_LIST_HEAD(&mlxsw_sp->router.nexthop_neighs_list);
	err = __mlxsw_sp_router_init(mlxsw_sp);
	if (err)
		return err;

2116 2117 2118 2119 2120
	err = rhashtable_init(&mlxsw_sp->router.nexthop_ht,
			      &mlxsw_sp_nexthop_ht_params);
	if (err)
		goto err_nexthop_ht_init;

2121 2122 2123 2124 2125
	err = rhashtable_init(&mlxsw_sp->router.nexthop_group_ht,
			      &mlxsw_sp_nexthop_group_ht_params);
	if (err)
		goto err_nexthop_group_ht_init;

2126 2127 2128 2129 2130
	mlxsw_sp_lpm_init(mlxsw_sp);
	err = mlxsw_sp_vrs_init(mlxsw_sp);
	if (err)
		goto err_vrs_init;

2131
	err = mlxsw_sp_neigh_init(mlxsw_sp);
2132 2133 2134 2135
	if (err)
		goto err_neigh_init;

	mlxsw_sp->fib_nb.notifier_call = mlxsw_sp_router_fib_event;
2136 2137 2138 2139 2140
	err = register_fib_notifier(&mlxsw_sp->fib_nb,
				    mlxsw_sp_router_fib_dump_flush);
	if (err)
		goto err_register_fib_notifier;

2141 2142
	return 0;

2143 2144
err_register_fib_notifier:
	mlxsw_sp_neigh_fini(mlxsw_sp);
2145 2146 2147
err_neigh_init:
	mlxsw_sp_vrs_fini(mlxsw_sp);
err_vrs_init:
2148 2149
	rhashtable_destroy(&mlxsw_sp->router.nexthop_group_ht);
err_nexthop_group_ht_init:
2150 2151
	rhashtable_destroy(&mlxsw_sp->router.nexthop_ht);
err_nexthop_ht_init:
2152 2153 2154 2155 2156 2157 2158 2159 2160
	__mlxsw_sp_router_fini(mlxsw_sp);
	return err;
}

void mlxsw_sp_router_fini(struct mlxsw_sp *mlxsw_sp)
{
	unregister_fib_notifier(&mlxsw_sp->fib_nb);
	mlxsw_sp_neigh_fini(mlxsw_sp);
	mlxsw_sp_vrs_fini(mlxsw_sp);
2161
	rhashtable_destroy(&mlxsw_sp->router.nexthop_group_ht);
2162
	rhashtable_destroy(&mlxsw_sp->router.nexthop_ht);
2163 2164
	__mlxsw_sp_router_fini(mlxsw_sp);
}