1 | // SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 |
2 | /* Copyright (c) 2018 Mellanox Technologies. All rights reserved */ |
3 | |
4 | #include <linux/kernel.h> |
5 | #include <linux/bitops.h> |
6 | |
7 | #include "spectrum.h" |
8 | #include "core.h" |
9 | #include "reg.h" |
10 | #include "resources.h" |
11 | |
12 | struct mlxsw_sp2_kvdl_part_info { |
13 | u8 res_type; |
14 | /* For each defined partititon we need to know how many |
15 | * usage bits we need and how many indexes there are |
16 | * represented by a single bit. This could be got from FW |
17 | * querying appropriate resources. So have the resource |
18 | * ids for this purpose in partition definition. |
19 | */ |
20 | enum mlxsw_res_id usage_bit_count_res_id; |
21 | enum mlxsw_res_id index_range_res_id; |
22 | }; |
23 | |
24 | #define MLXSW_SP2_KVDL_PART_INFO(_entry_type, _res_type, \ |
25 | _usage_bit_count_res_id, _index_range_res_id) \ |
26 | [MLXSW_SP_KVDL_ENTRY_TYPE_##_entry_type] = { \ |
27 | .res_type = _res_type, \ |
28 | .usage_bit_count_res_id = MLXSW_RES_ID_##_usage_bit_count_res_id, \ |
29 | .index_range_res_id = MLXSW_RES_ID_##_index_range_res_id, \ |
30 | } |
31 | |
32 | static const struct mlxsw_sp2_kvdl_part_info mlxsw_sp2_kvdl_parts_info[] = { |
33 | MLXSW_SP2_KVDL_PART_INFO(ADJ, 0x21, KVD_SIZE, MAX_KVD_LINEAR_RANGE), |
34 | MLXSW_SP2_KVDL_PART_INFO(ACTSET, 0x23, MAX_KVD_ACTION_SETS, |
35 | MAX_KVD_ACTION_SETS), |
36 | MLXSW_SP2_KVDL_PART_INFO(PBS, 0x24, KVD_SIZE, KVD_SIZE), |
37 | MLXSW_SP2_KVDL_PART_INFO(MCRIGR, 0x26, KVD_SIZE, KVD_SIZE), |
38 | MLXSW_SP2_KVDL_PART_INFO(IPV6_ADDRESS, 0x28, KVD_SIZE, KVD_SIZE), |
39 | MLXSW_SP2_KVDL_PART_INFO(TNUMT, 0x29, KVD_SIZE, KVD_SIZE), |
40 | }; |
41 | |
42 | #define MLXSW_SP2_KVDL_PARTS_INFO_LEN ARRAY_SIZE(mlxsw_sp2_kvdl_parts_info) |
43 | |
44 | struct mlxsw_sp2_kvdl_part { |
45 | const struct mlxsw_sp2_kvdl_part_info *info; |
46 | unsigned int usage_bit_count; |
47 | unsigned int indexes_per_usage_bit; |
48 | unsigned int last_allocated_bit; |
49 | unsigned long usage[]; /* Usage bits */ |
50 | }; |
51 | |
52 | struct mlxsw_sp2_kvdl { |
53 | struct mlxsw_sp2_kvdl_part *parts[MLXSW_SP2_KVDL_PARTS_INFO_LEN]; |
54 | }; |
55 | |
56 | static int mlxsw_sp2_kvdl_part_find_zero_bits(struct mlxsw_sp2_kvdl_part *part, |
57 | unsigned int bit_count, |
58 | unsigned int *p_bit) |
59 | { |
60 | unsigned int start_bit; |
61 | unsigned int bit; |
62 | unsigned int i; |
63 | bool wrap = false; |
64 | |
65 | start_bit = part->last_allocated_bit + 1; |
66 | if (start_bit == part->usage_bit_count) |
67 | start_bit = 0; |
68 | bit = start_bit; |
69 | again: |
70 | bit = find_next_zero_bit(addr: part->usage, size: part->usage_bit_count, offset: bit); |
71 | if (!wrap && bit + bit_count >= part->usage_bit_count) { |
72 | wrap = true; |
73 | bit = 0; |
74 | goto again; |
75 | } |
76 | if (wrap && bit + bit_count >= start_bit) |
77 | return -ENOBUFS; |
78 | for (i = 0; i < bit_count; i++) { |
79 | if (test_bit(bit + i, part->usage)) { |
80 | bit += bit_count; |
81 | goto again; |
82 | } |
83 | } |
84 | *p_bit = bit; |
85 | return 0; |
86 | } |
87 | |
88 | static int mlxsw_sp2_kvdl_part_alloc(struct mlxsw_sp2_kvdl_part *part, |
89 | unsigned int size, |
90 | u32 *p_kvdl_index) |
91 | { |
92 | unsigned int bit_count; |
93 | unsigned int bit; |
94 | unsigned int i; |
95 | int err; |
96 | |
97 | bit_count = DIV_ROUND_UP(size, part->indexes_per_usage_bit); |
98 | err = mlxsw_sp2_kvdl_part_find_zero_bits(part, bit_count, p_bit: &bit); |
99 | if (err) |
100 | return err; |
101 | for (i = 0; i < bit_count; i++) |
102 | __set_bit(bit + i, part->usage); |
103 | *p_kvdl_index = bit * part->indexes_per_usage_bit; |
104 | return 0; |
105 | } |
106 | |
107 | static int mlxsw_sp2_kvdl_rec_del(struct mlxsw_sp *mlxsw_sp, u8 res_type, |
108 | u16 size, u32 kvdl_index) |
109 | { |
110 | char *iedr_pl; |
111 | int err; |
112 | |
113 | iedr_pl = kmalloc(MLXSW_REG_IEDR_LEN, GFP_KERNEL); |
114 | if (!iedr_pl) |
115 | return -ENOMEM; |
116 | |
117 | mlxsw_reg_iedr_pack(payload: iedr_pl); |
118 | mlxsw_reg_iedr_rec_pack(payload: iedr_pl, rec_index: 0, rec_type: res_type, rec_size: size, rec_index_start: kvdl_index); |
119 | err = mlxsw_reg_write(mlxsw_core: mlxsw_sp->core, MLXSW_REG(iedr), payload: iedr_pl); |
120 | kfree(objp: iedr_pl); |
121 | return err; |
122 | } |
123 | |
124 | static void mlxsw_sp2_kvdl_part_free(struct mlxsw_sp *mlxsw_sp, |
125 | struct mlxsw_sp2_kvdl_part *part, |
126 | unsigned int size, u32 kvdl_index) |
127 | { |
128 | unsigned int bit_count; |
129 | unsigned int bit; |
130 | unsigned int i; |
131 | int err; |
132 | |
133 | /* We need to ask FW to delete previously used KVD linear index */ |
134 | err = mlxsw_sp2_kvdl_rec_del(mlxsw_sp, res_type: part->info->res_type, |
135 | size, kvdl_index); |
136 | if (err) |
137 | return; |
138 | |
139 | bit_count = DIV_ROUND_UP(size, part->indexes_per_usage_bit); |
140 | bit = kvdl_index / part->indexes_per_usage_bit; |
141 | for (i = 0; i < bit_count; i++) |
142 | __clear_bit(bit + i, part->usage); |
143 | } |
144 | |
145 | static int mlxsw_sp2_kvdl_alloc(struct mlxsw_sp *mlxsw_sp, void *priv, |
146 | enum mlxsw_sp_kvdl_entry_type type, |
147 | unsigned int entry_count, |
148 | u32 *p_entry_index) |
149 | { |
150 | unsigned int size = entry_count * mlxsw_sp_kvdl_entry_size(type); |
151 | struct mlxsw_sp2_kvdl *kvdl = priv; |
152 | struct mlxsw_sp2_kvdl_part *part = kvdl->parts[type]; |
153 | |
154 | return mlxsw_sp2_kvdl_part_alloc(part, size, p_kvdl_index: p_entry_index); |
155 | } |
156 | |
157 | static void mlxsw_sp2_kvdl_free(struct mlxsw_sp *mlxsw_sp, void *priv, |
158 | enum mlxsw_sp_kvdl_entry_type type, |
159 | unsigned int entry_count, |
160 | int entry_index) |
161 | { |
162 | unsigned int size = entry_count * mlxsw_sp_kvdl_entry_size(type); |
163 | struct mlxsw_sp2_kvdl *kvdl = priv; |
164 | struct mlxsw_sp2_kvdl_part *part = kvdl->parts[type]; |
165 | |
166 | return mlxsw_sp2_kvdl_part_free(mlxsw_sp, part, size, kvdl_index: entry_index); |
167 | } |
168 | |
169 | static int mlxsw_sp2_kvdl_alloc_size_query(struct mlxsw_sp *mlxsw_sp, |
170 | void *priv, |
171 | enum mlxsw_sp_kvdl_entry_type type, |
172 | unsigned int entry_count, |
173 | unsigned int *p_alloc_count) |
174 | { |
175 | *p_alloc_count = entry_count; |
176 | return 0; |
177 | } |
178 | |
179 | static struct mlxsw_sp2_kvdl_part * |
180 | mlxsw_sp2_kvdl_part_init(struct mlxsw_sp *mlxsw_sp, |
181 | const struct mlxsw_sp2_kvdl_part_info *info) |
182 | { |
183 | unsigned int indexes_per_usage_bit; |
184 | struct mlxsw_sp2_kvdl_part *part; |
185 | unsigned int index_range; |
186 | unsigned int usage_bit_count; |
187 | size_t usage_size; |
188 | |
189 | if (!mlxsw_core_res_valid(mlxsw_core: mlxsw_sp->core, |
190 | res_id: info->usage_bit_count_res_id) || |
191 | !mlxsw_core_res_valid(mlxsw_core: mlxsw_sp->core, |
192 | res_id: info->index_range_res_id)) |
193 | return ERR_PTR(error: -EIO); |
194 | usage_bit_count = mlxsw_core_res_get(mlxsw_core: mlxsw_sp->core, |
195 | res_id: info->usage_bit_count_res_id); |
196 | index_range = mlxsw_core_res_get(mlxsw_core: mlxsw_sp->core, |
197 | res_id: info->index_range_res_id); |
198 | |
199 | /* For some partitions, one usage bit represents a group of indexes. |
200 | * That's why we compute the number of indexes per usage bit here, |
201 | * according to queried resources. |
202 | */ |
203 | indexes_per_usage_bit = index_range / usage_bit_count; |
204 | |
205 | usage_size = BITS_TO_LONGS(usage_bit_count) * sizeof(unsigned long); |
206 | part = kzalloc(size: sizeof(*part) + usage_size, GFP_KERNEL); |
207 | if (!part) |
208 | return ERR_PTR(error: -ENOMEM); |
209 | part->info = info; |
210 | part->usage_bit_count = usage_bit_count; |
211 | part->indexes_per_usage_bit = indexes_per_usage_bit; |
212 | part->last_allocated_bit = usage_bit_count - 1; |
213 | return part; |
214 | } |
215 | |
216 | static void mlxsw_sp2_kvdl_part_fini(struct mlxsw_sp2_kvdl_part *part) |
217 | { |
218 | kfree(objp: part); |
219 | } |
220 | |
221 | static int mlxsw_sp2_kvdl_parts_init(struct mlxsw_sp *mlxsw_sp, |
222 | struct mlxsw_sp2_kvdl *kvdl) |
223 | { |
224 | const struct mlxsw_sp2_kvdl_part_info *info; |
225 | int i; |
226 | int err; |
227 | |
228 | for (i = 0; i < MLXSW_SP2_KVDL_PARTS_INFO_LEN; i++) { |
229 | info = &mlxsw_sp2_kvdl_parts_info[i]; |
230 | kvdl->parts[i] = mlxsw_sp2_kvdl_part_init(mlxsw_sp, info); |
231 | if (IS_ERR(ptr: kvdl->parts[i])) { |
232 | err = PTR_ERR(ptr: kvdl->parts[i]); |
233 | goto err_kvdl_part_init; |
234 | } |
235 | } |
236 | return 0; |
237 | |
238 | err_kvdl_part_init: |
239 | for (i--; i >= 0; i--) |
240 | mlxsw_sp2_kvdl_part_fini(part: kvdl->parts[i]); |
241 | return err; |
242 | } |
243 | |
244 | static void mlxsw_sp2_kvdl_parts_fini(struct mlxsw_sp2_kvdl *kvdl) |
245 | { |
246 | int i; |
247 | |
248 | for (i = 0; i < MLXSW_SP2_KVDL_PARTS_INFO_LEN; i++) |
249 | mlxsw_sp2_kvdl_part_fini(part: kvdl->parts[i]); |
250 | } |
251 | |
252 | static int mlxsw_sp2_kvdl_init(struct mlxsw_sp *mlxsw_sp, void *priv) |
253 | { |
254 | struct mlxsw_sp2_kvdl *kvdl = priv; |
255 | |
256 | return mlxsw_sp2_kvdl_parts_init(mlxsw_sp, kvdl); |
257 | } |
258 | |
259 | static void mlxsw_sp2_kvdl_fini(struct mlxsw_sp *mlxsw_sp, void *priv) |
260 | { |
261 | struct mlxsw_sp2_kvdl *kvdl = priv; |
262 | |
263 | mlxsw_sp2_kvdl_parts_fini(kvdl); |
264 | } |
265 | |
266 | const struct mlxsw_sp_kvdl_ops mlxsw_sp2_kvdl_ops = { |
267 | .priv_size = sizeof(struct mlxsw_sp2_kvdl), |
268 | .init = mlxsw_sp2_kvdl_init, |
269 | .fini = mlxsw_sp2_kvdl_fini, |
270 | .alloc = mlxsw_sp2_kvdl_alloc, |
271 | .free = mlxsw_sp2_kvdl_free, |
272 | .alloc_size_query = mlxsw_sp2_kvdl_alloc_size_query, |
273 | }; |
274 | |