// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 /* Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. */ #include <linux/bits.h> #include <linux/netlink.h> #include <linux/refcount.h> #include <linux/xarray.h> #include <net/devlink.h> #include "spectrum.h" struct mlxsw_sp_port_range_reg { struct mlxsw_sp_port_range range; refcount_t refcount; u32 index; }; struct mlxsw_sp_port_range_core { struct xarray prr_xa; struct xa_limit prr_ids; atomic_t prr_count; }; static int mlxsw_sp_port_range_reg_configure(struct mlxsw_sp *mlxsw_sp, const struct mlxsw_sp_port_range_reg *prr) { char pprr_pl[MLXSW_REG_PPRR_LEN]; /* We do not care if packet is IPv4/IPv6 and TCP/UDP, so set all four * fields. */ mlxsw_reg_pprr_pack(pprr_pl, prr->index); mlxsw_reg_pprr_ipv4_set(pprr_pl, true); mlxsw_reg_pprr_ipv6_set(pprr_pl, true); mlxsw_reg_pprr_src_set(pprr_pl, prr->range.source); mlxsw_reg_pprr_dst_set(pprr_pl, !prr->range.source); mlxsw_reg_pprr_tcp_set(pprr_pl, true); mlxsw_reg_pprr_udp_set(pprr_pl, true); mlxsw_reg_pprr_port_range_min_set(pprr_pl, prr->range.min); mlxsw_reg_pprr_port_range_max_set(pprr_pl, prr->range.max); return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pprr), pprr_pl); } static struct mlxsw_sp_port_range_reg * mlxsw_sp_port_range_reg_create(struct mlxsw_sp *mlxsw_sp, const struct mlxsw_sp_port_range *range, struct netlink_ext_ack *extack) { struct mlxsw_sp_port_range_core *pr_core = mlxsw_sp->pr_core; struct mlxsw_sp_port_range_reg *prr; int err; prr = kzalloc(sizeof(*prr), GFP_KERNEL); if (!prr) return ERR_PTR(-ENOMEM); prr->range = *range; refcount_set(&prr->refcount, 1); err = xa_alloc(&pr_core->prr_xa, &prr->index, prr, pr_core->prr_ids, GFP_KERNEL); if (err) { if (err == -EBUSY) NL_SET_ERR_MSG_MOD(extack, "Exceeded number of port range registers"); goto err_xa_alloc; } err = mlxsw_sp_port_range_reg_configure(mlxsw_sp, prr); if (err) { NL_SET_ERR_MSG_MOD(extack, "Failed to configure port range register"); goto err_reg_configure; } atomic_inc(&pr_core->prr_count); return prr; err_reg_configure: xa_erase(&pr_core->prr_xa, prr->index); err_xa_alloc: kfree(prr); return ERR_PTR(err); } static void mlxsw_sp_port_range_reg_destroy(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_port_range_reg *prr) { struct mlxsw_sp_port_range_core *pr_core = mlxsw_sp->pr_core; atomic_dec(&pr_core->prr_count); xa_erase(&pr_core->prr_xa, prr->index); kfree(prr); } static struct mlxsw_sp_port_range_reg * mlxsw_sp_port_range_reg_find(struct mlxsw_sp *mlxsw_sp, const struct mlxsw_sp_port_range *range) { struct mlxsw_sp_port_range_core *pr_core = mlxsw_sp->pr_core; struct mlxsw_sp_port_range_reg *prr; unsigned long index; xa_for_each(&pr_core->prr_xa, index, prr) { if (prr->range.min == range->min && prr->range.max == range->max && prr->range.source == range->source) return prr; } return NULL; } int mlxsw_sp_port_range_reg_get(struct mlxsw_sp *mlxsw_sp, const struct mlxsw_sp_port_range *range, struct netlink_ext_ack *extack, u8 *p_prr_index) { struct mlxsw_sp_port_range_reg *prr; prr = mlxsw_sp_port_range_reg_find(mlxsw_sp, range); if (prr) { refcount_inc(&prr->refcount); *p_prr_index = prr->index; return 0; } prr = mlxsw_sp_port_range_reg_create(mlxsw_sp, range, extack); if (IS_ERR(prr)) return PTR_ERR(prr); *p_prr_index = prr->index; return 0; } void mlxsw_sp_port_range_reg_put(struct mlxsw_sp *mlxsw_sp, u8 prr_index) { struct mlxsw_sp_port_range_core *pr_core = mlxsw_sp->pr_core; struct mlxsw_sp_port_range_reg *prr; prr = xa_load(&pr_core->prr_xa, prr_index); if (WARN_ON(!prr)) return; if (!refcount_dec_and_test(&prr->refcount)) return; mlxsw_sp_port_range_reg_destroy(mlxsw_sp, prr); } static u64 mlxsw_sp_port_range_reg_occ_get(void *priv) { struct mlxsw_sp_port_range_core *pr_core = priv; return atomic_read(&pr_core->prr_count); } int mlxsw_sp_port_range_init(struct mlxsw_sp *mlxsw_sp) { struct mlxsw_sp_port_range_core *pr_core; struct mlxsw_core *core = mlxsw_sp->core; u64 max; if (!MLXSW_CORE_RES_VALID(core, ACL_MAX_L4_PORT_RANGE)) return -EIO; max = MLXSW_CORE_RES_GET(core, ACL_MAX_L4_PORT_RANGE); /* Each port range register is represented using a single bit in the * two bytes "l4_port_range" ACL key element. */ WARN_ON(max > BITS_PER_BYTE * sizeof(u16)); pr_core = kzalloc(sizeof(*mlxsw_sp->pr_core), GFP_KERNEL); if (!pr_core) return -ENOMEM; mlxsw_sp->pr_core = pr_core; pr_core->prr_ids.max = max - 1; xa_init_flags(&pr_core->prr_xa, XA_FLAGS_ALLOC); devl_resource_occ_get_register(priv_to_devlink(core), MLXSW_SP_RESOURCE_PORT_RANGE_REGISTERS, mlxsw_sp_port_range_reg_occ_get, pr_core); return 0; } void mlxsw_sp_port_range_fini(struct mlxsw_sp *mlxsw_sp) { struct mlxsw_sp_port_range_core *pr_core = mlxsw_sp->pr_core; devl_resource_occ_get_unregister(priv_to_devlink(mlxsw_sp->core), MLXSW_SP_RESOURCE_PORT_RANGE_REGISTERS); WARN_ON(!xa_empty(&pr_core->prr_xa)); xa_destroy(&pr_core->prr_xa); kfree(pr_core); }