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

#include "fs_core.h"
#include "eswitch.h"

int mlx5_esw_adj_vport_modify(struct mlx5_core_dev *dev, u16 vport,
                              bool connect)
{
        u32 in[MLX5_ST_SZ_DW(modify_vport_state_in)] = {};

        MLX5_SET(modify_vport_state_in, in, opcode,
                 MLX5_CMD_OP_MODIFY_VPORT_STATE);
        MLX5_SET(modify_vport_state_in, in, op_mod,
                 MLX5_VPORT_STATE_OP_MOD_ESW_VPORT);
        MLX5_SET(modify_vport_state_in, in, other_vport, 1);
        MLX5_SET(modify_vport_state_in, in, vport_number, vport);
        MLX5_SET(modify_vport_state_in, in, ingress_connect_valid, 1);
        MLX5_SET(modify_vport_state_in, in, egress_connect_valid, 1);
        MLX5_SET(modify_vport_state_in, in, ingress_connect, connect);
        MLX5_SET(modify_vport_state_in, in, egress_connect, connect);
        MLX5_SET(modify_vport_state_in, in, admin_state, connect);
        return mlx5_cmd_exec_in(dev, modify_vport_state, in);
}

static void mlx5_esw_destroy_esw_vport(struct mlx5_core_dev *dev, u16 vport)
{
        u32 in[MLX5_ST_SZ_DW(destroy_esw_vport_in)] = {};

        MLX5_SET(destroy_esw_vport_in, in, opcode,
                 MLX5_CMD_OPCODE_DESTROY_ESW_VPORT);
        MLX5_SET(destroy_esw_vport_in, in, vport_num, vport);

        mlx5_cmd_exec_in(dev, destroy_esw_vport, in);
}

static int mlx5_esw_create_esw_vport(struct mlx5_core_dev *dev, u16 vhca_id,
                                     u16 *vport_num)
{
        u32 out[MLX5_ST_SZ_DW(create_esw_vport_out)] = {};
        u32 in[MLX5_ST_SZ_DW(create_esw_vport_in)] = {};
        int err;

        MLX5_SET(create_esw_vport_in, in, opcode,
                 MLX5_CMD_OPCODE_CREATE_ESW_VPORT);
        MLX5_SET(create_esw_vport_in, in, managed_vhca_id, vhca_id);

        err = mlx5_cmd_exec(dev, in, sizeof(in), out, sizeof(out));
        if (!err)
                *vport_num = MLX5_GET(create_esw_vport_out, out, vport_num);

        return err;
}

static int mlx5_esw_adj_vport_create(struct mlx5_eswitch *esw, u16 vhca_id,
                                     const void *rid_info_reg)
{
        struct mlx5_vport *vport;
        u16 vport_num;
        int err;

        err = mlx5_esw_create_esw_vport(esw->dev, vhca_id, &vport_num);
        if (err) {
                esw_warn(esw->dev,
                         "Failed to create adjacent vport for vhca_id %d, err %d\n",
                         vhca_id, err);
                return err;
        }

        esw_debug(esw->dev, "Created adjacent vport[%d] %d for vhca_id 0x%x\n",
                  esw->last_vport_idx, vport_num, vhca_id);

        err = mlx5_esw_vport_alloc(esw, esw->last_vport_idx++, vport_num);
        if (err)
                goto destroy_esw_vport;

        xa_set_mark(&esw->vports, vport_num, MLX5_ESW_VPT_VF);
        vport = mlx5_eswitch_get_vport(esw, vport_num);
        vport->adjacent = true;
        vport->vhca_id = vhca_id;

        vport->adj_info.parent_pci_devfn =
                MLX5_GET(function_vhca_rid_info_reg, rid_info_reg,
                         parent_pci_device_function);
        vport->adj_info.function_id =
                MLX5_GET(function_vhca_rid_info_reg, rid_info_reg, function_id);

        mlx5_fs_vport_egress_acl_ns_add(esw->dev->priv.steering, vport->index);
        mlx5_fs_vport_ingress_acl_ns_add(esw->dev->priv.steering, vport->index);
        err = mlx5_esw_offloads_rep_add(esw, vport);
        if (err)
                goto acl_ns_remove;

        return 0;

acl_ns_remove:
        mlx5_fs_vport_ingress_acl_ns_remove(esw->dev->priv.steering,
                                            vport->index);
        mlx5_fs_vport_egress_acl_ns_remove(esw->dev->priv.steering,
                                           vport->index);
        mlx5_esw_vport_free(esw, vport);
destroy_esw_vport:
        mlx5_esw_destroy_esw_vport(esw->dev, vport_num);
        return err;
}

