root/drivers/net/ethernet/mellanox/mlx5/core/steering/sws/dr_table.c
// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
/* Copyright (c) 2019 Mellanox Technologies. */

#include "dr_types.h"

static int dr_table_set_miss_action_nic(struct mlx5dr_domain *dmn,
                                        struct mlx5dr_table_rx_tx *nic_tbl,
                                        struct mlx5dr_action *action)
{
        struct mlx5dr_matcher_rx_tx *last_nic_matcher = NULL;
        struct mlx5dr_htbl_connect_info info;
        struct mlx5dr_ste_htbl *last_htbl;
        struct mlx5dr_icm_chunk *chunk;
        int ret;

        if (!list_empty(&nic_tbl->nic_matcher_list))
                last_nic_matcher = list_last_entry(&nic_tbl->nic_matcher_list,
                                                   struct mlx5dr_matcher_rx_tx,
                                                   list_node);

        if (last_nic_matcher)
                last_htbl = last_nic_matcher->e_anchor;
        else
                last_htbl = nic_tbl->s_anchor;

        if (action) {
                chunk = nic_tbl->nic_dmn->type == DR_DOMAIN_NIC_TYPE_RX ?
                        action->dest_tbl->tbl->rx.s_anchor->chunk :
                        action->dest_tbl->tbl->tx.s_anchor->chunk;
                nic_tbl->default_icm_addr = mlx5dr_icm_pool_get_chunk_icm_addr(chunk);
        } else {
                nic_tbl->default_icm_addr = nic_tbl->nic_dmn->default_icm_addr;
        }

        info.type = CONNECT_MISS;
        info.miss_icm_addr = nic_tbl->default_icm_addr;

        ret = mlx5dr_ste_htbl_init_and_postsend(dmn, nic_tbl->nic_dmn,
                                                last_htbl, &info, true);
        if (ret)
                mlx5dr_dbg(dmn, "Failed to set NIC RX/TX miss action, ret %d\n", ret);

        return ret;
}

int mlx5dr_table_set_miss_action(struct mlx5dr_table *tbl,
                                 struct mlx5dr_action *action)
{
        int ret = -EOPNOTSUPP;

        if (action && action->action_type != DR_ACTION_TYP_FT)
                return -EOPNOTSUPP;

        mlx5dr_domain_lock(tbl->dmn);

        if (tbl->dmn->type == MLX5DR_DOMAIN_TYPE_NIC_RX ||
            tbl->dmn->type == MLX5DR_DOMAIN_TYPE_FDB) {
                ret = dr_table_set_miss_action_nic(tbl->dmn, &tbl->rx, action);
                if (ret)
                        goto out;
        }

        if (tbl->dmn->type == MLX5DR_DOMAIN_TYPE_NIC_TX ||
            tbl->dmn->type == MLX5DR_DOMAIN_TYPE_FDB) {
                ret = dr_table_set_miss_action_nic(tbl->dmn, &tbl->tx, action);
                if (ret)
                        goto out;
        }

        if (ret)
                goto out;

        /* Release old action */
        if (tbl->miss_action)
                refcount_dec(&tbl->miss_action->refcount);

        /* Set new miss action */
        tbl->miss_action = action;
        if (tbl->miss_action)
                refcount_inc(&action->refcount);

out:
        mlx5dr_domain_unlock(tbl->dmn);
        return ret;
}

static void dr_table_uninit_nic(struct mlx5dr_table_rx_tx *nic_tbl)
{
        mlx5dr_htbl_put(nic_tbl->s_anchor);
}

static void dr_table_uninit_fdb(struct mlx5dr_table *tbl)
{
        dr_table_uninit_nic(&tbl->rx);
        dr_table_uninit_nic(&tbl->tx);
}

static void dr_table_uninit(struct mlx5dr_table *tbl)
{
        mlx5dr_domain_lock(tbl->dmn);

        switch (tbl->dmn->type) {
        case MLX5DR_DOMAIN_TYPE_NIC_RX:
                dr_table_uninit_nic(&tbl->rx);
                break;
        case MLX5DR_DOMAIN_TYPE_NIC_TX:
                dr_table_uninit_nic(&tbl->tx);
                break;
        case MLX5DR_DOMAIN_TYPE_FDB:
                dr_table_uninit_fdb(tbl);
                break;
        default:
                WARN_ON(true);
                break;
        }

        mlx5dr_domain_unlock(tbl->dmn);
}

static int dr_table_init_nic(struct mlx5dr_domain *dmn,
                             struct mlx5dr_table_rx_tx *nic_tbl)
{
        struct mlx5dr_domain_rx_tx *nic_dmn = nic_tbl->nic_dmn;
        struct mlx5dr_htbl_connect_info info;
        int ret;

        INIT_LIST_HEAD(&nic_tbl->nic_matcher_list);

        nic_tbl->default_icm_addr = nic_dmn->default_icm_addr;

        nic_tbl->s_anchor = mlx5dr_ste_htbl_alloc(dmn->ste_icm_pool,
                                                  DR_CHUNK_SIZE_1,
                                                  MLX5DR_STE_LU_TYPE_DONT_CARE,
                                                  0);
        if (!nic_tbl->s_anchor) {
                mlx5dr_err(dmn, "Failed allocating htbl\n");
                return -ENOMEM;
        }

        info.type = CONNECT_MISS;
        info.miss_icm_addr = nic_dmn->default_icm_addr;
        ret = mlx5dr_ste_htbl_init_and_postsend(dmn, nic_dmn,
                                                nic_tbl->s_anchor,
                                                &info, true);
        if (ret) {
                mlx5dr_err(dmn, "Failed int and send htbl\n");
                goto free_s_anchor;
        }

        mlx5dr_htbl_get(nic_tbl->s_anchor);

