root/net/devlink/linecard.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_linecard {
        struct list_head list;
        struct devlink *devlink;
        unsigned int index;
        const struct devlink_linecard_ops *ops;
        void *priv;
        enum devlink_linecard_state state;
        struct mutex state_lock; /* Protects state */
        const char *type;
        struct devlink_linecard_type *types;
        unsigned int types_count;
        u32 rel_index;
};

unsigned int devlink_linecard_index(struct devlink_linecard *linecard)
{
        return linecard->index;
}

static struct devlink_linecard *
devlink_linecard_get_by_index(struct devlink *devlink,
                              unsigned int linecard_index)
{
        struct devlink_linecard *devlink_linecard;

        list_for_each_entry(devlink_linecard, &devlink->linecard_list, list) {
                if (devlink_linecard->index == linecard_index)
                        return devlink_linecard;
        }
        return NULL;
}

static bool devlink_linecard_index_exists(struct devlink *devlink,
                                          unsigned int linecard_index)
{
        return devlink_linecard_get_by_index(devlink, linecard_index);
}

static struct devlink_linecard *
devlink_linecard_get_from_attrs(struct devlink *devlink, struct nlattr **attrs)
{
        if (attrs[DEVLINK_ATTR_LINECARD_INDEX]) {
                u32 linecard_index = nla_get_u32(attrs[DEVLINK_ATTR_LINECARD_INDEX]);
                struct devlink_linecard *linecard;

                linecard = devlink_linecard_get_by_index(devlink, linecard_index);
                if (!linecard)
                        return ERR_PTR(-ENODEV);
                return linecard;
        }
        return ERR_PTR(-EINVAL);
}

static struct devlink_linecard *
devlink_linecard_get_from_info(struct devlink *devlink, struct genl_info *info)
{
        return devlink_linecard_get_from_attrs(devlink, info->attrs);
}

struct devlink_linecard_type {
        const char *type;
        const void *priv;
};

static int devlink_nl_linecard_fill(struct sk_buff *msg,
                                    struct devlink *devlink,
                                    struct devlink_linecard *linecard,
                                    enum devlink_command cmd, u32 portid,
                                    u32 seq, int flags,
                                    struct netlink_ext_ack *extack)
{
        struct devlink_linecard_type *linecard_type;
        struct nlattr *attr;
        void *hdr;
        int i;

        hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd);
        if (!hdr)
                return -EMSGSIZE;

        if (devlink_nl_put_handle(msg, devlink))
                goto nla_put_failure;
        if (nla_put_u32(msg, DEVLINK_ATTR_LINECARD_INDEX, linecard->index))
                goto nla_put_failure;
        if (nla_put_u8(msg, DEVLINK_ATTR_LINECARD_STATE, linecard->state))
                goto nla_put_failure;
        if (linecard->type &&
            nla_put_string(msg, DEVLINK_ATTR_LINECARD_TYPE, linecard->type))
                goto nla_put_failure;

        if (linecard->types_count) {
                attr = nla_nest_start(msg,
                                      DEVLINK_ATTR_LINECARD_SUPPORTED_TYPES);
                if (!attr)
                        goto nla_put_failure;
                for (i = 0; i < linecard->types_count; i++) {
                        linecard_type = &linecard->types[i];
                        if (nla_put_string(msg, DEVLINK_ATTR_LINECARD_TYPE,
                                           linecard_type->type)) {
                                nla_nest_cancel(msg, attr);
                                goto nla_put_failure;
                        }
                }
                nla_nest_end(msg, attr);
        }

        if (devlink_rel_devlink_handle_put(msg, devlink,
                                           linecard->rel_index,
                                           DEVLINK_ATTR_NESTED_DEVLINK,
                                           NULL))
                goto nla_put_failure;

        genlmsg_end(msg, hdr);
        return 0;

nla_put_failure:
        genlmsg_cancel(msg, hdr);
        return -EMSGSIZE;
}

static void devlink_linecard_notify(struct devlink_linecard *linecard,
                                    enum devlink_command cmd)
{
        struct devlink *devlink = linecard->devlink;
        struct sk_buff *msg;
        int err;

