#include <linux/kernel.h>
#include <linux/types.h>
+#include <linux/rhashtable.h>
#include "spectrum.h"
#include "core.h"
[MLXSW_SP_L3_PROTO_IPV6] = 0, /* Currently unused. */
};
+#define MLXSW_SP_ROUTER_XM_L_VAL_MAX 16
+
struct mlxsw_sp_router_xm {
bool ipv4_supported;
bool ipv6_supported;
unsigned int entries_size;
+ struct rhashtable ltable_ht;
+};
+
+struct mlxsw_sp_router_xm_ltable_node {
+ struct rhash_head ht_node; /* Member of router_xm->ltable_ht */
+ u16 mindex;
+ u8 current_lvalue;
+ refcount_t refcnt;
+ unsigned int lvalue_ref[MLXSW_SP_ROUTER_XM_L_VAL_MAX + 1];
+};
+
+static const struct rhashtable_params mlxsw_sp_router_xm_ltable_ht_params = {
+ .key_offset = offsetof(struct mlxsw_sp_router_xm_ltable_node, mindex),
+ .head_offset = offsetof(struct mlxsw_sp_router_xm_ltable_node, ht_node),
+ .key_len = sizeof(u16),
+ .automatic_shrinking = true,
};
struct mlxsw_sp_router_xm_fib_entry {
bool committed;
+ struct mlxsw_sp_router_xm_ltable_node *ltable_node; /* Parent node */
+ u16 mindex; /* Store for processing from commit op */
+ u8 lvalue;
};
#define MLXSW_SP_ROUTE_LL_XM_ENTRIES_MAX \
return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(xraltb), xraltb_pl);
}
+static u16 mlxsw_sp_router_ll_xm_mindex_get4(const u32 addr)
+{
+ /* Currently the M-index is set to linear mode. That means it is defined
+ * as 16 MSB of IP address.
+ */
+ return addr >> MLXSW_SP_ROUTER_XM_L_VAL_MAX;
+}
+
+static u16 mlxsw_sp_router_ll_xm_mindex_get6(const unsigned char *addr)
+{
+ WARN_ON_ONCE(1);
+ return 0; /* currently unused */
+}
+
static void mlxsw_sp_router_ll_xm_op_ctx_check_init(struct mlxsw_sp_fib_entry_op_ctx *op_ctx,
struct mlxsw_sp_fib_entry_op_ctx_xm *op_ctx_xm)
{
len = mlxsw_reg_xmdr_c_ltr_pack4(op_ctx_xm->xmdr_pl, op_ctx_xm->trans_offset,
op_ctx_xm->entries_count, xmdr_c_ltr_op,
virtual_router, prefix_len, (u32 *) addr);
+ fib_entry->mindex = mlxsw_sp_router_ll_xm_mindex_get4(*((u32 *) addr));
break;
case MLXSW_SP_L3_PROTO_IPV6:
len = mlxsw_reg_xmdr_c_ltr_pack6(op_ctx_xm->xmdr_pl, op_ctx_xm->trans_offset,
op_ctx_xm->entries_count, xmdr_c_ltr_op,
virtual_router, prefix_len, addr);
+ fib_entry->mindex = mlxsw_sp_router_ll_xm_mindex_get6(addr);
break;
default:
WARN_ON_ONCE(1);
WARN_ON_ONCE(op_ctx_xm->trans_item_len != len);
op_ctx_xm->entries[op_ctx_xm->entries_count] = fib_entry;
+
+ fib_entry->lvalue = prefix_len > mlxsw_sp_router_xm_m_val[proto] ?
+ prefix_len - mlxsw_sp_router_xm_m_val[proto] : 0;
}
static void
tunnel_ptr);
}
+static struct mlxsw_sp_router_xm_ltable_node *
+mlxsw_sp_router_xm_ltable_node_get(struct mlxsw_sp_router_xm *router_xm, u16 mindex)
+{
+ struct mlxsw_sp_router_xm_ltable_node *ltable_node;
+ int err;
+
+ ltable_node = rhashtable_lookup_fast(&router_xm->ltable_ht, &mindex,
+ mlxsw_sp_router_xm_ltable_ht_params);
+ if (ltable_node) {
+ refcount_inc(<able_node->refcnt);
+ return ltable_node;
+ }
+ ltable_node = kzalloc(sizeof(*ltable_node), GFP_KERNEL);
+ if (!ltable_node)
+ return ERR_PTR(-ENOMEM);
+ ltable_node->mindex = mindex;
+ refcount_set(<able_node->refcnt, 1);
+
+ err = rhashtable_insert_fast(&router_xm->ltable_ht, <able_node->ht_node,
+ mlxsw_sp_router_xm_ltable_ht_params);
+ if (err)
+ goto err_insert;
+
+ return ltable_node;
+
+err_insert:
+ kfree(ltable_node);
+ return ERR_PTR(err);
+}
+
+static void mlxsw_sp_router_xm_ltable_node_put(struct mlxsw_sp_router_xm *router_xm,
+ struct mlxsw_sp_router_xm_ltable_node *ltable_node)
+{
+ if (!refcount_dec_and_test(<able_node->refcnt))
+ return;
+ rhashtable_remove_fast(&router_xm->ltable_ht, <able_node->ht_node,
+ mlxsw_sp_router_xm_ltable_ht_params);
+ kfree(ltable_node);
+}
+
+static int mlxsw_sp_router_xm_ltable_lvalue_set(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_router_xm_ltable_node *ltable_node)
+{
+ char xrmt_pl[MLXSW_REG_XRMT_LEN];
+
+ mlxsw_reg_xrmt_pack(xrmt_pl, ltable_node->mindex, ltable_node->current_lvalue);
+ return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(xrmt), xrmt_pl);
+}
+
+static int
+mlxsw_sp_router_xm_ml_entry_add(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_router_xm_fib_entry *fib_entry)
+{
+ struct mlxsw_sp_router_xm *router_xm = mlxsw_sp->router->xm;
+ struct mlxsw_sp_router_xm_ltable_node *ltable_node;
+ u8 lvalue = fib_entry->lvalue;
+ int err;
+
+ ltable_node = mlxsw_sp_router_xm_ltable_node_get(router_xm,
+ fib_entry->mindex);
+ if (IS_ERR(ltable_node))
+ return PTR_ERR(ltable_node);
+ if (lvalue > ltable_node->current_lvalue) {
+ /* The L-value is bigger then the one currently set, update. */
+ ltable_node->current_lvalue = lvalue;
+ err = mlxsw_sp_router_xm_ltable_lvalue_set(mlxsw_sp,
+ ltable_node);
+ if (err)
+ goto err_lvalue_set;
+ }
+
+ ltable_node->lvalue_ref[lvalue]++;
+ fib_entry->ltable_node = ltable_node;
+ return 0;
+
+err_lvalue_set:
+ mlxsw_sp_router_xm_ltable_node_put(router_xm, ltable_node);
+ return err;
+}
+
+static void
+mlxsw_sp_router_xm_ml_entry_del(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_router_xm_fib_entry *fib_entry)
+{
+ struct mlxsw_sp_router_xm_ltable_node *ltable_node =
+ fib_entry->ltable_node;
+ struct mlxsw_sp_router_xm *router_xm = mlxsw_sp->router->xm;
+ u8 lvalue = fib_entry->lvalue;
+
+ ltable_node->lvalue_ref[lvalue]--;
+ if (lvalue == ltable_node->current_lvalue && lvalue &&
+ !ltable_node->lvalue_ref[lvalue]) {
+ u8 new_lvalue = lvalue - 1;
+
+ /* Find the biggest L-value left out there. */
+ while (new_lvalue > 0 && !ltable_node->lvalue_ref[lvalue])
+ new_lvalue--;
+
+ ltable_node->current_lvalue = new_lvalue;
+ mlxsw_sp_router_xm_ltable_lvalue_set(mlxsw_sp, ltable_node);
+ }
+ mlxsw_sp_router_xm_ltable_node_put(router_xm, ltable_node);
+}
+
+static int
+mlxsw_sp_router_xm_ml_entries_add(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_fib_entry_op_ctx_xm *op_ctx_xm)
+{
+ struct mlxsw_sp_router_xm_fib_entry *fib_entry;
+ int err;
+ int i;
+
+ for (i = 0; i < op_ctx_xm->entries_count; i++) {
+ fib_entry = op_ctx_xm->entries[i];
+ err = mlxsw_sp_router_xm_ml_entry_add(mlxsw_sp, fib_entry);
+ if (err)
+ goto rollback;
+ }
+ return 0;
+
+rollback:
+ for (i--; i >= 0; i--) {
+ fib_entry = op_ctx_xm->entries[i];
+ mlxsw_sp_router_xm_ml_entry_del(mlxsw_sp, fib_entry);
+ }
+ return err;
+}
+
+static void
+mlxsw_sp_router_xm_ml_entries_del(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_fib_entry_op_ctx_xm *op_ctx_xm)
+{
+ struct mlxsw_sp_router_xm_fib_entry *fib_entry;
+ int i;
+
+ for (i = 0; i < op_ctx_xm->entries_count; i++) {
+ fib_entry = op_ctx_xm->entries[i];
+ mlxsw_sp_router_xm_ml_entry_del(mlxsw_sp, fib_entry);
+ }
+}
+
static int mlxsw_sp_router_ll_xm_fib_entry_commit(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_fib_entry_op_ctx *op_ctx,
bool *postponed_for_bulk)
return 0;
}
+ if (op_ctx->event == FIB_EVENT_ENTRY_REPLACE) {
+ /* The L-table is updated inside. It has to be done before
+ * the prefix is inserted.
+ */
+ err = mlxsw_sp_router_xm_ml_entries_add(mlxsw_sp, op_ctx_xm);
+ if (err)
+ goto out;
+ }
+
err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(xmdr), op_ctx_xm->xmdr_pl);
if (err)
goto out;
}
}
+ if (op_ctx->event == FIB_EVENT_ENTRY_DEL)
+ /* The L-table is updated inside. It has to be done after
+ * the prefix was removed.
+ */
+ mlxsw_sp_router_xm_ml_entries_del(mlxsw_sp, op_ctx_xm);
+
out:
/* Next pack call is going to do reinitialization */
op_ctx->initialized = false;
if (err)
goto err_rxltm_write;
+ err = rhashtable_init(&router_xm->ltable_ht, &mlxsw_sp_router_xm_ltable_ht_params);
+ if (err)
+ goto err_ltable_ht_init;
+
mlxsw_sp->router->xm = router_xm;
return 0;
+err_ltable_ht_init:
err_rxltm_write:
err_mindex_size_check:
err_device_id_check:
if (!mlxsw_sp->bus_info->xm_exists)
return;
+ rhashtable_destroy(&router_xm->ltable_ht);
kfree(router_xm);
}