|  | // SPDX-License-Identifier: GPL-2.0-or-later | 
|  | /* | 
|  | * Copyright (c) 2016 Mellanox Technologies. All rights reserved. | 
|  | * Copyright (c) 2016 Jiri Pirko <jiri@mellanox.com> | 
|  | */ | 
|  |  | 
|  | #include "devl_internal.h" | 
|  |  | 
|  | /** | 
|  | * struct devlink_resource - devlink resource | 
|  | * @name: name of the resource | 
|  | * @id: id, per devlink instance | 
|  | * @size: size of the resource | 
|  | * @size_new: updated size of the resource, reload is needed | 
|  | * @size_valid: valid in case the total size of the resource is valid | 
|  | *              including its children | 
|  | * @parent: parent resource | 
|  | * @size_params: size parameters | 
|  | * @list: parent list | 
|  | * @resource_list: list of child resources | 
|  | * @occ_get: occupancy getter callback | 
|  | * @occ_get_priv: occupancy getter callback priv | 
|  | */ | 
|  | struct devlink_resource { | 
|  | const char *name; | 
|  | u64 id; | 
|  | u64 size; | 
|  | u64 size_new; | 
|  | bool size_valid; | 
|  | struct devlink_resource *parent; | 
|  | struct devlink_resource_size_params size_params; | 
|  | struct list_head list; | 
|  | struct list_head resource_list; | 
|  | devlink_resource_occ_get_t *occ_get; | 
|  | void *occ_get_priv; | 
|  | }; | 
|  |  | 
|  | static struct devlink_resource * | 
|  | devlink_resource_find(struct devlink *devlink, | 
|  | struct devlink_resource *resource, u64 resource_id) | 
|  | { | 
|  | struct list_head *resource_list; | 
|  |  | 
|  | if (resource) | 
|  | resource_list = &resource->resource_list; | 
|  | else | 
|  | resource_list = &devlink->resource_list; | 
|  |  | 
|  | list_for_each_entry(resource, resource_list, list) { | 
|  | struct devlink_resource *child_resource; | 
|  |  | 
|  | if (resource->id == resource_id) | 
|  | return resource; | 
|  |  | 
|  | child_resource = devlink_resource_find(devlink, resource, | 
|  | resource_id); | 
|  | if (child_resource) | 
|  | return child_resource; | 
|  | } | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static void | 
|  | devlink_resource_validate_children(struct devlink_resource *resource) | 
|  | { | 
|  | struct devlink_resource *child_resource; | 
|  | bool size_valid = true; | 
|  | u64 parts_size = 0; | 
|  |  | 
|  | if (list_empty(&resource->resource_list)) | 
|  | goto out; | 
|  |  | 
|  | list_for_each_entry(child_resource, &resource->resource_list, list) | 
|  | parts_size += child_resource->size_new; | 
|  |  | 
|  | if (parts_size > resource->size_new) | 
|  | size_valid = false; | 
|  | out: | 
|  | resource->size_valid = size_valid; | 
|  | } | 
|  |  | 
|  | static int | 
|  | devlink_resource_validate_size(struct devlink_resource *resource, u64 size, | 
|  | struct netlink_ext_ack *extack) | 
|  | { | 
|  | u64 reminder; | 
|  | int err = 0; | 
|  |  | 
|  | if (size > resource->size_params.size_max) { | 
|  | NL_SET_ERR_MSG(extack, "Size larger than maximum"); | 
|  | err = -EINVAL; | 
|  | } | 
|  |  | 
|  | if (size < resource->size_params.size_min) { | 
|  | NL_SET_ERR_MSG(extack, "Size smaller than minimum"); | 
|  | err = -EINVAL; | 
|  | } | 
|  |  | 
|  | div64_u64_rem(size, resource->size_params.size_granularity, &reminder); | 
|  | if (reminder) { | 
|  | NL_SET_ERR_MSG(extack, "Wrong granularity"); | 
|  | err = -EINVAL; | 
|  | } | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | int devlink_nl_resource_set_doit(struct sk_buff *skb, struct genl_info *info) | 
|  | { | 
|  | struct devlink *devlink = info->user_ptr[0]; | 
|  | struct devlink_resource *resource; | 
|  | u64 resource_id; | 
|  | u64 size; | 
|  | int err; | 
|  |  | 
|  | if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_RESOURCE_ID) || | 
|  | GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_RESOURCE_SIZE)) | 
|  | return -EINVAL; | 
|  | resource_id = nla_get_u64(info->attrs[DEVLINK_ATTR_RESOURCE_ID]); | 
|  |  | 
|  | resource = devlink_resource_find(devlink, NULL, resource_id); | 
|  | if (!resource) | 
|  | return -EINVAL; | 
|  |  | 
|  | size = nla_get_u64(info->attrs[DEVLINK_ATTR_RESOURCE_SIZE]); | 
|  | err = devlink_resource_validate_size(resource, size, info->extack); | 
|  | if (err) | 
|  | return err; | 
|  |  | 
|  | resource->size_new = size; | 
|  | devlink_resource_validate_children(resource); | 
|  | if (resource->parent) | 
|  | devlink_resource_validate_children(resource->parent); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int | 
|  | devlink_resource_size_params_put(struct devlink_resource *resource, | 
|  | struct sk_buff *skb) | 
|  | { | 
|  | struct devlink_resource_size_params *size_params; | 
|  |  | 
|  | size_params = &resource->size_params; | 
|  | if (devlink_nl_put_u64(skb, DEVLINK_ATTR_RESOURCE_SIZE_GRAN, | 
|  | size_params->size_granularity) || | 
|  | devlink_nl_put_u64(skb, DEVLINK_ATTR_RESOURCE_SIZE_MAX, | 
|  | size_params->size_max) || | 
|  | devlink_nl_put_u64(skb, DEVLINK_ATTR_RESOURCE_SIZE_MIN, | 
|  | size_params->size_min) || | 
|  | nla_put_u8(skb, DEVLINK_ATTR_RESOURCE_UNIT, size_params->unit)) | 
|  | return -EMSGSIZE; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int devlink_resource_occ_put(struct devlink_resource *resource, | 
|  | struct sk_buff *skb) | 
|  | { | 
|  | if (!resource->occ_get) | 
|  | return 0; | 
|  | return devlink_nl_put_u64(skb, DEVLINK_ATTR_RESOURCE_OCC, | 
|  | resource->occ_get(resource->occ_get_priv)); | 
|  | } | 
|  |  | 
|  | static int devlink_resource_put(struct devlink *devlink, struct sk_buff *skb, | 
|  | struct devlink_resource *resource) | 
|  | { | 
|  | struct devlink_resource *child_resource; | 
|  | struct nlattr *child_resource_attr; | 
|  | struct nlattr *resource_attr; | 
|  |  | 
|  | resource_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_RESOURCE); | 
|  | if (!resource_attr) | 
|  | return -EMSGSIZE; | 
|  |  | 
|  | if (nla_put_string(skb, DEVLINK_ATTR_RESOURCE_NAME, resource->name) || | 
|  | devlink_nl_put_u64(skb, DEVLINK_ATTR_RESOURCE_SIZE, resource->size) || | 
|  | devlink_nl_put_u64(skb, DEVLINK_ATTR_RESOURCE_ID, resource->id)) | 
|  | goto nla_put_failure; | 
|  | if (resource->size != resource->size_new && | 
|  | devlink_nl_put_u64(skb, DEVLINK_ATTR_RESOURCE_SIZE_NEW, | 
|  | resource->size_new)) | 
|  | goto nla_put_failure; | 
|  | if (devlink_resource_occ_put(resource, skb)) | 
|  | goto nla_put_failure; | 
|  | if (devlink_resource_size_params_put(resource, skb)) | 
|  | goto nla_put_failure; | 
|  | if (list_empty(&resource->resource_list)) | 
|  | goto out; | 
|  |  | 
|  | if (nla_put_u8(skb, DEVLINK_ATTR_RESOURCE_SIZE_VALID, | 
|  | resource->size_valid)) | 
|  | goto nla_put_failure; | 
|  |  | 
|  | child_resource_attr = nla_nest_start_noflag(skb, | 
|  | DEVLINK_ATTR_RESOURCE_LIST); | 
|  | if (!child_resource_attr) | 
|  | goto nla_put_failure; | 
|  |  | 
|  | list_for_each_entry(child_resource, &resource->resource_list, list) { | 
|  | if (devlink_resource_put(devlink, skb, child_resource)) | 
|  | goto resource_put_failure; | 
|  | } | 
|  |  | 
|  | nla_nest_end(skb, child_resource_attr); | 
|  | out: | 
|  | nla_nest_end(skb, resource_attr); | 
|  | return 0; | 
|  |  | 
|  | resource_put_failure: | 
|  | nla_nest_cancel(skb, child_resource_attr); | 
|  | nla_put_failure: | 
|  | nla_nest_cancel(skb, resource_attr); | 
|  | return -EMSGSIZE; | 
|  | } | 
|  |  | 
|  | static int devlink_resource_fill(struct genl_info *info, | 
|  | enum devlink_command cmd, int flags) | 
|  | { | 
|  | struct devlink *devlink = info->user_ptr[0]; | 
|  | struct devlink_resource *resource; | 
|  | struct nlattr *resources_attr; | 
|  | struct sk_buff *skb = NULL; | 
|  | struct nlmsghdr *nlh; | 
|  | bool incomplete; | 
|  | void *hdr; | 
|  | int i; | 
|  | int err; | 
|  |  | 
|  | resource = list_first_entry(&devlink->resource_list, | 
|  | struct devlink_resource, list); | 
|  | start_again: | 
|  | err = devlink_nl_msg_reply_and_new(&skb, info); | 
|  | if (err) | 
|  | return err; | 
|  |  | 
|  | hdr = genlmsg_put(skb, info->snd_portid, info->snd_seq, | 
|  | &devlink_nl_family, NLM_F_MULTI, cmd); | 
|  | if (!hdr) { | 
|  | nlmsg_free(skb); | 
|  | return -EMSGSIZE; | 
|  | } | 
|  |  | 
|  | if (devlink_nl_put_handle(skb, devlink)) | 
|  | goto nla_put_failure; | 
|  |  | 
|  | resources_attr = nla_nest_start_noflag(skb, | 
|  | DEVLINK_ATTR_RESOURCE_LIST); | 
|  | if (!resources_attr) | 
|  | goto nla_put_failure; | 
|  |  | 
|  | incomplete = false; | 
|  | i = 0; | 
|  | list_for_each_entry_from(resource, &devlink->resource_list, list) { | 
|  | err = devlink_resource_put(devlink, skb, resource); | 
|  | if (err) { | 
|  | if (!i) | 
|  | goto err_resource_put; | 
|  | incomplete = true; | 
|  | break; | 
|  | } | 
|  | i++; | 
|  | } | 
|  | nla_nest_end(skb, resources_attr); | 
|  | genlmsg_end(skb, hdr); | 
|  | if (incomplete) | 
|  | goto start_again; | 
|  | send_done: | 
|  | nlh = nlmsg_put(skb, info->snd_portid, info->snd_seq, | 
|  | NLMSG_DONE, 0, flags | NLM_F_MULTI); | 
|  | if (!nlh) { | 
|  | err = devlink_nl_msg_reply_and_new(&skb, info); | 
|  | if (err) | 
|  | return err; | 
|  | goto send_done; | 
|  | } | 
|  | return genlmsg_reply(skb, info); | 
|  |  | 
|  | nla_put_failure: | 
|  | err = -EMSGSIZE; | 
|  | err_resource_put: | 
|  | nlmsg_free(skb); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | int devlink_nl_resource_dump_doit(struct sk_buff *skb, struct genl_info *info) | 
|  | { | 
|  | struct devlink *devlink = info->user_ptr[0]; | 
|  |  | 
|  | if (list_empty(&devlink->resource_list)) | 
|  | return -EOPNOTSUPP; | 
|  |  | 
|  | return devlink_resource_fill(info, DEVLINK_CMD_RESOURCE_DUMP, 0); | 
|  | } | 
|  |  | 
|  | int devlink_resources_validate(struct devlink *devlink, | 
|  | struct devlink_resource *resource, | 
|  | struct genl_info *info) | 
|  | { | 
|  | struct list_head *resource_list; | 
|  | int err = 0; | 
|  |  | 
|  | if (resource) | 
|  | resource_list = &resource->resource_list; | 
|  | else | 
|  | resource_list = &devlink->resource_list; | 
|  |  | 
|  | list_for_each_entry(resource, resource_list, list) { | 
|  | if (!resource->size_valid) | 
|  | return -EINVAL; | 
|  | err = devlink_resources_validate(devlink, resource, info); | 
|  | if (err) | 
|  | return err; | 
|  | } | 
|  | return err; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * devl_resource_register - devlink resource register | 
|  | * | 
|  | * @devlink: devlink | 
|  | * @resource_name: resource's name | 
|  | * @resource_size: resource's size | 
|  | * @resource_id: resource's id | 
|  | * @parent_resource_id: resource's parent id | 
|  | * @size_params: size parameters | 
|  | * | 
|  | * Generic resources should reuse the same names across drivers. | 
|  | * Please see the generic resources list at: | 
|  | * Documentation/networking/devlink/devlink-resource.rst | 
|  | */ | 
|  | int devl_resource_register(struct devlink *devlink, | 
|  | const char *resource_name, | 
|  | u64 resource_size, | 
|  | u64 resource_id, | 
|  | u64 parent_resource_id, | 
|  | const struct devlink_resource_size_params *size_params) | 
|  | { | 
|  | struct devlink_resource *resource; | 
|  | struct list_head *resource_list; | 
|  | bool top_hierarchy; | 
|  |  | 
|  | lockdep_assert_held(&devlink->lock); | 
|  |  | 
|  | top_hierarchy = parent_resource_id == DEVLINK_RESOURCE_ID_PARENT_TOP; | 
|  |  | 
|  | resource = devlink_resource_find(devlink, NULL, resource_id); | 
|  | if (resource) | 
|  | return -EEXIST; | 
|  |  | 
|  | resource = kzalloc(sizeof(*resource), GFP_KERNEL); | 
|  | if (!resource) | 
|  | return -ENOMEM; | 
|  |  | 
|  | if (top_hierarchy) { | 
|  | resource_list = &devlink->resource_list; | 
|  | } else { | 
|  | struct devlink_resource *parent_resource; | 
|  |  | 
|  | parent_resource = devlink_resource_find(devlink, NULL, | 
|  | parent_resource_id); | 
|  | if (parent_resource) { | 
|  | resource_list = &parent_resource->resource_list; | 
|  | resource->parent = parent_resource; | 
|  | } else { | 
|  | kfree(resource); | 
|  | return -EINVAL; | 
|  | } | 
|  | } | 
|  |  | 
|  | resource->name = resource_name; | 
|  | resource->size = resource_size; | 
|  | resource->size_new = resource_size; | 
|  | resource->id = resource_id; | 
|  | resource->size_valid = true; | 
|  | memcpy(&resource->size_params, size_params, | 
|  | sizeof(resource->size_params)); | 
|  | INIT_LIST_HEAD(&resource->resource_list); | 
|  | list_add_tail(&resource->list, resource_list); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(devl_resource_register); | 
|  |  | 
|  | static void devlink_resource_unregister(struct devlink *devlink, | 
|  | struct devlink_resource *resource) | 
|  | { | 
|  | struct devlink_resource *tmp, *child_resource; | 
|  |  | 
|  | list_for_each_entry_safe(child_resource, tmp, &resource->resource_list, | 
|  | list) { | 
|  | devlink_resource_unregister(devlink, child_resource); | 
|  | list_del(&child_resource->list); | 
|  | kfree(child_resource); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * devl_resources_unregister - free all resources | 
|  | * | 
|  | * @devlink: devlink | 
|  | */ | 
|  | void devl_resources_unregister(struct devlink *devlink) | 
|  | { | 
|  | struct devlink_resource *tmp, *child_resource; | 
|  |  | 
|  | lockdep_assert_held(&devlink->lock); | 
|  |  | 
|  | list_for_each_entry_safe(child_resource, tmp, &devlink->resource_list, | 
|  | list) { | 
|  | devlink_resource_unregister(devlink, child_resource); | 
|  | list_del(&child_resource->list); | 
|  | kfree(child_resource); | 
|  | } | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(devl_resources_unregister); | 
|  |  | 
|  | /** | 
|  | *	devlink_resources_unregister - free all resources | 
|  | * | 
|  | *	@devlink: devlink | 
|  | * | 
|  | *	Context: Takes and release devlink->lock <mutex>. | 
|  | */ | 
|  | void devlink_resources_unregister(struct devlink *devlink) | 
|  | { | 
|  | devl_lock(devlink); | 
|  | devl_resources_unregister(devlink); | 
|  | devl_unlock(devlink); | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(devlink_resources_unregister); | 
|  |  | 
|  | /** | 
|  | * devl_resource_size_get - get and update size | 
|  | * | 
|  | * @devlink: devlink | 
|  | * @resource_id: the requested resource id | 
|  | * @p_resource_size: ptr to update | 
|  | */ | 
|  | int devl_resource_size_get(struct devlink *devlink, | 
|  | u64 resource_id, | 
|  | u64 *p_resource_size) | 
|  | { | 
|  | struct devlink_resource *resource; | 
|  |  | 
|  | lockdep_assert_held(&devlink->lock); | 
|  |  | 
|  | resource = devlink_resource_find(devlink, NULL, resource_id); | 
|  | if (!resource) | 
|  | return -EINVAL; | 
|  | *p_resource_size = resource->size_new; | 
|  | resource->size = resource->size_new; | 
|  | return 0; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(devl_resource_size_get); | 
|  |  | 
|  | /** | 
|  | * devl_resource_occ_get_register - register occupancy getter | 
|  | * | 
|  | * @devlink: devlink | 
|  | * @resource_id: resource id | 
|  | * @occ_get: occupancy getter callback | 
|  | * @occ_get_priv: occupancy getter callback priv | 
|  | */ | 
|  | void devl_resource_occ_get_register(struct devlink *devlink, | 
|  | u64 resource_id, | 
|  | devlink_resource_occ_get_t *occ_get, | 
|  | void *occ_get_priv) | 
|  | { | 
|  | struct devlink_resource *resource; | 
|  |  | 
|  | lockdep_assert_held(&devlink->lock); | 
|  |  | 
|  | resource = devlink_resource_find(devlink, NULL, resource_id); | 
|  | if (WARN_ON(!resource)) | 
|  | return; | 
|  | WARN_ON(resource->occ_get); | 
|  |  | 
|  | resource->occ_get = occ_get; | 
|  | resource->occ_get_priv = occ_get_priv; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(devl_resource_occ_get_register); | 
|  |  | 
|  | /** | 
|  | * devl_resource_occ_get_unregister - unregister occupancy getter | 
|  | * | 
|  | * @devlink: devlink | 
|  | * @resource_id: resource id | 
|  | */ | 
|  | void devl_resource_occ_get_unregister(struct devlink *devlink, | 
|  | u64 resource_id) | 
|  | { | 
|  | struct devlink_resource *resource; | 
|  |  | 
|  | lockdep_assert_held(&devlink->lock); | 
|  |  | 
|  | resource = devlink_resource_find(devlink, NULL, resource_id); | 
|  | if (WARN_ON(!resource)) | 
|  | return; | 
|  | WARN_ON(!resource->occ_get); | 
|  |  | 
|  | resource->occ_get = NULL; | 
|  | resource->occ_get_priv = NULL; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(devl_resource_occ_get_unregister); |