// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
/* Copyright (c) 2018 Mellanox Technologies */
#include <linux/mlx5/vport.h>
#include "lib/devcom.h"
static LIST_HEAD(devcom_list);
#define devcom_for_each_component(priv, comp, iter) \
for (iter = 0; \
comp = &(priv)->components[iter], iter < MLX5_DEVCOM_NUM_COMPONENTS; \
iter++)
struct mlx5_devcom_component {
struct {
void *data;
} device[MLX5_MAX_PORTS];
mlx5_devcom_event_handler_t handler;
struct rw_semaphore sem;
bool paired;
};
struct mlx5_devcom_list {
struct list_head list;
struct mlx5_devcom_component components[MLX5_DEVCOM_NUM_COMPONENTS];
struct mlx5_core_dev *devs[MLX5_MAX_PORTS];
};
struct mlx5_devcom {
struct mlx5_devcom_list *priv;
int idx;
};
static struct mlx5_devcom_list *mlx5_devcom_list_alloc(void)
{
struct mlx5_devcom_component *comp;
struct mlx5_devcom_list *priv;
int i;
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return NULL;
devcom_for_each_component(priv, comp, i)
init_rwsem(&comp->sem);
return priv;
}
static struct mlx5_devcom *mlx5_devcom_alloc(struct mlx5_devcom_list *priv,
u8 idx)
{
struct mlx5_devcom *devcom;
devcom = kzalloc(sizeof(*devcom), GFP_KERNEL);
if (!devcom)
return NULL;
devcom->priv = priv;
devcom->idx = idx;
return devcom;
}
/* Must be called with intf_mutex held */
struct mlx5_devcom *mlx5_devcom_register_device(struct mlx5_core_dev *dev)
{
struct mlx5_devcom_list *priv = NULL, *iter;
struct mlx5_devcom *devcom = NULL;
bool new_priv = false;
u64 sguid0, sguid1;
int idx, i;
if (!mlx5_core_is_pf(dev))
return NULL;
sguid0 = mlx5_query_nic_system_image_guid(dev);
list_for_each_entry(iter, &devcom_list, list) {
struct mlx5_core_dev *tmp_dev = NULL;
idx = -1;
for (i = 0; i < MLX5_MAX_PORTS; i++) {
if (iter->devs[i])
tmp_dev = iter->devs[i];
else
idx = i;
}
if (idx == -1)
continue;
sguid1 = mlx5_query_nic_system_image_guid(tmp_dev);
if (sguid0 != sguid1)
continue;
priv = iter;
break;
}
if (!priv) {
priv = mlx5_devcom_list_alloc();
if (!priv)
return ERR_PTR(-ENOMEM);
idx = 0;
new_priv = true;
}
priv->devs[idx] = dev;
devcom = mlx5_devcom_alloc(priv, idx);
if (!devcom) {
kfree(priv);
return ERR_PTR(-ENOMEM);
}
if (new_priv)
list_add(&priv->list, &devcom_list);
return devcom;
}
/* Must be called with intf_mutex held */
void mlx5_devcom_unregister_device(struct mlx5_devcom *devcom)
{
struct mlx5_devcom_list *priv;
int i;
if (IS_ERR_OR_NULL(devcom))
return;
priv = devcom->priv;
priv->devs[devcom->idx] = NULL;
kfree(devcom);
for (i = 0; i < MLX5_MAX_PORTS; i++)
if (priv->devs[i])
break;
if (i != MLX5_MAX_PORTS)
return;
list_del(&priv->list);
kfree(priv);
}
void mlx5_devcom_register_component(struct mlx5_devcom *devcom,
enum mlx5_devcom_components id,
mlx5_devcom_event_handler_t handler,
void *data)
{
struct mlx5_devcom_component *comp;
if (IS_ERR_OR_NULL(devcom))
return;
WARN_ON(!data);
comp = &devcom->priv->components[id];
down_write(&comp->sem);
comp->handler = handler;
comp->device[devcom->idx].data = data;
up_write(&comp->sem);
}
void mlx5_devcom_unregister_component(struct mlx5_devcom *devcom,
enum mlx5_devcom_components id)
{
struct mlx5_devcom_component *comp;
if (IS_ERR_OR_NULL(devcom))
return;
comp = &devcom->priv->components[id];
down_write(&comp->sem);
comp->device[devcom->idx].data = NULL;
up_write(&comp->sem);
}
int mlx5_devcom_send_event(struct mlx5_devcom *devcom,
enum mlx5_devcom_components id,
int event,
void *event_data)
{
struct mlx5_devcom_component *comp;
int err = -ENODEV, i;
if (IS_ERR_OR_NULL(devcom))
return err;
comp = &devcom->priv->components[id];
down_write(&comp->sem);
for (i = 0; i < MLX5_MAX_PORTS; i++)
if (i != devcom->idx && comp->device[i].data) {
err = comp->handler(event, comp->device[i].data,
event_data);
break;
}
up_write(&comp->sem);
return err;
}
void mlx5_devcom_set_paired(struct mlx5_devcom *devcom,
enum mlx5_devcom_components id,
bool paired)
{
struct mlx5_devcom_component *comp;
comp = &devcom->priv->components[id];
WARN_ON(!rwsem_is_locked(&comp->sem));
comp->paired = paired;
}
bool mlx5_devcom_is_paired(struct mlx5_devcom *devcom,
enum mlx5_devcom_components id)
{
if (IS_ERR_OR_NULL(devcom))
return false;
return devcom->priv->components[id].paired;
}
void *mlx5_devcom_get_peer_data(struct mlx5_devcom *devcom,
enum mlx5_devcom_components id)
{
struct mlx5_devcom_component *comp;
int i;
if (IS_ERR_OR_NULL(devcom))
return NULL;
comp = &devcom->priv->components[id];
down_read(&comp->sem);
if (!comp->paired) {
up_read(&comp->sem);
return NULL;
}
for (i = 0; i < MLX5_MAX_PORTS; i++)
if (i != devcom->idx)
break;
return comp->device[i].data;
}
void mlx5_devcom_release_peer_data(struct mlx5_devcom *devcom,
enum mlx5_devcom_components id)
{
struct mlx5_devcom_component *comp = &devcom->priv->components[id];
up_read(&comp->sem);
}