// SPDX-License-Identifier: GPL-2.0 /* * media-dev-allocator.c - Media Controller Device Allocator API * * Copyright (c) 2019 Shuah Khan * * Credits: Suggested by Laurent Pinchart */ /* * 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 #include #include #include #include #include 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(sizeof(*mdi), GFP_KERNEL); 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);