root/drivers/media/mc/mc-dev-allocator.c
// SPDX-License-Identifier: GPL-2.0
/*
 * media-dev-allocator.c - Media Controller Device Allocator API
 *
 * Copyright (c) 2019 Shuah Khan <shuah@kernel.org>
 *
 * Credits: Suggested by Laurent Pinchart <laurent.pinchart@ideasonboard.com>
 */

/*
 * This file adds a global refcounted Media Controller Device Instance API.
 * A system wide global media device list is managed and each media device
 * includes a kref count. The last put on the media device releases the media
 * device instance.
 *
 */

#include <linux/kref.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb.h>

#include <media/media-device.h>
#include <media/media-dev-allocator.h>

static LIST_HEAD(media_device_list);
static DEFINE_MUTEX(media_device_lock);

struct media_device_instance {
        struct media_device mdev;
        struct module *owner;
        struct list_head list;
        struct kref refcount;
};

static inline struct media_device_instance *
to_media_device_instance(struct media_device *mdev)
{
        return container_of(mdev, struct media_device_instance, mdev);
}

static void media_device_instance_release(struct kref *kref)
{
        struct media_device_instance *mdi =
                container_of(kref, struct media_device_instance, refcount);

        dev_dbg(mdi->mdev.dev, "%s: releasing Media Device\n", __func__);

        mutex_lock(&media_device_lock);

        media_device_unregister(&mdi->mdev);
        media_device_cleanup(&mdi->mdev);

        list_del(&mdi->list);
        mutex_unlock(&media_device_lock);

        kfree(mdi);
}

/* Callers should hold media_device_lock when calling this function */
static struct media_device *__media_device_get(struct device *dev,
                                                const char *module_name,
                                                struct module *owner)
{
        struct media_device_instance *mdi;

        list_for_each_entry(mdi, &media_device_list, list) {
                if (mdi->mdev.dev != dev)
                        continue;

                kref_get(&mdi->refcount);

                /* get module reference for the media_device owner */
                if (owner != mdi->owner && !try_module_get(mdi->owner))
                        dev_err(dev,
                                "%s: module %s get owner reference error\n",
                                        __func__, module_name);
                else
                        dev_dbg(dev, "%s: module %s got owner reference\n",
                                        __func__, module_name);
                return &mdi->mdev;
        }

        mdi = kzalloc_obj(*mdi);
        if (!mdi)
                return NULL;

        mdi->owner = owner;
        kref_init(&mdi->refcount);
        list_add_tail(&mdi->list, &media_device_list);

        dev_dbg(dev, "%s: Allocated media device for owner %s\n",
                        __func__, module_name);
        return &mdi->mdev;
}

struct media_device *media_device_usb_allocate(struct usb_device *udev,
                                               const char *module_name,
                                               struct module *owner)
{
        struct media_device *mdev;

        mutex_lock(&media_device_lock);
        mdev = __media_device_get(&udev->dev, module_name, owner);
        if (!mdev) {
                mutex_unlock(&media_device_lock);
                return ERR_PTR(-ENOMEM);
        }

        /* check if media device is already initialized */
        if (!mdev->dev)
                __media_device_usb_init(mdev, udev, udev->product,
                                        module_name);
        mutex_unlock(&media_device_lock);
        return mdev;
}
EXPORT_SYMBOL_GPL(media_device_usb_allocate);

void media_device_delete(struct media_device *mdev, const char *module_name,
                         struct module *owner)
{
        struct media_device_instance *mdi = to_media_device_instance(mdev);

        mutex_lock(&media_device_lock);
        /* put module reference for the media_device owner */
        if (mdi->owner != owner) {
                module_put(mdi->owner);
                dev_dbg(mdi->mdev.dev,
                        "%s: module %s put owner module reference\n",
                        __func__, module_name);
        }
        mutex_unlock(&media_device_lock);
        kref_put(&mdi->refcount, media_device_instance_release);
}
EXPORT_SYMBOL_GPL(media_device_delete);