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

#include <linux/mlx5/device.h>
#include <linux/mlx5/vport.h>
#include "mlx5_core.h"
#include "eswitch.h"

static int esw_ipsec_vf_query_generic(struct mlx5_core_dev *dev, u16 vport_num, bool *result)
{
        int query_sz = MLX5_ST_SZ_BYTES(query_hca_cap_out);
        void *hca_cap, *query_cap;
        int err;

        if (!MLX5_CAP_GEN(dev, vhca_resource_manager))
                return -EOPNOTSUPP;

        if (!mlx5_esw_ipsec_vf_offload_supported(dev)) {
                *result = false;
                return 0;
        }

        query_cap = kvzalloc(query_sz, GFP_KERNEL);
        if (!query_cap)
                return -ENOMEM;

        err = mlx5_vport_get_other_func_general_cap(dev, vport_num, query_cap);
        if (err)
                goto free;

        hca_cap = MLX5_ADDR_OF(query_hca_cap_out, query_cap, capability);
        *result = MLX5_GET(cmd_hca_cap, hca_cap, ipsec_offload);
free:
        kvfree(query_cap);
        return err;
}

enum esw_vport_ipsec_offload {
        MLX5_ESW_VPORT_IPSEC_CRYPTO_OFFLOAD,
        MLX5_ESW_VPORT_IPSEC_PACKET_OFFLOAD,
};

int mlx5_esw_ipsec_vf_offload_get(struct mlx5_core_dev *dev, struct mlx5_vport *vport)
{
        int query_sz = MLX5_ST_SZ_BYTES(query_hca_cap_out);
        void *hca_cap, *query_cap;
        bool ipsec_enabled;
        int err;

        /* Querying IPsec caps only makes sense when generic ipsec_offload
         * HCA cap is enabled
         */
        err = esw_ipsec_vf_query_generic(dev, vport->vport, &ipsec_enabled);
        if (err)
                return err;

        if (!ipsec_enabled) {
                vport->info.ipsec_crypto_enabled = false;
                vport->info.ipsec_packet_enabled = false;
                return 0;
        }

        query_cap = kvzalloc(query_sz, GFP_KERNEL);
        if (!query_cap)
                return -ENOMEM;

        err = mlx5_vport_get_other_func_cap(dev, vport->vport, query_cap, MLX5_CAP_IPSEC);
        if (err)
                goto free;

        hca_cap = MLX5_ADDR_OF(query_hca_cap_out, query_cap, capability);
        vport->info.ipsec_crypto_enabled =
                MLX5_GET(ipsec_cap, hca_cap, ipsec_crypto_offload);
        vport->info.ipsec_packet_enabled =
                MLX5_GET(ipsec_cap, hca_cap, ipsec_full_offload);
free:
        kvfree(query_cap);
        return err;
}

static int esw_ipsec_vf_set_generic(struct mlx5_core_dev *dev, u16 vport_num, bool ipsec_ofld)
{
        int query_sz = MLX5_ST_SZ_BYTES(query_hca_cap_out);
        int set_sz = MLX5_ST_SZ_BYTES(set_hca_cap_in);
        void *hca_cap, *query_cap, *cap;
        int ret;

        if (!MLX5_CAP_GEN(dev, vhca_resource_manager))
                return -EOPNOTSUPP;

        query_cap = kvzalloc(query_sz, GFP_KERNEL);
        hca_cap = kvzalloc(set_sz, GFP_KERNEL);
        if (!hca_cap || !query_cap) {
                ret = -ENOMEM;
                goto free;
        }

        ret = mlx5_vport_get_other_func_general_cap(dev, vport_num, query_cap);
        if (ret)
                goto free;

        cap = MLX5_ADDR_OF(set_hca_cap_in, hca_cap, capability);
        memcpy(cap, MLX5_ADDR_OF(query_hca_cap_out, query_cap, capability),
               MLX5_UN_SZ_BYTES(hca_cap_union));
        MLX5_SET(cmd_hca_cap, cap, ipsec_offload, ipsec_ofld);

        MLX5_SET(set_hca_cap_in, hca_cap, opcode, MLX5_CMD_OP_SET_HCA_CAP);
        MLX5_SET(set_hca_cap_in, hca_cap, other_function, 1);
        MLX5_SET(set_hca_cap_in, hca_cap, function_id, vport_num);

        MLX5_SET(set_hca_cap_in, hca_cap, op_mod,
                 MLX5_SET_HCA_CAP_OP_MOD_GENERAL_DEVICE << 1);
        ret = mlx5_cmd_exec_in(dev, set_hca_cap, hca_cap);
free:
        kvfree(hca_cap);
        kvfree(query_cap);
        return ret;
}

