root/drivers/net/ethernet/mellanox/mlx5/core/steering/sws/dr_arg.c
// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
// Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.

#include "dr_types.h"

#define DR_ICM_MODIFY_HDR_GRANULARITY_4K 12

/* modify-header arg pool */
enum dr_arg_chunk_size {
        DR_ARG_CHUNK_SIZE_1,
        DR_ARG_CHUNK_SIZE_MIN = DR_ARG_CHUNK_SIZE_1, /* keep updated when changing */
        DR_ARG_CHUNK_SIZE_2,
        DR_ARG_CHUNK_SIZE_3,
        DR_ARG_CHUNK_SIZE_4,
        DR_ARG_CHUNK_SIZE_MAX,
};

/* argument pool area */
struct dr_arg_pool {
        enum dr_arg_chunk_size log_chunk_size;
        struct mlx5dr_domain *dmn;
        struct list_head free_list;
        struct mutex mutex; /* protect arg pool */
};

struct mlx5dr_arg_mgr {
        struct mlx5dr_domain *dmn;
        struct dr_arg_pool *pools[DR_ARG_CHUNK_SIZE_MAX];
};

static int dr_arg_pool_alloc_objs(struct dr_arg_pool *pool)
{
        struct mlx5dr_arg_obj *arg_obj, *tmp_arg;
        struct list_head cur_list;
        u16 object_range;
        int num_of_objects;
        u32 obj_id = 0;
        int i, ret;

        INIT_LIST_HEAD(&cur_list);

        object_range =
                pool->dmn->info.caps.log_header_modify_argument_granularity;

        object_range =
                max_t(u32, pool->dmn->info.caps.log_header_modify_argument_granularity,
                      DR_ICM_MODIFY_HDR_GRANULARITY_4K);
        object_range =
                min_t(u32, pool->dmn->info.caps.log_header_modify_argument_max_alloc,
                      object_range);

        if (pool->log_chunk_size > object_range) {
                mlx5dr_err(pool->dmn, "Required chunk size (%d) is not supported\n",
                           pool->log_chunk_size);
                return -ENOMEM;
        }

        num_of_objects = (1 << (object_range - pool->log_chunk_size));
        /* Only one devx object per range */
        ret = mlx5dr_cmd_create_modify_header_arg(pool->dmn->mdev,
                                                  object_range,
                                                  pool->dmn->pdn,
                                                  &obj_id);
        if (ret) {
                mlx5dr_err(pool->dmn, "failed allocating object with range: %d:\n",
                           object_range);
                return -EAGAIN;
        }

        for (i = 0; i < num_of_objects; i++) {
                arg_obj = kzalloc_obj(*arg_obj);
                if (!arg_obj) {
                        ret = -ENOMEM;
                        goto clean_arg_obj;
                }

                arg_obj->log_chunk_size = pool->log_chunk_size;

                list_add_tail(&arg_obj->list_node, &cur_list);

                arg_obj->obj_id = obj_id;
                arg_obj->obj_offset = i * (1 << pool->log_chunk_size);
        }
        list_splice_tail_init(&cur_list, &pool->free_list);

        return 0;

clean_arg_obj:
        mlx5dr_cmd_destroy_modify_header_arg(pool->dmn->mdev, obj_id);
        list_for_each_entry_safe(arg_obj, tmp_arg, &cur_list, list_node) {
                list_del(&arg_obj->list_node);
                kfree(arg_obj);
        }
        return ret;
}

static struct mlx5dr_arg_obj *dr_arg_pool_get_arg_obj(struct dr_arg_pool *pool)
{
        struct mlx5dr_arg_obj *arg_obj = NULL;
        int ret;

        mutex_lock(&pool->mutex);
        if (list_empty(&pool->free_list)) {
                ret = dr_arg_pool_alloc_objs(pool);
                if (ret)
                        goto out;
        }

        arg_obj = list_first_entry_or_null(&pool->free_list,
                                           struct mlx5dr_arg_obj,
                                           list_node);
        WARN(!arg_obj, "couldn't get dr arg obj from pool");

        if (arg_obj)
                list_del_init(&arg_obj->list_node);

out:
        mutex_unlock(&pool->mutex);
        return arg_obj;
}

static void dr_arg_pool_put_arg_obj(struct dr_arg_pool *pool,
                                    struct mlx5dr_arg_obj *arg_obj)
{
        mutex_lock(&pool->mutex);
        list_add(&arg_obj->list_node, &pool->free_list);
        mutex_unlock(&pool->mutex);
}

