root/net/devlink/resource.c
// 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_obj(*resource);
        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);