diff mbox series

[RFC,v8,1/4] media: Media Device Allocator API

Message ID e474dd16f1d6443c12b1361376193c9d0efcced6.1541109584.git.shuah@kernel.org (mailing list archive)
State New, archived
Headers show
Series Media Device Allocator API | expand

Commit Message

Shuah Nov. 2, 2018, 12:31 a.m. UTC
From: Shuah Khan <shuah@kernel.org>

Media Device Allocator API to allows multiple drivers share a media device.
Using this API, drivers can allocate a media device with the shared struct
device as the key. Once the media device is allocated by a driver, other
drivers can get a reference to it. The media device is released when all
the references are released.

Signed-off-by: Shuah Khan <shuah@kernel.org>
---
 Documentation/media/kapi/mc-core.rst |  37 ++++++++
 drivers/media/Makefile               |   3 +-
 drivers/media/media-dev-allocator.c  | 132 +++++++++++++++++++++++++++
 include/media/media-dev-allocator.h  |  53 +++++++++++
 4 files changed, 224 insertions(+), 1 deletion(-)
 create mode 100644 drivers/media/media-dev-allocator.c
 create mode 100644 include/media/media-dev-allocator.h

Comments

Pavel Machek Nov. 19, 2018, 8:59 a.m. UTC | #1
On Thu 2018-11-01 18:31:30, shuah@kernel.org wrote:
> From: Shuah Khan <shuah@kernel.org>
> 
> Media Device Allocator API to allows multiple drivers share a media device.
> Using this API, drivers can allocate a media device with the shared struct
> device as the key. Once the media device is allocated by a driver, other
> drivers can get a reference to it. The media device is released when all
> the references are released.

Sounds like a ... bad idea?

That's what new "media control" framework is for, no?

Why do you need this?
								Pavel
Hans Verkuil Nov. 20, 2018, 11:20 a.m. UTC | #2
On 11/02/2018 01:31 AM, shuah@kernel.org wrote:
> From: Shuah Khan <shuah@kernel.org>
> 
> Media Device Allocator API to allows multiple drivers share a media device.
> Using this API, drivers can allocate a media device with the shared struct
> device as the key. Once the media device is allocated by a driver, other
> drivers can get a reference to it. The media device is released when all
> the references are released.
> 
> Signed-off-by: Shuah Khan <shuah@kernel.org>
> ---
>  Documentation/media/kapi/mc-core.rst |  37 ++++++++
>  drivers/media/Makefile               |   3 +-
>  drivers/media/media-dev-allocator.c  | 132 +++++++++++++++++++++++++++
>  include/media/media-dev-allocator.h  |  53 +++++++++++
>  4 files changed, 224 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/media/media-dev-allocator.c
>  create mode 100644 include/media/media-dev-allocator.h
> 
> diff --git a/Documentation/media/kapi/mc-core.rst b/Documentation/media/kapi/mc-core.rst
> index 0c05503eaf1f..d6f409598065 100644
> --- a/Documentation/media/kapi/mc-core.rst
> +++ b/Documentation/media/kapi/mc-core.rst
> @@ -257,8 +257,45 @@ Subsystems should facilitate link validation by providing subsystem specific
>  helper functions to provide easy access for commonly needed information, and
>  in the end provide a way to use driver-specific callbacks.
>  
> +Media Controller Device Allocator API
> +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> +
> +When media device belongs to more than one driver, the shared media device

When -> When the

> +is allocated with the shared struct device as the key for look ups.
> +
> +Shared media device should stay in registered state until the last driver

Shared -> The shared

> +unregisters it. In addition, media device should be released when all the

media -> the media

> +references are released. Each driver gets a reference to the media device
> +during probe, when it allocates the media device. If media device is already
> +allocated, allocate API bumps up the refcount and return the existing media

allocate -> the allocate
return -> returns

> +device. Driver puts the reference back from its disconnect routine when it

Driver -> The driver
from -> in

> +calls :c:func:`media_device_delete()`.
> +
> +Media device is unregistered and cleaned up from the kref put handler to

Media -> The media
from -> in

> +ensure that the media device stays in registered state until the last driver
> +unregisters the media device.

What happens if an application still has the media device open and you forcibly
remove the last driver? I think it should be OK since the media_devnode struct
isn't freed until the last open filehandle closes. But it is good to test this.

> +
> +**Driver Usage**
> +
> +Drivers should use the media-core routines to get register reference and

