| // SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 |
| /* Copyright (c) 2020 Mellanox Technologies. All rights reserved */ |
| |
| #include <linux/idr.h> |
| #include <linux/log2.h> |
| #include <linux/mutex.h> |
| #include <linux/netlink.h> |
| #include <net/devlink.h> |
| |
| #include "spectrum.h" |
| |
| struct mlxsw_sp_policer_family { |
| enum mlxsw_sp_policer_type type; |
| enum mlxsw_reg_qpcr_g qpcr_type; |
| struct mlxsw_sp *mlxsw_sp; |
| u16 start_index; /* Inclusive */ |
| u16 end_index; /* Exclusive */ |
| struct idr policer_idr; |
| struct mutex lock; /* Protects policer_idr */ |
| atomic_t policers_count; |
| const struct mlxsw_sp_policer_family_ops *ops; |
| }; |
| |
| struct mlxsw_sp_policer { |
| struct mlxsw_sp_policer_params params; |
| u16 index; |
| }; |
| |
| struct mlxsw_sp_policer_family_ops { |
| int (*init)(struct mlxsw_sp_policer_family *family); |
| void (*fini)(struct mlxsw_sp_policer_family *family); |
| int (*policer_index_alloc)(struct mlxsw_sp_policer_family *family, |
| struct mlxsw_sp_policer *policer); |
| struct mlxsw_sp_policer * (*policer_index_free)(struct mlxsw_sp_policer_family *family, |
| u16 policer_index); |
| int (*policer_init)(struct mlxsw_sp_policer_family *family, |
| const struct mlxsw_sp_policer *policer); |
| int (*policer_params_check)(const struct mlxsw_sp_policer_family *family, |
| const struct mlxsw_sp_policer_params *params, |
| struct netlink_ext_ack *extack); |
| }; |
| |
| struct mlxsw_sp_policer_core { |
| struct mlxsw_sp_policer_family *family_arr[MLXSW_SP_POLICER_TYPE_MAX + 1]; |
| const struct mlxsw_sp_policer_core_ops *ops; |
| u8 lowest_bs_bits; |
| u8 highest_bs_bits; |
| }; |
| |
| struct mlxsw_sp_policer_core_ops { |
| int (*init)(struct mlxsw_sp_policer_core *policer_core); |
| }; |
| |
| static u64 mlxsw_sp_policer_rate_bytes_ps_kbps(u64 rate_bytes_ps) |
| { |
| return div_u64(rate_bytes_ps, 1000) * BITS_PER_BYTE; |
| } |
| |
| static u8 mlxsw_sp_policer_burst_bytes_hw_units(u64 burst_bytes) |
| { |
| /* Provided burst size is in bytes. The ASIC burst size value is |
| * (2 ^ bs) * 512 bits. Convert the provided size to 512-bit units. |
| */ |
| u64 bs512 = div_u64(burst_bytes, 64); |
| |
| if (!bs512) |
| return 0; |
| |
| return fls64(bs512) - 1; |
| } |
| |
| static u64 mlxsw_sp_policer_single_rate_occ_get(void *priv) |
| { |
| struct mlxsw_sp_policer_family *family = priv; |
| |
| return atomic_read(&family->policers_count); |
| } |
| |
| static int |
| mlxsw_sp_policer_single_rate_family_init(struct mlxsw_sp_policer_family *family) |
| { |
| struct mlxsw_core *core = family->mlxsw_sp->core; |
| struct devlink *devlink; |
| |
| /* CPU policers are allocated from the first N policers in the global |
| * range, so skip them. |
| */ |
| if (!MLXSW_CORE_RES_VALID(core, MAX_GLOBAL_POLICERS) || |
| !MLXSW_CORE_RES_VALID(core, MAX_CPU_POLICERS)) |
| return -EIO; |
| |
| family->start_index = MLXSW_CORE_RES_GET(core, MAX_CPU_POLICERS); |
| family->end_index = MLXSW_CORE_RES_GET(core, MAX_GLOBAL_POLICERS); |
| |
| atomic_set(&family->policers_count, 0); |
| devlink = priv_to_devlink(core); |
| devl_resource_occ_get_register(devlink, |
| MLXSW_SP_RESOURCE_SINGLE_RATE_POLICERS, |
| mlxsw_sp_policer_single_rate_occ_get, |
| family); |
| |
| return 0; |
| } |
| |
| static void |
| mlxsw_sp_policer_single_rate_family_fini(struct mlxsw_sp_policer_family *family) |
| { |
| struct devlink *devlink = priv_to_devlink(family->mlxsw_sp->core); |
| |
| devl_resource_occ_get_unregister(devlink, |
| MLXSW_SP_RESOURCE_SINGLE_RATE_POLICERS); |
| WARN_ON(atomic_read(&family->policers_count) != 0); |
| } |
| |
| static int |
| mlxsw_sp_policer_single_rate_index_alloc(struct mlxsw_sp_policer_family *family, |
| struct mlxsw_sp_policer *policer) |
| { |
| int id; |
| |
| mutex_lock(&family->lock); |
| id = idr_alloc(&family->policer_idr, policer, family->start_index, |
| family->end_index, GFP_KERNEL); |
| mutex_unlock(&family->lock); |
| |
| if (id < 0) |
| return id; |
| |
| atomic_inc(&family->policers_count); |
| policer->index = id; |
| |
| return 0; |
| } |
| |
| static struct mlxsw_sp_policer * |
| mlxsw_sp_policer_single_rate_index_free(struct mlxsw_sp_policer_family *family, |
| u16 policer_index) |
| { |
| struct mlxsw_sp_policer *policer; |
| |
| atomic_dec(&family->policers_count); |
| |
| mutex_lock(&family->lock); |
| policer = idr_remove(&family->policer_idr, policer_index); |
| mutex_unlock(&family->lock); |
| |
| WARN_ON(!policer); |
| |
| return policer; |
| } |
| |
| static int |
| mlxsw_sp_policer_single_rate_init(struct mlxsw_sp_policer_family *family, |
| const struct mlxsw_sp_policer *policer) |
| { |
| u64 rate_kbps = mlxsw_sp_policer_rate_bytes_ps_kbps(policer->params.rate); |
| u8 bs = mlxsw_sp_policer_burst_bytes_hw_units(policer->params.burst); |
| struct mlxsw_sp *mlxsw_sp = family->mlxsw_sp; |
| char qpcr_pl[MLXSW_REG_QPCR_LEN]; |
| |
| mlxsw_reg_qpcr_pack(qpcr_pl, policer->index, MLXSW_REG_QPCR_IR_UNITS_K, |
| true, rate_kbps, bs); |
| mlxsw_reg_qpcr_clear_counter_set(qpcr_pl, true); |
| |
| return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(qpcr), qpcr_pl); |
| } |
| |
| static int |
| mlxsw_sp_policer_single_rate_params_check(const struct mlxsw_sp_policer_family *family, |
| const struct mlxsw_sp_policer_params *params, |
| struct netlink_ext_ack *extack) |
| { |
| struct mlxsw_sp_policer_core *policer_core = family->mlxsw_sp->policer_core; |
| u64 rate_bps = params->rate * BITS_PER_BYTE; |
| u8 bs; |
| |
| if (!params->bytes) { |
| NL_SET_ERR_MSG_MOD(extack, "Only bandwidth policing is currently supported by single rate policers"); |
| return -EINVAL; |
| } |
| |
| if (!is_power_of_2(params->burst)) { |
| NL_SET_ERR_MSG_MOD(extack, "Policer burst size is not power of two"); |
| return -EINVAL; |
| } |
| |
| bs = mlxsw_sp_policer_burst_bytes_hw_units(params->burst); |
| |
| if (bs < policer_core->lowest_bs_bits) { |
| NL_SET_ERR_MSG_MOD(extack, "Policer burst size lower than limit"); |
| return -EINVAL; |
| } |
| |
| if (bs > policer_core->highest_bs_bits) { |
| NL_SET_ERR_MSG_MOD(extack, "Policer burst size higher than limit"); |
| return -EINVAL; |
| } |
| |
| if (rate_bps < MLXSW_REG_QPCR_LOWEST_CIR_BITS) { |
| NL_SET_ERR_MSG_MOD(extack, "Policer rate lower than limit"); |
| return -EINVAL; |
| } |
| |
| if (rate_bps > MLXSW_REG_QPCR_HIGHEST_CIR_BITS) { |
| NL_SET_ERR_MSG_MOD(extack, "Policer rate higher than limit"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static const struct mlxsw_sp_policer_family_ops mlxsw_sp_policer_single_rate_ops = { |
| .init = mlxsw_sp_policer_single_rate_family_init, |
| .fini = mlxsw_sp_policer_single_rate_family_fini, |
| .policer_index_alloc = mlxsw_sp_policer_single_rate_index_alloc, |
| .policer_index_free = mlxsw_sp_policer_single_rate_index_free, |
| .policer_init = mlxsw_sp_policer_single_rate_init, |
| .policer_params_check = mlxsw_sp_policer_single_rate_params_check, |
| }; |
| |
| static const struct mlxsw_sp_policer_family mlxsw_sp_policer_single_rate_family = { |
| .type = MLXSW_SP_POLICER_TYPE_SINGLE_RATE, |
| .qpcr_type = MLXSW_REG_QPCR_G_GLOBAL, |
| .ops = &mlxsw_sp_policer_single_rate_ops, |
| }; |
| |
| static const struct mlxsw_sp_policer_family *mlxsw_sp_policer_family_arr[] = { |
| [MLXSW_SP_POLICER_TYPE_SINGLE_RATE] = &mlxsw_sp_policer_single_rate_family, |
| }; |
| |
| int mlxsw_sp_policer_add(struct mlxsw_sp *mlxsw_sp, |
| enum mlxsw_sp_policer_type type, |
| const struct mlxsw_sp_policer_params *params, |
| struct netlink_ext_ack *extack, u16 *p_policer_index) |
| { |
| struct mlxsw_sp_policer_family *family; |
| struct mlxsw_sp_policer *policer; |
| int err; |
| |
| family = mlxsw_sp->policer_core->family_arr[type]; |
| |
| err = family->ops->policer_params_check(family, params, extack); |
| if (err) |
| return err; |
| |
| policer = kmalloc(sizeof(*policer), GFP_KERNEL); |
| if (!policer) |
| return -ENOMEM; |
| policer->params = *params; |
| |
| err = family->ops->policer_index_alloc(family, policer); |
| if (err) { |
| NL_SET_ERR_MSG_MOD(extack, "Failed to allocate policer index"); |
| goto err_policer_index_alloc; |
| } |
| |
| err = family->ops->policer_init(family, policer); |
| if (err) { |
| NL_SET_ERR_MSG_MOD(extack, "Failed to initialize policer"); |
| goto err_policer_init; |
| } |
| |
| *p_policer_index = policer->index; |
| |
| return 0; |
| |
| err_policer_init: |
| family->ops->policer_index_free(family, policer->index); |
| err_policer_index_alloc: |
| kfree(policer); |
| return err; |
| } |
| |
| void mlxsw_sp_policer_del(struct mlxsw_sp *mlxsw_sp, |
| enum mlxsw_sp_policer_type type, u16 policer_index) |
| { |
| struct mlxsw_sp_policer_family *family; |
| struct mlxsw_sp_policer *policer; |
| |
| family = mlxsw_sp->policer_core->family_arr[type]; |
| policer = family->ops->policer_index_free(family, policer_index); |
| kfree(policer); |
| } |
| |
| int mlxsw_sp_policer_drops_counter_get(struct mlxsw_sp *mlxsw_sp, |
| enum mlxsw_sp_policer_type type, |
| u16 policer_index, u64 *p_drops) |
| { |
| struct mlxsw_sp_policer_family *family; |
| char qpcr_pl[MLXSW_REG_QPCR_LEN]; |
| int err; |
| |
| family = mlxsw_sp->policer_core->family_arr[type]; |
| |
| MLXSW_REG_ZERO(qpcr, qpcr_pl); |
| mlxsw_reg_qpcr_pid_set(qpcr_pl, policer_index); |
| mlxsw_reg_qpcr_g_set(qpcr_pl, family->qpcr_type); |
| err = mlxsw_reg_query(mlxsw_sp->core, MLXSW_REG(qpcr), qpcr_pl); |
| if (err) |
| return err; |
| |
| *p_drops = mlxsw_reg_qpcr_violate_count_get(qpcr_pl); |
| |
| return 0; |
| } |
| |
| static int |
| mlxsw_sp_policer_family_register(struct mlxsw_sp *mlxsw_sp, |
| const struct mlxsw_sp_policer_family *tmpl) |
| { |
| struct mlxsw_sp_policer_family *family; |
| int err; |
| |
| family = kmemdup(tmpl, sizeof(*family), GFP_KERNEL); |
| if (!family) |
| return -ENOMEM; |
| |
| family->mlxsw_sp = mlxsw_sp; |
| idr_init(&family->policer_idr); |
| mutex_init(&family->lock); |
| |
| err = family->ops->init(family); |
| if (err) |
| goto err_family_init; |
| |
| if (WARN_ON(family->start_index >= family->end_index)) { |
| err = -EINVAL; |
| goto err_index_check; |
| } |
| |
| mlxsw_sp->policer_core->family_arr[tmpl->type] = family; |
| |
| return 0; |
| |
| err_index_check: |
| family->ops->fini(family); |
| err_family_init: |
| mutex_destroy(&family->lock); |
| idr_destroy(&family->policer_idr); |
| kfree(family); |
| return err; |
| } |
| |
| static void |
| mlxsw_sp_policer_family_unregister(struct mlxsw_sp *mlxsw_sp, |
| struct mlxsw_sp_policer_family *family) |
| { |
| family->ops->fini(family); |
| mutex_destroy(&family->lock); |
| WARN_ON(!idr_is_empty(&family->policer_idr)); |
| idr_destroy(&family->policer_idr); |
| kfree(family); |
| } |
| |
| int mlxsw_sp_policers_init(struct mlxsw_sp *mlxsw_sp) |
| { |
| struct mlxsw_sp_policer_core *policer_core; |
| int i, err; |
| |
| policer_core = kzalloc(sizeof(*policer_core), GFP_KERNEL); |
| if (!policer_core) |
| return -ENOMEM; |
| mlxsw_sp->policer_core = policer_core; |
| policer_core->ops = mlxsw_sp->policer_core_ops; |
| |
| err = policer_core->ops->init(policer_core); |
| if (err) |
| goto err_init; |
| |
| for (i = 0; i < MLXSW_SP_POLICER_TYPE_MAX + 1; i++) { |
| err = mlxsw_sp_policer_family_register(mlxsw_sp, mlxsw_sp_policer_family_arr[i]); |
| if (err) |
| goto err_family_register; |
| } |
| |
| return 0; |
| |
| err_family_register: |
| for (i--; i >= 0; i--) { |
| struct mlxsw_sp_policer_family *family; |
| |
| family = mlxsw_sp->policer_core->family_arr[i]; |
| mlxsw_sp_policer_family_unregister(mlxsw_sp, family); |
| } |
| err_init: |
| kfree(mlxsw_sp->policer_core); |
| return err; |
| } |
| |
| void mlxsw_sp_policers_fini(struct mlxsw_sp *mlxsw_sp) |
| { |
| int i; |
| |
| for (i = MLXSW_SP_POLICER_TYPE_MAX; i >= 0; i--) { |
| struct mlxsw_sp_policer_family *family; |
| |
| family = mlxsw_sp->policer_core->family_arr[i]; |
| mlxsw_sp_policer_family_unregister(mlxsw_sp, family); |
| } |
| |
| kfree(mlxsw_sp->policer_core); |
| } |
| |
| int mlxsw_sp_policer_resources_register(struct mlxsw_core *mlxsw_core) |
| { |
| u64 global_policers, cpu_policers, single_rate_policers; |
| struct devlink *devlink = priv_to_devlink(mlxsw_core); |
| struct devlink_resource_size_params size_params; |
| int err; |
| |
| if (!MLXSW_CORE_RES_VALID(mlxsw_core, MAX_GLOBAL_POLICERS) || |
| !MLXSW_CORE_RES_VALID(mlxsw_core, MAX_CPU_POLICERS)) |
| return -EIO; |
| |
| global_policers = MLXSW_CORE_RES_GET(mlxsw_core, MAX_GLOBAL_POLICERS); |
| cpu_policers = MLXSW_CORE_RES_GET(mlxsw_core, MAX_CPU_POLICERS); |
| single_rate_policers = global_policers - cpu_policers; |
| |
| devlink_resource_size_params_init(&size_params, global_policers, |
| global_policers, 1, |
| DEVLINK_RESOURCE_UNIT_ENTRY); |
| err = devl_resource_register(devlink, "global_policers", |
| global_policers, |
| MLXSW_SP_RESOURCE_GLOBAL_POLICERS, |
| DEVLINK_RESOURCE_ID_PARENT_TOP, |
| &size_params); |
| if (err) |
| return err; |
| |
| devlink_resource_size_params_init(&size_params, single_rate_policers, |
| single_rate_policers, 1, |
| DEVLINK_RESOURCE_UNIT_ENTRY); |
| err = devl_resource_register(devlink, "single_rate_policers", |
| single_rate_policers, |
| MLXSW_SP_RESOURCE_SINGLE_RATE_POLICERS, |
| MLXSW_SP_RESOURCE_GLOBAL_POLICERS, |
| &size_params); |
| if (err) |
| return err; |
| |
| return 0; |
| } |
| |
| static int |
| mlxsw_sp1_policer_core_init(struct mlxsw_sp_policer_core *policer_core) |
| { |
| policer_core->lowest_bs_bits = MLXSW_REG_QPCR_LOWEST_CBS_BITS_SP1; |
| policer_core->highest_bs_bits = MLXSW_REG_QPCR_HIGHEST_CBS_BITS_SP1; |
| |
| return 0; |
| } |
| |
| const struct mlxsw_sp_policer_core_ops mlxsw_sp1_policer_core_ops = { |
| .init = mlxsw_sp1_policer_core_init, |
| }; |
| |
| static int |
| mlxsw_sp2_policer_core_init(struct mlxsw_sp_policer_core *policer_core) |
| { |
| policer_core->lowest_bs_bits = MLXSW_REG_QPCR_LOWEST_CBS_BITS_SP2; |
| policer_core->highest_bs_bits = MLXSW_REG_QPCR_HIGHEST_CBS_BITS_SP2; |
| |
| return 0; |
| } |
| |
| const struct mlxsw_sp_policer_core_ops mlxsw_sp2_policer_core_ops = { |
| .init = mlxsw_sp2_policer_core_init, |
| }; |