root/drivers/net/ethernet/mellanox/mlx5/core/sf/hw_table.c
// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
/* Copyright (c) 2020 Mellanox Technologies Ltd */
#include <linux/mlx5/driver.h>
#include "vhca_event.h"
#include "priv.h"
#include "sf.h"
#include "mlx5_ifc_vhca_event.h"
#include "ecpf.h"
#include "mlx5_core.h"
#include "eswitch.h"
#include "diag/sf_tracepoint.h"
#include "devlink.h"

struct mlx5_sf_hw {
        u32 usr_sfnum;
        u8 allocated: 1;
        u8 pending_delete: 1;
};

struct mlx5_sf_hwc_table {
        struct mlx5_sf_hw *sfs;
        int max_fn;
        u16 start_fn_id;
};

enum mlx5_sf_hwc_index {
        MLX5_SF_HWC_LOCAL,
        MLX5_SF_HWC_EXTERNAL,
        MLX5_SF_HWC_MAX,
};

struct mlx5_sf_hw_table {
        struct mutex table_lock; /* Serializes sf deletion and vhca state change handler. */
        struct mlx5_sf_hwc_table hwc[MLX5_SF_HWC_MAX];
};

static struct mlx5_sf_hwc_table *
mlx5_sf_controller_to_hwc(struct mlx5_core_dev *dev, u32 controller)
{
        int idx = !!controller;

        return &dev->priv.sf_hw_table->hwc[idx];
}

u16 mlx5_sf_sw_to_hw_id(struct mlx5_core_dev *dev, u32 controller, u16 sw_id)
{
        struct mlx5_sf_hwc_table *hwc;

        hwc = mlx5_sf_controller_to_hwc(dev, controller);
        return hwc->start_fn_id + sw_id;
}

static u16 mlx5_sf_hw_to_sw_id(struct mlx5_sf_hwc_table *hwc, u16 hw_id)
{
        return hw_id - hwc->start_fn_id;
}

static struct mlx5_sf_hwc_table *
mlx5_sf_table_fn_to_hwc(struct mlx5_sf_hw_table *table, u16 fn_id)
{
        int i;

        for (i = 0; i < ARRAY_SIZE(table->hwc); i++) {
                if (table->hwc[i].max_fn &&
                    fn_id >= table->hwc[i].start_fn_id &&
                    fn_id < (table->hwc[i].start_fn_id + table->hwc[i].max_fn))
                        return &table->hwc[i];
        }
        return NULL;
}

static int mlx5_sf_hw_table_id_alloc(struct mlx5_core_dev *dev,
                                     struct mlx5_sf_hw_table *table,
                                     u32 controller,
                                     u32 usr_sfnum)
{
        struct mlx5_sf_hwc_table *hwc;
        int free_idx = -1;
        int i;

        hwc = mlx5_sf_controller_to_hwc(dev, controller);
        if (!hwc->sfs)
                return -ENOSPC;

        for (i = 0; i < hwc->max_fn; i++) {
                if (!hwc->sfs[i].allocated && free_idx == -1) {
                        free_idx = i;
                        continue;
                }

                if (hwc->sfs[i].allocated && hwc->sfs[i].usr_sfnum == usr_sfnum)
                        return -EEXIST;
        }

        if (free_idx == -1)
                return -ENOSPC;

        hwc->sfs[free_idx].usr_sfnum = usr_sfnum;
        hwc->sfs[free_idx].allocated = true;
        return free_idx;
}

static void mlx5_sf_hw_table_id_free(struct mlx5_core_dev *dev,
                                     struct mlx5_sf_hw_table *table,
                                     u32 controller, int id)
{
        struct mlx5_sf_hwc_table *hwc;

        hwc = mlx5_sf_controller_to_hwc(dev, controller);
        hwc->sfs[id].allocated = false;
        hwc->sfs[id].pending_delete = false;
}