'get register reference'? Not sure what you meant to say.

> +call :c:func:`media_device_delete()` routine to make sure the shared media
> +device delete is handled correctly.
> +
> +**driver probe:**
> +Call :c:func:`media_device_usb_allocate()` to allocate or get a reference
> +Call :c:func:`media_device_register()`, if media devnode isn't registered
> +
> +**driver disconnect:**
> +Call :c:func:`media_device_delete()` to free the media_device. Free'ing is

Free'ing -> Freeing

> +handled by the kref put handler.
> +
> +API Definitions
> +^^^^^^^^^^^^^^^
> +
>  .. kernel-doc:: include/media/media-device.h
>  
>  .. kernel-doc:: include/media/media-devnode.h
>  
>  .. kernel-doc:: include/media/media-entity.h
> +
> +.. kernel-doc:: include/media/media-dev-allocator.h
> diff --git a/drivers/media/Makefile b/drivers/media/Makefile
> index 594b462ddf0e..8608f0a41dca 100644
> --- a/drivers/media/Makefile
> +++ b/drivers/media/Makefile
> @@ -3,7 +3,8 @@
>  # Makefile for the kernel multimedia device drivers.
>  #
>  
> -media-objs	:= media-device.o media-devnode.o media-entity.o
> +media-objs	:= media-device.o media-devnode.o media-entity.o \
> +		   media-dev-allocator.o

Perhaps only add media-dev-allocator if CONFIG_USB is enabled?

>  
>  #
>  # I2C drivers should come before other drivers, otherwise they'll fail
> diff --git a/drivers/media/media-dev-allocator.c b/drivers/media/media-dev-allocator.c
> new file mode 100644
> index 000000000000..262d1293dc13
> --- /dev/null
> +++ b/drivers/media/media-dev-allocator.c
> @@ -0,0 +1,132 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * media-dev-allocator.c - Media Controller Device Allocator API
> + *
> + * Copyright (c) 2018 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>
> +
> +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: mdev=%p\n", __func__, &mdi->mdev);
> +
> +	mutex_lock(&media_device_lock);

Can't the lock be moved down to just before list_del? Or am I missing something?

> +
> +	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,
> +					       char *module_name)

const char *

> +{
> +	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 (find_module(module_name) != mdi->owner &&
> +		    !try_module_get(mdi->owner))
> +			dev_err(dev, "%s: try_module_get() error\n", __func__);
> +		dev_dbg(dev, "%s: get mdev=%p module_name %s\n",
> +			__func__, &mdi->mdev, module_name);
> +		return &mdi->mdev;
> +	}
> +
> +	mdi = kzalloc(sizeof(*mdi), GFP_KERNEL);
> +	if (!mdi)
> +		return NULL;
> +
> +	mdi->owner = find_module(module_name);
> +	kref_init(&mdi->refcount);
> +	list_add_tail(&mdi->list, &media_device_list);
> +
> +	dev_dbg(dev, "%s: alloc mdev=%p module_name %s\n", __func__,
> +		&mdi->mdev, module_name);
> +	return &mdi->mdev;
> +}
> +
> +#if IS_ENABLED(CONFIG_USB)
> +struct media_device *media_device_usb_allocate(struct usb_device *udev,
> +					       char *module_name)

const char *

> +{
> +	struct media_device *mdev;
> +
> +	mutex_lock(&media_device_lock);
> +	mdev = __media_device_get(&udev->dev, module_name);
> +	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);
> +#endif
> +
> +void media_device_delete(struct media_device *mdev, char *module_name)
> +{
> +	struct media_device_instance *mdi = to_media_device_instance(mdev);
> +
> +	dev_dbg(mdi->mdev.dev, "%s: mdev=%p module_name %s\n",
> +		__func__, &mdi->mdev, module_name);
> +
> +	mutex_lock(&media_device_lock);
> +	/* put module reference if media_device owner is not THIS_MODULE */
> +	if (mdi->owner != find_module(module_name)) {
> +		module_put(mdi->owner);
> +		dev_dbg(mdi->mdev.dev,
> +			"%s decremented owner module reference\n", __func__);
> +	}
> +	mutex_unlock(&media_device_lock);
> +	kref_put(&mdi->refcount, media_device_instance_release);
> +}
> +EXPORT_SYMBOL_GPL(media_device_delete);
> diff --git a/include/media/media-dev-allocator.h b/include/media/media-dev-allocator.h
> new file mode 100644
> index 000000000000..8fbadff2cef8
> --- /dev/null
> +++ b/include/media/media-dev-allocator.h
> @@ -0,0 +1,53 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * media-dev-allocator.h - Media Controller Device Allocator API
> + *
> + * Copyright (c) 2018 Shuah Khan <shuah@kernel.org>
> + *
> + * Credits: Suggested by Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> + */
> +
> +/*
> + * This file adds a global ref-counted 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.
> + */
> +
> +#ifndef _MEDIA_DEV_ALLOCTOR_H
> +#define _MEDIA_DEV_ALLOCTOR_H
> +
> +struct usb_device;
> +
> +#ifdef CONFIG_MEDIA_CONTROLLER

