blob: d1e91870a820b08637371bea836a3e07239131b3 [file] [log] [blame] [edit]
/*
* Copyright (c) 2019, Mellanox Technologies. All rights reserved.
*
* This software is available to you under a choice of one of two
* licenses. You may choose to be licensed under the terms of the GNU
* General Public License (GPL) Version 2, available from the file
* COPYING in the main directory of this source tree, or the
* OpenIB.org BSD license below:
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <unistd.h>
#include <stdlib.h>
#include "mlx5dv_dr.h"
enum {
MLX5DV_DR_DOMAIN_SYNC_SUP_FLAGS =
(MLX5DV_DR_DOMAIN_SYNC_FLAGS_SW |
MLX5DV_DR_DOMAIN_SYNC_FLAGS_HW |
MLX5DV_DR_DOMAIN_SYNC_FLAGS_MEM),
};
static int dr_domain_init_resources(struct mlx5dv_dr_domain *dmn)
{
int ret = -1;
dmn->ste_ctx = dr_ste_get_ctx(dmn->info.caps.sw_format_ver);
if (!dmn->ste_ctx) {
dr_dbg(dmn, "Couldn't initialize STE context\n");
return errno;
}
dmn->pd = ibv_alloc_pd(dmn->ctx);
if (!dmn->pd) {
dr_dbg(dmn, "Couldn't allocate PD\n");
return ret;
}
dmn->uar = mlx5dv_devx_alloc_uar(dmn->ctx,
MLX5_IB_UAPI_UAR_ALLOC_TYPE_NC);
if (!dmn->uar)
dmn->uar = mlx5dv_devx_alloc_uar(dmn->ctx,
MLX5_IB_UAPI_UAR_ALLOC_TYPE_BF);
if (!dmn->uar) {
dr_dbg(dmn, "Can't allocate UAR\n");
goto clean_pd;
}
dmn->ste_icm_pool = dr_icm_pool_create(dmn, DR_ICM_TYPE_STE);
if (!dmn->ste_icm_pool) {
dr_dbg(dmn, "Couldn't get icm memory for %s\n",
ibv_get_device_name(dmn->ctx->device));
goto clean_uar;
}
dmn->action_icm_pool = dr_icm_pool_create(dmn, DR_ICM_TYPE_MODIFY_ACTION);
if (!dmn->action_icm_pool) {
dr_dbg(dmn, "Couldn't get action icm memory for %s\n",
ibv_get_device_name(dmn->ctx->device));
goto free_ste_icm_pool;
}
ret = dr_send_ring_alloc(dmn);
if (ret) {
dr_dbg(dmn, "Couldn't create send-ring for %s\n",
ibv_get_device_name(dmn->ctx->device));
goto free_action_icm_pool;
}
return 0;
free_action_icm_pool:
dr_icm_pool_destroy(dmn->action_icm_pool);
free_ste_icm_pool:
dr_icm_pool_destroy(dmn->ste_icm_pool);
clean_uar:
mlx5dv_devx_free_uar(dmn->uar);
clean_pd:
ibv_dealloc_pd(dmn->pd);
return ret;
}
static void dr_free_resources(struct mlx5dv_dr_domain *dmn)
{
dr_send_ring_free(dmn);
dr_icm_pool_destroy(dmn->action_icm_pool);
dr_icm_pool_destroy(dmn->ste_icm_pool);
mlx5dv_devx_free_uar(dmn->uar);
ibv_dealloc_pd(dmn->pd);
}
static int dr_domain_vports_init(struct mlx5dv_dr_domain *dmn)
{
struct dr_devx_vports *vports = &dmn->info.caps.vports;
int ret;
ret = pthread_spin_init(&vports->lock,
PTHREAD_PROCESS_PRIVATE);
if (ret) {
errno = ret;
return ret;
}
vports->vports = dr_vports_table_create(dmn);
if (!vports->vports)
goto free_spin_lock;
dr_vports_table_add_wire(vports);
return 0;
free_spin_lock:
pthread_spin_destroy(&vports->lock);
return errno;
}
static void dr_domain_vports_uninit(struct mlx5dv_dr_domain *dmn)
{
struct dr_devx_vports *vports = &dmn->info.caps.vports;
if (vports->vports) {
/* Wire port must be deleted before destroying vports table,
* since it is not allocated dynamically but inserted to table
* as such.
*/
dr_vports_table_del_wire(vports);
dr_vports_table_destroy(vports->vports);
vports->vports = NULL;
}
pthread_spin_destroy(&vports->lock);
if (vports->ib_ports)
free(vports->ib_ports);
}
static int dr_domain_query_esw_mgr(struct mlx5dv_dr_domain *dmn,
struct dr_devx_vport_cap *esw_mngr)
{
int ret;
/* Query E-Switch manager PF/ECPF */
ret = dr_devx_query_esw_vport_context(dmn->ctx, false, 0,
&esw_mngr->icm_address_rx,
&esw_mngr->icm_address_tx);
if (ret)
return ret;
/* E-Switch manager gvmi and vhca id are the same */
esw_mngr->vhca_gvmi = dmn->info.caps.gvmi;
esw_mngr->vport_gvmi = dmn->info.caps.gvmi;
return 0;
}
static int dr_domain_query_and_set_ib_ports(struct mlx5dv_dr_domain *dmn)
{
struct dr_devx_vports *vports = &dmn->info.caps.vports;
int i;
vports->ib_ports = calloc(vports->num_ports, sizeof(struct dr_devx_vport_cap *));
if (!vports->ib_ports) {
errno = ENOMEM;
return errno;
}
/* Best effort to query available ib ports */
for (i = 1; i <= vports->num_ports; i++)
dr_vports_table_get_ib_port_cap(&dmn->info.caps, i);
return 0;
}
static int dr_domain_query_fdb_caps(struct ibv_context *ctx,
struct mlx5dv_dr_domain *dmn)
{
struct dr_devx_vports *vports = &dmn->info.caps.vports;
struct dr_esw_caps esw_caps = {};
int ret;
if (!dmn->info.caps.eswitch_manager)
return 0;
ret = dr_domain_query_esw_mgr(dmn, &vports->esw_mngr);
if (ret)
return ret;
ret = dr_devx_query_esw_caps(ctx, &esw_caps);
if (ret)
return ret;
vports->num_ports = dmn->info.attr.phys_port_cnt_ex;
/* Set uplink */
vports->wire.icm_address_rx = esw_caps.uplink_icm_address_rx;
vports->wire.icm_address_tx = esw_caps.uplink_icm_address_tx;
vports->wire.vhca_gvmi = vports->esw_mngr.vhca_gvmi;
vports->wire.num = WIRE_PORT;
/* Set FDB general caps */
dmn->info.caps.fdb_sw_owner = esw_caps.sw_owner;
dmn->info.caps.fdb_sw_owner_v2 = esw_caps.sw_owner_v2;
dmn->info.caps.esw_rx_drop_address = esw_caps.drop_icm_address_rx;
dmn->info.caps.esw_tx_drop_address = esw_caps.drop_icm_address_tx;
/* Query all ib ports if supported */
ret = dr_domain_query_and_set_ib_ports(dmn);
if (ret) {
dr_dbg(dmn, "Failed to query ib vports\n");
return ret;
}
return 0;
}
static int dr_domain_caps_init(struct ibv_context *ctx,
struct mlx5dv_dr_domain *dmn)
{
struct ibv_port_attr port_attr = {};
int ret;
dmn->info.caps.dmn = dmn;
ret = ibv_query_port(ctx, 1, &port_attr);
if (ret) {
dr_dbg(dmn, "Failed to query port\n");
return ret;
}
if (port_attr.link_layer != IBV_LINK_LAYER_ETHERNET) {
dr_dbg(dmn, "Failed to allocate domain, bad link type\n");
errno = EOPNOTSUPP;
return errno;
}
ret = ibv_query_device_ex(ctx, NULL, &dmn->info.attr);
if (ret)
return ret;
ret = dr_devx_query_device(ctx, &dmn->info.caps);
if (ret)
/* Ignore devx query failure to allow steering on root level
* tables in case devx is not supported over mlx5dv_dr API
*/
return 0;
/* Non FDB type is supported over root table or when we can enable
* force-loopback.
*/
if ((dmn->type != MLX5DV_DR_DOMAIN_TYPE_FDB) &&
!dr_send_allow_fl(&dmn->info.caps))
return 0;
ret = dr_domain_vports_init(dmn);
if (ret)
return ret;
ret = dr_domain_query_fdb_caps(ctx, dmn);
if (ret)
goto uninit_vports;
switch (dmn->type) {
case MLX5DV_DR_DOMAIN_TYPE_NIC_RX:
if (!dmn->info.caps.rx_sw_owner &&
!(dmn->info.caps.rx_sw_owner_v2 &&
dmn->info.caps.sw_format_ver <= MLX5_HW_CONNECTX_6DX))
return 0;
dmn->info.supp_sw_steering = true;
dmn->info.rx.type = DR_DOMAIN_NIC_TYPE_RX;
dmn->info.rx.default_icm_addr = dmn->info.caps.nic_rx_drop_address;
dmn->info.rx.drop_icm_addr = dmn->info.caps.nic_rx_drop_address;
break;
case MLX5DV_DR_DOMAIN_TYPE_NIC_TX:
if (!dmn->info.caps.tx_sw_owner &&
!(dmn->info.caps.tx_sw_owner_v2 &&
dmn->info.caps.sw_format_ver <= MLX5_HW_CONNECTX_6DX))
return 0;
dmn->info.supp_sw_steering = true;
dmn->info.tx.type = DR_DOMAIN_NIC_TYPE_TX;
dmn->info.tx.default_icm_addr = dmn->info.caps.nic_tx_allow_address;
dmn->info.tx.drop_icm_addr = dmn->info.caps.nic_tx_drop_address;
break;
case MLX5DV_DR_DOMAIN_TYPE_FDB:
if (!dmn->info.caps.eswitch_manager)
return 0;
if (!dmn->info.caps.fdb_sw_owner &&
!(dmn->info.caps.fdb_sw_owner_v2 &&
dmn->info.caps.sw_format_ver <= MLX5_HW_CONNECTX_6DX))
return 0;
dmn->info.rx.type = DR_DOMAIN_NIC_TYPE_RX;
dmn->info.tx.type = DR_DOMAIN_NIC_TYPE_TX;
dmn->info.supp_sw_steering = true;
dmn->info.tx.default_icm_addr = dmn->info.caps.vports.esw_mngr.icm_address_tx;
dmn->info.rx.default_icm_addr = dmn->info.caps.vports.esw_mngr.icm_address_rx;
dmn->info.rx.drop_icm_addr = dmn->info.caps.esw_rx_drop_address;
dmn->info.tx.drop_icm_addr = dmn->info.caps.esw_tx_drop_address;
break;
default:
dr_dbg(dmn, "Invalid domain\n");
ret = EINVAL;
goto uninit_vports;
}
return ret;
uninit_vports:
dr_domain_vports_uninit(dmn);
return ret;
}
static void dr_domain_caps_uninit(struct mlx5dv_dr_domain *dmn)
{
dr_domain_vports_uninit(dmn);
}
bool dr_domain_is_support_ste_icm_size(struct mlx5dv_dr_domain *dmn,
uint32_t req_log_icm_sz)
{
if (dmn->info.caps.log_icm_size < req_log_icm_sz + DR_STE_LOG_SIZE)
return false;
return true;
}
bool dr_domain_set_max_ste_icm_size(struct mlx5dv_dr_domain *dmn,
uint32_t req_log_icm_sz)
{
if (!dr_domain_is_support_ste_icm_size(dmn, req_log_icm_sz))
return false;
if (dmn->info.max_log_sw_icm_sz < req_log_icm_sz) {
dmn->info.max_log_sw_icm_sz = req_log_icm_sz;
dr_icm_pool_set_pool_max_log_chunk_sz(dmn->ste_icm_pool,
dmn->info.max_log_sw_icm_sz);
}
return true;
}
static int dr_domain_check_icm_memory_caps(struct mlx5dv_dr_domain *dmn)
{
uint32_t max_req_bytes_log, max_req_chunks_log;
/* Check for minimum ICM log byte size requirements */
if (dmn->info.caps.log_modify_hdr_icm_size < DR_CHUNK_SIZE_4K +
DR_MODIFY_ACTION_LOG_SIZE) {
errno = ENOMEM;
return errno;
}
if (dmn->info.caps.log_icm_size < DR_CHUNK_SIZE_1024K +
DR_STE_LOG_SIZE) {
errno = ENOMEM;
return errno;
}
/* Current code tries to use large allocations to improve our internal
* memory allocation (less DMs and less FW calls).
* When creating multiple domains on the same PF, we want to make sure
* we don't deplete all of the ICM resources on a single domain.
* To provide some functionality with a limited resource we will use
* up to 1/8 of the total available size allowing opening a domain
* of each type.
*/
max_req_bytes_log = dmn->info.caps.log_modify_hdr_icm_size - 3;
max_req_chunks_log = max_req_bytes_log - DR_MODIFY_ACTION_LOG_SIZE;
dmn->info.max_log_action_icm_sz =
min_t(uint32_t, DR_CHUNK_SIZE_1024K, max_req_chunks_log);
max_req_bytes_log = dmn->info.caps.log_icm_size - 3;
max_req_chunks_log = max_req_bytes_log - DR_STE_LOG_SIZE;
dmn->info.max_log_sw_icm_sz =
min_t(uint32_t, DR_CHUNK_SIZE_1024K, max_req_chunks_log);
return 0;
}
struct mlx5dv_dr_domain *
mlx5dv_dr_domain_create(struct ibv_context *ctx,
enum mlx5dv_dr_domain_type type)
{
struct mlx5dv_dr_domain *dmn;
int ret;
if (type > MLX5DV_DR_DOMAIN_TYPE_FDB) {
errno = EINVAL;
return NULL;
}
dmn = calloc(1, sizeof(*dmn));
if (!dmn) {
errno = ENOMEM;
return NULL;
}
dmn->ctx = ctx;
dmn->type = type;
atomic_init(&dmn->refcount, 1);
list_head_init(&dmn->tbl_list);
ret = pthread_spin_init(&dmn->debug_lock, PTHREAD_PROCESS_PRIVATE);
if (ret) {
errno = ret;
goto free_domain;
}
ret = dr_domain_nic_lock_init(&dmn->info.rx);
if (ret)
goto free_debug_lock;
ret = dr_domain_nic_lock_init(&dmn->info.tx);
if (ret)
goto uninit_rx_locks;
if (dr_domain_caps_init(ctx, dmn)) {
dr_dbg(dmn, "Failed init domain, no caps\n");
goto uninit_tx_locks;
}
/* Allocate resources */
if (dmn->info.supp_sw_steering) {
if (dr_domain_check_icm_memory_caps(dmn))
goto uninit_caps;
ret = dr_domain_init_resources(dmn);
if (ret) {
dr_dbg(dmn, "Failed init domain resources for %s\n",
ibv_get_device_name(ctx->device));
goto uninit_caps;
}
/* Init CRC table for htbl CRC calculation */
dr_crc32_init_table();
}
return dmn;
uninit_caps:
dr_domain_caps_uninit(dmn);
uninit_tx_locks:
dr_domain_nic_lock_uninit(&dmn->info.tx);
uninit_rx_locks:
dr_domain_nic_lock_uninit(&dmn->info.rx);
free_debug_lock:
pthread_spin_destroy(&dmn->debug_lock);
free_domain:
free(dmn);
return NULL;
}
/*
* Assure synchronization of the device steering tables with updates made by SW
* insertion.
*/
int mlx5dv_dr_domain_sync(struct mlx5dv_dr_domain *dmn, uint32_t flags)
{
int ret = 0;
if (!dmn->info.supp_sw_steering ||
!check_comp_mask(flags, MLX5DV_DR_DOMAIN_SYNC_SUP_FLAGS)) {
errno = EOPNOTSUPP;
return errno;
}
if (flags & MLX5DV_DR_DOMAIN_SYNC_FLAGS_SW) {
ret = dr_send_ring_force_drain(dmn);
if (ret)
return ret;
}
if (flags & MLX5DV_DR_DOMAIN_SYNC_FLAGS_HW) {
ret = dr_devx_sync_steering(dmn->ctx);
if (ret)
return ret;
}
if (flags & MLX5DV_DR_DOMAIN_SYNC_FLAGS_MEM) {
if (dmn->ste_icm_pool) {
ret = dr_icm_pool_sync_pool(dmn->ste_icm_pool);
if (ret)
return ret;
}
if (dmn->action_icm_pool)
ret = dr_icm_pool_sync_pool(dmn->action_icm_pool);
}
return ret;
}
void mlx5dv_dr_domain_set_reclaim_device_memory(struct mlx5dv_dr_domain *dmn,
bool enable)
{
dr_domain_lock(dmn);
if (enable)
dmn->flags |= DR_DOMAIN_FLAG_MEMORY_RECLAIM;
else
dmn->flags &= ~DR_DOMAIN_FLAG_MEMORY_RECLAIM;
dr_domain_unlock(dmn);
}
void mlx5dv_dr_domain_allow_duplicate_rules(struct mlx5dv_dr_domain *dmn,
bool allow)
{
dr_domain_lock(dmn);
if (allow)
dmn->flags &= ~DR_DOMAIN_FLAG_DISABLE_DUPLICATE_RULES;
else
dmn->flags |= DR_DOMAIN_FLAG_DISABLE_DUPLICATE_RULES;
dr_domain_unlock(dmn);
}
int mlx5dv_dr_domain_destroy(struct mlx5dv_dr_domain *dmn)
{
if (atomic_load(&dmn->refcount) > 1)
return EBUSY;
if (dmn->info.supp_sw_steering) {
/* make sure resources are not used by the hardware */
dr_devx_sync_steering(dmn->ctx);
dr_free_resources(dmn);
}
dr_domain_caps_uninit(dmn);
dr_domain_nic_lock_uninit(&dmn->info.tx);
dr_domain_nic_lock_uninit(&dmn->info.rx);
pthread_spin_destroy(&dmn->debug_lock);
free(dmn);
return 0;
}