// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 /* Copyright (c) 2020 Mellanox Technologies. All rights reserved */ #include #include #include #include #include #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); devlink_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); devlink_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 = devlink_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 = devlink_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, };