Perhaps change this to:

#if defined(CONFIG_MEDIA_CONTROLLER) && defined(CONFIG_USB)

Since for now this only makes sense for USB devices.

> +/**
> + * media_device_usb_allocate() - Allocate and return struct &media device
> + *
> + * @udev:		struct &usb_device pointer
> + * @module_name:	should be filled with %KBUILD_MODNAME
> + *
> + * This interface should be called to allocate a Media Device when multiple
> + * drivers share usb_device and the media device. This interface allocates
> + * &media_device structure and calls media_device_usb_init() to initialize
> + * it.
> + *
> + */
> +struct media_device *media_device_usb_allocate(struct usb_device *udev,
> +					       char *module_name);
> +/**
> + * media_device_delete() - Release media device. Calls kref_put().
> + *
> + * @mdev:		struct &media_device pointer
> + * @module_name:	should be filled with %KBUILD_MODNAME
> + *
> + * This interface should be called to put Media Device Instance kref.
> + */
> +void media_device_delete(struct media_device *mdev, char *module_name);
> +#else
> +static inline struct media_device *media_device_usb_allocate(
> +			struct usb_device *udev, char *module_name)
> +			{ return NULL; }
> +static inline void media_device_delete(
> +			struct media_device *mdev, char *module_name) { }
> +#endif /* CONFIG_MEDIA_CONTROLLER */
> +#endif
> 

Regards,

	Hans
Shuah Dec. 6, 2018, 3:27 p.m. UTC | #3
Hi Hans,

On 11/20/18 4:20 AM, Hans Verkuil wrote:
> On 11/02/2018 01:31 AM, shuah@kernel.org wrote:
>> From: Shuah Khan <shuah@kernel.org>
>>
>> Media Device Allocator API to allows multiple drivers share a media device.
>> Using this API, drivers can allocate a media device with the shared struct
>> device as the key. Once the media device is allocated by a driver, other
>> drivers can get a reference to it. The media device is released when all
>> the references are released.
>>
>> Signed-off-by: Shuah Khan <shuah@kernel.org>

Thanks for catching the documentation corrections. Fixed them all and
working on the next revision of the patch.

