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(head: &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(dividend: size, divisor: resource->size_params.size_granularity, remainder: &reminder); |
100 | if (reminder) { |
101 | NL_SET_ERR_MSG(extack, "Wrong granularity" ); |
102 | err = -EINVAL; |
103 | } |
104 | |
105 | return err; |
106 | } |
107 | |
108 | int devlink_nl_resource_set_doit(struct sk_buff *skb, struct genl_info *info) |
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(nla: 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(nla: info->attrs[DEVLINK_ATTR_RESOURCE_SIZE]); |
126 | err = devlink_resource_validate_size(resource, size, extack: 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: 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, attrtype: DEVLINK_ATTR_RESOURCE_SIZE_GRAN, |
145 | value: size_params->size_granularity, padattr: DEVLINK_ATTR_PAD) || |
146 | nla_put_u64_64bit(skb, attrtype: DEVLINK_ATTR_RESOURCE_SIZE_MAX, |
147 | value: size_params->size_max, padattr: DEVLINK_ATTR_PAD) || |
148 | nla_put_u64_64bit(skb, attrtype: DEVLINK_ATTR_RESOURCE_SIZE_MIN, |
149 | value: size_params->size_min, padattr: DEVLINK_ATTR_PAD) || |
150 | nla_put_u8(skb, attrtype: DEVLINK_ATTR_RESOURCE_UNIT, value: 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, attrtype: DEVLINK_ATTR_RESOURCE_OCC, |
161 | value: resource->occ_get(resource->occ_get_priv), |
162 | padattr: 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, attrtype: DEVLINK_ATTR_RESOURCE); |
173 | if (!resource_attr) |
174 | return -EMSGSIZE; |
175 | |
176 | if (nla_put_string(skb, attrtype: DEVLINK_ATTR_RESOURCE_NAME, str: resource->name) || |
177 | nla_put_u64_64bit(skb, attrtype: DEVLINK_ATTR_RESOURCE_SIZE, value: resource->size, |
178 | padattr: DEVLINK_ATTR_PAD) || |
179 | nla_put_u64_64bit(skb, attrtype: DEVLINK_ATTR_RESOURCE_ID, value: resource->id, |
180 | padattr: DEVLINK_ATTR_PAD)) |
181 | goto nla_put_failure; |
182 | if (resource->size != resource->size_new && |
183 | nla_put_u64_64bit(skb, attrtype: DEVLINK_ATTR_RESOURCE_SIZE_NEW, |
184 | value: resource->size_new, padattr: 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(head: &resource->resource_list)) |
191 | goto out; |
192 | |
193 | if (nla_put_u8(skb, attrtype: DEVLINK_ATTR_RESOURCE_SIZE_VALID, |
194 | value: resource->size_valid)) |
195 | goto nla_put_failure; |
196 | |
197 | child_resource_attr = nla_nest_start_noflag(skb, |
198 | attrtype: 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, resource: child_resource)) |
204 | goto resource_put_failure; |
205 | } |
206 | |
207 | nla_nest_end(skb, start: child_resource_attr); |
208 | out: |
209 | nla_nest_end(skb, start: resource_attr); |
210 | return 0; |
211 | |
212 | resource_put_failure: |
213 | nla_nest_cancel(skb, start: child_resource_attr); |
214 | nla_put_failure: |
215 | nla_nest_cancel(skb, start: 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(msg: &skb, info); |
236 | if (err) |
237 | return err; |
238 | |
239 | hdr = genlmsg_put(skb, portid: info->snd_portid, seq: info->snd_seq, |
240 | family: &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(msg: skb, devlink)) |
247 | goto nla_put_failure; |
248 | |
249 | resources_attr = nla_nest_start_noflag(skb, |
250 | attrtype: 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, start: resources_attr); |
267 | genlmsg_end(skb, hdr); |
268 | if (incomplete) |
269 | goto start_again; |
270 | send_done: |
271 | nlh = nlmsg_put(skb, portid: info->snd_portid, seq: info->snd_seq, |
272 | NLMSG_DONE, payload: 0, flags: flags | NLM_F_MULTI); |
273 | if (!nlh) { |
274 | err = devlink_nl_msg_reply_and_new(msg: &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 | |
288 | int devlink_nl_resource_dump_doit(struct sk_buff *skb, struct genl_info *info) |
289 | { |
290 | struct devlink *devlink = info->user_ptr[0]; |
291 | |
292 | if (list_empty(head: &devlink->resource_list)) |
293 | return -EOPNOTSUPP; |
294 | |
295 | return devlink_resource_fill(info, cmd: DEVLINK_CMD_RESOURCE_DUMP, flags: 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(size: 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 | resource_id: parent_resource_id); |
364 | if (parent_resource) { |
365 | resource_list = &parent_resource->resource_list; |
366 | resource->parent = parent_resource; |
367 | } else { |
368 | kfree(objp: 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(list: &resource->resource_list); |
381 | list_add_tail(new: &resource->list, head: 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, resource: child_resource); |
428 | list_del(entry: &child_resource->list); |
429 | kfree(objp: 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, resource: child_resource); |
447 | list_del(entry: &child_resource->list); |
448 | kfree(objp: 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); |
580 | |