1b3eb04beSIdo Schimmel // SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
2b3eb04beSIdo Schimmel /* Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. */
3b3eb04beSIdo Schimmel 
4b3eb04beSIdo Schimmel #include <linux/bits.h>
5b3eb04beSIdo Schimmel #include <linux/netlink.h>
6b3eb04beSIdo Schimmel #include <linux/refcount.h>
7b3eb04beSIdo Schimmel #include <linux/xarray.h>
8*74d6786cSIdo Schimmel #include <net/devlink.h>
9b3eb04beSIdo Schimmel 
10b3eb04beSIdo Schimmel #include "spectrum.h"
11b3eb04beSIdo Schimmel 
12b3eb04beSIdo Schimmel struct mlxsw_sp_port_range_reg {
13b3eb04beSIdo Schimmel 	struct mlxsw_sp_port_range range;
14b3eb04beSIdo Schimmel 	refcount_t refcount;
15b3eb04beSIdo Schimmel 	u32 index;
16b3eb04beSIdo Schimmel };
17b3eb04beSIdo Schimmel 
18b3eb04beSIdo Schimmel struct mlxsw_sp_port_range_core {
19b3eb04beSIdo Schimmel 	struct xarray prr_xa;
20b3eb04beSIdo Schimmel 	struct xa_limit prr_ids;
21*74d6786cSIdo Schimmel 	atomic_t prr_count;
22b3eb04beSIdo Schimmel };
23b3eb04beSIdo Schimmel 
24b3eb04beSIdo Schimmel static int
mlxsw_sp_port_range_reg_configure(struct mlxsw_sp * mlxsw_sp,const struct mlxsw_sp_port_range_reg * prr)25b3eb04beSIdo Schimmel mlxsw_sp_port_range_reg_configure(struct mlxsw_sp *mlxsw_sp,
26b3eb04beSIdo Schimmel 				  const struct mlxsw_sp_port_range_reg *prr)
27b3eb04beSIdo Schimmel {
28b3eb04beSIdo Schimmel 	char pprr_pl[MLXSW_REG_PPRR_LEN];
29b3eb04beSIdo Schimmel 
30b3eb04beSIdo Schimmel 	/* We do not care if packet is IPv4/IPv6 and TCP/UDP, so set all four
31b3eb04beSIdo Schimmel 	 * fields.
32b3eb04beSIdo Schimmel 	 */
33b3eb04beSIdo Schimmel 	mlxsw_reg_pprr_pack(pprr_pl, prr->index);
34b3eb04beSIdo Schimmel 	mlxsw_reg_pprr_ipv4_set(pprr_pl, true);
35b3eb04beSIdo Schimmel 	mlxsw_reg_pprr_ipv6_set(pprr_pl, true);
36b3eb04beSIdo Schimmel 	mlxsw_reg_pprr_src_set(pprr_pl, prr->range.source);
37b3eb04beSIdo Schimmel 	mlxsw_reg_pprr_dst_set(pprr_pl, !prr->range.source);
38b3eb04beSIdo Schimmel 	mlxsw_reg_pprr_tcp_set(pprr_pl, true);
39b3eb04beSIdo Schimmel 	mlxsw_reg_pprr_udp_set(pprr_pl, true);
40b3eb04beSIdo Schimmel 	mlxsw_reg_pprr_port_range_min_set(pprr_pl, prr->range.min);
41b3eb04beSIdo Schimmel 	mlxsw_reg_pprr_port_range_max_set(pprr_pl, prr->range.max);
42b3eb04beSIdo Schimmel 
43b3eb04beSIdo Schimmel 	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pprr), pprr_pl);
44b3eb04beSIdo Schimmel }
45b3eb04beSIdo Schimmel 
46b3eb04beSIdo Schimmel 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)47b3eb04beSIdo Schimmel mlxsw_sp_port_range_reg_create(struct mlxsw_sp *mlxsw_sp,
48b3eb04beSIdo Schimmel 			       const struct mlxsw_sp_port_range *range,
49b3eb04beSIdo Schimmel 			       struct netlink_ext_ack *extack)
50b3eb04beSIdo Schimmel {
51b3eb04beSIdo Schimmel 	struct mlxsw_sp_port_range_core *pr_core = mlxsw_sp->pr_core;
52b3eb04beSIdo Schimmel 	struct mlxsw_sp_port_range_reg *prr;
53b3eb04beSIdo Schimmel 	int err;
54b3eb04beSIdo Schimmel 
55b3eb04beSIdo Schimmel 	prr = kzalloc(sizeof(*prr), GFP_KERNEL);
56b3eb04beSIdo Schimmel 	if (!prr)
57b3eb04beSIdo Schimmel 		return ERR_PTR(-ENOMEM);
58b3eb04beSIdo Schimmel 
59b3eb04beSIdo Schimmel 	prr->range = *range;
60b3eb04beSIdo Schimmel 	refcount_set(&prr->refcount, 1);
61b3eb04beSIdo Schimmel 
62b3eb04beSIdo Schimmel 	err = xa_alloc(&pr_core->prr_xa, &prr->index, prr, pr_core->prr_ids,
63b3eb04beSIdo Schimmel 		       GFP_KERNEL);
64b3eb04beSIdo Schimmel 	if (err) {
65b3eb04beSIdo Schimmel 		if (err == -EBUSY)
66b3eb04beSIdo Schimmel 			NL_SET_ERR_MSG_MOD(extack, "Exceeded number of port range registers");
67b3eb04beSIdo Schimmel 		goto err_xa_alloc;
68b3eb04beSIdo Schimmel 	}
69b3eb04beSIdo Schimmel 
70b3eb04beSIdo Schimmel 	err = mlxsw_sp_port_range_reg_configure(mlxsw_sp, prr);
71b3eb04beSIdo Schimmel 	if (err) {
72b3eb04beSIdo Schimmel 		NL_SET_ERR_MSG_MOD(extack, "Failed to configure port range register");
73b3eb04beSIdo Schimmel 		goto err_reg_configure;
74b3eb04beSIdo Schimmel 	}
75b3eb04beSIdo Schimmel 
76*74d6786cSIdo Schimmel 	atomic_inc(&pr_core->prr_count);
77*74d6786cSIdo Schimmel 
78b3eb04beSIdo Schimmel 	return prr;
79b3eb04beSIdo Schimmel 
80b3eb04beSIdo Schimmel err_reg_configure:
81b3eb04beSIdo Schimmel 	xa_erase(&pr_core->prr_xa, prr->index);
82b3eb04beSIdo Schimmel err_xa_alloc:
83b3eb04beSIdo Schimmel 	kfree(prr);
84b3eb04beSIdo Schimmel 	return ERR_PTR(err);
85b3eb04beSIdo Schimmel }
86b3eb04beSIdo Schimmel 
mlxsw_sp_port_range_reg_destroy(struct mlxsw_sp * mlxsw_sp,struct mlxsw_sp_port_range_reg * prr)87b3eb04beSIdo Schimmel static void mlxsw_sp_port_range_reg_destroy(struct mlxsw_sp *mlxsw_sp,
88b3eb04beSIdo Schimmel 					    struct mlxsw_sp_port_range_reg *prr)
89b3eb04beSIdo Schimmel {
90b3eb04beSIdo Schimmel 	struct mlxsw_sp_port_range_core *pr_core = mlxsw_sp->pr_core;
91b3eb04beSIdo Schimmel 
92*74d6786cSIdo Schimmel 	atomic_dec(&pr_core->prr_count);
93b3eb04beSIdo Schimmel 	xa_erase(&pr_core->prr_xa, prr->index);
94b3eb04beSIdo Schimmel 	kfree(prr);
95b3eb04beSIdo Schimmel }
96b3eb04beSIdo Schimmel 
97b3eb04beSIdo Schimmel 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)98b3eb04beSIdo Schimmel mlxsw_sp_port_range_reg_find(struct mlxsw_sp *mlxsw_sp,
99b3eb04beSIdo Schimmel 			     const struct mlxsw_sp_port_range *range)
100b3eb04beSIdo Schimmel {
101b3eb04beSIdo Schimmel 	struct mlxsw_sp_port_range_core *pr_core = mlxsw_sp->pr_core;
102b3eb04beSIdo Schimmel 	struct mlxsw_sp_port_range_reg *prr;
103b3eb04beSIdo Schimmel 	unsigned long index;
104b3eb04beSIdo Schimmel 
105b3eb04beSIdo Schimmel 	xa_for_each(&pr_core->prr_xa, index, prr) {
106b3eb04beSIdo Schimmel 		if (prr->range.min == range->min &&
107b3eb04beSIdo Schimmel 		    prr->range.max == range->max &&
108b3eb04beSIdo Schimmel 		    prr->range.source == range->source)
109b3eb04beSIdo Schimmel 			return prr;
110b3eb04beSIdo Schimmel 	}
111b3eb04beSIdo Schimmel 
112b3eb04beSIdo Schimmel 	return NULL;
113b3eb04beSIdo Schimmel }
114b3eb04beSIdo Schimmel 
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)115b3eb04beSIdo Schimmel int mlxsw_sp_port_range_reg_get(struct mlxsw_sp *mlxsw_sp,
116b3eb04beSIdo Schimmel 				const struct mlxsw_sp_port_range *range,
117b3eb04beSIdo Schimmel 				struct netlink_ext_ack *extack,
118b3eb04beSIdo Schimmel 				u8 *p_prr_index)
119b3eb04beSIdo Schimmel {
120b3eb04beSIdo Schimmel 	struct mlxsw_sp_port_range_reg *prr;
121b3eb04beSIdo Schimmel 
122b3eb04beSIdo Schimmel 	prr = mlxsw_sp_port_range_reg_find(mlxsw_sp, range);
123b3eb04beSIdo Schimmel 	if (prr) {
124b3eb04beSIdo Schimmel 		refcount_inc(&prr->refcount);
125b3eb04beSIdo Schimmel 		*p_prr_index = prr->index;
126b3eb04beSIdo Schimmel 		return 0;
127b3eb04beSIdo Schimmel 	}
128b3eb04beSIdo Schimmel 
129b3eb04beSIdo Schimmel 	prr = mlxsw_sp_port_range_reg_create(mlxsw_sp, range, extack);
130b3eb04beSIdo Schimmel 	if (IS_ERR(prr))
131b3eb04beSIdo Schimmel 		return PTR_ERR(prr);
132b3eb04beSIdo Schimmel 
133b3eb04beSIdo Schimmel 	*p_prr_index = prr->index;
134b3eb04beSIdo Schimmel 
135b3eb04beSIdo Schimmel 	return 0;
136b3eb04beSIdo Schimmel }
137b3eb04beSIdo Schimmel 
mlxsw_sp_port_range_reg_put(struct mlxsw_sp * mlxsw_sp,u8 prr_index)138b3eb04beSIdo Schimmel void mlxsw_sp_port_range_reg_put(struct mlxsw_sp *mlxsw_sp, u8 prr_index)
139b3eb04beSIdo Schimmel {
140b3eb04beSIdo Schimmel 	struct mlxsw_sp_port_range_core *pr_core = mlxsw_sp->pr_core;
141b3eb04beSIdo Schimmel 	struct mlxsw_sp_port_range_reg *prr;
142b3eb04beSIdo Schimmel 
143b3eb04beSIdo Schimmel 	prr = xa_load(&pr_core->prr_xa, prr_index);
144b3eb04beSIdo Schimmel 	if (WARN_ON(!prr))
145b3eb04beSIdo Schimmel 		return;
146b3eb04beSIdo Schimmel 
147b3eb04beSIdo Schimmel 	if (!refcount_dec_and_test(&prr->refcount))
148b3eb04beSIdo Schimmel 		return;
149b3eb04beSIdo Schimmel 
150b3eb04beSIdo Schimmel 	mlxsw_sp_port_range_reg_destroy(mlxsw_sp, prr);
151b3eb04beSIdo Schimmel }
152b3eb04beSIdo Schimmel 
mlxsw_sp_port_range_reg_occ_get(void * priv)153*74d6786cSIdo Schimmel static u64 mlxsw_sp_port_range_reg_occ_get(void *priv)
154*74d6786cSIdo Schimmel {
155*74d6786cSIdo Schimmel 	struct mlxsw_sp_port_range_core *pr_core = priv;
156*74d6786cSIdo Schimmel 
157*74d6786cSIdo Schimmel 	return atomic_read(&pr_core->prr_count);
158*74d6786cSIdo Schimmel }
159*74d6786cSIdo Schimmel 
mlxsw_sp_port_range_init(struct mlxsw_sp * mlxsw_sp)160b3eb04beSIdo Schimmel int mlxsw_sp_port_range_init(struct mlxsw_sp *mlxsw_sp)
161b3eb04beSIdo Schimmel {
162b3eb04beSIdo Schimmel 	struct mlxsw_sp_port_range_core *pr_core;
163b3eb04beSIdo Schimmel 	struct mlxsw_core *core = mlxsw_sp->core;
164b3eb04beSIdo Schimmel 	u64 max;
165b3eb04beSIdo Schimmel 
166b3eb04beSIdo Schimmel 	if (!MLXSW_CORE_RES_VALID(core, ACL_MAX_L4_PORT_RANGE))
167b3eb04beSIdo Schimmel 		return -EIO;
168b3eb04beSIdo Schimmel 	max = MLXSW_CORE_RES_GET(core, ACL_MAX_L4_PORT_RANGE);
169b3eb04beSIdo Schimmel 
170b3eb04beSIdo Schimmel 	/* Each port range register is represented using a single bit in the
171b3eb04beSIdo Schimmel 	 * two bytes "l4_port_range" ACL key element.
172b3eb04beSIdo Schimmel 	 */
173b3eb04beSIdo Schimmel 	WARN_ON(max > BITS_PER_BYTE * sizeof(u16));
174b3eb04beSIdo Schimmel 
175b3eb04beSIdo Schimmel 	pr_core = kzalloc(sizeof(*mlxsw_sp->pr_core), GFP_KERNEL);
176b3eb04beSIdo Schimmel 	if (!pr_core)
177b3eb04beSIdo Schimmel 		return -ENOMEM;
178b3eb04beSIdo Schimmel 	mlxsw_sp->pr_core = pr_core;
179b3eb04beSIdo Schimmel 
180b3eb04beSIdo Schimmel 	pr_core->prr_ids.max = max - 1;
181b3eb04beSIdo Schimmel 	xa_init_flags(&pr_core->prr_xa, XA_FLAGS_ALLOC);
182b3eb04beSIdo Schimmel 
183*74d6786cSIdo Schimmel 	devl_resource_occ_get_register(priv_to_devlink(core),
184*74d6786cSIdo Schimmel 				       MLXSW_SP_RESOURCE_PORT_RANGE_REGISTERS,
185*74d6786cSIdo Schimmel 				       mlxsw_sp_port_range_reg_occ_get,
186*74d6786cSIdo Schimmel 				       pr_core);
187*74d6786cSIdo Schimmel 
188b3eb04beSIdo Schimmel 	return 0;
189b3eb04beSIdo Schimmel }
190b3eb04beSIdo Schimmel 
mlxsw_sp_port_range_fini(struct mlxsw_sp * mlxsw_sp)191b3eb04beSIdo Schimmel void mlxsw_sp_port_range_fini(struct mlxsw_sp *mlxsw_sp)
192b3eb04beSIdo Schimmel {
193b3eb04beSIdo Schimmel 	struct mlxsw_sp_port_range_core *pr_core = mlxsw_sp->pr_core;
194b3eb04beSIdo Schimmel 
195*74d6786cSIdo Schimmel 	devl_resource_occ_get_unregister(priv_to_devlink(mlxsw_sp->core),
196*74d6786cSIdo Schimmel 					 MLXSW_SP_RESOURCE_PORT_RANGE_REGISTERS);
197b3eb04beSIdo Schimmel 	WARN_ON(!xa_empty(&pr_core->prr_xa));
198b3eb04beSIdo Schimmel 	xa_destroy(&pr_core->prr_xa);
199b3eb04beSIdo Schimmel 	kfree(pr_core);
200b3eb04beSIdo Schimmel }
201