        WARN_ON(cmd != DEVLINK_CMD_LINECARD_NEW &&
                cmd != DEVLINK_CMD_LINECARD_DEL);

        if (!__devl_is_registered(devlink) || !devlink_nl_notify_need(devlink))
                return;

        msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
        if (!msg)
                return;

        err = devlink_nl_linecard_fill(msg, devlink, linecard, cmd, 0, 0, 0,
                                       NULL);
        if (err) {
                nlmsg_free(msg);
                return;
        }

        devlink_nl_notify_send(devlink, msg);
}

void devlink_linecards_notify_register(struct devlink *devlink)
{
        struct devlink_linecard *linecard;

        list_for_each_entry(linecard, &devlink->linecard_list, list)
                devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW);
}

void devlink_linecards_notify_unregister(struct devlink *devlink)
{
        struct devlink_linecard *linecard;

        list_for_each_entry_reverse(linecard, &devlink->linecard_list, list)
                devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_DEL);
}

int devlink_nl_linecard_get_doit(struct sk_buff *skb, struct genl_info *info)
{
        struct devlink *devlink = info->user_ptr[0];
        struct devlink_linecard *linecard;
        struct sk_buff *msg;
        int err;

        linecard = devlink_linecard_get_from_info(devlink, info);
        if (IS_ERR(linecard))
                return PTR_ERR(linecard);

        msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
        if (!msg)
                return -ENOMEM;

        mutex_lock(&linecard->state_lock);
        err = devlink_nl_linecard_fill(msg, devlink, linecard,
                                       DEVLINK_CMD_LINECARD_NEW,
                                       info->snd_portid, info->snd_seq, 0,
                                       info->extack);
        mutex_unlock(&linecard->state_lock);
        if (err) {
                nlmsg_free(msg);
                return err;
        }

        return genlmsg_reply(msg, info);
}

static int devlink_nl_linecard_get_dump_one(struct sk_buff *msg,
                                            struct devlink *devlink,
                                            struct netlink_callback *cb,
                                            int flags)
{
        struct devlink_nl_dump_state *state = devlink_dump_state(cb);
        struct devlink_linecard *linecard;
        int idx = 0;
        int err = 0;

        list_for_each_entry(linecard, &devlink->linecard_list, list) {
                if (idx < state->idx) {
                        idx++;
                        continue;
                }
                mutex_lock(&linecard->state_lock);
                err = devlink_nl_linecard_fill(msg, devlink, linecard,
                                               DEVLINK_CMD_LINECARD_NEW,
                                               NETLINK_CB(cb->skb).portid,
                                               cb->nlh->nlmsg_seq, flags,
                                               cb->extack);
                mutex_unlock(&linecard->state_lock);
                if (err) {
                        state->idx = idx;
                        break;
                }
                idx++;
        }

        return err;
}

int devlink_nl_linecard_get_dumpit(struct sk_buff *skb,
                                   struct netlink_callback *cb)
{
        return devlink_nl_dumpit(skb, cb, devlink_nl_linecard_get_dump_one);
}

static struct devlink_linecard_type *
devlink_linecard_type_lookup(struct devlink_linecard *linecard,
                             const char *type)
{
        struct devlink_linecard_type *linecard_type;
        int i;

        for (i = 0; i < linecard->types_count; i++) {
                linecard_type = &linecard->types[i];
                if (!strcmp(type, linecard_type->type))
                        return linecard_type;
        }
        return NULL;
}

static int devlink_linecard_type_set(struct devlink_linecard *linecard,
                                     const char *type,
                                     struct netlink_ext_ack *extack)
{
        const struct devlink_linecard_ops *ops = linecard->ops;
        struct devlink_linecard_type *linecard_type;
        int err;

        mutex_lock(&linecard->state_lock);
        if (linecard->state == DEVLINK_LINECARD_STATE_PROVISIONING) {
                NL_SET_ERR_MSG(extack, "Line card is currently being provisioned");
                err = -EBUSY;
                goto out;
        }
        if (linecard->state == DEVLINK_LINECARD_STATE_UNPROVISIONING) {
                NL_SET_ERR_MSG(extack, "Line card is currently being unprovisioned");
                err = -EBUSY;
                goto out;
        }

