blob: 966a20f3bc0d35bbc470f8a87dc6417b90f3d0a5 [file] [log] [blame]
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
/* Copyright (c) 2020 Mellanox Technologies. All rights reserved */
#include <linux/kernel.h>
#include <linux/types.h>
#include "spectrum.h"
#include "core.h"
#include "reg.h"
#include "spectrum_router.h"
#define MLXSW_SP_ROUTER_XM_M_VAL 16
static const u8 mlxsw_sp_router_xm_m_val[] = {
[MLXSW_SP_L3_PROTO_IPV4] = MLXSW_SP_ROUTER_XM_M_VAL,
[MLXSW_SP_L3_PROTO_IPV6] = 0, /* Currently unused. */
};
struct mlxsw_sp_router_xm {
bool ipv4_supported;
bool ipv6_supported;
unsigned int entries_size;
};
struct mlxsw_sp_router_xm_fib_entry {
bool committed;
};
#define MLXSW_SP_ROUTE_LL_XM_ENTRIES_MAX \
(MLXSW_REG_XMDR_TRANS_LEN / MLXSW_REG_XMDR_C_LT_ROUTE_V4_LEN)
struct mlxsw_sp_fib_entry_op_ctx_xm {
bool initialized;
char xmdr_pl[MLXSW_REG_XMDR_LEN];
unsigned int trans_offset; /* Offset of the current command within one
* transaction of XMDR register.
*/
unsigned int trans_item_len; /* The current command length. This is used
* to advance 'trans_offset' when the next
* command is appended.
*/
unsigned int entries_count;
struct mlxsw_sp_router_xm_fib_entry *entries[MLXSW_SP_ROUTE_LL_XM_ENTRIES_MAX];
};
static int mlxsw_sp_router_ll_xm_init(struct mlxsw_sp *mlxsw_sp, u16 vr_id,
enum mlxsw_sp_l3proto proto)
{
char rxlte_pl[MLXSW_REG_RXLTE_LEN];
mlxsw_reg_rxlte_pack(rxlte_pl, vr_id,
(enum mlxsw_reg_rxlte_protocol) proto, true);
return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(rxlte), rxlte_pl);
}
static int mlxsw_sp_router_ll_xm_ralta_write(struct mlxsw_sp *mlxsw_sp, char *xralta_pl)
{
return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(xralta), xralta_pl);
}
static int mlxsw_sp_router_ll_xm_ralst_write(struct mlxsw_sp *mlxsw_sp, char *xralst_pl)
{
return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(xralst), xralst_pl);
}
static int mlxsw_sp_router_ll_xm_raltb_write(struct mlxsw_sp *mlxsw_sp, char *xraltb_pl)
{
return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(xraltb), xraltb_pl);
}
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)
{
if (op_ctx->initialized)
return;
op_ctx->initialized = true;
mlxsw_reg_xmdr_pack(op_ctx_xm->xmdr_pl, true);
op_ctx_xm->trans_offset = 0;
op_ctx_xm->entries_count = 0;
}
static void mlxsw_sp_router_ll_xm_fib_entry_pack(struct mlxsw_sp_fib_entry_op_ctx *op_ctx,
enum mlxsw_sp_l3proto proto,
enum mlxsw_sp_fib_entry_op op,
u16 virtual_router, u8 prefix_len,
unsigned char *addr,
struct mlxsw_sp_fib_entry_priv *priv)
{
struct mlxsw_sp_fib_entry_op_ctx_xm *op_ctx_xm = (void *) op_ctx->ll_priv;
struct mlxsw_sp_router_xm_fib_entry *fib_entry = (void *) priv->priv;
enum mlxsw_reg_xmdr_c_ltr_op xmdr_c_ltr_op;
unsigned int len;
mlxsw_sp_router_ll_xm_op_ctx_check_init(op_ctx, op_ctx_xm);
switch (op) {
case MLXSW_SP_FIB_ENTRY_OP_WRITE:
xmdr_c_ltr_op = MLXSW_REG_XMDR_C_LTR_OP_WRITE;
break;
case MLXSW_SP_FIB_ENTRY_OP_UPDATE:
xmdr_c_ltr_op = MLXSW_REG_XMDR_C_LTR_OP_UPDATE;
break;
case MLXSW_SP_FIB_ENTRY_OP_DELETE:
xmdr_c_ltr_op = MLXSW_REG_XMDR_C_LTR_OP_DELETE;
break;
default:
WARN_ON_ONCE(1);
return;
}
switch (proto) {
case MLXSW_SP_L3_PROTO_IPV4:
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);
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);
break;
default:
WARN_ON_ONCE(1);
return;
}
if (!op_ctx_xm->trans_offset)
op_ctx_xm->trans_item_len = len;
else
WARN_ON_ONCE(op_ctx_xm->trans_item_len != len);
op_ctx_xm->entries[op_ctx_xm->entries_count] = fib_entry;
}
static void
mlxsw_sp_router_ll_xm_fib_entry_act_remote_pack(struct mlxsw_sp_fib_entry_op_ctx *op_ctx,
enum mlxsw_reg_ralue_trap_action trap_action,
u16 trap_id, u32 adjacency_index, u16 ecmp_size)
{
struct mlxsw_sp_fib_entry_op_ctx_xm *op_ctx_xm = (void *) op_ctx->ll_priv;
mlxsw_reg_xmdr_c_ltr_act_remote_pack(op_ctx_xm->xmdr_pl, op_ctx_xm->trans_offset,
trap_action, trap_id, adjacency_index, ecmp_size);
}
static void
mlxsw_sp_router_ll_xm_fib_entry_act_local_pack(struct mlxsw_sp_fib_entry_op_ctx *op_ctx,
enum mlxsw_reg_ralue_trap_action trap_action,
u16 trap_id, u16 local_erif)
{
struct mlxsw_sp_fib_entry_op_ctx_xm *op_ctx_xm = (void *) op_ctx->ll_priv;
mlxsw_reg_xmdr_c_ltr_act_local_pack(op_ctx_xm->xmdr_pl, op_ctx_xm->trans_offset,
trap_action, trap_id, local_erif);
}
static void
mlxsw_sp_router_ll_xm_fib_entry_act_ip2me_pack(struct mlxsw_sp_fib_entry_op_ctx *op_ctx)
{
struct mlxsw_sp_fib_entry_op_ctx_xm *op_ctx_xm = (void *) op_ctx->ll_priv;
mlxsw_reg_xmdr_c_ltr_act_ip2me_pack(op_ctx_xm->xmdr_pl, op_ctx_xm->trans_offset);
}
static void
mlxsw_sp_router_ll_xm_fib_entry_act_ip2me_tun_pack(struct mlxsw_sp_fib_entry_op_ctx *op_ctx,
u32 tunnel_ptr)
{
struct mlxsw_sp_fib_entry_op_ctx_xm *op_ctx_xm = (void *) op_ctx->ll_priv;
mlxsw_reg_xmdr_c_ltr_act_ip2me_tun_pack(op_ctx_xm->xmdr_pl, op_ctx_xm->trans_offset,
tunnel_ptr);
}
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)
{
struct mlxsw_sp_fib_entry_op_ctx_xm *op_ctx_xm = (void *) op_ctx->ll_priv;
struct mlxsw_sp_router_xm_fib_entry *fib_entry;
u8 num_rec;
int err;
int i;
op_ctx_xm->trans_offset += op_ctx_xm->trans_item_len;
op_ctx_xm->entries_count++;
/* Check if bulking is possible and there is still room for another
* FIB entry record. The size of 'trans_item_len' is either size of IPv4
* command or size of IPv6 command. Not possible to mix those in a
* single XMDR write.
*/
if (op_ctx->bulk_ok &&
op_ctx_xm->trans_offset + op_ctx_xm->trans_item_len <= MLXSW_REG_XMDR_TRANS_LEN) {
if (postponed_for_bulk)
*postponed_for_bulk = true;
return 0;
}
err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(xmdr), op_ctx_xm->xmdr_pl);
if (err)
goto out;
num_rec = mlxsw_reg_xmdr_num_rec_get(op_ctx_xm->xmdr_pl);
if (num_rec > op_ctx_xm->entries_count) {
dev_err(mlxsw_sp->bus_info->dev, "Invalid XMDR number of records\n");
err = -EIO;
goto out;
}
for (i = 0; i < num_rec; i++) {
if (!mlxsw_reg_xmdr_reply_vect_get(op_ctx_xm->xmdr_pl, i)) {
dev_err(mlxsw_sp->bus_info->dev, "Command send over XMDR failed\n");
err = -EIO;
goto out;
} else {
fib_entry = op_ctx_xm->entries[i];
fib_entry->committed = true;
}
}
out:
/* Next pack call is going to do reinitialization */
op_ctx->initialized = false;
return err;
}
static bool mlxsw_sp_router_ll_xm_fib_entry_is_committed(struct mlxsw_sp_fib_entry_priv *priv)
{
struct mlxsw_sp_router_xm_fib_entry *fib_entry = (void *) priv->priv;
return fib_entry->committed;
}
const struct mlxsw_sp_router_ll_ops mlxsw_sp_router_ll_xm_ops = {
.init = mlxsw_sp_router_ll_xm_init,
.ralta_write = mlxsw_sp_router_ll_xm_ralta_write,
.ralst_write = mlxsw_sp_router_ll_xm_ralst_write,
.raltb_write = mlxsw_sp_router_ll_xm_raltb_write,
.fib_entry_op_ctx_size = sizeof(struct mlxsw_sp_fib_entry_op_ctx_xm),
.fib_entry_priv_size = sizeof(struct mlxsw_sp_router_xm_fib_entry),
.fib_entry_pack = mlxsw_sp_router_ll_xm_fib_entry_pack,
.fib_entry_act_remote_pack = mlxsw_sp_router_ll_xm_fib_entry_act_remote_pack,
.fib_entry_act_local_pack = mlxsw_sp_router_ll_xm_fib_entry_act_local_pack,
.fib_entry_act_ip2me_pack = mlxsw_sp_router_ll_xm_fib_entry_act_ip2me_pack,
.fib_entry_act_ip2me_tun_pack = mlxsw_sp_router_ll_xm_fib_entry_act_ip2me_tun_pack,
.fib_entry_commit = mlxsw_sp_router_ll_xm_fib_entry_commit,
.fib_entry_is_committed = mlxsw_sp_router_ll_xm_fib_entry_is_committed,
};
#define MLXSW_SP_ROUTER_XM_MINDEX_SIZE (64 * 1024)
int mlxsw_sp_router_xm_init(struct mlxsw_sp *mlxsw_sp)
{
struct mlxsw_sp_router_xm *router_xm;
char rxltm_pl[MLXSW_REG_RXLTM_LEN];
char xltq_pl[MLXSW_REG_XLTQ_LEN];
u32 mindex_size;
u16 device_id;
int err;
if (!mlxsw_sp->bus_info->xm_exists)
return 0;
router_xm = kzalloc(sizeof(*router_xm), GFP_KERNEL);
if (!router_xm)
return -ENOMEM;
mlxsw_reg_xltq_pack(xltq_pl);
err = mlxsw_reg_query(mlxsw_sp->core, MLXSW_REG(xltq), xltq_pl);
if (err)
goto err_xltq_query;
mlxsw_reg_xltq_unpack(xltq_pl, &device_id, &router_xm->ipv4_supported,
&router_xm->ipv6_supported, &router_xm->entries_size, &mindex_size);
if (device_id != MLXSW_REG_XLTQ_XM_DEVICE_ID_XLT) {
dev_err(mlxsw_sp->bus_info->dev, "Invalid XM device id\n");
err = -EINVAL;
goto err_device_id_check;
}
if (mindex_size != MLXSW_SP_ROUTER_XM_MINDEX_SIZE) {
dev_err(mlxsw_sp->bus_info->dev, "Unexpected M-index size\n");
err = -EINVAL;
goto err_mindex_size_check;
}
mlxsw_reg_rxltm_pack(rxltm_pl, mlxsw_sp_router_xm_m_val[MLXSW_SP_L3_PROTO_IPV4],
mlxsw_sp_router_xm_m_val[MLXSW_SP_L3_PROTO_IPV6]);
err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(rxltm), rxltm_pl);
if (err)
goto err_rxltm_write;
mlxsw_sp->router->xm = router_xm;
return 0;
err_rxltm_write:
err_mindex_size_check:
err_device_id_check:
err_xltq_query:
kfree(router_xm);
return err;
}
void mlxsw_sp_router_xm_fini(struct mlxsw_sp *mlxsw_sp)
{
struct mlxsw_sp_router_xm *router_xm = mlxsw_sp->router->xm;
if (!mlxsw_sp->bus_info->xm_exists)
return;
kfree(router_xm);
}