static struct dr_arg_pool *dr_arg_pool_create(struct mlx5dr_domain *dmn,
                                              enum dr_arg_chunk_size chunk_size)
{
        struct dr_arg_pool *pool;

        pool = kzalloc_obj(*pool);
        if (!pool)
                return NULL;

        pool->dmn = dmn;

        INIT_LIST_HEAD(&pool->free_list);
        mutex_init(&pool->mutex);

        pool->log_chunk_size = chunk_size;
        if (dr_arg_pool_alloc_objs(pool))
                goto free_pool;

        return pool;

free_pool:
        kfree(pool);

        return NULL;
}

static void dr_arg_pool_destroy(struct dr_arg_pool *pool)
{
        struct mlx5dr_arg_obj *arg_obj, *tmp_arg;

        list_for_each_entry_safe(arg_obj, tmp_arg, &pool->free_list, list_node) {
                list_del(&arg_obj->list_node);
                if (!arg_obj->obj_offset) /* the first in range */
                        mlx5dr_cmd_destroy_modify_header_arg(pool->dmn->mdev, arg_obj->obj_id);
                kfree(arg_obj);
        }

        mutex_destroy(&pool->mutex);
        kfree(pool);
}

static enum dr_arg_chunk_size dr_arg_get_chunk_size(u16 num_of_actions)
{
        if (num_of_actions <= 8)
                return DR_ARG_CHUNK_SIZE_1;
        if (num_of_actions <= 16)
                return DR_ARG_CHUNK_SIZE_2;
        if (num_of_actions <= 32)
                return DR_ARG_CHUNK_SIZE_3;
        if (num_of_actions <= 64)
                return DR_ARG_CHUNK_SIZE_4;

        return DR_ARG_CHUNK_SIZE_MAX;
}

u32 mlx5dr_arg_get_obj_id(struct mlx5dr_arg_obj *arg_obj)
{
        return (arg_obj->obj_id + arg_obj->obj_offset);
}

struct mlx5dr_arg_obj *mlx5dr_arg_get_obj(struct mlx5dr_arg_mgr *mgr,
                                          u16 num_of_actions,
                                          u8 *data)
{
        u32 size = dr_arg_get_chunk_size(num_of_actions);
        struct mlx5dr_arg_obj *arg_obj;
        int ret;

        if (size >= DR_ARG_CHUNK_SIZE_MAX)
                return NULL;

        arg_obj = dr_arg_pool_get_arg_obj(mgr->pools[size]);
        if (!arg_obj) {
                mlx5dr_err(mgr->dmn, "Failed allocating args object for modify header\n");
                return NULL;
        }

        /* write it into the hw */
        ret = mlx5dr_send_postsend_args(mgr->dmn,
                                        mlx5dr_arg_get_obj_id(arg_obj),
                                        num_of_actions, data);
        if (ret) {
                mlx5dr_err(mgr->dmn, "Failed writing args object\n");
                goto put_obj;
        }

        return arg_obj;

put_obj:
        mlx5dr_arg_put_obj(mgr, arg_obj);
        return NULL;
}

void mlx5dr_arg_put_obj(struct mlx5dr_arg_mgr *mgr,
                        struct mlx5dr_arg_obj *arg_obj)
{
        dr_arg_pool_put_arg_obj(mgr->pools[arg_obj->log_chunk_size], arg_obj);
}

struct mlx5dr_arg_mgr*
mlx5dr_arg_mgr_create(struct mlx5dr_domain *dmn)
{
        struct mlx5dr_arg_mgr *pool_mgr;
        int i;

        if (!mlx5dr_domain_is_support_ptrn_arg(dmn))
                return NULL;

        pool_mgr = kzalloc_obj(*pool_mgr);
        if (!pool_mgr)
                return NULL;

        pool_mgr->dmn = dmn;

        for (i = 0; i < DR_ARG_CHUNK_SIZE_MAX; i++) {
                pool_mgr->pools[i] = dr_arg_pool_create(dmn, i);
                if (!pool_mgr->pools[i])
                        goto clean_pools;
        }

        return pool_mgr;

clean_pools:
        for (i--; i >= 0; i--)
                dr_arg_pool_destroy(pool_mgr->pools[i]);

        kfree(pool_mgr);
        return NULL;
}

void mlx5dr_arg_mgr_destroy(struct mlx5dr_arg_mgr *mgr)
{
        struct dr_arg_pool **pools;
        int i;

        if (!mgr)
                return;

        pools = mgr->pools;
        for (i = 0; i < DR_ARG_CHUNK_SIZE_MAX; i++)
                dr_arg_pool_destroy(pools[i]);

        kfree(mgr);
}