        linecard_type = devlink_linecard_type_lookup(linecard, type);
        if (!linecard_type) {
                NL_SET_ERR_MSG(extack, "Unsupported line card type provided");
                err = -EINVAL;
                goto out;
        }

        if (linecard->state != DEVLINK_LINECARD_STATE_UNPROVISIONED &&
            linecard->state != DEVLINK_LINECARD_STATE_PROVISIONING_FAILED) {
                NL_SET_ERR_MSG(extack, "Line card already provisioned");
                err = -EBUSY;
                /* Check if the line card is provisioned in the same
                 * way the user asks. In case it is, make the operation
                 * to return success.
                 */
                if (ops->same_provision &&
                    ops->same_provision(linecard, linecard->priv,
                                        linecard_type->type,
                                        linecard_type->priv))
                        err = 0;
                goto out;
        }

        linecard->state = DEVLINK_LINECARD_STATE_PROVISIONING;
        linecard->type = linecard_type->type;
        devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW);
        mutex_unlock(&linecard->state_lock);
        err = ops->provision(linecard, linecard->priv, linecard_type->type,
                             linecard_type->priv, extack);
        if (err) {
                /* Provisioning failed. Assume the linecard is unprovisioned
                 * for future operations.
                 */
                mutex_lock(&linecard->state_lock);
                linecard->state = DEVLINK_LINECARD_STATE_UNPROVISIONED;
                linecard->type = NULL;
                devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW);
                mutex_unlock(&linecard->state_lock);
        }
        return err;

out:
        mutex_unlock(&linecard->state_lock);
        return err;
}

static int devlink_linecard_type_unset(struct devlink_linecard *linecard,
                                       struct netlink_ext_ack *extack)
{
        int err;

        mutex_lock(&linecard->state_lock);
        if (linecard->state == DEVLINK_LINECARD_STATE_PROVISIONING) {
                NL_SET_ERR_MSG(extack, "Line card is currently being provisioned");
                err = -EBUSY;
                goto out;
        }
        if (linecard->state == DEVLINK_LINECARD_STATE_UNPROVISIONING) {
                NL_SET_ERR_MSG(extack, "Line card is currently being unprovisioned");
                err = -EBUSY;
                goto out;
        }
        if (linecard->state == DEVLINK_LINECARD_STATE_PROVISIONING_FAILED) {
                linecard->state = DEVLINK_LINECARD_STATE_UNPROVISIONED;
                linecard->type = NULL;
                devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW);
                err = 0;
                goto out;
        }

        if (linecard->state == DEVLINK_LINECARD_STATE_UNPROVISIONED) {
                NL_SET_ERR_MSG(extack, "Line card is not provisioned");
                err = 0;
                goto out;
        }
        linecard->state = DEVLINK_LINECARD_STATE_UNPROVISIONING;
        devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW);
        mutex_unlock(&linecard->state_lock);
        err = linecard->ops->unprovision(linecard, linecard->priv,
                                         extack);
        if (err) {
                /* Unprovisioning failed. Assume the linecard is unprovisioned
                 * for future operations.
                 */
                mutex_lock(&linecard->state_lock);
                linecard->state = DEVLINK_LINECARD_STATE_UNPROVISIONED;
                linecard->type = NULL;
                devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW);
                mutex_unlock(&linecard->state_lock);
        }
        return err;

out:
        mutex_unlock(&linecard->state_lock);
        return err;
}

