Commit | Line | Data |
---|---|---|
a9f96007 JP |
1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* | |
3 | * Copyright (c) 2016 Mellanox Technologies. All rights reserved. | |
4 | * Copyright (c) 2016 Jiri Pirko <jiri@mellanox.com> | |
5 | */ | |
6 | ||
7 | #include "devl_internal.h" | |
8 | ||
9 | /** | |
10 | * struct devlink_resource - devlink resource | |
11 | * @name: name of the resource | |
12 | * @id: id, per devlink instance | |
13 | * @size: size of the resource | |
14 | * @size_new: updated size of the resource, reload is needed | |
15 | * @size_valid: valid in case the total size of the resource is valid | |
16 | * including its children | |
17 | * @parent: parent resource | |
18 | * @size_params: size parameters | |
19 | * @list: parent list | |
20 | * @resource_list: list of child resources | |
21 | * @occ_get: occupancy getter callback | |
22 | * @occ_get_priv: occupancy getter callback priv | |
23 | */ | |
24 | struct devlink_resource { | |
25 | const char *name; | |
26 | u64 id; | |
27 | u64 size; | |
28 | u64 size_new; | |
29 | bool size_valid; | |
30 | struct devlink_resource *parent; | |
31 | struct devlink_resource_size_params size_params; | |
32 | struct list_head list; | |
33 | struct list_head resource_list; | |
34 | devlink_resource_occ_get_t *occ_get; | |
35 | void *occ_get_priv; | |
36 | }; | |
37 | ||
38 | static struct devlink_resource * | |
39 | devlink_resource_find(struct devlink *devlink, | |
40 | struct devlink_resource *resource, u64 resource_id) | |
41 | { | |
42 | struct list_head *resource_list; | |
43 | ||
44 | if (resource) | |
45 | resource_list = &resource->resource_list; | |
46 | else | |
47 | resource_list = &devlink->resource_list; | |
48 | ||
49 | list_for_each_entry(resource, resource_list, list) { | |
50 | struct devlink_resource *child_resource; | |
51 | ||
52 | if (resource->id == resource_id) | |
53 | return resource; | |
54 | ||
55 | child_resource = devlink_resource_find(devlink, resource, | |
56 | resource_id); | |
57 | if (child_resource) | |
58 | return child_resource; | |
59 | } | |
60 | return NULL; | |
61 | } | |
62 | ||
63 | static void | |
64 | devlink_resource_validate_children(struct devlink_resource *resource) | |
65 | { | |
66 | struct devlink_resource *child_resource; | |
67 | bool size_valid = true; | |
68 | u64 parts_size = 0; | |
69 | ||
70 | if (list_empty(&resource->resource_list)) | |
71 | goto out; | |
72 | ||
73 | list_for_each_entry(child_resource, &resource->resource_list, list) | |
74 | parts_size += child_resource->size_new; | |
75 | ||
76 | if (parts_size > resource->size_new) | |
77 | size_valid = false; | |
78 | out: | |
79 | resource->size_valid = size_valid; | |
80 | } | |
81 | ||
82 | static int | |
83 | devlink_resource_validate_size(struct devlink_resource *resource, u64 size, | |
84 | struct netlink_ext_ack *extack) | |
85 | { | |
86 | u64 reminder; | |
87 | int err = 0; | |
88 | ||
89 | if (size > resource->size_params.size_max) { | |
90 | NL_SET_ERR_MSG(extack, "Size larger than maximum"); | |
91 | err = -EINVAL; | |
92 | } | |
93 | ||
94 | if (size < resource->size_params.size_min) { | |
95 | NL_SET_ERR_MSG(extack, "Size smaller than minimum"); | |
96 | err = -EINVAL; | |
97 | } | |
98 | ||
99 | div64_u64_rem(size, resource->size_params.size_granularity, &reminder); | |
100 | if (reminder) { | |
101 | NL_SET_ERR_MSG(extack, "Wrong granularity"); | |
102 | err = -EINVAL; | |
103 | } | |
104 | ||
105 | return err; | |
106 | } | |
107 | ||
53590934 | 108 | int devlink_nl_resource_set_doit(struct sk_buff *skb, struct genl_info *info) |
a9f96007 JP |
109 | { |
110 | struct devlink *devlink = info->user_ptr[0]; | |
111 | struct devlink_resource *resource; | |
112 | u64 resource_id; | |
113 | u64 size; | |
114 | int err; | |
115 | ||
116 | if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_RESOURCE_ID) || | |
117 | GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_RESOURCE_SIZE)) | |
118 | return -EINVAL; | |
119 | resource_id = nla_get_u64(info->attrs[DEVLINK_ATTR_RESOURCE_ID]); | |
120 | ||
121 | resource = devlink_resource_find(devlink, NULL, resource_id); | |
122 | if (!resource) | |
123 | return -EINVAL; | |
124 | ||
125 | size = nla_get_u64(info->attrs[DEVLINK_ATTR_RESOURCE_SIZE]); | |
126 | err = devlink_resource_validate_size(resource, size, info->extack); | |
127 | if (err) | |
128 | return err; | |
129 | ||
130 | resource->size_new = size; | |
131 | devlink_resource_validate_children(resource); | |
132 | if (resource->parent) | |
133 | devlink_resource_validate_children(resource->parent); | |
134 | return 0; | |
135 | } | |
136 | ||
137 | static int | |
138 | devlink_resource_size_params_put(struct devlink_resource *resource, | |
139 | struct sk_buff *skb) | |
140 | { | |
141 | struct devlink_resource_size_params *size_params; | |
142 | ||
143 | size_params = &resource->size_params; | |
144 | if (nla_put_u64_64bit(skb, DEVLINK_ATTR_RESOURCE_SIZE_GRAN, | |
145 | size_params->size_granularity, DEVLINK_ATTR_PAD) || | |
146 | nla_put_u64_64bit(skb, DEVLINK_ATTR_RESOURCE_SIZE_MAX, | |
147 | size_params->size_max, DEVLINK_ATTR_PAD) || | |
148 | nla_put_u64_64bit(skb, DEVLINK_ATTR_RESOURCE_SIZE_MIN, | |
149 | size_params->size_min, DEVLINK_ATTR_PAD) || | |
150 | nla_put_u8(skb, DEVLINK_ATTR_RESOURCE_UNIT, size_params->unit)) | |
151 | return -EMSGSIZE; | |
152 | return 0; | |
153 | } | |
154 | ||
155 | static int devlink_resource_occ_put(struct devlink_resource *resource, | |
156 | struct sk_buff *skb) | |
157 | { | |
158 | if (!resource->occ_get) | |
159 | return 0; | |
160 | return nla_put_u64_64bit(skb, DEVLINK_ATTR_RESOURCE_OCC, | |
161 | resource->occ_get(resource->occ_get_priv), | |
162 | DEVLINK_ATTR_PAD); | |
163 | } | |
164 | ||
165 | static int devlink_resource_put(struct devlink *devlink, struct sk_buff *skb, | |
166 | struct devlink_resource *resource) | |
167 | { | |
168 | struct devlink_resource *child_resource; | |
169 | struct nlattr *child_resource_attr; | |
170 | struct nlattr *resource_attr; | |
171 | ||
172 | resource_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_RESOURCE); | |
173 | if (!resource_attr) | |
174 | return -EMSGSIZE; | |
175 | ||
176 | if (nla_put_string(skb, DEVLINK_ATTR_RESOURCE_NAME, resource->name) || | |
177 | nla_put_u64_64bit(skb, DEVLINK_ATTR_RESOURCE_SIZE, resource->size, | |
178 | DEVLINK_ATTR_PAD) || | |
179 | nla_put_u64_64bit(skb, DEVLINK_ATTR_RESOURCE_ID, resource->id, | |
180 | DEVLINK_ATTR_PAD)) | |
181 | goto nla_put_failure; | |
182 | if (resource->size != resource->size_new && | |
183 | nla_put_u64_64bit(skb, DEVLINK_ATTR_RESOURCE_SIZE_NEW, | |
184 | resource->size_new, DEVLINK_ATTR_PAD)) | |
185 | goto nla_put_failure; | |
186 | if (devlink_resource_occ_put(resource, skb)) | |
187 | goto nla_put_failure; | |
188 | if (devlink_resource_size_params_put(resource, skb)) | |
189 | goto nla_put_failure; | |
190 | if (list_empty(&resource->resource_list)) | |
191 | goto out; | |
192 | ||
193 | if (nla_put_u8(skb, DEVLINK_ATTR_RESOURCE_SIZE_VALID, | |
194 | resource->size_valid)) | |
195 | goto nla_put_failure; | |
196 | ||
197 | child_resource_attr = nla_nest_start_noflag(skb, | |
198 | DEVLINK_ATTR_RESOURCE_LIST); | |
199 | if (!child_resource_attr) | |
200 | goto nla_put_failure; | |
201 | ||
202 | list_for_each_entry(child_resource, &resource->resource_list, list) { | |
203 | if (devlink_resource_put(devlink, skb, child_resource)) | |
204 | goto resource_put_failure; | |
205 | } | |
206 | ||
207 | nla_nest_end(skb, child_resource_attr); | |
208 | out: | |
209 | nla_nest_end(skb, resource_attr); | |
210 | return 0; | |
211 | ||
212 | resource_put_failure: | |
213 | nla_nest_cancel(skb, child_resource_attr); | |
214 | nla_put_failure: | |
215 | nla_nest_cancel(skb, resource_attr); | |
216 | return -EMSGSIZE; | |
217 | } | |
218 | ||
219 | static int devlink_resource_fill(struct genl_info *info, | |
220 | enum devlink_command cmd, int flags) | |
221 | { | |
222 | struct devlink *devlink = info->user_ptr[0]; | |
223 | struct devlink_resource *resource; | |
224 | struct nlattr *resources_attr; | |
225 | struct sk_buff *skb = NULL; | |
226 | struct nlmsghdr *nlh; | |
227 | bool incomplete; | |
228 | void *hdr; | |
229 | int i; | |
230 | int err; | |
231 | ||
232 | resource = list_first_entry(&devlink->resource_list, | |
233 | struct devlink_resource, list); | |
234 | start_again: | |
235 | err = devlink_nl_msg_reply_and_new(&skb, info); | |
236 | if (err) | |
237 | return err; | |
238 | ||
239 | hdr = genlmsg_put(skb, info->snd_portid, info->snd_seq, | |
240 | &devlink_nl_family, NLM_F_MULTI, cmd); | |
241 | if (!hdr) { | |
242 | nlmsg_free(skb); | |
243 | return -EMSGSIZE; | |
244 | } | |
245 | ||
246 | if (devlink_nl_put_handle(skb, devlink)) | |
247 | goto nla_put_failure; | |
248 | ||
249 | resources_attr = nla_nest_start_noflag(skb, | |
250 | DEVLINK_ATTR_RESOURCE_LIST); | |
251 | if (!resources_attr) | |
252 | goto nla_put_failure; | |
253 | ||
254 | incomplete = false; | |
255 | i = 0; | |
256 | list_for_each_entry_from(resource, &devlink->resource_list, list) { | |
257 | err = devlink_resource_put(devlink, skb, resource); | |
258 | if (err) { | |
259 | if (!i) | |
260 | goto err_resource_put; | |
261 | incomplete = true; | |
262 | break; | |
263 | } | |
264 | i++; | |
265 | } | |
266 | nla_nest_end(skb, resources_attr); | |
267 | genlmsg_end(skb, hdr); | |
268 | if (incomplete) | |
269 | goto start_again; | |
270 | send_done: | |
271 | nlh = nlmsg_put(skb, info->snd_portid, info->snd_seq, | |
272 | NLMSG_DONE, 0, flags | NLM_F_MULTI); | |
273 | if (!nlh) { | |
274 | err = devlink_nl_msg_reply_and_new(&skb, info); | |
275 | if (err) | |
276 | return err; | |
277 | goto send_done; | |
278 | } | |
279 | return genlmsg_reply(skb, info); | |
280 | ||
281 | nla_put_failure: | |
282 | err = -EMSGSIZE; | |
283 | err_resource_put: | |
284 | nlmsg_free(skb); | |
285 | return err; | |
286 | } | |
287 | ||
53590934 | 288 | int devlink_nl_resource_dump_doit(struct sk_buff *skb, struct genl_info *info) |
a9f96007 JP |
289 | { |
290 | struct devlink *devlink = info->user_ptr[0]; | |
291 | ||
292 | if (list_empty(&devlink->resource_list)) | |
293 | return -EOPNOTSUPP; | |
294 | ||
295 | return devlink_resource_fill(info, DEVLINK_CMD_RESOURCE_DUMP, 0); | |
296 | } | |
297 | ||
298 | int devlink_resources_validate(struct devlink *devlink, | |
299 | struct devlink_resource *resource, | |
300 | struct genl_info *info) | |
301 | { | |
302 | struct list_head *resource_list; | |
303 | int err = 0; | |
304 | ||
305 | if (resource) | |
306 | resource_list = &resource->resource_list; | |
307 | else | |
308 | resource_list = &devlink->resource_list; | |
309 | ||
310 | list_for_each_entry(resource, resource_list, list) { | |
311 | if (!resource->size_valid) | |
312 | return -EINVAL; | |
313 | err = devlink_resources_validate(devlink, resource, info); | |
314 | if (err) | |
315 | return err; | |
316 | } | |
317 | return err; | |
318 | } | |
319 | ||
320 | /** | |
321 | * devl_resource_register - devlink resource register | |
322 | * | |
323 | * @devlink: devlink | |
324 | * @resource_name: resource's name | |
325 | * @resource_size: resource's size | |
326 | * @resource_id: resource's id | |
327 | * @parent_resource_id: resource's parent id | |
328 | * @size_params: size parameters | |
329 | * | |
330 | * Generic resources should reuse the same names across drivers. | |
331 | * Please see the generic resources list at: | |
332 | * Documentation/networking/devlink/devlink-resource.rst | |
333 | */ | |
334 | int devl_resource_register(struct devlink *devlink, | |
335 | const char *resource_name, | |
336 | u64 resource_size, | |
337 | u64 resource_id, | |
338 | u64 parent_resource_id, | |
339 | const struct devlink_resource_size_params *size_params) | |
340 | { | |
341 | struct devlink_resource *resource; | |
342 | struct list_head *resource_list; | |
343 | bool top_hierarchy; | |
344 | ||
345 | lockdep_assert_held(&devlink->lock); | |
346 | ||
347 | top_hierarchy = parent_resource_id == DEVLINK_RESOURCE_ID_PARENT_TOP; | |
348 | ||
349 | resource = devlink_resource_find(devlink, NULL, resource_id); | |
350 | if (resource) | |
351 | return -EINVAL; | |
352 | ||
353 | resource = kzalloc(sizeof(*resource), GFP_KERNEL); | |
354 | if (!resource) | |
355 | return -ENOMEM; | |
356 | ||
357 | if (top_hierarchy) { | |
358 | resource_list = &devlink->resource_list; | |
359 | } else { | |
360 | struct devlink_resource *parent_resource; | |
361 | ||
362 | parent_resource = devlink_resource_find(devlink, NULL, | |
363 | parent_resource_id); | |
364 | if (parent_resource) { | |
365 | resource_list = &parent_resource->resource_list; | |
366 | resource->parent = parent_resource; | |
367 | } else { | |
368 | kfree(resource); | |
369 | return -EINVAL; | |
370 | } | |
371 | } | |
372 | ||
373 | resource->name = resource_name; | |
374 | resource->size = resource_size; | |
375 | resource->size_new = resource_size; | |
376 | resource->id = resource_id; | |
377 | resource->size_valid = true; | |
378 | memcpy(&resource->size_params, size_params, | |
379 | sizeof(resource->size_params)); | |
380 | INIT_LIST_HEAD(&resource->resource_list); | |
381 | list_add_tail(&resource->list, resource_list); | |
382 | ||
383 | return 0; | |
384 | } | |
385 | EXPORT_SYMBOL_GPL(devl_resource_register); | |
386 | ||
387 | /** | |
388 | * devlink_resource_register - devlink resource register | |
389 | * | |
390 | * @devlink: devlink | |
391 | * @resource_name: resource's name | |
392 | * @resource_size: resource's size | |
393 | * @resource_id: resource's id | |
394 | * @parent_resource_id: resource's parent id | |
395 | * @size_params: size parameters | |
396 | * | |
397 | * Generic resources should reuse the same names across drivers. | |
398 | * Please see the generic resources list at: | |
399 | * Documentation/networking/devlink/devlink-resource.rst | |
400 | * | |
401 | * Context: Takes and release devlink->lock <mutex>. | |
402 | */ | |
403 | int devlink_resource_register(struct devlink *devlink, | |
404 | const char *resource_name, | |
405 | u64 resource_size, | |
406 | u64 resource_id, | |
407 | u64 parent_resource_id, | |
408 | const struct devlink_resource_size_params *size_params) | |
409 | { | |
410 | int err; | |
411 | ||
412 | devl_lock(devlink); | |
413 | err = devl_resource_register(devlink, resource_name, resource_size, | |
414 | resource_id, parent_resource_id, size_params); | |
415 | devl_unlock(devlink); | |
416 | return err; | |
417 | } | |
418 | EXPORT_SYMBOL_GPL(devlink_resource_register); | |
419 | ||
420 | static void devlink_resource_unregister(struct devlink *devlink, | |
421 | struct devlink_resource *resource) | |
422 | { | |
423 | struct devlink_resource *tmp, *child_resource; | |
424 | ||
425 | list_for_each_entry_safe(child_resource, tmp, &resource->resource_list, | |
426 | list) { | |
427 | devlink_resource_unregister(devlink, child_resource); | |
428 | list_del(&child_resource->list); | |
429 | kfree(child_resource); | |
430 | } | |
431 | } | |
432 | ||
433 | /** | |
434 | * devl_resources_unregister - free all resources | |
435 | * | |
436 | * @devlink: devlink | |
437 | */ | |
438 | void devl_resources_unregister(struct devlink *devlink) | |
439 | { | |
440 | struct devlink_resource *tmp, *child_resource; | |
441 | ||
442 | lockdep_assert_held(&devlink->lock); | |
443 | ||
444 | list_for_each_entry_safe(child_resource, tmp, &devlink->resource_list, | |
445 | list) { | |
446 | devlink_resource_unregister(devlink, child_resource); | |
447 | list_del(&child_resource->list); | |
448 | kfree(child_resource); | |
449 | } | |
450 | } | |
451 | EXPORT_SYMBOL_GPL(devl_resources_unregister); | |
452 | ||
453 | /** | |
454 | * devlink_resources_unregister - free all resources | |
455 | * | |
456 | * @devlink: devlink | |
457 | * | |
458 | * Context: Takes and release devlink->lock <mutex>. | |
459 | */ | |
460 | void devlink_resources_unregister(struct devlink *devlink) | |
461 | { | |
462 | devl_lock(devlink); | |
463 | devl_resources_unregister(devlink); | |
464 | devl_unlock(devlink); | |
465 | } | |
466 | EXPORT_SYMBOL_GPL(devlink_resources_unregister); | |
467 | ||
468 | /** | |
469 | * devl_resource_size_get - get and update size | |
470 | * | |
471 | * @devlink: devlink | |
472 | * @resource_id: the requested resource id | |
473 | * @p_resource_size: ptr to update | |
474 | */ | |
475 | int devl_resource_size_get(struct devlink *devlink, | |
476 | u64 resource_id, | |
477 | u64 *p_resource_size) | |
478 | { | |
479 | struct devlink_resource *resource; | |
480 | ||
481 | lockdep_assert_held(&devlink->lock); | |
482 | ||
483 | resource = devlink_resource_find(devlink, NULL, resource_id); | |
484 | if (!resource) | |
485 | return -EINVAL; | |
486 | *p_resource_size = resource->size_new; | |
487 | resource->size = resource->size_new; | |
488 | return 0; | |
489 | } | |
490 | EXPORT_SYMBOL_GPL(devl_resource_size_get); | |
491 | ||
492 | /** | |
493 | * devl_resource_occ_get_register - register occupancy getter | |
494 | * | |
495 | * @devlink: devlink | |
496 | * @resource_id: resource id | |
497 | * @occ_get: occupancy getter callback | |
498 | * @occ_get_priv: occupancy getter callback priv | |
499 | */ | |
500 | void devl_resource_occ_get_register(struct devlink *devlink, | |
501 | u64 resource_id, | |
502 | devlink_resource_occ_get_t *occ_get, | |
503 | void *occ_get_priv) | |
504 | { | |
505 | struct devlink_resource *resource; | |
506 | ||
507 | lockdep_assert_held(&devlink->lock); | |
508 | ||
509 | resource = devlink_resource_find(devlink, NULL, resource_id); | |
510 | if (WARN_ON(!resource)) | |
511 | return; | |
512 | WARN_ON(resource->occ_get); | |
513 | ||
514 | resource->occ_get = occ_get; | |
515 | resource->occ_get_priv = occ_get_priv; | |
516 | } | |
517 | EXPORT_SYMBOL_GPL(devl_resource_occ_get_register); | |
518 | ||
519 | /** | |
520 | * devlink_resource_occ_get_register - register occupancy getter | |
521 | * | |
522 | * @devlink: devlink | |
523 | * @resource_id: resource id | |
524 | * @occ_get: occupancy getter callback | |
525 | * @occ_get_priv: occupancy getter callback priv | |
526 | * | |
527 | * Context: Takes and release devlink->lock <mutex>. | |
528 | */ | |
529 | void devlink_resource_occ_get_register(struct devlink *devlink, | |
530 | u64 resource_id, | |
531 | devlink_resource_occ_get_t *occ_get, | |
532 | void *occ_get_priv) | |
533 | { | |
534 | devl_lock(devlink); | |
535 | devl_resource_occ_get_register(devlink, resource_id, | |
536 | occ_get, occ_get_priv); | |
537 | devl_unlock(devlink); | |
538 | } | |
539 | EXPORT_SYMBOL_GPL(devlink_resource_occ_get_register); | |
540 | ||
541 | /** | |
542 | * devl_resource_occ_get_unregister - unregister occupancy getter | |
543 | * | |
544 | * @devlink: devlink | |
545 | * @resource_id: resource id | |
546 | */ | |
547 | void devl_resource_occ_get_unregister(struct devlink *devlink, | |
548 | u64 resource_id) | |
549 | { | |
550 | struct devlink_resource *resource; | |
551 | ||
552 | lockdep_assert_held(&devlink->lock); | |
553 | ||
554 | resource = devlink_resource_find(devlink, NULL, resource_id); | |
555 | if (WARN_ON(!resource)) | |
556 | return; | |
557 | WARN_ON(!resource->occ_get); | |
558 | ||
559 | resource->occ_get = NULL; | |
560 | resource->occ_get_priv = NULL; | |
561 | } | |
562 | EXPORT_SYMBOL_GPL(devl_resource_occ_get_unregister); | |
563 | ||
564 | /** | |
565 | * devlink_resource_occ_get_unregister - unregister occupancy getter | |
566 | * | |
567 | * @devlink: devlink | |
568 | * @resource_id: resource id | |
569 | * | |
570 | * Context: Takes and release devlink->lock <mutex>. | |
571 | */ | |
572 | void devlink_resource_occ_get_unregister(struct devlink *devlink, | |
573 | u64 resource_id) | |
574 | { | |
575 | devl_lock(devlink); | |
576 | devl_resource_occ_get_unregister(devlink, resource_id); | |
577 | devl_unlock(devlink); | |
578 | } | |
579 | EXPORT_SYMBOL_GPL(devlink_resource_occ_get_unregister); |