root/net/devlink/sh_dev.c
// SPDX-License-Identifier: GPL-2.0-or-later
/* Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. */

#include <net/devlink.h>

#include "devl_internal.h"

static LIST_HEAD(shd_list);
static DEFINE_MUTEX(shd_mutex); /* Protects shd_list and shd->list */

/* This structure represents a shared devlink instance,
 * there is one created per identifier (e.g., serial number).
 */
struct devlink_shd {
        struct list_head list; /* Node in shd list */
        const char *id; /* Identifier string (e.g., serial number) */
        refcount_t refcount; /* Reference count */
        size_t priv_size; /* Size of driver private data */
        char priv[] __aligned(NETDEV_ALIGN) __counted_by(priv_size);
};

static struct devlink_shd *devlink_shd_lookup(const char *id)
{
        struct devlink_shd *shd;

        list_for_each_entry(shd, &shd_list, list) {
                if (!strcmp(shd->id, id))
                        return shd;
        }

        return NULL;
}

static struct devlink_shd *devlink_shd_create(const char *id,
                                              const struct devlink_ops *ops,
                                              size_t priv_size,
                                              const struct device_driver *driver)
{
        struct devlink_shd *shd;
        struct devlink *devlink;

        devlink = __devlink_alloc(ops, sizeof(struct devlink_shd) + priv_size,
                                  &init_net, NULL, driver);
        if (!devlink)
                return NULL;
        shd = devlink_priv(devlink);

        shd->id = kstrdup(id, GFP_KERNEL);
        if (!shd->id)
                goto err_devlink_free;
        shd->priv_size = priv_size;
        refcount_set(&shd->refcount, 1);

        devl_lock(devlink);
        devl_register(devlink);
        devl_unlock(devlink);

        list_add_tail(&shd->list, &shd_list);

        return shd;

err_devlink_free:
        devlink_free(devlink);
        return NULL;
}

static void devlink_shd_destroy(struct devlink_shd *shd)
{
        struct devlink *devlink = priv_to_devlink(shd);

        list_del(&shd->list);
        devl_lock(devlink);
        devl_unregister(devlink);
        devl_unlock(devlink);
        kfree(shd->id);
        devlink_free(devlink);
}

/**
 * devlink_shd_get - Get or create a shared devlink instance
 * @id: Identifier string (e.g., serial number) for the shared instance
 * @ops: Devlink operations structure
 * @priv_size: Size of private data structure
 * @driver: Driver associated with the shared devlink instance
 *
 * Get an existing shared devlink instance identified by @id, or create
 * a new one if it doesn't exist. Return the devlink instance with a
 * reference held. The caller must call devlink_shd_put() when done.
 *
 * All callers sharing the same @id must pass identical @ops, @priv_size
 * and @driver. A mismatch triggers a warning and returns NULL.
 *
 * Return: Pointer to the shared devlink instance on success,
 *         NULL on failure
 */
struct devlink *devlink_shd_get(const char *id,
                                const struct devlink_ops *ops,
                                size_t priv_size,
                                const struct device_driver *driver)
{
        struct devlink *devlink;
        struct devlink_shd *shd;

        mutex_lock(&shd_mutex);

        shd = devlink_shd_lookup(id);
        if (!shd) {
                shd = devlink_shd_create(id, ops, priv_size, driver);
                goto unlock;
        }

        devlink = priv_to_devlink(shd);
        if (WARN_ON_ONCE(devlink->ops != ops ||
                         shd->priv_size != priv_size ||
                         devlink->dev_driver != driver)) {
                shd = NULL;
                goto unlock;
        }
        refcount_inc(&shd->refcount);

unlock:
        mutex_unlock(&shd_mutex);
        return shd ? priv_to_devlink(shd) : NULL;
}
EXPORT_SYMBOL_GPL(devlink_shd_get);

/**
 * devlink_shd_put - Release a reference on a shared devlink instance
 * @devlink: Shared devlink instance
 *
 * Release a reference on a shared devlink instance obtained via
 * devlink_shd_get().
 */
void devlink_shd_put(struct devlink *devlink)
{
        struct devlink_shd *shd;

        mutex_lock(&shd_mutex);
        shd = devlink_priv(devlink);
        if (refcount_dec_and_test(&shd->refcount))
                devlink_shd_destroy(shd);
        mutex_unlock(&shd_mutex);
}
EXPORT_SYMBOL_GPL(devlink_shd_put);

/**
 * devlink_shd_get_priv - Get private data from shared devlink instance
 * @devlink: Devlink instance
 *
 * Returns a pointer to the driver's private data structure within
 * the shared devlink instance.
 *
 * Return: Pointer to private data
 */
void *devlink_shd_get_priv(struct devlink *devlink)
{
        struct devlink_shd *shd = devlink_priv(devlink);

        return shd->priv;
}
EXPORT_SYMBOL_GPL(devlink_shd_get_priv);