static int esw_ipsec_vf_set_bytype(struct mlx5_core_dev *dev, struct mlx5_vport *vport,
                                   bool enable, enum esw_vport_ipsec_offload type)
{
        int query_sz = MLX5_ST_SZ_BYTES(query_hca_cap_out);
        int set_sz = MLX5_ST_SZ_BYTES(set_hca_cap_in);
        void *hca_cap, *query_cap, *cap;
        int ret;

        if (!MLX5_CAP_GEN(dev, vhca_resource_manager))
                return -EOPNOTSUPP;

        query_cap = kvzalloc(query_sz, GFP_KERNEL);
        hca_cap = kvzalloc(set_sz, GFP_KERNEL);
        if (!hca_cap || !query_cap) {
                ret = -ENOMEM;
                goto free;
        }

        ret = mlx5_vport_get_other_func_cap(dev, vport->vport, query_cap, MLX5_CAP_IPSEC);
        if (ret)
                goto free;

        cap = MLX5_ADDR_OF(set_hca_cap_in, hca_cap, capability);
        memcpy(cap, MLX5_ADDR_OF(query_hca_cap_out, query_cap, capability),
               MLX5_UN_SZ_BYTES(hca_cap_union));

        switch (type) {
        case MLX5_ESW_VPORT_IPSEC_CRYPTO_OFFLOAD:
                MLX5_SET(ipsec_cap, cap, ipsec_crypto_offload, enable);
                break;
        case MLX5_ESW_VPORT_IPSEC_PACKET_OFFLOAD:
                MLX5_SET(ipsec_cap, cap, ipsec_full_offload, enable);
                break;
        default:
                ret = -EOPNOTSUPP;
                goto free;
        }

        MLX5_SET(set_hca_cap_in, hca_cap, opcode, MLX5_CMD_OP_SET_HCA_CAP);
        MLX5_SET(set_hca_cap_in, hca_cap, other_function, 1);
        MLX5_SET(set_hca_cap_in, hca_cap, function_id, vport->vport);

        MLX5_SET(set_hca_cap_in, hca_cap, op_mod,
                 MLX5_SET_HCA_CAP_OP_MOD_IPSEC << 1);
        ret = mlx5_cmd_exec_in(dev, set_hca_cap, hca_cap);
free:
        kvfree(hca_cap);
        kvfree(query_cap);
        return ret;
}

static int esw_ipsec_vf_crypto_aux_caps_set(struct mlx5_core_dev *dev, u16 vport_num, bool enable)
{
        int query_sz = MLX5_ST_SZ_BYTES(query_hca_cap_out);
        int set_sz = MLX5_ST_SZ_BYTES(set_hca_cap_in);
        struct mlx5_eswitch *esw = dev->priv.eswitch;
        void *hca_cap, *query_cap, *cap;
        int ret;

        query_cap = kvzalloc(query_sz, GFP_KERNEL);
        hca_cap = kvzalloc(set_sz, GFP_KERNEL);
        if (!hca_cap || !query_cap) {
                ret = -ENOMEM;
                goto free;
        }

        ret = mlx5_vport_get_other_func_cap(dev, vport_num, query_cap, MLX5_CAP_ETHERNET_OFFLOADS);
        if (ret)
                goto free;

        cap = MLX5_ADDR_OF(set_hca_cap_in, hca_cap, capability);
        memcpy(cap, MLX5_ADDR_OF(query_hca_cap_out, query_cap, capability),
               MLX5_UN_SZ_BYTES(hca_cap_union));
        MLX5_SET(per_protocol_networking_offload_caps, cap, insert_trailer, enable);
        MLX5_SET(set_hca_cap_in, hca_cap, opcode, MLX5_CMD_OP_SET_HCA_CAP);
        MLX5_SET(set_hca_cap_in, hca_cap, other_function, 1);
        MLX5_SET(set_hca_cap_in, hca_cap, function_id, vport_num);
        MLX5_SET(set_hca_cap_in, hca_cap, op_mod,
                 MLX5_SET_HCA_CAP_OP_MOD_ETHERNET_OFFLOADS << 1);
        ret = mlx5_cmd_exec_in(esw->dev, set_hca_cap, hca_cap);
free:
        kvfree(hca_cap);
        kvfree(query_cap);
        return ret;
}

static int esw_ipsec_vf_offload_set_bytype(struct mlx5_eswitch *esw, struct mlx5_vport *vport,
                                           bool enable, enum esw_vport_ipsec_offload type)
{
        struct mlx5_core_dev *dev = esw->dev;
        int err;

        if (vport->vport == MLX5_VPORT_PF)
                return -EOPNOTSUPP;

        if (type == MLX5_ESW_VPORT_IPSEC_CRYPTO_OFFLOAD) {
                err = esw_ipsec_vf_crypto_aux_caps_set(dev, vport->vport, enable);
                if (err)
                        return err;
        }

        if (enable) {
                err = esw_ipsec_vf_set_generic(dev, vport->vport, enable);
                if (err)
                        return err;
                err = esw_ipsec_vf_set_bytype(dev, vport, enable, type);
                if (err)
                        return err;
        } else {
                err = esw_ipsec_vf_set_bytype(dev, vport, enable, type);
                if (err)
                        return err;
                err = mlx5_esw_ipsec_vf_offload_get(dev, vport);
                if (err)
                        return err;

                /* The generic ipsec_offload cap can be disabled only if both
                 * ipsec_crypto_offload and ipsec_full_offload aren't enabled.
                 */
                if (!vport->info.ipsec_crypto_enabled &&
                    !vport->info.ipsec_packet_enabled) {
                        err = esw_ipsec_vf_set_generic(dev, vport->vport, enable);
                        if (err)
                                return err;
                }
        }

        switch (type) {
        case MLX5_ESW_VPORT_IPSEC_CRYPTO_OFFLOAD:
                vport->info.ipsec_crypto_enabled = enable;
                break;
        case MLX5_ESW_VPORT_IPSEC_PACKET_OFFLOAD:
                vport->info.ipsec_packet_enabled = enable;
                break;
        default:
                return -EINVAL;
        }

        return 0;
}

