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 | |
9 | #define MLXSW_SP1_KVDL_SINGLE_BASE 0 |
10 | #define MLXSW_SP1_KVDL_SINGLE_SIZE 16384 |
11 | #define MLXSW_SP1_KVDL_SINGLE_END \ |
12 | (MLXSW_SP1_KVDL_SINGLE_SIZE + MLXSW_SP1_KVDL_SINGLE_BASE - 1) |
13 | |
14 | #define MLXSW_SP1_KVDL_CHUNKS_BASE \ |
15 | (MLXSW_SP1_KVDL_SINGLE_BASE + MLXSW_SP1_KVDL_SINGLE_SIZE) |
16 | #define MLXSW_SP1_KVDL_CHUNKS_SIZE 49152 |
17 | #define MLXSW_SP1_KVDL_CHUNKS_END \ |
18 | (MLXSW_SP1_KVDL_CHUNKS_SIZE + MLXSW_SP1_KVDL_CHUNKS_BASE - 1) |
19 | |
20 | #define MLXSW_SP1_KVDL_LARGE_CHUNKS_BASE \ |
21 | (MLXSW_SP1_KVDL_CHUNKS_BASE + MLXSW_SP1_KVDL_CHUNKS_SIZE) |
22 | #define MLXSW_SP1_KVDL_LARGE_CHUNKS_SIZE \ |
23 | (MLXSW_SP_KVD_LINEAR_SIZE - MLXSW_SP1_KVDL_LARGE_CHUNKS_BASE) |
24 | #define MLXSW_SP1_KVDL_LARGE_CHUNKS_END \ |
25 | (MLXSW_SP1_KVDL_LARGE_CHUNKS_SIZE + MLXSW_SP1_KVDL_LARGE_CHUNKS_BASE - 1) |
26 | |
27 | #define MLXSW_SP1_KVDL_SINGLE_ALLOC_SIZE 1 |
28 | #define MLXSW_SP1_KVDL_CHUNKS_ALLOC_SIZE 32 |
29 | #define MLXSW_SP1_KVDL_LARGE_CHUNKS_ALLOC_SIZE 512 |
30 | |
31 | struct mlxsw_sp1_kvdl_part_info { |
32 | unsigned int part_index; |
33 | unsigned int start_index; |
34 | unsigned int end_index; |
35 | unsigned int alloc_size; |
36 | enum mlxsw_sp_resource_id resource_id; |
37 | }; |
38 | |
39 | enum mlxsw_sp1_kvdl_part_id { |
40 | MLXSW_SP1_KVDL_PART_ID_SINGLE, |
41 | MLXSW_SP1_KVDL_PART_ID_CHUNKS, |
42 | MLXSW_SP1_KVDL_PART_ID_LARGE_CHUNKS, |
43 | }; |
44 | |
45 | #define MLXSW_SP1_KVDL_PART_INFO(id) \ |
46 | [MLXSW_SP1_KVDL_PART_ID_##id] = { \ |
47 | .start_index = MLXSW_SP1_KVDL_##id##_BASE, \ |
48 | .end_index = MLXSW_SP1_KVDL_##id##_END, \ |
49 | .alloc_size = MLXSW_SP1_KVDL_##id##_ALLOC_SIZE, \ |
50 | .resource_id = MLXSW_SP_RESOURCE_KVD_LINEAR_##id, \ |
51 | } |
52 | |
53 | static const struct mlxsw_sp1_kvdl_part_info mlxsw_sp1_kvdl_parts_info[] = { |
54 | MLXSW_SP1_KVDL_PART_INFO(SINGLE), |
55 | MLXSW_SP1_KVDL_PART_INFO(CHUNKS), |
56 | MLXSW_SP1_KVDL_PART_INFO(LARGE_CHUNKS), |
57 | }; |
58 | |
59 | #define MLXSW_SP1_KVDL_PARTS_INFO_LEN ARRAY_SIZE(mlxsw_sp1_kvdl_parts_info) |
60 | |
61 | struct mlxsw_sp1_kvdl_part { |
62 | struct mlxsw_sp1_kvdl_part_info info; |
63 | unsigned long usage[]; /* Entries */ |
64 | }; |
65 | |
66 | struct mlxsw_sp1_kvdl { |
67 | struct mlxsw_sp1_kvdl_part *parts[MLXSW_SP1_KVDL_PARTS_INFO_LEN]; |
68 | }; |
69 | |
70 | static struct mlxsw_sp1_kvdl_part * |
71 | mlxsw_sp1_kvdl_alloc_size_part(struct mlxsw_sp1_kvdl *kvdl, |
72 | unsigned int alloc_size) |
73 | { |
74 | struct mlxsw_sp1_kvdl_part *part, *min_part = NULL; |
75 | int i; |
76 | |
77 | for (i = 0; i < MLXSW_SP1_KVDL_PARTS_INFO_LEN; i++) { |
78 | part = kvdl->parts[i]; |
79 | if (alloc_size <= part->info.alloc_size && |
80 | (!min_part || |
81 | part->info.alloc_size <= min_part->info.alloc_size)) |
82 | min_part = part; |
83 | } |
84 | |
85 | return min_part ?: ERR_PTR(error: -ENOBUFS); |
86 | } |
87 | |
88 | static struct mlxsw_sp1_kvdl_part * |
89 | mlxsw_sp1_kvdl_index_part(struct mlxsw_sp1_kvdl *kvdl, u32 kvdl_index) |
90 | { |
91 | struct mlxsw_sp1_kvdl_part *part; |
92 | int i; |
93 | |
94 | for (i = 0; i < MLXSW_SP1_KVDL_PARTS_INFO_LEN; i++) { |
95 | part = kvdl->parts[i]; |
96 | if (kvdl_index >= part->info.start_index && |
97 | kvdl_index <= part->info.end_index) |
98 | return part; |
99 | } |
100 | |
101 | return ERR_PTR(error: -EINVAL); |
102 | } |
103 | |
104 | static u32 |
105 | mlxsw_sp1_kvdl_to_kvdl_index(const struct mlxsw_sp1_kvdl_part_info *info, |
106 | unsigned int entry_index) |
107 | { |
108 | return info->start_index + entry_index * info->alloc_size; |
109 | } |
110 | |
111 | static unsigned int |
112 | mlxsw_sp1_kvdl_to_entry_index(const struct mlxsw_sp1_kvdl_part_info *info, |
113 | u32 kvdl_index) |
114 | { |
115 | return (kvdl_index - info->start_index) / info->alloc_size; |
116 | } |
117 | |
118 | static int mlxsw_sp1_kvdl_part_alloc(struct mlxsw_sp1_kvdl_part *part, |
119 | u32 *p_kvdl_index) |
120 | { |
121 | const struct mlxsw_sp1_kvdl_part_info *info = &part->info; |
122 | unsigned int entry_index, nr_entries; |
123 | |
124 | nr_entries = (info->end_index - info->start_index + 1) / |
125 | info->alloc_size; |
126 | entry_index = find_first_zero_bit(addr: part->usage, size: nr_entries); |
127 | if (entry_index == nr_entries) |
128 | return -ENOBUFS; |
129 | __set_bit(entry_index, part->usage); |
130 | |
131 | *p_kvdl_index = mlxsw_sp1_kvdl_to_kvdl_index(info, entry_index); |
132 | |
133 | return 0; |
134 | } |
135 | |
136 | static void mlxsw_sp1_kvdl_part_free(struct mlxsw_sp1_kvdl_part *part, |
137 | u32 kvdl_index) |
138 | { |
139 | const struct mlxsw_sp1_kvdl_part_info *info = &part->info; |
140 | unsigned int entry_index; |
141 | |
142 | entry_index = mlxsw_sp1_kvdl_to_entry_index(info, kvdl_index); |
143 | __clear_bit(entry_index, part->usage); |
144 | } |
145 | |
146 | static int mlxsw_sp1_kvdl_alloc(struct mlxsw_sp *mlxsw_sp, void *priv, |
147 | enum mlxsw_sp_kvdl_entry_type type, |
148 | unsigned int entry_count, |
149 | u32 *p_entry_index) |
150 | { |
151 | struct mlxsw_sp1_kvdl *kvdl = priv; |
152 | struct mlxsw_sp1_kvdl_part *part; |
153 | |
154 | /* Find partition with smallest allocation size satisfying the |
155 | * requested size. |
156 | */ |
157 | part = mlxsw_sp1_kvdl_alloc_size_part(kvdl, alloc_size: entry_count); |
158 | if (IS_ERR(ptr: part)) |
159 | return PTR_ERR(ptr: part); |
160 | |
161 | return mlxsw_sp1_kvdl_part_alloc(part, p_kvdl_index: p_entry_index); |
162 | } |
163 | |
164 | static void mlxsw_sp1_kvdl_free(struct mlxsw_sp *mlxsw_sp, void *priv, |
165 | enum mlxsw_sp_kvdl_entry_type type, |
166 | unsigned int entry_count, int entry_index) |
167 | { |
168 | struct mlxsw_sp1_kvdl *kvdl = priv; |
169 | struct mlxsw_sp1_kvdl_part *part; |
170 | |
171 | part = mlxsw_sp1_kvdl_index_part(kvdl, kvdl_index: entry_index); |
172 | if (IS_ERR(ptr: part)) |
173 | return; |
174 | mlxsw_sp1_kvdl_part_free(part, kvdl_index: entry_index); |
175 | } |
176 | |
177 | static int mlxsw_sp1_kvdl_alloc_size_query(struct mlxsw_sp *mlxsw_sp, |
178 | void *priv, |
179 | enum mlxsw_sp_kvdl_entry_type type, |
180 | unsigned int entry_count, |
181 | unsigned int *p_alloc_size) |
182 | { |
183 | struct mlxsw_sp1_kvdl *kvdl = priv; |
184 | struct mlxsw_sp1_kvdl_part *part; |
185 | |
186 | part = mlxsw_sp1_kvdl_alloc_size_part(kvdl, alloc_size: entry_count); |
187 | if (IS_ERR(ptr: part)) |
188 | return PTR_ERR(ptr: part); |
189 | |
190 | *p_alloc_size = part->info.alloc_size; |
191 | |
192 | return 0; |
193 | } |
194 | |
195 | static void mlxsw_sp1_kvdl_part_update(struct mlxsw_sp1_kvdl_part *part, |
196 | struct mlxsw_sp1_kvdl_part *part_prev, |
197 | unsigned int size) |
198 | { |
199 | if (!part_prev) { |
200 | part->info.end_index = size - 1; |
201 | } else { |
202 | part->info.start_index = part_prev->info.end_index + 1; |
203 | part->info.end_index = part->info.start_index + size - 1; |
204 | } |
205 | } |
206 | |
207 | static struct mlxsw_sp1_kvdl_part * |
208 | mlxsw_sp1_kvdl_part_init(struct mlxsw_sp *mlxsw_sp, |
209 | const struct mlxsw_sp1_kvdl_part_info *info, |
210 | struct mlxsw_sp1_kvdl_part *part_prev) |
211 | { |
212 | struct devlink *devlink = priv_to_devlink(priv: mlxsw_sp->core); |
213 | struct mlxsw_sp1_kvdl_part *part; |
214 | bool need_update = true; |
215 | unsigned int nr_entries; |
216 | u64 resource_size; |
217 | int err; |
218 | |
219 | err = devl_resource_size_get(devlink, resource_id: info->resource_id, |
220 | p_resource_size: &resource_size); |
221 | if (err) { |
222 | need_update = false; |
223 | resource_size = info->end_index - info->start_index + 1; |
224 | } |
225 | |
226 | nr_entries = div_u64(dividend: resource_size, divisor: info->alloc_size); |
227 | part = kzalloc(struct_size(part, usage, BITS_TO_LONGS(nr_entries)), |
228 | GFP_KERNEL); |
229 | if (!part) |
230 | return ERR_PTR(error: -ENOMEM); |
231 | |
232 | memcpy(&part->info, info, sizeof(part->info)); |
233 | |
234 | if (need_update) |
235 | mlxsw_sp1_kvdl_part_update(part, part_prev, size: resource_size); |
236 | return part; |
237 | } |
238 | |
239 | static void mlxsw_sp1_kvdl_part_fini(struct mlxsw_sp1_kvdl_part *part) |
240 | { |
241 | kfree(objp: part); |
242 | } |
243 | |
244 | static int mlxsw_sp1_kvdl_parts_init(struct mlxsw_sp *mlxsw_sp, |
245 | struct mlxsw_sp1_kvdl *kvdl) |
246 | { |
247 | const struct mlxsw_sp1_kvdl_part_info *info; |
248 | struct mlxsw_sp1_kvdl_part *part_prev = NULL; |
249 | int err, i; |
250 | |
251 | for (i = 0; i < MLXSW_SP1_KVDL_PARTS_INFO_LEN; i++) { |
252 | info = &mlxsw_sp1_kvdl_parts_info[i]; |
253 | kvdl->parts[i] = mlxsw_sp1_kvdl_part_init(mlxsw_sp, info, |
254 | part_prev); |
255 | if (IS_ERR(ptr: kvdl->parts[i])) { |
256 | err = PTR_ERR(ptr: kvdl->parts[i]); |
257 | goto err_kvdl_part_init; |
258 | } |
259 | part_prev = kvdl->parts[i]; |
260 | } |
261 | return 0; |
262 | |
263 | err_kvdl_part_init: |
264 | for (i--; i >= 0; i--) |
265 | mlxsw_sp1_kvdl_part_fini(part: kvdl->parts[i]); |
266 | return err; |
267 | } |
268 | |
269 | static void mlxsw_sp1_kvdl_parts_fini(struct mlxsw_sp1_kvdl *kvdl) |
270 | { |
271 | int i; |
272 | |
273 | for (i = 0; i < MLXSW_SP1_KVDL_PARTS_INFO_LEN; i++) |
274 | mlxsw_sp1_kvdl_part_fini(part: kvdl->parts[i]); |
275 | } |
276 | |
277 | static u64 mlxsw_sp1_kvdl_part_occ(struct mlxsw_sp1_kvdl_part *part) |
278 | { |
279 | const struct mlxsw_sp1_kvdl_part_info *info = &part->info; |
280 | unsigned int nr_entries; |
281 | int bit = -1; |
282 | u64 occ = 0; |
283 | |
284 | nr_entries = (info->end_index - |
285 | info->start_index + 1) / |
286 | info->alloc_size; |
287 | while ((bit = find_next_bit(addr: part->usage, size: nr_entries, offset: bit + 1)) |
288 | < nr_entries) |
289 | occ += info->alloc_size; |
290 | return occ; |
291 | } |
292 | |
293 | static u64 mlxsw_sp1_kvdl_occ_get(void *priv) |
294 | { |
295 | const struct mlxsw_sp1_kvdl *kvdl = priv; |
296 | u64 occ = 0; |
297 | int i; |
298 | |
299 | for (i = 0; i < MLXSW_SP1_KVDL_PARTS_INFO_LEN; i++) |
300 | occ += mlxsw_sp1_kvdl_part_occ(part: kvdl->parts[i]); |
301 | |
302 | return occ; |
303 | } |
304 | |
305 | static u64 mlxsw_sp1_kvdl_single_occ_get(void *priv) |
306 | { |
307 | const struct mlxsw_sp1_kvdl *kvdl = priv; |
308 | struct mlxsw_sp1_kvdl_part *part; |
309 | |
310 | part = kvdl->parts[MLXSW_SP1_KVDL_PART_ID_SINGLE]; |
311 | return mlxsw_sp1_kvdl_part_occ(part); |
312 | } |
313 | |
314 | static u64 mlxsw_sp1_kvdl_chunks_occ_get(void *priv) |
315 | { |
316 | const struct mlxsw_sp1_kvdl *kvdl = priv; |
317 | struct mlxsw_sp1_kvdl_part *part; |
318 | |
319 | part = kvdl->parts[MLXSW_SP1_KVDL_PART_ID_CHUNKS]; |
320 | return mlxsw_sp1_kvdl_part_occ(part); |
321 | } |
322 | |
323 | static u64 mlxsw_sp1_kvdl_large_chunks_occ_get(void *priv) |
324 | { |
325 | const struct mlxsw_sp1_kvdl *kvdl = priv; |
326 | struct mlxsw_sp1_kvdl_part *part; |
327 | |
328 | part = kvdl->parts[MLXSW_SP1_KVDL_PART_ID_LARGE_CHUNKS]; |
329 | return mlxsw_sp1_kvdl_part_occ(part); |
330 | } |
331 | |
332 | static int mlxsw_sp1_kvdl_init(struct mlxsw_sp *mlxsw_sp, void *priv) |
333 | { |
334 | struct devlink *devlink = priv_to_devlink(priv: mlxsw_sp->core); |
335 | struct mlxsw_sp1_kvdl *kvdl = priv; |
336 | int err; |
337 | |
338 | err = mlxsw_sp1_kvdl_parts_init(mlxsw_sp, kvdl); |
339 | if (err) |
340 | return err; |
341 | devl_resource_occ_get_register(devlink, |
342 | resource_id: MLXSW_SP_RESOURCE_KVD_LINEAR, |
343 | occ_get: mlxsw_sp1_kvdl_occ_get, |
344 | occ_get_priv: kvdl); |
345 | devl_resource_occ_get_register(devlink, |
346 | resource_id: MLXSW_SP_RESOURCE_KVD_LINEAR_SINGLE, |
347 | occ_get: mlxsw_sp1_kvdl_single_occ_get, |
348 | occ_get_priv: kvdl); |
349 | devl_resource_occ_get_register(devlink, |
350 | resource_id: MLXSW_SP_RESOURCE_KVD_LINEAR_CHUNKS, |
351 | occ_get: mlxsw_sp1_kvdl_chunks_occ_get, |
352 | occ_get_priv: kvdl); |
353 | devl_resource_occ_get_register(devlink, |
354 | resource_id: MLXSW_SP_RESOURCE_KVD_LINEAR_LARGE_CHUNKS, |
355 | occ_get: mlxsw_sp1_kvdl_large_chunks_occ_get, |
356 | occ_get_priv: kvdl); |
357 | return 0; |
358 | } |
359 | |
360 | static void mlxsw_sp1_kvdl_fini(struct mlxsw_sp *mlxsw_sp, void *priv) |
361 | { |
362 | struct devlink *devlink = priv_to_devlink(priv: mlxsw_sp->core); |
363 | struct mlxsw_sp1_kvdl *kvdl = priv; |
364 | |
365 | devl_resource_occ_get_unregister(devlink, |
366 | resource_id: MLXSW_SP_RESOURCE_KVD_LINEAR_LARGE_CHUNKS); |
367 | devl_resource_occ_get_unregister(devlink, |
368 | resource_id: MLXSW_SP_RESOURCE_KVD_LINEAR_CHUNKS); |
369 | devl_resource_occ_get_unregister(devlink, |
370 | resource_id: MLXSW_SP_RESOURCE_KVD_LINEAR_SINGLE); |
371 | devl_resource_occ_get_unregister(devlink, |
372 | resource_id: MLXSW_SP_RESOURCE_KVD_LINEAR); |
373 | mlxsw_sp1_kvdl_parts_fini(kvdl); |
374 | } |
375 | |
376 | const struct mlxsw_sp_kvdl_ops mlxsw_sp1_kvdl_ops = { |
377 | .priv_size = sizeof(struct mlxsw_sp1_kvdl), |
378 | .init = mlxsw_sp1_kvdl_init, |
379 | .fini = mlxsw_sp1_kvdl_fini, |
380 | .alloc = mlxsw_sp1_kvdl_alloc, |
381 | .free = mlxsw_sp1_kvdl_free, |
382 | .alloc_size_query = mlxsw_sp1_kvdl_alloc_size_query, |
383 | }; |
384 | |
385 | int mlxsw_sp1_kvdl_resources_register(struct mlxsw_core *mlxsw_core) |
386 | { |
387 | struct devlink *devlink = priv_to_devlink(priv: mlxsw_core); |
388 | static struct devlink_resource_size_params size_params; |
389 | u32 kvdl_max_size; |
390 | int err; |
391 | |
392 | kvdl_max_size = MLXSW_CORE_RES_GET(mlxsw_core, KVD_SIZE) - |
393 | MLXSW_CORE_RES_GET(mlxsw_core, KVD_SINGLE_MIN_SIZE) - |
394 | MLXSW_CORE_RES_GET(mlxsw_core, KVD_DOUBLE_MIN_SIZE); |
395 | |
396 | devlink_resource_size_params_init(size_params: &size_params, size_min: 0, size_max: kvdl_max_size, |
397 | MLXSW_SP1_KVDL_SINGLE_ALLOC_SIZE, |
398 | unit: DEVLINK_RESOURCE_UNIT_ENTRY); |
399 | err = devl_resource_register(devlink, MLXSW_SP_RESOURCE_NAME_KVD_LINEAR_SINGLES, |
400 | MLXSW_SP1_KVDL_SINGLE_SIZE, |
401 | resource_id: MLXSW_SP_RESOURCE_KVD_LINEAR_SINGLE, |
402 | parent_resource_id: MLXSW_SP_RESOURCE_KVD_LINEAR, |
403 | size_params: &size_params); |
404 | if (err) |
405 | return err; |
406 | |
407 | devlink_resource_size_params_init(size_params: &size_params, size_min: 0, size_max: kvdl_max_size, |
408 | MLXSW_SP1_KVDL_CHUNKS_ALLOC_SIZE, |
409 | unit: DEVLINK_RESOURCE_UNIT_ENTRY); |
410 | err = devl_resource_register(devlink, MLXSW_SP_RESOURCE_NAME_KVD_LINEAR_CHUNKS, |
411 | MLXSW_SP1_KVDL_CHUNKS_SIZE, |
412 | resource_id: MLXSW_SP_RESOURCE_KVD_LINEAR_CHUNKS, |
413 | parent_resource_id: MLXSW_SP_RESOURCE_KVD_LINEAR, |
414 | size_params: &size_params); |
415 | if (err) |
416 | return err; |
417 | |
418 | devlink_resource_size_params_init(size_params: &size_params, size_min: 0, size_max: kvdl_max_size, |
419 | MLXSW_SP1_KVDL_LARGE_CHUNKS_ALLOC_SIZE, |
420 | unit: DEVLINK_RESOURCE_UNIT_ENTRY); |
421 | err = devl_resource_register(devlink, MLXSW_SP_RESOURCE_NAME_KVD_LINEAR_LARGE_CHUNKS, |
422 | MLXSW_SP1_KVDL_LARGE_CHUNKS_SIZE, |
423 | resource_id: MLXSW_SP_RESOURCE_KVD_LINEAR_LARGE_CHUNKS, |
424 | parent_resource_id: MLXSW_SP_RESOURCE_KVD_LINEAR, |
425 | size_params: &size_params); |
426 | return err; |
427 | } |
428 | |