18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * media-dev-allocator.c - Media Controller Device Allocator API
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (c) 2019 Shuah Khan <shuah@kernel.org>
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Credits: Suggested by Laurent Pinchart <laurent.pinchart@ideasonboard.com>
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci/*
118c2ecf20Sopenharmony_ci * This file adds a global refcounted Media Controller Device Instance API.
128c2ecf20Sopenharmony_ci * A system wide global media device list is managed and each media device
138c2ecf20Sopenharmony_ci * includes a kref count. The last put on the media device releases the media
148c2ecf20Sopenharmony_ci * device instance.
158c2ecf20Sopenharmony_ci *
168c2ecf20Sopenharmony_ci */
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci#include <linux/kref.h>
198c2ecf20Sopenharmony_ci#include <linux/module.h>
208c2ecf20Sopenharmony_ci#include <linux/slab.h>
218c2ecf20Sopenharmony_ci#include <linux/usb.h>
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#include <media/media-device.h>
248c2ecf20Sopenharmony_ci#include <media/media-dev-allocator.h>
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistatic LIST_HEAD(media_device_list);
278c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(media_device_lock);
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cistruct media_device_instance {
308c2ecf20Sopenharmony_ci	struct media_device mdev;
318c2ecf20Sopenharmony_ci	struct module *owner;
328c2ecf20Sopenharmony_ci	struct list_head list;
338c2ecf20Sopenharmony_ci	struct kref refcount;
348c2ecf20Sopenharmony_ci};
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_cistatic inline struct media_device_instance *
378c2ecf20Sopenharmony_cito_media_device_instance(struct media_device *mdev)
388c2ecf20Sopenharmony_ci{
398c2ecf20Sopenharmony_ci	return container_of(mdev, struct media_device_instance, mdev);
408c2ecf20Sopenharmony_ci}
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_cistatic void media_device_instance_release(struct kref *kref)
438c2ecf20Sopenharmony_ci{
448c2ecf20Sopenharmony_ci	struct media_device_instance *mdi =
458c2ecf20Sopenharmony_ci		container_of(kref, struct media_device_instance, refcount);
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	dev_dbg(mdi->mdev.dev, "%s: releasing Media Device\n", __func__);
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci	mutex_lock(&media_device_lock);
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci	media_device_unregister(&mdi->mdev);
528c2ecf20Sopenharmony_ci	media_device_cleanup(&mdi->mdev);
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	list_del(&mdi->list);
558c2ecf20Sopenharmony_ci	mutex_unlock(&media_device_lock);
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci	kfree(mdi);
588c2ecf20Sopenharmony_ci}
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci/* Callers should hold media_device_lock when calling this function */
618c2ecf20Sopenharmony_cistatic struct media_device *__media_device_get(struct device *dev,
628c2ecf20Sopenharmony_ci						const char *module_name,
638c2ecf20Sopenharmony_ci						struct module *owner)
648c2ecf20Sopenharmony_ci{
658c2ecf20Sopenharmony_ci	struct media_device_instance *mdi;
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	list_for_each_entry(mdi, &media_device_list, list) {
688c2ecf20Sopenharmony_ci		if (mdi->mdev.dev != dev)
698c2ecf20Sopenharmony_ci			continue;
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci		kref_get(&mdi->refcount);
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci		/* get module reference for the media_device owner */
748c2ecf20Sopenharmony_ci		if (owner != mdi->owner && !try_module_get(mdi->owner))
758c2ecf20Sopenharmony_ci			dev_err(dev,
768c2ecf20Sopenharmony_ci				"%s: module %s get owner reference error\n",
778c2ecf20Sopenharmony_ci					__func__, module_name);
788c2ecf20Sopenharmony_ci		else
798c2ecf20Sopenharmony_ci			dev_dbg(dev, "%s: module %s got owner reference\n",
808c2ecf20Sopenharmony_ci					__func__, module_name);
818c2ecf20Sopenharmony_ci		return &mdi->mdev;
828c2ecf20Sopenharmony_ci	}
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	mdi = kzalloc(sizeof(*mdi), GFP_KERNEL);
858c2ecf20Sopenharmony_ci	if (!mdi)
868c2ecf20Sopenharmony_ci		return NULL;
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	mdi->owner = owner;
898c2ecf20Sopenharmony_ci	kref_init(&mdi->refcount);
908c2ecf20Sopenharmony_ci	list_add_tail(&mdi->list, &media_device_list);
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	dev_dbg(dev, "%s: Allocated media device for owner %s\n",
938c2ecf20Sopenharmony_ci			__func__, module_name);
948c2ecf20Sopenharmony_ci	return &mdi->mdev;
958c2ecf20Sopenharmony_ci}
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_cistruct media_device *media_device_usb_allocate(struct usb_device *udev,
988c2ecf20Sopenharmony_ci					       const char *module_name,
998c2ecf20Sopenharmony_ci					       struct module *owner)
1008c2ecf20Sopenharmony_ci{
1018c2ecf20Sopenharmony_ci	struct media_device *mdev;
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	mutex_lock(&media_device_lock);
1048c2ecf20Sopenharmony_ci	mdev = __media_device_get(&udev->dev, module_name, owner);
1058c2ecf20Sopenharmony_ci	if (!mdev) {
1068c2ecf20Sopenharmony_ci		mutex_unlock(&media_device_lock);
1078c2ecf20Sopenharmony_ci		return ERR_PTR(-ENOMEM);
1088c2ecf20Sopenharmony_ci	}
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	/* check if media device is already initialized */
1118c2ecf20Sopenharmony_ci	if (!mdev->dev)
1128c2ecf20Sopenharmony_ci		__media_device_usb_init(mdev, udev, udev->product,
1138c2ecf20Sopenharmony_ci					module_name);
1148c2ecf20Sopenharmony_ci	mutex_unlock(&media_device_lock);
1158c2ecf20Sopenharmony_ci	return mdev;
1168c2ecf20Sopenharmony_ci}
1178c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(media_device_usb_allocate);
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_civoid media_device_delete(struct media_device *mdev, const char *module_name,
1208c2ecf20Sopenharmony_ci			 struct module *owner)
1218c2ecf20Sopenharmony_ci{
1228c2ecf20Sopenharmony_ci	struct media_device_instance *mdi = to_media_device_instance(mdev);
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	mutex_lock(&media_device_lock);
1258c2ecf20Sopenharmony_ci	/* put module reference for the media_device owner */
1268c2ecf20Sopenharmony_ci	if (mdi->owner != owner) {
1278c2ecf20Sopenharmony_ci		module_put(mdi->owner);
1288c2ecf20Sopenharmony_ci		dev_dbg(mdi->mdev.dev,
1298c2ecf20Sopenharmony_ci			"%s: module %s put owner module reference\n",
1308c2ecf20Sopenharmony_ci			__func__, module_name);
1318c2ecf20Sopenharmony_ci	}
1328c2ecf20Sopenharmony_ci	mutex_unlock(&media_device_lock);
1338c2ecf20Sopenharmony_ci	kref_put(&mdi->refcount, media_device_instance_release);
1348c2ecf20Sopenharmony_ci}
1358c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(media_device_delete);
136