| // 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; |
| } |