int mlx5_sf_hw_table_sf_alloc(struct mlx5_core_dev *dev, u32 controller, u32 usr_sfnum)
{
        struct mlx5_sf_hw_table *table = dev->priv.sf_hw_table;
        u16 hw_fn_id;
        int sw_id;
        int err;

        if (!table)
                return -EOPNOTSUPP;

        mutex_lock(&table->table_lock);
        sw_id = mlx5_sf_hw_table_id_alloc(dev, table, controller, usr_sfnum);
        if (sw_id < 0) {
                err = sw_id;
                goto exist_err;
        }

        hw_fn_id = mlx5_sf_sw_to_hw_id(dev, controller, sw_id);
        err = mlx5_cmd_alloc_sf(dev, hw_fn_id);
        if (err)
                goto err;

        err = mlx5_modify_vhca_sw_id(dev, hw_fn_id, usr_sfnum);
        if (err)
                goto vhca_err;

        if (controller) {
                /* If this SF is for external controller, SF manager
                 * needs to arm firmware to receive the events.
                 */
                err = mlx5_vhca_event_arm(dev, hw_fn_id);
                if (err)
                        goto vhca_err;
        }

        trace_mlx5_sf_hwc_alloc(dev, controller, hw_fn_id, usr_sfnum);
        mutex_unlock(&table->table_lock);
        return sw_id;

vhca_err:
        mlx5_cmd_dealloc_sf(dev, hw_fn_id);
err:
        mlx5_sf_hw_table_id_free(dev, table, controller, sw_id);
exist_err:
        mutex_unlock(&table->table_lock);
        return err;
}

void mlx5_sf_hw_table_sf_free(struct mlx5_core_dev *dev, u32 controller, u16 id)
{
        struct mlx5_sf_hw_table *table = dev->priv.sf_hw_table;
        u16 hw_fn_id;

        mutex_lock(&table->table_lock);
        hw_fn_id = mlx5_sf_sw_to_hw_id(dev, controller, id);
        mlx5_cmd_dealloc_sf(dev, hw_fn_id);
        mlx5_sf_hw_table_id_free(dev, table, controller, id);
        mutex_unlock(&table->table_lock);
}

static void mlx5_sf_hw_table_hwc_sf_free(struct mlx5_core_dev *dev,
                                         struct mlx5_sf_hwc_table *hwc, int idx)
{
        mlx5_cmd_dealloc_sf(dev, hwc->start_fn_id + idx);
        hwc->sfs[idx].allocated = false;
        hwc->sfs[idx].pending_delete = false;
        trace_mlx5_sf_hwc_free(dev, hwc->start_fn_id + idx);
}

void mlx5_sf_hw_table_sf_deferred_free(struct mlx5_core_dev *dev, u32 controller, u16 id)
{
        struct mlx5_sf_hw_table *table = dev->priv.sf_hw_table;
        u32 out[MLX5_ST_SZ_DW(query_vhca_state_out)] = {};
        struct mlx5_sf_hwc_table *hwc;
        u16 hw_fn_id;
        u8 state;
        int err;

        hw_fn_id = mlx5_sf_sw_to_hw_id(dev, controller, id);
        hwc = mlx5_sf_controller_to_hwc(dev, controller);
        mutex_lock(&table->table_lock);
        err = mlx5_cmd_query_vhca_state(dev, hw_fn_id, out, sizeof(out));
        if (err)
                goto err;
        state = MLX5_GET(query_vhca_state_out, out, vhca_state_context.vhca_state);
        if (state == MLX5_VHCA_STATE_ALLOCATED) {
                mlx5_cmd_dealloc_sf(dev, hw_fn_id);
                hwc->sfs[id].allocated = false;
        } else {
                hwc->sfs[id].pending_delete = true;
                trace_mlx5_sf_hwc_deferred_free(dev, hw_fn_id);
        }
err:
        mutex_unlock(&table->table_lock);
}

