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