int devlink_nl_linecard_set_doit(struct sk_buff *skb, struct genl_info *info)
{
        struct netlink_ext_ack *extack = info->extack;
        struct devlink *devlink = info->user_ptr[0];
        struct devlink_linecard *linecard;
        int err;

        linecard = devlink_linecard_get_from_info(devlink, info);
        if (IS_ERR(linecard))
                return PTR_ERR(linecard);

        if (info->attrs[DEVLINK_ATTR_LINECARD_TYPE]) {
                const char *type;

                type = nla_data(info->attrs[DEVLINK_ATTR_LINECARD_TYPE]);
                if (strcmp(type, "")) {
                        err = devlink_linecard_type_set(linecard, type, extack);
                        if (err)
                                return err;
                } else {
                        err = devlink_linecard_type_unset(linecard, extack);
                        if (err)
                                return err;
                }
        }

        return 0;
}

static int devlink_linecard_types_init(struct devlink_linecard *linecard)
{
        struct devlink_linecard_type *linecard_type;
        unsigned int count;
        int i;

        count = linecard->ops->types_count(linecard, linecard->priv);
        linecard->types = kmalloc_objs(*linecard_type, count);
        if (!linecard->types)
                return -ENOMEM;
        linecard->types_count = count;

        for (i = 0; i < count; i++) {
                linecard_type = &linecard->types[i];
                linecard->ops->types_get(linecard, linecard->priv, i,
                                         &linecard_type->type,
                                         &linecard_type->priv);
        }
        return 0;
}

static void devlink_linecard_types_fini(struct devlink_linecard *linecard)
{
        kfree(linecard->types);
}

/**
 *      devl_linecard_create - Create devlink linecard
 *
 *      @devlink: devlink
 *      @linecard_index: driver-specific numerical identifier of the linecard
 *      @ops: linecards ops
 *      @priv: user priv pointer
 *
 *      Create devlink linecard instance with provided linecard index.
 *      Caller can use any indexing, even hw-related one.
 *
 *      Return: Line card structure or an ERR_PTR() encoded error code.
 */
struct devlink_linecard *
devl_linecard_create(struct devlink *devlink, unsigned int linecard_index,
                     const struct devlink_linecard_ops *ops, void *priv)
{
        struct devlink_linecard *linecard;
        int err;

        if (WARN_ON(!ops || !ops->provision || !ops->unprovision ||
                    !ops->types_count || !ops->types_get))
                return ERR_PTR(-EINVAL);

        if (devlink_linecard_index_exists(devlink, linecard_index))
                return ERR_PTR(-EEXIST);

        linecard = kzalloc_obj(*linecard);
        if (!linecard)
                return ERR_PTR(-ENOMEM);

        linecard->devlink = devlink;
        linecard->index = linecard_index;
        linecard->ops = ops;
        linecard->priv = priv;
        linecard->state = DEVLINK_LINECARD_STATE_UNPROVISIONED;
        mutex_init(&linecard->state_lock);

        err = devlink_linecard_types_init(linecard);
        if (err) {
                mutex_destroy(&linecard->state_lock);
                kfree(linecard);
                return ERR_PTR(err);
        }

        list_add_tail(&linecard->list, &devlink->linecard_list);
        devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW);
        return linecard;
}
EXPORT_SYMBOL_GPL(devl_linecard_create);

/**
 *      devl_linecard_destroy - Destroy devlink linecard
 *
 *      @linecard: devlink linecard
 */
void devl_linecard_destroy(struct devlink_linecard *linecard)
{
        devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_DEL);
        list_del(&linecard->list);
        devlink_linecard_types_fini(linecard);
        mutex_destroy(&linecard->state_lock);
        kfree(linecard);
}
EXPORT_SYMBOL_GPL(devl_linecard_destroy);

/**
 *      devlink_linecard_provision_set - Set provisioning on linecard
 *
 *      @linecard: devlink linecard
 *      @type: linecard type
 *
 *      This is either called directly from the provision() op call or
 *      as a result of the provision() op call asynchronously.
 */
void devlink_linecard_provision_set(struct devlink_linecard *linecard,
                                    const char *type)
{
        mutex_lock(&linecard->state_lock);
        WARN_ON(linecard->type && strcmp(linecard->type, type));
        linecard->state = DEVLINK_LINECARD_STATE_PROVISIONED;
        linecard->type = type;
        devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW);
        mutex_unlock(&linecard->state_lock);
}
EXPORT_SYMBOL_GPL(devlink_linecard_provision_set);