static void mlx5_sf_hw_table_hwc_dealloc_all(struct mlx5_core_dev *dev,
                                             struct mlx5_sf_hwc_table *hwc)
{
        int i;

        for (i = 0; i < hwc->max_fn; i++) {
                if (hwc->sfs[i].allocated)
                        mlx5_sf_hw_table_hwc_sf_free(dev, hwc, i);
        }
}

static void mlx5_sf_hw_table_dealloc_all(struct mlx5_core_dev *dev,
                                         struct mlx5_sf_hw_table *table)
{
        mlx5_sf_hw_table_hwc_dealloc_all(dev,
                                         &table->hwc[MLX5_SF_HWC_EXTERNAL]);
        mlx5_sf_hw_table_hwc_dealloc_all(dev, &table->hwc[MLX5_SF_HWC_LOCAL]);
}

static int mlx5_sf_hw_table_hwc_init(struct mlx5_sf_hwc_table *hwc, u16 max_fn, u16 base_id)
{
        struct mlx5_sf_hw *sfs;

        if (!max_fn)
                return 0;

        sfs = kzalloc_objs(*sfs, max_fn);
        if (!sfs)
                return -ENOMEM;

        hwc->sfs = sfs;
        hwc->max_fn = max_fn;
        hwc->start_fn_id = base_id;
        return 0;
}

static void mlx5_sf_hw_table_hwc_cleanup(struct mlx5_sf_hwc_table *hwc)
{
        kfree(hwc->sfs);
}

static void mlx5_sf_hw_table_res_unregister(struct mlx5_core_dev *dev)
{
        devl_resources_unregister(priv_to_devlink(dev));
}

static int mlx5_sf_hw_table_res_register(struct mlx5_core_dev *dev, u16 max_fn,
                                         u16 max_ext_fn)
{
        struct devlink_resource_size_params size_params;
        struct devlink *devlink = priv_to_devlink(dev);
        int err;

        devlink_resource_size_params_init(&size_params, max_fn, max_fn, 1,
                                          DEVLINK_RESOURCE_UNIT_ENTRY);
        err = devl_resource_register(devlink, "max_local_SFs", max_fn, MLX5_DL_RES_MAX_LOCAL_SFS,
                                     DEVLINK_RESOURCE_ID_PARENT_TOP, &size_params);
        if (err)
                return err;

        devlink_resource_size_params_init(&size_params, max_ext_fn, max_ext_fn, 1,
                                          DEVLINK_RESOURCE_UNIT_ENTRY);
        return devl_resource_register(devlink, "max_external_SFs", max_ext_fn,
                                      MLX5_DL_RES_MAX_EXTERNAL_SFS, DEVLINK_RESOURCE_ID_PARENT_TOP,
                                      &size_params);
}

int mlx5_sf_hw_table_init(struct mlx5_core_dev *dev)
{
        struct mlx5_sf_hw_table *table;
        u16 max_ext_fn = 0;
        u16 ext_base_id = 0;
        u16 base_id;
        u16 max_fn;
        int err;

        if (!mlx5_vhca_event_supported(dev))
                return 0;

        max_fn = mlx5_sf_max_functions(dev);

        err = mlx5_esw_sf_max_hpf_functions(dev, &max_ext_fn, &ext_base_id);
        if (err)
                return err;

        if (mlx5_sf_hw_table_res_register(dev, max_fn, max_ext_fn))
                mlx5_core_dbg(dev, "failed to register max SFs resources");

        if (!max_fn && !max_ext_fn)
                return 0;

        table = kzalloc_obj(*table);
        if (!table) {
                err = -ENOMEM;
                goto alloc_err;
        }

        mutex_init(&table->table_lock);
        dev->priv.sf_hw_table = table;

        base_id = mlx5_sf_start_function_id(dev);
        err = mlx5_sf_hw_table_hwc_init(&table->hwc[MLX5_SF_HWC_LOCAL], max_fn, base_id);
        if (err)
                goto table_err;

        err = mlx5_sf_hw_table_hwc_init(&table->hwc[MLX5_SF_HWC_EXTERNAL],
                                        max_ext_fn, ext_base_id);
        if (err)
                goto ext_err;

        mlx5_core_dbg(dev, "SF HW table: max sfs = %d, ext sfs = %d\n", max_fn, max_ext_fn);
        return 0;

ext_err:
        mlx5_sf_hw_table_hwc_cleanup(&table->hwc[MLX5_SF_HWC_LOCAL]);
table_err:
        mutex_destroy(&table->table_lock);
        kfree(table);
alloc_err:
        mlx5_sf_hw_table_res_unregister(dev);
        return err;
}