>> ---
>>   Documentation/media/kapi/mc-core.rst |  37 ++++++++
>>   drivers/media/Makefile               |   3 +-
>>   drivers/media/media-dev-allocator.c  | 132 +++++++++++++++++++++++++++
>>   include/media/media-dev-allocator.h  |  53 +++++++++++
>>   4 files changed, 224 insertions(+), 1 deletion(-)
>>   create mode 100644 drivers/media/media-dev-allocator.c
>>   create mode 100644 include/media/media-dev-allocator.h
>>
>> diff --git a/Documentation/media/kapi/mc-core.rst b/Documentation/media/kapi/mc-core.rst
>> index 0c05503eaf1f..d6f409598065 100644
>> --- a/Documentation/media/kapi/mc-core.rst
>> +++ b/Documentation/media/kapi/mc-core.rst
>> @@ -257,8 +257,45 @@ Subsystems should facilitate link validation by providing subsystem specific
>>   helper functions to provide easy access for commonly needed information, and
>>   in the end provide a way to use driver-specific callbacks.
>>   
>> +Media Controller Device Allocator API
>> +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>> +
>> +When media device belongs to more than one driver, the shared media device
> 
> When -> When the
> 
>> +is allocated with the shared struct device as the key for look ups.
>> +
>> +Shared media device should stay in registered state until the last driver
> 
> Shared -> The shared
> 
>> +unregisters it. In addition, media device should be released when all the
> 
> media -> the media
> 
>> +references are released. Each driver gets a reference to the media device
>> +during probe, when it allocates the media device. If media device is already
>> +allocated, allocate API bumps up the refcount and return the existing media
> 
> allocate -> the allocate
> return -> returns
> 
>> +device. Driver puts the reference back from its disconnect routine when it
> 
> Driver -> The driver
> from -> in
> 
>> +calls :c:func:`media_device_delete()`.
>> +
>> +Media device is unregistered and cleaned up from the kref put handler to
> 
> Media -> The media
> from -> in
> 
>> +ensure that the media device stays in registered state until the last driver
>> +unregisters the media device.
> 
> What happens if an application still has the media device open and you forcibly
> remove the last driver? I think it should be OK since the media_devnode struct
> isn't freed until the last open filehandle closes. But it is good to test this.
> 
>> +
>> +**Driver Usage**
>> +
>> +Drivers should use the media-core routines to get register reference and
> 
> 'get register reference'? Not sure what you meant to say.
> 
>> +call :c:func:`media_device_delete()` routine to make sure the shared media
>> +device delete is handled correctly.
>> +
>> +**driver probe:**
>> +Call :c:func:`media_device_usb_allocate()` to allocate or get a reference
>> +Call :c:func:`media_device_register()`, if media devnode isn't registered
>> +
>> +**driver disconnect:**
>> +Call :c:func:`media_device_delete()` to free the media_device. Free'ing is
> 
> Free'ing -> Freeing
> 
>> +handled by the kref put handler.
>> +
>> +API Definitions
>> +^^^^^^^^^^^^^^^
>> +
>>   .. kernel-doc:: include/media/media-device.h
>>   
>>   .. kernel-doc:: include/media/media-devnode.h
>>   
>>   .. kernel-doc:: include/media/media-entity.h
>> +
>> +.. kernel-doc:: include/media/media-dev-allocator.h
>> diff --git a/drivers/media/Makefile b/drivers/media/Makefile
>> index 594b462ddf0e..8608f0a41dca 100644
>> --- a/drivers/media/Makefile
>> +++ b/drivers/media/Makefile
>> @@ -3,7 +3,8 @@
>>   # Makefile for the kernel multimedia device drivers.
>>   #
>>   
>> -media-objs	:= media-device.o media-devnode.o media-entity.o
>> +media-objs	:= media-device.o media-devnode.o media-entity.o \
>> +		   media-dev-allocator.o
> 
> Perhaps only add media-dev-allocator if CONFIG_USB is enabled?
> 
>>   
>>   #
>>   # I2C drivers should come before other drivers, otherwise they'll fail
>> diff --git a/drivers/media/media-dev-allocator.c b/drivers/media/media-dev-allocator.c
>> new file mode 100644
>> index 000000000000..262d1293dc13
>> --- /dev/null
>> +++ b/drivers/media/media-dev-allocator.c
>> @@ -0,0 +1,132 @@
>> +// SPDX-License-Identifier: GPL-2.0-or-later
>> +/*
>> + * media-dev-allocator.c - Media Controller Device Allocator API
>> + *
>> + * Copyright (c) 2018 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>
>> +
>> +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: mdev=%p\n", __func__, &mdi->mdev);
>> +
>> +	mutex_lock(&media_device_lock);
> 
> Can't the lock be moved down to just before list_del? Or am I missing something?
> 

The lock needs to be held while media_device_unregister() and 
media_device_cleanup() to avoid races in the unregister path if two 
drivers call media_device_delete().

>> +
>> +	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,
>> +					       char *module_name)
> 
> const char *

Thanks fixed all of these.

I am running final tests and will send the next version this week.

thanks,
-- Shuah
Shuah Dec. 6, 2018, 3:33 p.m. UTC | #4
On 11/19/18 1:59 AM, Pavel Machek wrote:
> On Thu 2018-11-01 18:31:30, shuah@kernel.org wrote:
>> From: Shuah Khan <shuah@kernel.org>
>>
>> Media Device Allocator API to allows multiple drivers share a media device.
>> Using this API, drivers can allocate a media device with the shared struct
>> device as the key. Once the media device is allocated by a driver, other
>> drivers can get a reference to it. The media device is released when all
>> the references are released.
> 
> Sounds like a ... bad idea?
> 
> That's what new "media control" framework is for, no?
> 
> Why do you need this?
> 								Pavel
> 

Media control framework doesn't address this problem of ownership of the 
media device when non-media drivers have to own the pipeline. In this 
case, snd-usb owns the audio pipeline when an audio application is using 
the device. Without this work, media drivers won't be able to tell if 
snd-usb is using the tuner and owns the media pipeline.

I am going to clarify this in the commit log.

thanks,
-- Shuah
Pavel Machek Dec. 9, 2018, 8:09 a.m. UTC | #5
On Thu 2018-12-06 08:33:14, shuah wrote:
> On 11/19/18 1:59 AM, Pavel Machek wrote:
> >On Thu 2018-11-01 18:31:30, shuah@kernel.org wrote:
> >>From: Shuah Khan <shuah@kernel.org>
> >>
> >>Media Device Allocator API to allows multiple drivers share a media device.
> >>Using this API, drivers can allocate a media device with the shared struct
> >>device as the key. Once the media device is allocated by a driver, other
> >>drivers can get a reference to it. The media device is released when all
> >>the references are released.
> >
> >Sounds like a ... bad idea?
> >
> >That's what new "media control" framework is for, no?
> >
> >Why do you need this?
> 
> Media control framework doesn't address this problem of ownership of the
> media device when non-media drivers have to own the pipeline. In this case,
> snd-usb owns the audio pipeline when an audio application is using the
> device. Without this work, media drivers won't be able to tell if snd-usb is
> using the tuner and owns the media pipeline.
> 
> I am going to clarify this in the commit log.

I guess I'll need the explanation, yes.

How can usb soundcard use the tuner? I thought we'd always have
userspace component active and moving data between tuner and usb sound
card?

									Pavel
Mauro Carvalho Chehab Dec. 9, 2018, 11:27 a.m. UTC | #6
Em Sun, 9 Dec 2018 09:09:44 +0100
Pavel Machek <pavel@ucw.cz> escreveu:

> On Thu 2018-12-06 08:33:14, shuah wrote:
> > On 11/19/18 1:59 AM, Pavel Machek wrote:  
> > >On Thu 2018-11-01 18:31:30, shuah@kernel.org wrote:  
> > >>From: Shuah Khan <shuah@kernel.org>
> > >>
> > >>Media Device Allocator API to allows multiple drivers share a media device.
> > >>Using this API, drivers can allocate a media device with the shared struct
> > >>device as the key. Once the media device is allocated by a driver, other
> > >>drivers can get a reference to it. The media device is released when all
> > >>the references are released.  
> > >
> > >Sounds like a ... bad idea?
> > >
> > >That's what new "media control" framework is for, no?
> > >
> > >Why do you need this?  
> > 
> > Media control framework doesn't address this problem of ownership of the
> > media device when non-media drivers have to own the pipeline. In this case,
> > snd-usb owns the audio pipeline when an audio application is using the
> > device. Without this work, media drivers won't be able to tell if snd-usb is
> > using the tuner and owns the media pipeline.
> > 
> > I am going to clarify this in the commit log.  
> 
> I guess I'll need the explanation, yes.
> 
> How can usb soundcard use the tuner? I thought we'd always have
> userspace component active and moving data between tuner and usb sound
> card?

It sounds that the description of the patch is not 100%, as it seems
that you're not seeing the hole picture.

This is designed to solve a very common usecase for media devices
where one physical device (an USB stick) provides both audio
and video.

That's, for example, the case of cameras with microphones and 
TV USB devices. Those usually expose the audio via standard
USB Audio Class, and video either via USB Video Class or via
some proprietary vendor class.

Due to the way USB Audio Class is handled, it means that two
independent drivers will provide the pipelines for a single
physical USB bridge.

The same problem also applies to more sophisticated embedded devices,
like on  SOCs designed to be used on TVs and Set Top Boxes, where the
hardware pipeline has both audio and video components on it, logically
mapped into different drivers (using Linux DTV API, V4L2 API and ALSA).

On such kind of devices, it is important to have a way to see
and control the entire audio and video pipeline present on them 
through a single media controller device, specially if one wants
to provide a hardware pipeline within the SoC that won't be 
copying data between Kernel-userspace.

Now, if the audio is implemented on a separate device (like an
Intel HDA compatible chipset at the motherboard), it should
be exposed as a separate media controller.

So, for example, a system that has both an USB audio/video
stick and an Intel HDA-compatible chipset, both exposed via
the media controller, will have two media controller devices,
one for each physically independent device.

On the other hand, an SoC designed for TV products will likely
expose a single media controller, even if each part of the
pipeline is exposed via independent Linux device drivers.

Thanks,
Mauro
Pavel Machek Dec. 9, 2018, 11:37 a.m. UTC | #7
Hi!

> > On Thu 2018-12-06 08:33:14, shuah wrote:
> > > On 11/19/18 1:59 AM, Pavel Machek wrote:  
> > > >On Thu 2018-11-01 18:31:30, shuah@kernel.org wrote:  
> > > >>From: Shuah Khan <shuah@kernel.org>
> > > >>
> > > >>Media Device Allocator API to allows multiple drivers share a media device.
> > > >>Using this API, drivers can allocate a media device with the shared struct
> > > >>device as the key. Once the media device is allocated by a driver, other
> > > >>drivers can get a reference to it. The media device is released when all
> > > >>the references are released.  
> > > >
> > > >Sounds like a ... bad idea?
> > > >
> > > >That's what new "media control" framework is for, no?
> > > >
> > > >Why do you need this?  
> > > 
> > > Media control framework doesn't address this problem of ownership of the
> > > media device when non-media drivers have to own the pipeline. In this case,
> > > snd-usb owns the audio pipeline when an audio application is using the
> > > device. Without this work, media drivers won't be able to tell if snd-usb is
> > > using the tuner and owns the media pipeline.
> > > 
> > > I am going to clarify this in the commit log.  
> > 
> > I guess I'll need the explanation, yes.
> > 
> > How can usb soundcard use the tuner? I thought we'd always have
> > userspace component active and moving data between tuner and usb sound
> > card?
> 
> It sounds that the description of the patch is not 100%, as it seems
> that you're not seeing the hole picture.
> 
> This is designed to solve a very common usecase for media devices
> where one physical device (an USB stick) provides both audio
> and video.

Aha, ok, it makes sense now. Thanks!
									Pavel
diff mbox series

Patch

diff --git a/Documentation/media/kapi/mc-core.rst b/Documentation/media/kapi/mc-core.rst
index 0c05503eaf1f..d6f409598065 100644
--- a/Documentation/media/kapi/mc-core.rst
+++ b/Documentation/media/kapi/mc-core.rst
@@ -257,8 +257,45 @@  Subsystems should facilitate link validation by providing subsystem specific
 helper functions to provide easy access for commonly needed information, and
 in the end provide a way to use driver-specific callbacks.
 
+Media Controller Device Allocator API
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When media device belongs to more than one driver, the shared media device
+is allocated with the shared struct device as the key for look ups.
+
+Shared media device should stay in registered state until the last driver
+unregisters it. In addition, media device should be released when all the
+references are released. Each driver gets a reference to the media device
+during probe, when it allocates the media device. If media device is already
+allocated, allocate API bumps up the refcount and return the existing media
+device. Driver puts the reference back from its disconnect routine when it
+calls :c:func:`media_device_delete()`.
+
+Media device is unregistered and cleaned up from the kref put handler to
+ensure that the media device stays in registered state until the last driver
+unregisters the media device.
+
+**Driver Usage**
+
+Drivers should use the media-core routines to get register reference and
+call :c:func:`media_device_delete()` routine to make sure the shared media
+device delete is handled correctly.
+
+**driver probe:**
+Call :c:func:`media_device_usb_allocate()` to allocate or get a reference
+Call :c:func:`media_device_register()`, if media devnode isn't registered
+
+**driver disconnect:**
+Call :c:func:`media_device_delete()` to free the media_device. Free'ing is
+handled by the kref put handler.
+
+API Definitions
+^^^^^^^^^^^^^^^
+
 .. kernel-doc:: include/media/media-device.h
 
 .. kernel-doc:: include/media/media-devnode.h
 
 .. kernel-doc:: include/media/media-entity.h
+
+.. kernel-doc:: include/media/media-dev-allocator.h
diff --git a/drivers/media/Makefile b/drivers/media/Makefile
index 594b462ddf0e..8608f0a41dca 100644
--- a/drivers/media/Makefile
+++ b/drivers/media/Makefile
@@ -3,7 +3,8 @@ 
 # Makefile for the kernel multimedia device drivers.
 #
 
-media-objs	:= media-device.o media-devnode.o media-entity.o
+media-objs	:= media-device.o media-devnode.o media-entity.o \
+		   media-dev-allocator.o
 
 #
 # I2C drivers should come before other drivers, otherwise they'll fail
diff --git a/drivers/media/media-dev-allocator.c b/drivers/media/media-dev-allocator.c
new file mode 100644
index 000000000000..262d1293dc13
--- /dev/null
+++ b/drivers/media/media-dev-allocator.c
@@ -0,0 +1,132 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * media-dev-allocator.c - Media Controller Device Allocator API
+ *
+ * Copyright (c) 2018 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>
+
+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: mdev=%p\n", __func__, &mdi->mdev);
+
+	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,
+					       char *module_name)
+{
+	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 (find_module(module_name) != mdi->owner &&
+		    !try_module_get(mdi->owner))
+			dev_err(dev, "%s: try_module_get() error\n", __func__);
+		dev_dbg(dev, "%s: get mdev=%p module_name %s\n",
+			__func__, &mdi->mdev, module_name);
+		return &mdi->mdev;
+	}
+
+	mdi = kzalloc(sizeof(*mdi), GFP_KERNEL);
+	if (!mdi)
+		return NULL;
+
+	mdi->owner = find_module(module_name);
+	kref_init(&mdi->refcount);
+	list_add_tail(&mdi->list, &media_device_list);
+
+	dev_dbg(dev, "%s: alloc mdev=%p module_name %s\n", __func__,
+		&mdi->mdev, module_name);
+	return &mdi->mdev;
+}
+
+#if IS_ENABLED(CONFIG_USB)
+struct media_device *media_device_usb_allocate(struct usb_device *udev,
+					       char *module_name)
+{
+	struct media_device *mdev;
+
+	mutex_lock(&media_device_lock);
+	mdev = __media_device_get(&udev->dev, module_name);
+	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);
+#endif
+
+void media_device_delete(struct media_device *mdev, char *module_name)
+{
+	struct media_device_instance *mdi = to_media_device_instance(mdev);
+
+	dev_dbg(mdi->mdev.dev, "%s: mdev=%p module_name %s\n",
+		__func__, &mdi->mdev, module_name);
+
+	mutex_lock(&media_device_lock);
+	/* put module reference if media_device owner is not THIS_MODULE */
+	if (mdi->owner != find_module(module_name)) {
+		module_put(mdi->owner);
+		dev_dbg(mdi->mdev.dev,
+			"%s decremented owner module reference\n", __func__);
+	}
+	mutex_unlock(&media_device_lock);
+	kref_put(&mdi->refcount, media_device_instance_release);
+}
+EXPORT_SYMBOL_GPL(media_device_delete);
diff --git a/include/media/media-dev-allocator.h b/include/media/media-dev-allocator.h
new file mode 100644
index 000000000000..8fbadff2cef8
--- /dev/null
+++ b/include/media/media-dev-allocator.h
@@ -0,0 +1,53 @@ 
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * media-dev-allocator.h - Media Controller Device Allocator API
+ *
+ * Copyright (c) 2018 Shuah Khan <shuah@kernel.org>
+ *
+ * Credits: Suggested by Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ */
+
+/*
+ * This file adds a global ref-counted 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.
+ */
+
+#ifndef _MEDIA_DEV_ALLOCTOR_H
+#define _MEDIA_DEV_ALLOCTOR_H
+
+struct usb_device;
+
+#ifdef CONFIG_MEDIA_CONTROLLER
+/**
+ * media_device_usb_allocate() - Allocate and return struct &media device
+ *
+ * @udev:		struct &usb_device pointer
+ * @module_name:	should be filled with %KBUILD_MODNAME
+ *
+ * This interface should be called to allocate a Media Device when multiple
+ * drivers share usb_device and the media device. This interface allocates
+ * &media_device structure and calls media_device_usb_init() to initialize
+ * it.
+ *
+ */
+struct media_device *media_device_usb_allocate(struct usb_device *udev,
+					       char *module_name);
+/**
+ * media_device_delete() - Release media device. Calls kref_put().
+ *
+ * @mdev:		struct &media_device pointer
+ * @module_name:	should be filled with %KBUILD_MODNAME
+ *
+ * This interface should be called to put Media Device Instance kref.
+ */
+void media_device_delete(struct media_device *mdev, char *module_name);
+#else
+static inline struct media_device *media_device_usb_allocate(
+			struct usb_device *udev, char *module_name)
+			{ return NULL; }
+static inline void media_device_delete(
+			struct media_device *mdev, char *module_name) { }
+#endif /* CONFIG_MEDIA_CONTROLLER */
+#endif