root/drivers/net/ethernet/mellanox/mlxsw/core_linecard_dev.c
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
/* Copyright (c) 2022 NVIDIA Corporation and Mellanox Technologies. All rights reserved */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/types.h>
#include <linux/auxiliary_bus.h>
#include <linux/idr.h>
#include <linux/gfp.h>
#include <linux/slab.h>
#include <net/devlink.h>
#include "core.h"

#define MLXSW_LINECARD_DEV_ID_NAME "lc"

struct mlxsw_linecard_dev {
        struct mlxsw_linecard *linecard;
};

struct mlxsw_linecard_bdev {
        struct auxiliary_device adev;
        struct mlxsw_linecard *linecard;
        struct mlxsw_linecard_dev *linecard_dev;
};

static DEFINE_IDA(mlxsw_linecard_bdev_ida);

static int mlxsw_linecard_bdev_id_alloc(void)
{
        return ida_alloc(&mlxsw_linecard_bdev_ida, GFP_KERNEL);
}

static void mlxsw_linecard_bdev_id_free(int id)
{
        ida_free(&mlxsw_linecard_bdev_ida, id);
}

static void mlxsw_linecard_bdev_release(struct device *device)
{
        struct auxiliary_device *adev =
                        container_of(device, struct auxiliary_device, dev);
        struct mlxsw_linecard_bdev *linecard_bdev =
                        container_of(adev, struct mlxsw_linecard_bdev, adev);

        mlxsw_linecard_bdev_id_free(adev->id);
        kfree(linecard_bdev);
}

int mlxsw_linecard_bdev_add(struct mlxsw_linecard *linecard)
{
        struct mlxsw_linecard_bdev *linecard_bdev;
        int err;
        int id;

        id = mlxsw_linecard_bdev_id_alloc();
        if (id < 0)
                return id;

        linecard_bdev = kzalloc_obj(*linecard_bdev);
        if (!linecard_bdev) {
                mlxsw_linecard_bdev_id_free(id);
                return -ENOMEM;
        }
        linecard_bdev->adev.id = id;
        linecard_bdev->adev.name = MLXSW_LINECARD_DEV_ID_NAME;
        linecard_bdev->adev.dev.release = mlxsw_linecard_bdev_release;
        linecard_bdev->adev.dev.parent = linecard->linecards->bus_info->dev;
        linecard_bdev->linecard = linecard;

        err = auxiliary_device_init(&linecard_bdev->adev);
        if (err) {
                mlxsw_linecard_bdev_id_free(id);
                kfree(linecard_bdev);
                return err;
        }

        err = auxiliary_device_add(&linecard_bdev->adev);
        if (err) {
                auxiliary_device_uninit(&linecard_bdev->adev);
                return err;
        }

        linecard->bdev = linecard_bdev;
        return 0;
}

void mlxsw_linecard_bdev_del(struct mlxsw_linecard *linecard)
{
        struct mlxsw_linecard_bdev *linecard_bdev = linecard->bdev;

        if (!linecard_bdev)
                /* Unprovisioned line cards do not have an auxiliary device. */
                return;
        auxiliary_device_delete(&linecard_bdev->adev);
        auxiliary_device_uninit(&linecard_bdev->adev);
        linecard->bdev = NULL;
}

static int mlxsw_linecard_dev_devlink_info_get(struct devlink *devlink,
                                               struct devlink_info_req *req,
                                               struct netlink_ext_ack *extack)
{
        struct mlxsw_linecard_dev *linecard_dev = devlink_priv(devlink);
        struct mlxsw_linecard *linecard = linecard_dev->linecard;

        return mlxsw_linecard_devlink_info_get(linecard, req, extack);
}

static int
mlxsw_linecard_dev_devlink_flash_update(struct devlink *devlink,
                                        struct devlink_flash_update_params *params,
                                        struct netlink_ext_ack *extack)
{
        struct mlxsw_linecard_dev *linecard_dev = devlink_priv(devlink);
        struct mlxsw_linecard *linecard = linecard_dev->linecard;

        return mlxsw_linecard_flash_update(devlink, linecard,
                                           params->fw, extack);
}

static const struct devlink_ops mlxsw_linecard_dev_devlink_ops = {
        .info_get                       = mlxsw_linecard_dev_devlink_info_get,
        .flash_update                   = mlxsw_linecard_dev_devlink_flash_update,
};

static int mlxsw_linecard_bdev_probe(struct auxiliary_device *adev,
                                     const struct auxiliary_device_id *id)
{
        struct mlxsw_linecard_bdev *linecard_bdev =
                        container_of(adev, struct mlxsw_linecard_bdev, adev);
        struct mlxsw_linecard *linecard = linecard_bdev->linecard;
        struct mlxsw_linecard_dev *linecard_dev;
        struct devlink *devlink;
        int err;

        devlink = devlink_alloc(&mlxsw_linecard_dev_devlink_ops,
                                sizeof(*linecard_dev), &adev->dev);
        if (!devlink)
                return -ENOMEM;
        linecard_dev = devlink_priv(devlink);
        linecard_dev->linecard = linecard_bdev->linecard;
        linecard_bdev->linecard_dev = linecard_dev;

        err = devlink_linecard_nested_dl_set(linecard->devlink_linecard, devlink);
        if (err) {
                devlink_free(devlink);
                return err;
        }
        devlink_register(devlink);
        return 0;
}

static void mlxsw_linecard_bdev_remove(struct auxiliary_device *adev)
{
        struct mlxsw_linecard_bdev *linecard_bdev =
                        container_of(adev, struct mlxsw_linecard_bdev, adev);
        struct devlink *devlink = priv_to_devlink(linecard_bdev->linecard_dev);

        devlink_unregister(devlink);
        devlink_free(devlink);
}

static const struct auxiliary_device_id mlxsw_linecard_bdev_id_table[] = {
        { .name = KBUILD_MODNAME "." MLXSW_LINECARD_DEV_ID_NAME },
        {},
};

MODULE_DEVICE_TABLE(auxiliary, mlxsw_linecard_bdev_id_table);

static struct auxiliary_driver mlxsw_linecard_driver = {
        .name = MLXSW_LINECARD_DEV_ID_NAME,
        .probe = mlxsw_linecard_bdev_probe,
        .remove = mlxsw_linecard_bdev_remove,
        .id_table = mlxsw_linecard_bdev_id_table,
};

int mlxsw_linecard_driver_register(void)
{
        return auxiliary_driver_register(&mlxsw_linecard_driver);
}

void mlxsw_linecard_driver_unregister(void)
{
        auxiliary_driver_unregister(&mlxsw_linecard_driver);
}