162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * media-dev-allocator.c - Media Controller Device Allocator API
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2019 Shuah Khan <shuah@kernel.org>
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Credits: Suggested by Laurent Pinchart <laurent.pinchart@ideasonboard.com>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci/*
1162306a36Sopenharmony_ci * This file adds a global refcounted Media Controller Device Instance API.
1262306a36Sopenharmony_ci * A system wide global media device list is managed and each media device
1362306a36Sopenharmony_ci * includes a kref count. The last put on the media device releases the media
1462306a36Sopenharmony_ci * device instance.
1562306a36Sopenharmony_ci *
1662306a36Sopenharmony_ci */
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include <linux/kref.h>
1962306a36Sopenharmony_ci#include <linux/module.h>
2062306a36Sopenharmony_ci#include <linux/slab.h>
2162306a36Sopenharmony_ci#include <linux/usb.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#include <media/media-device.h>
2462306a36Sopenharmony_ci#include <media/media-dev-allocator.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic LIST_HEAD(media_device_list);
2762306a36Sopenharmony_cistatic DEFINE_MUTEX(media_device_lock);
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistruct media_device_instance {
3062306a36Sopenharmony_ci	struct media_device mdev;
3162306a36Sopenharmony_ci	struct module *owner;
3262306a36Sopenharmony_ci	struct list_head list;
3362306a36Sopenharmony_ci	struct kref refcount;
3462306a36Sopenharmony_ci};
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic inline struct media_device_instance *
3762306a36Sopenharmony_cito_media_device_instance(struct media_device *mdev)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	return container_of(mdev, struct media_device_instance, mdev);
4062306a36Sopenharmony_ci}
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic void media_device_instance_release(struct kref *kref)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	struct media_device_instance *mdi =
4562306a36Sopenharmony_ci		container_of(kref, struct media_device_instance, refcount);
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	dev_dbg(mdi->mdev.dev, "%s: releasing Media Device\n", __func__);
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	mutex_lock(&media_device_lock);
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	media_device_unregister(&mdi->mdev);
5262306a36Sopenharmony_ci	media_device_cleanup(&mdi->mdev);
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	list_del(&mdi->list);
5562306a36Sopenharmony_ci	mutex_unlock(&media_device_lock);
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	kfree(mdi);
5862306a36Sopenharmony_ci}
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci/* Callers should hold media_device_lock when calling this function */
6162306a36Sopenharmony_cistatic struct media_device *__media_device_get(struct device *dev,
6262306a36Sopenharmony_ci						const char *module_name,
6362306a36Sopenharmony_ci						struct module *owner)
6462306a36Sopenharmony_ci{
6562306a36Sopenharmony_ci	struct media_device_instance *mdi;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	list_for_each_entry(mdi, &media_device_list, list) {
6862306a36Sopenharmony_ci		if (mdi->mdev.dev != dev)
6962306a36Sopenharmony_ci			continue;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci		kref_get(&mdi->refcount);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci		/* get module reference for the media_device owner */
7462306a36Sopenharmony_ci		if (owner != mdi->owner && !try_module_get(mdi->owner))
7562306a36Sopenharmony_ci			dev_err(dev,
7662306a36Sopenharmony_ci				"%s: module %s get owner reference error\n",
7762306a36Sopenharmony_ci					__func__, module_name);
7862306a36Sopenharmony_ci		else
7962306a36Sopenharmony_ci			dev_dbg(dev, "%s: module %s got owner reference\n",
8062306a36Sopenharmony_ci					__func__, module_name);
8162306a36Sopenharmony_ci		return &mdi->mdev;
8262306a36Sopenharmony_ci	}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	mdi = kzalloc(sizeof(*mdi), GFP_KERNEL);
8562306a36Sopenharmony_ci	if (!mdi)
8662306a36Sopenharmony_ci		return NULL;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	mdi->owner = owner;
8962306a36Sopenharmony_ci	kref_init(&mdi->refcount);
9062306a36Sopenharmony_ci	list_add_tail(&mdi->list, &media_device_list);
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	dev_dbg(dev, "%s: Allocated media device for owner %s\n",
9362306a36Sopenharmony_ci			__func__, module_name);
9462306a36Sopenharmony_ci	return &mdi->mdev;
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistruct media_device *media_device_usb_allocate(struct usb_device *udev,
9862306a36Sopenharmony_ci					       const char *module_name,
9962306a36Sopenharmony_ci					       struct module *owner)
10062306a36Sopenharmony_ci{
10162306a36Sopenharmony_ci	struct media_device *mdev;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	mutex_lock(&media_device_lock);
10462306a36Sopenharmony_ci	mdev = __media_device_get(&udev->dev, module_name, owner);
10562306a36Sopenharmony_ci	if (!mdev) {
10662306a36Sopenharmony_ci		mutex_unlock(&media_device_lock);
10762306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
10862306a36Sopenharmony_ci	}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	/* check if media device is already initialized */
11162306a36Sopenharmony_ci	if (!mdev->dev)
11262306a36Sopenharmony_ci		__media_device_usb_init(mdev, udev, udev->product,
11362306a36Sopenharmony_ci					module_name);
11462306a36Sopenharmony_ci	mutex_unlock(&media_device_lock);
11562306a36Sopenharmony_ci	return mdev;
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(media_device_usb_allocate);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_civoid media_device_delete(struct media_device *mdev, const char *module_name,
12062306a36Sopenharmony_ci			 struct module *owner)
12162306a36Sopenharmony_ci{
12262306a36Sopenharmony_ci	struct media_device_instance *mdi = to_media_device_instance(mdev);
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	mutex_lock(&media_device_lock);
12562306a36Sopenharmony_ci	/* put module reference for the media_device owner */
12662306a36Sopenharmony_ci	if (mdi->owner != owner) {
12762306a36Sopenharmony_ci		module_put(mdi->owner);
12862306a36Sopenharmony_ci		dev_dbg(mdi->mdev.dev,
12962306a36Sopenharmony_ci			"%s: module %s put owner module reference\n",
13062306a36Sopenharmony_ci			__func__, module_name);
13162306a36Sopenharmony_ci	}
13262306a36Sopenharmony_ci	mutex_unlock(&media_device_lock);
13362306a36Sopenharmony_ci	kref_put(&mdi->refcount, media_device_instance_release);
13462306a36Sopenharmony_ci}
13562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(media_device_delete);
136