// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB /* Copyright (c) 2020 Mellanox Technologies Ltd */ #include #include "vhca_event.h" #include "priv.h" #include "sf.h" #include "mlx5_ifc_vhca_event.h" #include "vhca_event.h" #include "ecpf.h" struct mlx5_sf_hw { u32 usr_sfnum; u8 allocated: 1; u8 pending_delete: 1; }; struct mlx5_sf_hw_table { struct mlx5_core_dev *dev; struct mlx5_sf_hw *sfs; int max_local_functions; u8 ecpu: 1; struct mutex table_lock; /* Serializes sf deletion and vhca state change handler. */ struct notifier_block vhca_nb; }; u16 mlx5_sf_sw_to_hw_id(const struct mlx5_core_dev *dev, u16 sw_id) { return sw_id + mlx5_sf_start_function_id(dev); } static u16 mlx5_sf_hw_to_sw_id(const struct mlx5_core_dev *dev, u16 hw_id) { return hw_id - mlx5_sf_start_function_id(dev); } int mlx5_sf_hw_table_sf_alloc(struct mlx5_core_dev *dev, u32 usr_sfnum) { struct mlx5_sf_hw_table *table = dev->priv.sf_hw_table; int sw_id = -ENOSPC; u16 hw_fn_id; int err; int i; if (!table->max_local_functions) return -EOPNOTSUPP; mutex_lock(&table->table_lock); /* Check if sf with same sfnum already exists or not. */ for (i = 0; i < table->max_local_functions; i++) { if (table->sfs[i].allocated && table->sfs[i].usr_sfnum == usr_sfnum) { err = -EEXIST; goto exist_err; } } /* Find the free entry and allocate the entry from the array */ for (i = 0; i < table->max_local_functions; i++) { if (!table->sfs[i].allocated) { table->sfs[i].usr_sfnum = usr_sfnum; table->sfs[i].allocated = true; sw_id = i; break; } } if (sw_id == -ENOSPC) { err = -ENOSPC; goto err; } hw_fn_id = mlx5_sf_sw_to_hw_id(table->dev, sw_id); err = mlx5_cmd_alloc_sf(table->dev, hw_fn_id); if (err) goto err; err = mlx5_modify_vhca_sw_id(dev, hw_fn_id, table->ecpu, usr_sfnum); if (err) goto vhca_err; mutex_unlock(&table->table_lock); return sw_id; vhca_err: mlx5_cmd_dealloc_sf(table->dev, hw_fn_id); err: table->sfs[i].allocated = false; exist_err: mutex_unlock(&table->table_lock); return err; } static void _mlx5_sf_hw_id_free(struct mlx5_core_dev *dev, u16 id) { struct mlx5_sf_hw_table *table = dev->priv.sf_hw_table; u16 hw_fn_id; hw_fn_id = mlx5_sf_sw_to_hw_id(table->dev, id); mlx5_cmd_dealloc_sf(table->dev, hw_fn_id); table->sfs[id].allocated = false; table->sfs[id].pending_delete = false; } void mlx5_sf_hw_table_sf_free(struct mlx5_core_dev *dev, u16 id) { struct mlx5_sf_hw_table *table = dev->priv.sf_hw_table; mutex_lock(&table->table_lock); _mlx5_sf_hw_id_free(dev, id); mutex_unlock(&table->table_lock); } void mlx5_sf_hw_table_sf_deferred_free(struct mlx5_core_dev *dev, u16 id) { struct mlx5_sf_hw_table *table = dev->priv.sf_hw_table; u32 out[MLX5_ST_SZ_DW(query_vhca_state_out)] = {}; u16 hw_fn_id; u8 state; int err; hw_fn_id = mlx5_sf_sw_to_hw_id(dev, id); mutex_lock(&table->table_lock); err = mlx5_cmd_query_vhca_state(dev, hw_fn_id, table->ecpu, out, sizeof(out)); if (err) goto err; state = MLX5_GET(query_vhca_state_out, out, vhca_state_context.vhca_state); if (state == MLX5_VHCA_STATE_ALLOCATED) { mlx5_cmd_dealloc_sf(table->dev, hw_fn_id); table->sfs[id].allocated = false; } else { table->sfs[id].pending_delete = true; } err: mutex_unlock(&table->table_lock); } static void mlx5_sf_hw_dealloc_all(struct mlx5_sf_hw_table *table) { int i; for (i = 0; i < table->max_local_functions; i++) { if (table->sfs[i].allocated) _mlx5_sf_hw_id_free(table->dev, i); } } int mlx5_sf_hw_table_init(struct mlx5_core_dev *dev) { struct mlx5_sf_hw_table *table; struct mlx5_sf_hw *sfs; int max_functions; if (!mlx5_sf_supported(dev) || !mlx5_vhca_event_supported(dev)) return 0; max_functions = mlx5_sf_max_functions(dev); table = kzalloc(sizeof(*table), GFP_KERNEL); if (!table) return -ENOMEM; sfs = kcalloc(max_functions, sizeof(*sfs), GFP_KERNEL); if (!sfs) goto table_err; mutex_init(&table->table_lock); table->dev = dev; table->sfs = sfs; table->max_local_functions = max_functions; table->ecpu = mlx5_read_embedded_cpu(dev); dev->priv.sf_hw_table = table; mlx5_core_dbg(dev, "SF HW table: max sfs = %d\n", max_functions); return 0; table_err: kfree(table); return -ENOMEM; } void mlx5_sf_hw_table_cleanup(struct mlx5_core_dev *dev) { struct mlx5_sf_hw_table *table = dev->priv.sf_hw_table; if (!table) return; mutex_destroy(&table->table_lock); kfree(table->sfs); kfree(table); } static int mlx5_sf_hw_vhca_event(struct notifier_block *nb, unsigned long opcode, void *data) { struct mlx5_sf_hw_table *table = container_of(nb, struct mlx5_sf_hw_table, vhca_nb); const struct mlx5_vhca_state_event *event = data; struct mlx5_sf_hw *sf_hw; u16 sw_id; if (event->new_vhca_state != MLX5_VHCA_STATE_ALLOCATED) return 0; sw_id = mlx5_sf_hw_to_sw_id(table->dev, event->function_id); sf_hw = &table->sfs[sw_id]; mutex_lock(&table->table_lock); /* SF driver notified through firmware that SF is finally detached. * Hence recycle the sf hardware id for reuse. */ if (sf_hw->allocated && sf_hw->pending_delete) _mlx5_sf_hw_id_free(table->dev, sw_id); mutex_unlock(&table->table_lock); return 0; } int mlx5_sf_hw_table_create(struct mlx5_core_dev *dev) { struct mlx5_sf_hw_table *table = dev->priv.sf_hw_table; if (!table) return 0; table->vhca_nb.notifier_call = mlx5_sf_hw_vhca_event; return mlx5_vhca_event_notifier_register(table->dev, &table->vhca_nb); } void mlx5_sf_hw_table_destroy(struct mlx5_core_dev *dev) { struct mlx5_sf_hw_table *table = dev->priv.sf_hw_table; if (!table) return; mlx5_vhca_event_notifier_unregister(table->dev, &table->vhca_nb); /* Dealloc SFs whose firmware event has been missed. */ mlx5_sf_hw_dealloc_all(table); }