static void mlx5_esw_adj_vport_destroy(struct mlx5_eswitch *esw,
                                       struct mlx5_vport *vport)
{
        u16 vport_num = vport->vport;

        esw_debug(esw->dev, "Destroying adjacent vport %d for vhca_id 0x%x\n",
                  vport_num, vport->vhca_id);

        mlx5_esw_offloads_rep_remove(esw, vport);
        mlx5_fs_vport_egress_acl_ns_remove(esw->dev->priv.steering,
                                           vport->index);
        mlx5_fs_vport_ingress_acl_ns_remove(esw->dev->priv.steering,
                                            vport->index);
        mlx5_esw_vport_free(esw, vport);
        /* Reset the vport index back so new adj vports can use this index.
         * When vport count can incrementally change, this needs to be modified.
         */
        esw->last_vport_idx--;
        mlx5_esw_destroy_esw_vport(esw->dev, vport_num);
}

void mlx5_esw_adjacent_vhcas_cleanup(struct mlx5_eswitch *esw)
{
        struct mlx5_vport *vport;
        unsigned long i;

        if (!MLX5_CAP_GEN_2(esw->dev, delegated_vhca_max))
                return;

        mlx5_esw_for_each_vf_vport(esw, i, vport, U16_MAX) {
                if (!vport->adjacent)
                        continue;
                mlx5_esw_adj_vport_destroy(esw, vport);
        }
}

void mlx5_esw_adjacent_vhcas_setup(struct mlx5_eswitch *esw)
{
        u32 delegated_vhca_max = MLX5_CAP_GEN_2(esw->dev, delegated_vhca_max);
        u32 in[MLX5_ST_SZ_DW(query_delegated_vhca_in)] = {};
        int outlen, err, i = 0;
        u8 *out;
        u32 count;

        if (!delegated_vhca_max)
                return;

        outlen = MLX5_ST_SZ_BYTES(query_delegated_vhca_out) +
                 delegated_vhca_max *
                 MLX5_ST_SZ_BYTES(delegated_function_vhca_rid_info);

        esw_debug(esw->dev, "delegated_vhca_max=%d\n", delegated_vhca_max);

        out = kvzalloc(outlen, GFP_KERNEL);
        if (!out)
                return;

        MLX5_SET(query_delegated_vhca_in, in, opcode,
                 MLX5_CMD_OPCODE_QUERY_DELEGATED_VHCA);

        err = mlx5_cmd_exec(esw->dev, in, sizeof(in), out, outlen);
        if (err) {
                kvfree(out);
                esw_warn(esw->dev, "Failed to query delegated vhca, err %d\n",
                         err);
                return;
        }

        count = MLX5_GET(query_delegated_vhca_out, out, functions_count);
        esw_debug(esw->dev, "Delegated vhca functions count %d\n", count);

        for (i = 0; i < count; i++) {
                const void *rid_info, *rid_info_reg;
                u16 vhca_id;

                rid_info = MLX5_ADDR_OF(query_delegated_vhca_out, out,
                                        delegated_function_vhca_rid_info[i]);

                rid_info_reg = MLX5_ADDR_OF(delegated_function_vhca_rid_info,
                                            rid_info, function_vhca_rid_info);

                vhca_id = MLX5_GET(function_vhca_rid_info_reg, rid_info_reg,
                                   vhca_id);
                esw_debug(esw->dev, "Delegating vhca_id 0x%x\n", vhca_id);

                err = mlx5_esw_adj_vport_create(esw, vhca_id, rid_info_reg);
                if (err) {
                        esw_warn(esw->dev,
                                 "Failed to init adjacent vhca 0x%x, err %d\n",
                                 vhca_id, err);
                        break;
                }
        }

        kvfree(out);
}