/**
 *      devlink_linecard_provision_clear - Clear provisioning on linecard
 *
 *      @linecard: devlink linecard
 *
 *      This is either called directly from the unprovision() op call or
 *      as a result of the unprovision() op call asynchronously.
 */
void devlink_linecard_provision_clear(struct devlink_linecard *linecard)
{
        mutex_lock(&linecard->state_lock);
        linecard->state = DEVLINK_LINECARD_STATE_UNPROVISIONED;
        linecard->type = NULL;
        devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW);
        mutex_unlock(&linecard->state_lock);
}
EXPORT_SYMBOL_GPL(devlink_linecard_provision_clear);

/**
 *      devlink_linecard_provision_fail - Fail provisioning on linecard
 *
 *      @linecard: devlink linecard
 *
 *      This is either called directly from the provision() op call or
 *      as a result of the provision() op call asynchronously.
 */
void devlink_linecard_provision_fail(struct devlink_linecard *linecard)
{
        mutex_lock(&linecard->state_lock);
        linecard->state = DEVLINK_LINECARD_STATE_PROVISIONING_FAILED;
        devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW);
        mutex_unlock(&linecard->state_lock);
}
EXPORT_SYMBOL_GPL(devlink_linecard_provision_fail);

/**
 *      devlink_linecard_activate - Set linecard active
 *
 *      @linecard: devlink linecard
 */
void devlink_linecard_activate(struct devlink_linecard *linecard)
{
        mutex_lock(&linecard->state_lock);
        WARN_ON(linecard->state != DEVLINK_LINECARD_STATE_PROVISIONED);
        linecard->state = DEVLINK_LINECARD_STATE_ACTIVE;
        devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW);
        mutex_unlock(&linecard->state_lock);
}
EXPORT_SYMBOL_GPL(devlink_linecard_activate);

/**
 *      devlink_linecard_deactivate - Set linecard inactive
 *
 *      @linecard: devlink linecard
 */
void devlink_linecard_deactivate(struct devlink_linecard *linecard)
{
        mutex_lock(&linecard->state_lock);
        switch (linecard->state) {
        case DEVLINK_LINECARD_STATE_ACTIVE:
                linecard->state = DEVLINK_LINECARD_STATE_PROVISIONED;
                devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW);
                break;
        case DEVLINK_LINECARD_STATE_UNPROVISIONING:
                /* Line card is being deactivated as part
                 * of unprovisioning flow.
                 */
                break;
        default:
                WARN_ON(1);
                break;
        }
        mutex_unlock(&linecard->state_lock);
}
EXPORT_SYMBOL_GPL(devlink_linecard_deactivate);

static void devlink_linecard_rel_notify_cb(struct devlink *devlink,
                                           u32 linecard_index)
{
        struct devlink_linecard *linecard;

        linecard = devlink_linecard_get_by_index(devlink, linecard_index);
        if (!linecard)
                return;
        devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW);
}

static void devlink_linecard_rel_cleanup_cb(struct devlink *devlink,
                                            u32 linecard_index, u32 rel_index)
{
        struct devlink_linecard *linecard;

        linecard = devlink_linecard_get_by_index(devlink, linecard_index);
        if (linecard && linecard->rel_index == rel_index)
                linecard->rel_index = 0;
}

/**
 *      devlink_linecard_nested_dl_set - Attach/detach nested devlink
 *                                       instance to linecard.
 *
 *      @linecard: devlink linecard
 *      @nested_devlink: devlink instance to attach or NULL to detach
 */
int devlink_linecard_nested_dl_set(struct devlink_linecard *linecard,
                                   struct devlink *nested_devlink)
{
        return devlink_rel_nested_in_add(&linecard->rel_index,
                                         linecard->devlink->index,
                                         linecard->index,
                                         devlink_linecard_rel_notify_cb,
                                         devlink_linecard_rel_cleanup_cb,
                                         nested_devlink);
}
EXPORT_SYMBOL_GPL(devlink_linecard_nested_dl_set);