        return 0;

free_s_anchor:
        mlx5dr_ste_htbl_free(nic_tbl->s_anchor);
        return ret;
}

static int dr_table_init_fdb(struct mlx5dr_table *tbl)
{
        int ret;

        ret = dr_table_init_nic(tbl->dmn, &tbl->rx);
        if (ret)
                return ret;

        ret = dr_table_init_nic(tbl->dmn, &tbl->tx);
        if (ret)
                goto destroy_rx;

        return 0;

destroy_rx:
        dr_table_uninit_nic(&tbl->rx);
        return ret;
}

static int dr_table_init(struct mlx5dr_table *tbl)
{
        int ret = 0;

        INIT_LIST_HEAD(&tbl->matcher_list);

        mlx5dr_domain_lock(tbl->dmn);

        switch (tbl->dmn->type) {
        case MLX5DR_DOMAIN_TYPE_NIC_RX:
                tbl->table_type = MLX5_FLOW_TABLE_TYPE_NIC_RX;
                tbl->rx.nic_dmn = &tbl->dmn->info.rx;
                ret = dr_table_init_nic(tbl->dmn, &tbl->rx);
                break;
        case MLX5DR_DOMAIN_TYPE_NIC_TX:
                tbl->table_type = MLX5_FLOW_TABLE_TYPE_NIC_TX;
                tbl->tx.nic_dmn = &tbl->dmn->info.tx;
                ret = dr_table_init_nic(tbl->dmn, &tbl->tx);
                break;
        case MLX5DR_DOMAIN_TYPE_FDB:
                tbl->table_type = MLX5_FLOW_TABLE_TYPE_FDB;
                tbl->rx.nic_dmn = &tbl->dmn->info.rx;
                tbl->tx.nic_dmn = &tbl->dmn->info.tx;
                ret = dr_table_init_fdb(tbl);
                break;
        default:
                WARN_ON(true);
                break;
        }

        mlx5dr_domain_unlock(tbl->dmn);

        return ret;
}

static int dr_table_destroy_sw_owned_tbl(struct mlx5dr_table *tbl)
{
        return mlx5dr_cmd_destroy_flow_table(tbl->dmn->mdev,
                                             tbl->table_id,
                                             tbl->table_type);
}

static int dr_table_create_sw_owned_tbl(struct mlx5dr_table *tbl, u16 uid)
{
        bool en_encap = !!(tbl->flags & MLX5_FLOW_TABLE_TUNNEL_EN_REFORMAT);
        bool en_decap = !!(tbl->flags & MLX5_FLOW_TABLE_TUNNEL_EN_DECAP);
        struct mlx5dr_cmd_create_flow_table_attr ft_attr = {};
        u64 icm_addr_rx = 0;
        u64 icm_addr_tx = 0;
        int ret;

        if (tbl->rx.s_anchor)
                icm_addr_rx = mlx5dr_icm_pool_get_chunk_icm_addr(tbl->rx.s_anchor->chunk);

        if (tbl->tx.s_anchor)
                icm_addr_tx = mlx5dr_icm_pool_get_chunk_icm_addr(tbl->tx.s_anchor->chunk);

        ft_attr.table_type = tbl->table_type;
        ft_attr.icm_addr_rx = icm_addr_rx;
        ft_attr.icm_addr_tx = icm_addr_tx;
        ft_attr.level = tbl->dmn->info.caps.max_ft_level - 1;
        ft_attr.sw_owner = true;
        ft_attr.decap_en = en_decap;
        ft_attr.reformat_en = en_encap;
        ft_attr.uid = uid;

        ret = mlx5dr_cmd_create_flow_table(tbl->dmn->mdev, &ft_attr,
                                           NULL, &tbl->table_id);

        return ret;
}

struct mlx5dr_table *mlx5dr_table_create(struct mlx5dr_domain *dmn, u32 level,
                                         u32 flags, u16 uid)
{
        struct mlx5dr_table *tbl;
        int ret;

        refcount_inc(&dmn->refcount);

        tbl = kzalloc_obj(*tbl);
        if (!tbl)
                goto dec_ref;

        tbl->dmn = dmn;
        tbl->level = level;
        tbl->flags = flags;
        refcount_set(&tbl->refcount, 1);

        ret = dr_table_init(tbl);
        if (ret)
                goto free_tbl;

        ret = dr_table_create_sw_owned_tbl(tbl, uid);
        if (ret)
                goto uninit_tbl;

        INIT_LIST_HEAD(&tbl->dbg_node);
        mlx5dr_dbg_tbl_add(tbl);
        return tbl;

uninit_tbl:
        dr_table_uninit(tbl);
free_tbl:
        kfree(tbl);
dec_ref:
        refcount_dec(&dmn->refcount);
        return NULL;
}

int mlx5dr_table_destroy(struct mlx5dr_table *tbl)
{
        int ret;

        if (WARN_ON_ONCE(refcount_read(&tbl->refcount) > 1))
                return -EBUSY;

        mlx5dr_dbg_tbl_del(tbl);
        ret = dr_table_destroy_sw_owned_tbl(tbl);
        if (ret)
                mlx5dr_err(tbl->dmn, "Failed to destroy sw owned table\n");

        dr_table_uninit(tbl);

        if (tbl->miss_action)
                refcount_dec(&tbl->miss_action->refcount);

        refcount_dec(&tbl->dmn->refcount);
        kfree(tbl);

        return ret;
}

u32 mlx5dr_table_get_id(struct mlx5dr_table *tbl)
{
        return tbl->table_id;
}

struct mlx5dr_table *mlx5dr_table_get_from_fs_ft(struct mlx5_flow_table *ft)
{
        return ft->fs_dr_table.dr_table;
}