static int esw_ipsec_offload_supported(struct mlx5_core_dev *dev, u16 vport_num)
{
        int query_sz = MLX5_ST_SZ_BYTES(query_hca_cap_out);
        void *hca_cap, *query_cap;
        int ret;

        query_cap = kvzalloc(query_sz, GFP_KERNEL);
        if (!query_cap)
                return -ENOMEM;

        ret = mlx5_vport_get_other_func_cap(dev, vport_num, query_cap, MLX5_CAP_GENERAL);
        if (ret)
                goto free;

        hca_cap = MLX5_ADDR_OF(query_hca_cap_out, query_cap, capability);
        if (!MLX5_GET(cmd_hca_cap, hca_cap, log_max_dek))
                ret = -EOPNOTSUPP;
free:
        kvfree(query_cap);
        return ret;
}

bool mlx5_esw_ipsec_vf_offload_supported(struct mlx5_core_dev *dev)
{
        /* Old firmware doesn't support ipsec_offload capability for VFs. This
         * can be detected by checking reformat_add_esp_trasport capability -
         * when this cap isn't supported it means firmware cannot be trusted
         * about what it reports for ipsec_offload cap.
         */
        return MLX5_CAP_FLOWTABLE_NIC_TX(dev, reformat_add_esp_trasport);
}

int mlx5_esw_ipsec_vf_crypto_offload_supported(struct mlx5_core_dev *dev,
                                               u16 vport_num)
{
        int query_sz = MLX5_ST_SZ_BYTES(query_hca_cap_out);
        void *hca_cap, *query_cap;
        int err;

        if (!mlx5_esw_ipsec_vf_offload_supported(dev))
                return -EOPNOTSUPP;

        err = esw_ipsec_offload_supported(dev, vport_num);
        if (err)
                return err;

        query_cap = kvzalloc(query_sz, GFP_KERNEL);
        if (!query_cap)
                return -ENOMEM;

        err = mlx5_vport_get_other_func_cap(dev, vport_num, query_cap, MLX5_CAP_ETHERNET_OFFLOADS);
        if (err)
                goto free;

        hca_cap = MLX5_ADDR_OF(query_hca_cap_out, query_cap, capability);
        if (!MLX5_GET(per_protocol_networking_offload_caps, hca_cap, swp))
                goto free;

free:
        kvfree(query_cap);
        return err;
}

int mlx5_esw_ipsec_vf_packet_offload_supported(struct mlx5_core_dev *dev,
                                               u16 vport_num)
{
        int query_sz = MLX5_ST_SZ_BYTES(query_hca_cap_out);
        void *hca_cap, *query_cap;
        int ret;

        if (!mlx5_esw_ipsec_vf_offload_supported(dev))
                return -EOPNOTSUPP;

        ret = esw_ipsec_offload_supported(dev, vport_num);
        if (ret)
                return ret;

        query_cap = kvzalloc(query_sz, GFP_KERNEL);
        if (!query_cap)
                return -ENOMEM;

        ret = mlx5_vport_get_other_func_cap(dev, vport_num, query_cap, MLX5_CAP_FLOW_TABLE);
        if (ret)
                goto out;

        hca_cap = MLX5_ADDR_OF(query_hca_cap_out, query_cap, capability);
        if (!MLX5_GET(flow_table_nic_cap, hca_cap, flow_table_properties_nic_receive.decap)) {
                ret = -EOPNOTSUPP;
                goto out;
        }

out:
        kvfree(query_cap);
        return ret;
}

int mlx5_esw_ipsec_vf_crypto_offload_set(struct mlx5_eswitch *esw, struct mlx5_vport *vport,
                                         bool enable)
{
        return esw_ipsec_vf_offload_set_bytype(esw, vport, enable,
                                               MLX5_ESW_VPORT_IPSEC_CRYPTO_OFFLOAD);
}

int mlx5_esw_ipsec_vf_packet_offload_set(struct mlx5_eswitch *esw, struct mlx5_vport *vport,
                                         bool enable)
{
        return esw_ipsec_vf_offload_set_bytype(esw, vport, enable,
                                               MLX5_ESW_VPORT_IPSEC_PACKET_OFFLOAD);
}