// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
|
// Copyright (c) 2018 Mellanox Technologies
|
|
#include <linux/hyperv.h>
|
#include "mlx5_core.h"
|
#include "lib/hv.h"
|
#include "lib/hv_vhca.h"
|
|
struct mlx5_hv_vhca {
|
struct mlx5_core_dev *dev;
|
struct workqueue_struct *work_queue;
|
struct mlx5_hv_vhca_agent *agents[MLX5_HV_VHCA_AGENT_MAX];
|
struct mutex agents_lock; /* Protect agents array */
|
};
|
|
struct mlx5_hv_vhca_work {
|
struct work_struct invalidate_work;
|
struct mlx5_hv_vhca *hv_vhca;
|
u64 block_mask;
|
};
|
|
struct mlx5_hv_vhca_data_block {
|
u16 sequence;
|
u16 offset;
|
u8 reserved[4];
|
u64 data[15];
|
};
|
|
struct mlx5_hv_vhca_agent {
|
enum mlx5_hv_vhca_agent_type type;
|
struct mlx5_hv_vhca *hv_vhca;
|
void *priv;
|
u16 seq;
|
void (*control)(struct mlx5_hv_vhca_agent *agent,
|
struct mlx5_hv_vhca_control_block *block);
|
void (*invalidate)(struct mlx5_hv_vhca_agent *agent,
|
u64 block_mask);
|
void (*cleanup)(struct mlx5_hv_vhca_agent *agent);
|
};
|
|
struct mlx5_hv_vhca *mlx5_hv_vhca_create(struct mlx5_core_dev *dev)
|
{
|
struct mlx5_hv_vhca *hv_vhca = NULL;
|
|
hv_vhca = kzalloc(sizeof(*hv_vhca), GFP_KERNEL);
|
if (!hv_vhca)
|
return ERR_PTR(-ENOMEM);
|
|
hv_vhca->work_queue = create_singlethread_workqueue("mlx5_hv_vhca");
|
if (!hv_vhca->work_queue) {
|
kfree(hv_vhca);
|
return ERR_PTR(-ENOMEM);
|
}
|
|
hv_vhca->dev = dev;
|
mutex_init(&hv_vhca->agents_lock);
|
|
return hv_vhca;
|
}
|
|
void mlx5_hv_vhca_destroy(struct mlx5_hv_vhca *hv_vhca)
|
{
|
if (IS_ERR_OR_NULL(hv_vhca))
|
return;
|
|
destroy_workqueue(hv_vhca->work_queue);
|
kfree(hv_vhca);
|
}
|
|
static void mlx5_hv_vhca_invalidate_work(struct work_struct *work)
|
{
|
struct mlx5_hv_vhca_work *hwork;
|
struct mlx5_hv_vhca *hv_vhca;
|
int i;
|
|
hwork = container_of(work, struct mlx5_hv_vhca_work, invalidate_work);
|
hv_vhca = hwork->hv_vhca;
|
|
mutex_lock(&hv_vhca->agents_lock);
|
for (i = 0; i < MLX5_HV_VHCA_AGENT_MAX; i++) {
|
struct mlx5_hv_vhca_agent *agent = hv_vhca->agents[i];
|
|
if (!agent || !agent->invalidate)
|
continue;
|
|
if (!(BIT(agent->type) & hwork->block_mask))
|
continue;
|
|
agent->invalidate(agent, hwork->block_mask);
|
}
|
mutex_unlock(&hv_vhca->agents_lock);
|
|
kfree(hwork);
|
}
|
|
void mlx5_hv_vhca_invalidate(void *context, u64 block_mask)
|
{
|
struct mlx5_hv_vhca *hv_vhca = (struct mlx5_hv_vhca *)context;
|
struct mlx5_hv_vhca_work *work;
|
|
work = kzalloc(sizeof(*work), GFP_ATOMIC);
|
if (!work)
|
return;
|
|
INIT_WORK(&work->invalidate_work, mlx5_hv_vhca_invalidate_work);
|
work->hv_vhca = hv_vhca;
|
work->block_mask = block_mask;
|
|
queue_work(hv_vhca->work_queue, &work->invalidate_work);
|
}
|
|
#define AGENT_MASK(type) (type ? BIT(type - 1) : 0 /* control */)
|
|
static void mlx5_hv_vhca_agents_control(struct mlx5_hv_vhca *hv_vhca,
|
struct mlx5_hv_vhca_control_block *block)
|
{
|
int i;
|
|
for (i = 0; i < MLX5_HV_VHCA_AGENT_MAX; i++) {
|
struct mlx5_hv_vhca_agent *agent = hv_vhca->agents[i];
|
|
if (!agent || !agent->control)
|
continue;
|
|
if (!(AGENT_MASK(agent->type) & block->control))
|
continue;
|
|
agent->control(agent, block);
|
}
|
}
|
|
static void mlx5_hv_vhca_capabilities(struct mlx5_hv_vhca *hv_vhca,
|
u32 *capabilities)
|
{
|
int i;
|
|
for (i = 0; i < MLX5_HV_VHCA_AGENT_MAX; i++) {
|
struct mlx5_hv_vhca_agent *agent = hv_vhca->agents[i];
|
|
if (agent)
|
*capabilities |= AGENT_MASK(agent->type);
|
}
|
}
|
|
static void
|
mlx5_hv_vhca_control_agent_invalidate(struct mlx5_hv_vhca_agent *agent,
|
u64 block_mask)
|
{
|
struct mlx5_hv_vhca *hv_vhca = agent->hv_vhca;
|
struct mlx5_core_dev *dev = hv_vhca->dev;
|
struct mlx5_hv_vhca_control_block *block;
|
u32 capabilities = 0;
|
int err;
|
|
block = kzalloc(sizeof(*block), GFP_KERNEL);
|
if (!block)
|
return;
|
|
err = mlx5_hv_read_config(dev, block, sizeof(*block), 0);
|
if (err)
|
goto free_block;
|
|
mlx5_hv_vhca_capabilities(hv_vhca, &capabilities);
|
|
/* In case no capabilities, send empty block in return */
|
if (!capabilities) {
|
memset(block, 0, sizeof(*block));
|
goto write;
|
}
|
|
if (block->capabilities != capabilities)
|
block->capabilities = capabilities;
|
|
if (block->control & ~capabilities)
|
goto free_block;
|
|
mlx5_hv_vhca_agents_control(hv_vhca, block);
|
block->command_ack = block->command;
|
|
write:
|
mlx5_hv_write_config(dev, block, sizeof(*block), 0);
|
|
free_block:
|
kfree(block);
|
}
|
|
static struct mlx5_hv_vhca_agent *
|
mlx5_hv_vhca_control_agent_create(struct mlx5_hv_vhca *hv_vhca)
|
{
|
return mlx5_hv_vhca_agent_create(hv_vhca, MLX5_HV_VHCA_AGENT_CONTROL,
|
NULL,
|
mlx5_hv_vhca_control_agent_invalidate,
|
NULL, NULL);
|
}
|
|
static void mlx5_hv_vhca_control_agent_destroy(struct mlx5_hv_vhca_agent *agent)
|
{
|
mlx5_hv_vhca_agent_destroy(agent);
|
}
|
|
int mlx5_hv_vhca_init(struct mlx5_hv_vhca *hv_vhca)
|
{
|
struct mlx5_hv_vhca_agent *agent;
|
int err;
|
|
if (IS_ERR_OR_NULL(hv_vhca))
|
return IS_ERR_OR_NULL(hv_vhca);
|
|
err = mlx5_hv_register_invalidate(hv_vhca->dev, hv_vhca,
|
mlx5_hv_vhca_invalidate);
|
if (err)
|
return err;
|
|
agent = mlx5_hv_vhca_control_agent_create(hv_vhca);
|
if (IS_ERR_OR_NULL(agent)) {
|
mlx5_hv_unregister_invalidate(hv_vhca->dev);
|
return IS_ERR_OR_NULL(agent);
|
}
|
|
hv_vhca->agents[MLX5_HV_VHCA_AGENT_CONTROL] = agent;
|
|
return 0;
|
}
|
|
void mlx5_hv_vhca_cleanup(struct mlx5_hv_vhca *hv_vhca)
|
{
|
struct mlx5_hv_vhca_agent *agent;
|
int i;
|
|
if (IS_ERR_OR_NULL(hv_vhca))
|
return;
|
|
agent = hv_vhca->agents[MLX5_HV_VHCA_AGENT_CONTROL];
|
if (agent)
|
mlx5_hv_vhca_control_agent_destroy(agent);
|
|
mutex_lock(&hv_vhca->agents_lock);
|
for (i = 0; i < MLX5_HV_VHCA_AGENT_MAX; i++)
|
WARN_ON(hv_vhca->agents[i]);
|
|
mutex_unlock(&hv_vhca->agents_lock);
|
|
mlx5_hv_unregister_invalidate(hv_vhca->dev);
|
}
|
|
static void mlx5_hv_vhca_agents_update(struct mlx5_hv_vhca *hv_vhca)
|
{
|
mlx5_hv_vhca_invalidate(hv_vhca, BIT(MLX5_HV_VHCA_AGENT_CONTROL));
|
}
|
|
struct mlx5_hv_vhca_agent *
|
mlx5_hv_vhca_agent_create(struct mlx5_hv_vhca *hv_vhca,
|
enum mlx5_hv_vhca_agent_type type,
|
void (*control)(struct mlx5_hv_vhca_agent*,
|
struct mlx5_hv_vhca_control_block *block),
|
void (*invalidate)(struct mlx5_hv_vhca_agent*,
|
u64 block_mask),
|
void (*cleaup)(struct mlx5_hv_vhca_agent *agent),
|
void *priv)
|
{
|
struct mlx5_hv_vhca_agent *agent;
|
|
if (IS_ERR_OR_NULL(hv_vhca))
|
return ERR_PTR(-ENOMEM);
|
|
if (type >= MLX5_HV_VHCA_AGENT_MAX)
|
return ERR_PTR(-EINVAL);
|
|
mutex_lock(&hv_vhca->agents_lock);
|
if (hv_vhca->agents[type]) {
|
mutex_unlock(&hv_vhca->agents_lock);
|
return ERR_PTR(-EINVAL);
|
}
|
mutex_unlock(&hv_vhca->agents_lock);
|
|
agent = kzalloc(sizeof(*agent), GFP_KERNEL);
|
if (!agent)
|
return ERR_PTR(-ENOMEM);
|
|
agent->type = type;
|
agent->hv_vhca = hv_vhca;
|
agent->priv = priv;
|
agent->control = control;
|
agent->invalidate = invalidate;
|
agent->cleanup = cleaup;
|
|
mutex_lock(&hv_vhca->agents_lock);
|
hv_vhca->agents[type] = agent;
|
mutex_unlock(&hv_vhca->agents_lock);
|
|
mlx5_hv_vhca_agents_update(hv_vhca);
|
|
return agent;
|
}
|
|
void mlx5_hv_vhca_agent_destroy(struct mlx5_hv_vhca_agent *agent)
|
{
|
struct mlx5_hv_vhca *hv_vhca = agent->hv_vhca;
|
|
mutex_lock(&hv_vhca->agents_lock);
|
|
if (WARN_ON(agent != hv_vhca->agents[agent->type])) {
|
mutex_unlock(&hv_vhca->agents_lock);
|
return;
|
}
|
|
hv_vhca->agents[agent->type] = NULL;
|
mutex_unlock(&hv_vhca->agents_lock);
|
|
if (agent->cleanup)
|
agent->cleanup(agent);
|
|
kfree(agent);
|
|
mlx5_hv_vhca_agents_update(hv_vhca);
|
}
|
|
static int mlx5_hv_vhca_data_block_prepare(struct mlx5_hv_vhca_agent *agent,
|
struct mlx5_hv_vhca_data_block *data_block,
|
void *src, int len, int *offset)
|
{
|
int bytes = min_t(int, (int)sizeof(data_block->data), len);
|
|
data_block->sequence = agent->seq;
|
data_block->offset = (*offset)++;
|
memcpy(data_block->data, src, bytes);
|
|
return bytes;
|
}
|
|
static void mlx5_hv_vhca_agent_seq_update(struct mlx5_hv_vhca_agent *agent)
|
{
|
agent->seq++;
|
}
|
|
int mlx5_hv_vhca_agent_write(struct mlx5_hv_vhca_agent *agent,
|
void *buf, int len)
|
{
|
int offset = agent->type * HV_CONFIG_BLOCK_SIZE_MAX;
|
int block_offset = 0;
|
int total = 0;
|
int err;
|
|
while (len) {
|
struct mlx5_hv_vhca_data_block data_block = {0};
|
int bytes;
|
|
bytes = mlx5_hv_vhca_data_block_prepare(agent, &data_block,
|
buf + total,
|
len, &block_offset);
|
if (!bytes)
|
return -ENOMEM;
|
|
err = mlx5_hv_write_config(agent->hv_vhca->dev, &data_block,
|
sizeof(data_block), offset);
|
if (err)
|
return err;
|
|
total += bytes;
|
len -= bytes;
|
}
|
|
mlx5_hv_vhca_agent_seq_update(agent);
|
|
return 0;
|
}
|
|
void *mlx5_hv_vhca_agent_priv(struct mlx5_hv_vhca_agent *agent)
|
{
|
return agent->priv;
|
}
|