blob: 6c11e075cab0b96d4082456a9c9bffc3c8a04ec6 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
/* Copyright (c) 2020 Mellanox Technologies Ltd */
#include <linux/mlx5/driver.h>
#include "eswitch.h"
#include "priv.h"
#include "sf/dev/dev.h"
#include "mlx5_ifc_vhca_event.h"
#include "vhca_event.h"
#include "ecpf.h"
#define CREATE_TRACE_POINTS
#include "diag/sf_tracepoint.h"
struct mlx5_sf {
struct mlx5_devlink_port dl_port;
unsigned int port_index;
u32 controller;
u16 id;
u16 hw_fn_id;
u16 hw_state;
};
static void *mlx5_sf_by_dl_port(struct devlink_port *dl_port)
{
struct mlx5_devlink_port *mlx5_dl_port = mlx5_devlink_port_get(dl_port);
return container_of(mlx5_dl_port, struct mlx5_sf, dl_port);
}
struct mlx5_sf_table {
struct mlx5_core_dev *dev; /* To refer from notifier context. */
struct xarray function_ids; /* function id based lookup. */
struct mutex sf_state_lock; /* Serializes sf state among user cmds & vhca event handler. */
struct notifier_block esw_nb;
struct notifier_block vhca_nb;
struct notifier_block mdev_nb;
};
static struct mlx5_sf *
mlx5_sf_lookup_by_function_id(struct mlx5_sf_table *table, unsigned int fn_id)
{
return xa_load(&table->function_ids, fn_id);
}
static int mlx5_sf_function_id_insert(struct mlx5_sf_table *table, struct mlx5_sf *sf)
{
return xa_insert(&table->function_ids, sf->hw_fn_id, sf, GFP_KERNEL);
}
static void mlx5_sf_function_id_erase(struct mlx5_sf_table *table, struct mlx5_sf *sf)
{
xa_erase(&table->function_ids, sf->hw_fn_id);
}
static struct mlx5_sf *
mlx5_sf_alloc(struct mlx5_sf_table *table, struct mlx5_eswitch *esw,
u32 controller, u32 sfnum, struct netlink_ext_ack *extack)
{
unsigned int dl_port_index;
struct mlx5_sf *sf;
u16 hw_fn_id;
int id_err;
int err;
if (!mlx5_esw_offloads_controller_valid(esw, controller)) {
NL_SET_ERR_MSG_MOD(extack, "Invalid controller number");
return ERR_PTR(-EINVAL);
}
id_err = mlx5_sf_hw_table_sf_alloc(table->dev, controller, sfnum);
if (id_err < 0) {
err = id_err;
goto id_err;
}
sf = kzalloc(sizeof(*sf), GFP_KERNEL);
if (!sf) {
err = -ENOMEM;
goto alloc_err;
}
sf->id = id_err;
hw_fn_id = mlx5_sf_sw_to_hw_id(table->dev, controller, sf->id);
dl_port_index = mlx5_esw_vport_to_devlink_port_index(table->dev, hw_fn_id);
sf->port_index = dl_port_index;
sf->hw_fn_id = hw_fn_id;
sf->hw_state = MLX5_VHCA_STATE_ALLOCATED;
sf->controller = controller;
err = mlx5_sf_function_id_insert(table, sf);
if (err)
goto insert_err;
return sf;
insert_err:
kfree(sf);
alloc_err:
mlx5_sf_hw_table_sf_free(table->dev, controller, id_err);
id_err:
if (err == -EEXIST)
NL_SET_ERR_MSG_MOD(extack, "SF already exist. Choose different sfnum");
return ERR_PTR(err);
}
static void mlx5_sf_free(struct mlx5_sf_table *table, struct mlx5_sf *sf)
{
mlx5_sf_hw_table_sf_free(table->dev, sf->controller, sf->id);
trace_mlx5_sf_free(table->dev, sf->port_index, sf->controller, sf->hw_fn_id);
kfree(sf);
}
static enum devlink_port_fn_state mlx5_sf_to_devlink_state(u8 hw_state)
{
switch (hw_state) {
case MLX5_VHCA_STATE_ACTIVE:
case MLX5_VHCA_STATE_IN_USE:
return DEVLINK_PORT_FN_STATE_ACTIVE;
case MLX5_VHCA_STATE_INVALID:
case MLX5_VHCA_STATE_ALLOCATED:
case MLX5_VHCA_STATE_TEARDOWN_REQUEST:
default:
return DEVLINK_PORT_FN_STATE_INACTIVE;
}
}
static enum devlink_port_fn_opstate mlx5_sf_to_devlink_opstate(u8 hw_state)
{
switch (hw_state) {
case MLX5_VHCA_STATE_IN_USE:
case MLX5_VHCA_STATE_TEARDOWN_REQUEST:
return DEVLINK_PORT_FN_OPSTATE_ATTACHED;
case MLX5_VHCA_STATE_INVALID:
case MLX5_VHCA_STATE_ALLOCATED:
case MLX5_VHCA_STATE_ACTIVE:
default:
return DEVLINK_PORT_FN_OPSTATE_DETACHED;
}
}
static bool mlx5_sf_is_active(const struct mlx5_sf *sf)
{
return sf->hw_state == MLX5_VHCA_STATE_ACTIVE || sf->hw_state == MLX5_VHCA_STATE_IN_USE;
}
int mlx5_devlink_sf_port_fn_state_get(struct devlink_port *dl_port,
enum devlink_port_fn_state *state,
enum devlink_port_fn_opstate *opstate,
struct netlink_ext_ack *extack)
{
struct mlx5_core_dev *dev = devlink_priv(dl_port->devlink);
struct mlx5_sf_table *table = dev->priv.sf_table;
struct mlx5_sf *sf = mlx5_sf_by_dl_port(dl_port);
mutex_lock(&table->sf_state_lock);
*state = mlx5_sf_to_devlink_state(sf->hw_state);
*opstate = mlx5_sf_to_devlink_opstate(sf->hw_state);
mutex_unlock(&table->sf_state_lock);
return 0;
}
static int mlx5_sf_activate(struct mlx5_core_dev *dev, struct mlx5_sf *sf,
struct netlink_ext_ack *extack)
{
int err;
if (mlx5_sf_is_active(sf))
return 0;
if (sf->hw_state != MLX5_VHCA_STATE_ALLOCATED) {
NL_SET_ERR_MSG_MOD(extack, "SF is inactivated but it is still attached");
return -EBUSY;
}
err = mlx5_cmd_sf_enable_hca(dev, sf->hw_fn_id);
if (err)
return err;
sf->hw_state = MLX5_VHCA_STATE_ACTIVE;
trace_mlx5_sf_activate(dev, sf->port_index, sf->controller, sf->hw_fn_id);
return 0;
}
static int mlx5_sf_deactivate(struct mlx5_core_dev *dev, struct mlx5_sf *sf)
{
int err;
if (!mlx5_sf_is_active(sf))
return 0;
err = mlx5_cmd_sf_disable_hca(dev, sf->hw_fn_id);
if (err)
return err;
sf->hw_state = MLX5_VHCA_STATE_TEARDOWN_REQUEST;
trace_mlx5_sf_deactivate(dev, sf->port_index, sf->controller, sf->hw_fn_id);
return 0;
}
static int mlx5_sf_state_set(struct mlx5_core_dev *dev, struct mlx5_sf_table *table,
struct mlx5_sf *sf,
enum devlink_port_fn_state state,
struct netlink_ext_ack *extack)
{
int err = 0;
mutex_lock(&table->sf_state_lock);
if (state == mlx5_sf_to_devlink_state(sf->hw_state))
goto out;
if (state == DEVLINK_PORT_FN_STATE_ACTIVE)
err = mlx5_sf_activate(dev, sf, extack);
else if (state == DEVLINK_PORT_FN_STATE_INACTIVE)
err = mlx5_sf_deactivate(dev, sf);
else
err = -EINVAL;
out:
mutex_unlock(&table->sf_state_lock);
return err;
}
int mlx5_devlink_sf_port_fn_state_set(struct devlink_port *dl_port,
enum devlink_port_fn_state state,
struct netlink_ext_ack *extack)
{
struct mlx5_core_dev *dev = devlink_priv(dl_port->devlink);
struct mlx5_sf_table *table = dev->priv.sf_table;
struct mlx5_sf *sf = mlx5_sf_by_dl_port(dl_port);
return mlx5_sf_state_set(dev, table, sf, state, extack);
}
static int mlx5_sf_add(struct mlx5_core_dev *dev, struct mlx5_sf_table *table,
const struct devlink_port_new_attrs *new_attr,
struct netlink_ext_ack *extack,
struct devlink_port **dl_port)
{
struct mlx5_eswitch *esw = dev->priv.eswitch;
struct mlx5_sf *sf;
int err;
sf = mlx5_sf_alloc(table, esw, new_attr->controller, new_attr->sfnum, extack);
if (IS_ERR(sf))
return PTR_ERR(sf);
err = mlx5_eswitch_load_sf_vport(esw, sf->hw_fn_id, MLX5_VPORT_UC_ADDR_CHANGE,
&sf->dl_port, new_attr->controller, new_attr->sfnum);
if (err)
goto esw_err;
*dl_port = &sf->dl_port.dl_port;
trace_mlx5_sf_add(dev, sf->port_index, sf->controller, sf->hw_fn_id, new_attr->sfnum);
return 0;
esw_err:
mlx5_sf_free(table, sf);
return err;
}
static int
mlx5_sf_new_check_attr(struct mlx5_core_dev *dev, const struct devlink_port_new_attrs *new_attr,
struct netlink_ext_ack *extack)
{
if (new_attr->flavour != DEVLINK_PORT_FLAVOUR_PCI_SF) {
NL_SET_ERR_MSG_MOD(extack, "Driver supports only SF port addition");
return -EOPNOTSUPP;
}
if (new_attr->port_index_valid) {
NL_SET_ERR_MSG_MOD(extack,
"Driver does not support user defined port index assignment");
return -EOPNOTSUPP;
}
if (!new_attr->sfnum_valid) {
NL_SET_ERR_MSG_MOD(extack,
"User must provide unique sfnum. Driver does not support auto assignment");
return -EOPNOTSUPP;
}
if (new_attr->controller_valid && new_attr->controller &&
!mlx5_core_is_ecpf_esw_manager(dev)) {
NL_SET_ERR_MSG_MOD(extack, "External controller is unsupported");
return -EOPNOTSUPP;
}
if (new_attr->pfnum != mlx5_get_dev_index(dev)) {
NL_SET_ERR_MSG_MOD(extack, "Invalid pfnum supplied");
return -EOPNOTSUPP;
}
return 0;
}
static bool mlx5_sf_table_supported(const struct mlx5_core_dev *dev)
{
return dev->priv.eswitch && MLX5_ESWITCH_MANAGER(dev) &&
mlx5_sf_hw_table_supported(dev);
}
int mlx5_devlink_sf_port_new(struct devlink *devlink,
const struct devlink_port_new_attrs *new_attr,
struct netlink_ext_ack *extack,
struct devlink_port **dl_port)
{
struct mlx5_core_dev *dev = devlink_priv(devlink);
struct mlx5_sf_table *table = dev->priv.sf_table;
int err;
err = mlx5_sf_new_check_attr(dev, new_attr, extack);
if (err)
return err;
if (!mlx5_sf_table_supported(dev)) {
NL_SET_ERR_MSG_MOD(extack, "SF ports are not supported.");
return -EOPNOTSUPP;
}
if (!is_mdev_switchdev_mode(dev)) {
NL_SET_ERR_MSG_MOD(extack,
"SF ports are only supported in eswitch switchdev mode.");
return -EOPNOTSUPP;
}
return mlx5_sf_add(dev, table, new_attr, extack, dl_port);
}
static void mlx5_sf_dealloc(struct mlx5_sf_table *table, struct mlx5_sf *sf)
{
mutex_lock(&table->sf_state_lock);
mlx5_sf_function_id_erase(table, sf);
if (sf->hw_state == MLX5_VHCA_STATE_ALLOCATED) {
mlx5_sf_free(table, sf);
} else if (mlx5_sf_is_active(sf)) {
/* Even if its active, it is treated as in_use because by the time,
* it is disabled here, it may getting used. So it is safe to
* always look for the event to ensure that it is recycled only after
* firmware gives confirmation that it is detached by the driver.
*/
mlx5_cmd_sf_disable_hca(table->dev, sf->hw_fn_id);
mlx5_sf_hw_table_sf_deferred_free(table->dev, sf->controller, sf->id);
kfree(sf);
} else {
mlx5_sf_hw_table_sf_deferred_free(table->dev, sf->controller, sf->id);
kfree(sf);
}
mutex_unlock(&table->sf_state_lock);
}
static void mlx5_sf_del(struct mlx5_sf_table *table, struct mlx5_sf *sf)
{
struct mlx5_eswitch *esw = table->dev->priv.eswitch;
mlx5_eswitch_unload_sf_vport(esw, sf->hw_fn_id);
mlx5_sf_dealloc(table, sf);
}
int mlx5_devlink_sf_port_del(struct devlink *devlink,
struct devlink_port *dl_port,
struct netlink_ext_ack *extack)
{
struct mlx5_core_dev *dev = devlink_priv(devlink);
struct mlx5_sf_table *table = dev->priv.sf_table;
struct mlx5_sf *sf = mlx5_sf_by_dl_port(dl_port);
mlx5_sf_del(table, sf);
return 0;
}
static bool mlx5_sf_state_update_check(const struct mlx5_sf *sf, u8 new_state)
{
if (sf->hw_state == MLX5_VHCA_STATE_ACTIVE && new_state == MLX5_VHCA_STATE_IN_USE)
return true;
if (sf->hw_state == MLX5_VHCA_STATE_IN_USE && new_state == MLX5_VHCA_STATE_ACTIVE)
return true;
if (sf->hw_state == MLX5_VHCA_STATE_TEARDOWN_REQUEST &&
new_state == MLX5_VHCA_STATE_ALLOCATED)
return true;
return false;
}
static int mlx5_sf_vhca_event(struct notifier_block *nb, unsigned long opcode, void *data)
{
struct mlx5_sf_table *table = container_of(nb, struct mlx5_sf_table, vhca_nb);
const struct mlx5_vhca_state_event *event = data;
bool update = false;
struct mlx5_sf *sf;
mutex_lock(&table->sf_state_lock);
sf = mlx5_sf_lookup_by_function_id(table, event->function_id);
if (!sf)
goto unlock;
/* When driver is attached or detached to a function, an event
* notifies such state change.
*/
update = mlx5_sf_state_update_check(sf, event->new_vhca_state);
if (update)
sf->hw_state = event->new_vhca_state;
trace_mlx5_sf_update_state(table->dev, sf->port_index, sf->controller,
sf->hw_fn_id, sf->hw_state);
unlock:
mutex_unlock(&table->sf_state_lock);
return 0;
}
static void mlx5_sf_del_all(struct mlx5_sf_table *table)
{
unsigned long index;
struct mlx5_sf *sf;
xa_for_each(&table->function_ids, index, sf)
mlx5_sf_del(table, sf);
}
static int mlx5_sf_esw_event(struct notifier_block *nb, unsigned long event, void *data)
{
struct mlx5_sf_table *table = container_of(nb, struct mlx5_sf_table, esw_nb);
const struct mlx5_esw_event_info *mode = data;
switch (mode->new_mode) {
case MLX5_ESWITCH_LEGACY:
mlx5_sf_del_all(table);
break;
default:
break;
}
return 0;
}
static int mlx5_sf_mdev_event(struct notifier_block *nb, unsigned long event, void *data)
{
struct mlx5_sf_table *table = container_of(nb, struct mlx5_sf_table, mdev_nb);
struct mlx5_sf_peer_devlink_event_ctx *event_ctx = data;
int ret = NOTIFY_DONE;
struct mlx5_sf *sf;
if (event != MLX5_DRIVER_EVENT_SF_PEER_DEVLINK)
return NOTIFY_DONE;
mutex_lock(&table->sf_state_lock);
sf = mlx5_sf_lookup_by_function_id(table, event_ctx->fn_id);
if (!sf)
goto out;
event_ctx->err = devl_port_fn_devlink_set(&sf->dl_port.dl_port,
event_ctx->devlink);
ret = NOTIFY_OK;
out:
mutex_unlock(&table->sf_state_lock);
return ret;
}
int mlx5_sf_table_init(struct mlx5_core_dev *dev)
{
struct mlx5_sf_table *table;
int err;
if (!mlx5_sf_table_supported(dev) || !mlx5_vhca_event_supported(dev))
return 0;
table = kzalloc(sizeof(*table), GFP_KERNEL);
if (!table)
return -ENOMEM;
mutex_init(&table->sf_state_lock);
table->dev = dev;
xa_init(&table->function_ids);
dev->priv.sf_table = table;
table->esw_nb.notifier_call = mlx5_sf_esw_event;
err = mlx5_esw_event_notifier_register(dev->priv.eswitch, &table->esw_nb);
if (err)
goto reg_err;
table->vhca_nb.notifier_call = mlx5_sf_vhca_event;
err = mlx5_vhca_event_notifier_register(table->dev, &table->vhca_nb);
if (err)
goto vhca_err;
table->mdev_nb.notifier_call = mlx5_sf_mdev_event;
mlx5_blocking_notifier_register(dev, &table->mdev_nb);
return 0;
vhca_err:
mlx5_esw_event_notifier_unregister(dev->priv.eswitch, &table->esw_nb);
reg_err:
mutex_destroy(&table->sf_state_lock);
kfree(table);
dev->priv.sf_table = NULL;
return err;
}
void mlx5_sf_table_cleanup(struct mlx5_core_dev *dev)
{
struct mlx5_sf_table *table = dev->priv.sf_table;
if (!table)
return;
mlx5_blocking_notifier_unregister(dev, &table->mdev_nb);
mlx5_vhca_event_notifier_unregister(table->dev, &table->vhca_nb);
mlx5_esw_event_notifier_unregister(dev->priv.eswitch, &table->esw_nb);
mutex_destroy(&table->sf_state_lock);
WARN_ON(!xa_empty(&table->function_ids));
kfree(table);
}