void mlx5_sf_hw_table_cleanup(struct mlx5_core_dev *dev)
{
        struct mlx5_sf_hw_table *table = dev->priv.sf_hw_table;

        if (!table)
                goto res_unregister;

        mlx5_sf_hw_table_hwc_cleanup(&table->hwc[MLX5_SF_HWC_EXTERNAL]);
        mlx5_sf_hw_table_hwc_cleanup(&table->hwc[MLX5_SF_HWC_LOCAL]);
        mutex_destroy(&table->table_lock);
        kfree(table);
        dev->priv.sf_hw_table = NULL;
res_unregister:
        mlx5_sf_hw_table_res_unregister(dev);
}

static int mlx5_sf_hw_vhca_event(struct notifier_block *nb, unsigned long opcode, void *data)
{
        struct mlx5_core_dev *dev = container_of(nb, struct mlx5_core_dev,
                                                 priv.sf_hw_table_vhca_nb);
        struct mlx5_sf_hw_table *table = dev->priv.sf_hw_table;
        const struct mlx5_vhca_state_event *event = data;
        struct mlx5_sf_hwc_table *hwc;
        struct mlx5_sf_hw *sf_hw;
        u16 sw_id;

        if (!table || event->new_vhca_state != MLX5_VHCA_STATE_ALLOCATED)
                return 0;

        hwc = mlx5_sf_table_fn_to_hwc(table, event->function_id);
        if (!hwc)
                return 0;

        sw_id = mlx5_sf_hw_to_sw_id(hwc, event->function_id);
        sf_hw = &hwc->sfs[sw_id];

        mutex_lock(&table->table_lock);
        /* SF driver notified through firmware that SF is finally detached.
         * Hence recycle the sf hardware id for reuse.
         */
        if (sf_hw->allocated && sf_hw->pending_delete)
                mlx5_sf_hw_table_hwc_sf_free(dev, hwc, sw_id);
        mutex_unlock(&table->table_lock);
        return 0;
}

int mlx5_sf_hw_notifier_init(struct mlx5_core_dev *dev)
{
        if (mlx5_core_is_sf(dev))
                return 0;

        dev->priv.sf_hw_table_vhca_nb.notifier_call = mlx5_sf_hw_vhca_event;
        return mlx5_vhca_event_notifier_register(dev,
                                                 &dev->priv.sf_hw_table_vhca_nb);
}

void mlx5_sf_hw_notifier_cleanup(struct mlx5_core_dev *dev)
{
        if (mlx5_core_is_sf(dev))
                return;

        mlx5_vhca_event_notifier_unregister(dev,
                                            &dev->priv.sf_hw_table_vhca_nb);
}

void mlx5_sf_hw_table_destroy(struct mlx5_core_dev *dev)
{
        struct mlx5_sf_hw_table *table = dev->priv.sf_hw_table;

        if (!table)
                return;

        /* Dealloc SFs whose firmware event has been missed. */
        mlx5_sf_hw_table_dealloc_all(dev, table);
}

bool mlx5_sf_hw_table_supported(const struct mlx5_core_dev *dev)
{
        return !!dev->priv.sf_hw_table;
}