root/drivers/net/ethernet/mellanox/mlx5/core/en/mod_hdr.c
// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
// Copyright (c) 2020 Mellanox Technologies

#include <linux/jhash.h>
#include "mod_hdr.h"

#define MLX5_MH_ACT_SZ MLX5_UN_SZ_BYTES(set_add_copy_action_in_auto)

struct mod_hdr_key {
        int num_actions;
        void *actions;
};

struct mlx5e_mod_hdr_handle {
        /* a node of a hash table which keeps all the mod_hdr entries */
        struct hlist_node mod_hdr_hlist;

        struct mod_hdr_key key;

        struct mlx5_modify_hdr *modify_hdr;

        refcount_t refcnt;
        struct completion res_ready;
        int compl_result;
};

static u32 hash_mod_hdr_info(struct mod_hdr_key *key)
{
        return jhash(key->actions,
                     key->num_actions * MLX5_MH_ACT_SZ, 0);
}

static int cmp_mod_hdr_info(struct mod_hdr_key *a, struct mod_hdr_key *b)
{
        if (a->num_actions != b->num_actions)
                return 1;

        return memcmp(a->actions, b->actions,
                      a->num_actions * MLX5_MH_ACT_SZ);
}

void mlx5e_mod_hdr_tbl_init(struct mod_hdr_tbl *tbl)
{
        mutex_init(&tbl->lock);
        hash_init(tbl->hlist);
}

void mlx5e_mod_hdr_tbl_destroy(struct mod_hdr_tbl *tbl)
{
        WARN_ON(!hash_empty(tbl->hlist));
        mutex_destroy(&tbl->lock);
}

static struct mlx5e_mod_hdr_handle *mod_hdr_get(struct mod_hdr_tbl *tbl,
                                                struct mod_hdr_key *key,
                                                u32 hash_key)
{
        struct mlx5e_mod_hdr_handle *mh, *found = NULL;

        hash_for_each_possible(tbl->hlist, mh, mod_hdr_hlist, hash_key) {
                if (!cmp_mod_hdr_info(&mh->key, key)) {
                        refcount_inc(&mh->refcnt);
                        found = mh;
                        break;
                }
        }

        return found;
}

struct mlx5e_mod_hdr_handle *
mlx5e_mod_hdr_attach(struct mlx5_core_dev *mdev,
                     struct mod_hdr_tbl *tbl,
                     enum mlx5_flow_namespace_type namespace,
                     struct mlx5e_tc_mod_hdr_acts *mod_hdr_acts)
{
        int num_actions, actions_size, err;
        struct mlx5e_mod_hdr_handle *mh;
        struct mod_hdr_key key;
        u32 hash_key;

        num_actions  = mod_hdr_acts->num_actions;
        actions_size = MLX5_MH_ACT_SZ * num_actions;

        key.actions = mod_hdr_acts->actions;
        key.num_actions = num_actions;

        hash_key = hash_mod_hdr_info(&key);

        mutex_lock(&tbl->lock);
        mh = mod_hdr_get(tbl, &key, hash_key);
        if (mh) {
                mutex_unlock(&tbl->lock);
                wait_for_completion(&mh->res_ready);

                if (mh->compl_result < 0) {
                        err = -EREMOTEIO;
                        goto attach_header_err;
                }
                goto attach_header;
        }

        mh = kzalloc(sizeof(*mh) + actions_size, GFP_KERNEL);
        if (!mh) {
                mutex_unlock(&tbl->lock);
                return ERR_PTR(-ENOMEM);
        }

        mh->key.actions = (void *)mh + sizeof(*mh);
        memcpy(mh->key.actions, key.actions, actions_size);
        mh->key.num_actions = num_actions;
        refcount_set(&mh->refcnt, 1);
        init_completion(&mh->res_ready);

        hash_add(tbl->hlist, &mh->mod_hdr_hlist, hash_key);
        mutex_unlock(&tbl->lock);

        mh->modify_hdr = mlx5_modify_header_alloc(mdev, namespace,
                                                  mh->key.num_actions,
                                                  mh->key.actions);
        if (IS_ERR(mh->modify_hdr)) {
                err = PTR_ERR(mh->modify_hdr);
                mh->compl_result = err;
                goto alloc_header_err;
        }
        mh->compl_result = 1;
        complete_all(&mh->res_ready);

attach_header:
        return mh;

alloc_header_err:
        complete_all(&mh->res_ready);
attach_header_err:
        mlx5e_mod_hdr_detach(mdev, tbl, mh);
        return ERR_PTR(err);
}

void mlx5e_mod_hdr_detach(struct mlx5_core_dev *mdev,
                          struct mod_hdr_tbl *tbl,
                          struct mlx5e_mod_hdr_handle *mh)
{
        if (!refcount_dec_and_mutex_lock(&mh->refcnt, &tbl->lock))
                return;
        hash_del(&mh->mod_hdr_hlist);
        mutex_unlock(&tbl->lock);

        if (mh->compl_result > 0)
                mlx5_modify_header_dealloc(mdev, mh->modify_hdr);

        kfree(mh);
}

struct mlx5_modify_hdr *mlx5e_mod_hdr_get(struct mlx5e_mod_hdr_handle *mh)
{
        return mh->modify_hdr;
}

char *
mlx5e_mod_hdr_alloc(struct mlx5_core_dev *mdev, int namespace,
                    struct mlx5e_tc_mod_hdr_acts *mod_hdr_acts)
{
        int new_num_actions, max_hw_actions;
        size_t new_sz, old_sz;
        void *ret;

        if (mod_hdr_acts->num_actions < mod_hdr_acts->max_actions)
                goto out;

        max_hw_actions = mlx5e_mod_hdr_max_actions(mdev, namespace);
        new_num_actions = min(max_hw_actions,
                              mod_hdr_acts->actions ?
                              mod_hdr_acts->max_actions * 2 : 1);
        if (mod_hdr_acts->max_actions == new_num_actions)
                return ERR_PTR(-ENOSPC);

        new_sz = MLX5_MH_ACT_SZ * new_num_actions;
        old_sz = mod_hdr_acts->max_actions * MLX5_MH_ACT_SZ;

        if (mod_hdr_acts->is_static) {
                ret = kzalloc(new_sz, GFP_KERNEL);
                if (ret) {
                        memcpy(ret, mod_hdr_acts->actions, old_sz);
                        mod_hdr_acts->is_static = false;
                }
        } else {
                ret = krealloc(mod_hdr_acts->actions, new_sz, GFP_KERNEL);
                if (ret)
                        memset(ret + old_sz, 0, new_sz - old_sz);
        }
        if (!ret)
                return ERR_PTR(-ENOMEM);

        mod_hdr_acts->actions = ret;
        mod_hdr_acts->max_actions = new_num_actions;

out:
        return mod_hdr_acts->actions + (mod_hdr_acts->num_actions * MLX5_MH_ACT_SZ);
}

void
mlx5e_mod_hdr_dealloc(struct mlx5e_tc_mod_hdr_acts *mod_hdr_acts)
{
        if (!mod_hdr_acts->is_static)
                kfree(mod_hdr_acts->actions);

        mod_hdr_acts->actions = NULL;
        mod_hdr_acts->num_actions = 0;
        mod_hdr_acts->max_actions = 0;
}

char *
mlx5e_mod_hdr_get_item(struct mlx5e_tc_mod_hdr_acts *mod_hdr_acts, int pos)
{
        return mod_hdr_acts->actions + (pos * MLX5_MH_ACT_SZ);
}