|
@@ -46,6 +46,19 @@
|
|
#define mlxsw_sp_prefix_usage_for_each(prefix, prefix_usage) \
|
|
#define mlxsw_sp_prefix_usage_for_each(prefix, prefix_usage) \
|
|
for_each_set_bit(prefix, (prefix_usage)->b, MLXSW_SP_PREFIX_COUNT)
|
|
for_each_set_bit(prefix, (prefix_usage)->b, MLXSW_SP_PREFIX_COUNT)
|
|
|
|
|
|
|
|
+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;
|
|
|
|
+}
|
|
|
|
+
|
|
static bool
|
|
static bool
|
|
mlxsw_sp_prefix_usage_eq(struct mlxsw_sp_prefix_usage *prefix_usage1,
|
|
mlxsw_sp_prefix_usage_eq(struct mlxsw_sp_prefix_usage *prefix_usage1,
|
|
struct mlxsw_sp_prefix_usage *prefix_usage2)
|
|
struct mlxsw_sp_prefix_usage *prefix_usage2)
|
|
@@ -53,6 +66,27 @@ mlxsw_sp_prefix_usage_eq(struct mlxsw_sp_prefix_usage *prefix_usage1,
|
|
return !memcmp(prefix_usage1, prefix_usage2, sizeof(*prefix_usage1));
|
|
return !memcmp(prefix_usage1, prefix_usage2, sizeof(*prefix_usage1));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+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));
|
|
|
|
+}
|
|
|
|
+
|
|
static void
|
|
static void
|
|
mlxsw_sp_prefix_usage_set(struct mlxsw_sp_prefix_usage *prefix_usage,
|
|
mlxsw_sp_prefix_usage_set(struct mlxsw_sp_prefix_usage *prefix_usage,
|
|
unsigned char prefix_len)
|
|
unsigned char prefix_len)
|
|
@@ -307,6 +341,199 @@ static void mlxsw_sp_lpm_init(struct mlxsw_sp *mlxsw_sp)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static struct mlxsw_sp_vr *mlxsw_sp_vr_find_unused(struct mlxsw_sp *mlxsw_sp)
|
|
|
|
+{
|
|
|
|
+ struct mlxsw_sp_vr *vr;
|
|
|
|
+ int i;
|
|
|
|
+
|
|
|
|
+ for (i = 0; i < MLXSW_SP_VIRTUAL_ROUTER_MAX; i++) {
|
|
|
|
+ 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];
|
|
|
|
+
|
|
|
|
+ mlxsw_reg_raltb_pack(raltb_pl, vr->id, vr->proto, vr->lpm_tree->id);
|
|
|
|
+ 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 */
|
|
|
|
+ mlxsw_reg_raltb_pack(raltb_pl, vr->id, vr->proto, 0);
|
|
|
|
+ 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);
|
|
|
|
+ for (i = 0; i < MLXSW_SP_VIRTUAL_ROUTER_MAX; i++) {
|
|
|
|
+ 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);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void mlxsw_sp_vrs_init(struct mlxsw_sp *mlxsw_sp)
|
|
|
|
+{
|
|
|
|
+ struct mlxsw_sp_vr *vr;
|
|
|
|
+ int i;
|
|
|
|
+
|
|
|
|
+ for (i = 0; i < MLXSW_SP_VIRTUAL_ROUTER_MAX; i++) {
|
|
|
|
+ vr = &mlxsw_sp->router.vrs[i];
|
|
|
|
+ vr->id = i;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
static int __mlxsw_sp_router_init(struct mlxsw_sp *mlxsw_sp)
|
|
static int __mlxsw_sp_router_init(struct mlxsw_sp *mlxsw_sp)
|
|
{
|
|
{
|
|
char rgcr_pl[MLXSW_REG_RGCR_LEN];
|
|
char rgcr_pl[MLXSW_REG_RGCR_LEN];
|
|
@@ -332,6 +559,7 @@ int mlxsw_sp_router_init(struct mlxsw_sp *mlxsw_sp)
|
|
if (err)
|
|
if (err)
|
|
return err;
|
|
return err;
|
|
mlxsw_sp_lpm_init(mlxsw_sp);
|
|
mlxsw_sp_lpm_init(mlxsw_sp);
|
|
|
|
+ mlxsw_sp_vrs_init(mlxsw_sp);
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|