Re: [RFC/PATCH 01/10] media: Media device node support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wednesday 14 July 2010 15:30:10 Laurent Pinchart wrote:
> The media_devnode structure provides support for registering and
> unregistering character devices using a dynamic major number. Reference
> counting is handled internally, making device drivers easier to write
> without having to solve the open/disconnect race condition issue over
> and over again.
> 
> The code is copied mostly verbatim from video/v4l2-dev.c.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@xxxxxxxxxxxxxxxx>
> ---
>  drivers/media/Makefile        |    8 +-
>  drivers/media/media-devnode.c |  479 +++++++++++++++++++++++++++++++++++++++++
>  include/media/media-devnode.h |   97 +++++++++
>  3 files changed, 582 insertions(+), 2 deletions(-)
>  create mode 100644 drivers/media/media-devnode.c
>  create mode 100644 include/media/media-devnode.h
> 
> diff --git a/drivers/media/Makefile b/drivers/media/Makefile
> index 499b081..c1b5938 100644
> --- a/drivers/media/Makefile
> +++ b/drivers/media/Makefile
> @@ -2,7 +2,11 @@
>  # Makefile for the kernel multimedia device drivers.
>  #
>  
> +media-objs	:= media-devnode.o
> +
> +obj-$(CONFIG_MEDIA_SUPPORT)	+= media.o
> +
>  obj-y += common/ IR/ video/
>  
> -obj-$(CONFIG_VIDEO_DEV) += radio/
> -obj-$(CONFIG_DVB_CORE)  += dvb/
> +obj-$(CONFIG_VIDEO_DEV)		+= radio/
> +obj-$(CONFIG_DVB_CORE)		+= dvb/
> diff --git a/drivers/media/media-devnode.c b/drivers/media/media-devnode.c
> new file mode 100644
> index 0000000..53f618b
> --- /dev/null
> +++ b/drivers/media/media-devnode.c
> @@ -0,0 +1,479 @@
> +/*
> + * Media device node
> + *
> + * Generic media device node infrastructure to register and unregister
> + * character devices using a dynamic major number and proper reference
> + * counting.
> + *
> + * Copyright 2010 Laurent Pinchart <laurent.pinchart@xxxxxxxxxxxxxxxx>
> + *
> + * Based on drivers/media/video/v4l2_dev.c code authored by
> + *
> + *	Mauro Carvalho Chehab <mchehab@xxxxxxxxxxxxx> (version 2)
> + *	Alan Cox, <alan@xxxxxxxxxxxxxxxxxxx> (version 1)
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * as published by the Free Software Foundation; either version
> + * 2 of the License, or (at your option) any later version.
> + *
> + */
> +
> +#include <linux/errno.h>
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/kmod.h>
> +#include <linux/slab.h>
> +#include <linux/mm.h>
> +#include <linux/smp_lock.h>
> +#include <linux/string.h>
> +#include <linux/types.h>
> +#include <linux/uaccess.h>
> +#include <asm/system.h>
> +
> +#include <media/media-devnode.h>
> +
> +#define MEDIA_NUM_DEVICES	256
> +#define MEDIA_NAME		"media"
> +
> +static dev_t media_dev_t;
> +
> +/*
> + *	sysfs stuff
> + */
> +
> +static ssize_t show_name(struct device *cd,
> +			 struct device_attribute *attr, char *buf)
> +{
> +	struct media_devnode *mdev = to_media_devnode(cd);
> +
> +	return sprintf(buf, "%.*s\n", (int)sizeof(mdev->name), mdev->name);
> +}
> +
> +static struct device_attribute media_devnode_attrs[] = {
> +	__ATTR(name, S_IRUGO, show_name, NULL),
> +	__ATTR_NULL
> +};
> +
> +/*
> + *	Active devices
> + */
> +static struct media_devnode *media_devnodes[MEDIA_NUM_DEVICES];
> +static DEFINE_MUTEX(media_devnode_lock);
> +static DECLARE_BITMAP(devnode_nums[MEDIA_TYPE_MAX], MEDIA_NUM_DEVICES);
> +
> +/* Device node utility functions */
> +
> +/* Note: these utility functions all assume that type is in the range
> +   [0, MEDIA_TYPE_MAX-1]. */
> +
> +/* Return the bitmap corresponding to type. */
> +static inline unsigned long *devnode_bits(int type)
> +{
> +	return devnode_nums[type];
> +}
> +
> +/* Mark device node number mdev->num as used */
> +static inline void devnode_set(struct media_devnode *mdev)
> +{
> +	set_bit(mdev->num, devnode_bits(mdev->type));
> +}
> +
> +/* Mark device node number mdev->num as unused */
> +static inline void devnode_clear(struct media_devnode *mdev)
> +{
> +	clear_bit(mdev->num, devnode_bits(mdev->type));
> +}
> +
> +/* Try to find a free device node number in the range [from, to> */
> +static inline int devnode_find(struct media_devnode *mdev, int from, int to)
> +{
> +	return find_next_zero_bit(devnode_bits(mdev->type), to, from);
> +}

We really don't need media_devnodes and devnode_nums. Or allow for other
media types for that matter. That's all legacy stuff from v4l2 that can
just be nuked for this new character driver.

> +
> +static inline void media_get(struct media_devnode *mdev)
> +{
> +	get_device(&mdev->dev);
> +}
> +
> +static inline void media_put(struct media_devnode *mdev)
> +{
> +	put_device(&mdev->dev);
> +}
> +
> +/* Called when the last user of the media device exits. */
> +static void media_devnode_release(struct device *cd)
> +{
> +	struct media_devnode *mdev = to_media_devnode(cd);
> +
> +	mutex_lock(&media_devnode_lock);
> +	if (media_devnodes[mdev->minor] != mdev) {
> +		mutex_unlock(&media_devnode_lock);
> +		/* should not happen */
> +		WARN_ON(1);
> +		return;
> +	}
> +
> +	/* Free up this device for reuse */
> +	media_devnodes[mdev->minor] = NULL;
> +
> +	/* Delete the cdev on this minor as well */
> +	cdev_del(mdev->cdev);
> +	/* Just in case some driver tries to access this from the release()
> +	 * callback.
> +	 */
> +	mdev->cdev = NULL;
> +
> +	/* Mark device node number as free */
> +	devnode_clear(mdev);
> +
> +	mutex_unlock(&media_devnode_lock);
> +
> +	/* Release media_devnode and perform other cleanups as needed. */
> +	if (mdev->release)
> +		mdev->release(mdev);
> +}
> +
> +static struct class media_class = {
> +	.name = MEDIA_NAME,
> +	.dev_attrs = media_devnode_attrs,
> +};
> +
> +struct media_devnode *media_devnode_data(struct file *file)
> +{
> +	return media_devnodes[iminor(file->f_path.dentry->d_inode)];

This can become:

struct media_devnode *media_devnode_data(struct inode *inode)
{
	return container_of(inode->i_cdev, struct media_devnode, cdev);
}

where struct cdev is embedded in struct media_devnode. See also the
'Linux Device Drivers' book.

It's probably better if the media_open function does this operation and sets
filp->private_data to media_devnode. Then we can just use:

struct media_devnode *media_devnode_data(struct file *filep)
{
	return filp->private_data;
}


> +}
> +EXPORT_SYMBOL(media_devnode_data);
> +
> +static ssize_t media_read(struct file *filp, char __user *buf,
> +		size_t sz, loff_t *off)
> +{
> +	struct media_devnode *mdev = media_devnode_data(filp);
> +
> +	if (!mdev->fops->read)
> +		return -EINVAL;
> +	if (!media_devnode_is_registered(mdev))
> +		return -EIO;
> +	return mdev->fops->read(filp, buf, sz, off);
> +}
> +
> +static ssize_t media_write(struct file *filp, const char __user *buf,
> +		size_t sz, loff_t *off)
> +{
> +	struct media_devnode *mdev = media_devnode_data(filp);
> +
> +	if (!mdev->fops->write)
> +		return -EINVAL;
> +	if (!media_devnode_is_registered(mdev))
> +		return -EIO;
> +	return mdev->fops->write(filp, buf, sz, off);
> +}
> +
> +static unsigned int media_poll(struct file *filp,
> +			       struct poll_table_struct *poll)
> +{
> +	struct media_devnode *mdev = media_devnode_data(filp);
> +
> +	if (!mdev->fops->poll || !media_devnode_is_registered(mdev))
> +		return DEFAULT_POLLMASK;
> +	return mdev->fops->poll(filp, poll);
> +}
> +
> +static long media_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> +{
> +	struct media_devnode *mdev = media_devnode_data(filp);
> +	int ret = -ENOTTY;
> +
> +	/* Allow ioctl to continue even if the device was unregistered.
> +	   Things like dequeueing buffers might still be useful. */

Definitely out of date comment.

I would bail out if the device was unregistered. Unless there is a good
solid use case for this.

> +	if (mdev->fops->unlocked_ioctl)
> +		ret = mdev->fops->unlocked_ioctl(filp, cmd, arg);

I would only support unlocked_ioctl. Let's not introduce a BKL in this new
char device.

> +	else if (mdev->fops->ioctl) {
> +		lock_kernel();
> +		ret = mdev->fops->ioctl(filp, cmd, arg);
> +		unlock_kernel();
> +	}
> +
> +	return ret;
> +}
> +
> +#ifdef CONFIG_MMU
> +#define media_get_unmapped_area NULL
> +#else
> +static unsigned long media_get_unmapped_area(struct file *filp,
> +		unsigned long addr, unsigned long len, unsigned long pgoff,
> +		unsigned long flags)
> +{
> +	struct media_devnode *mdev = media_devnode_data(filp);
> +
> +	if (!mdev->fops->get_unmapped_area)
> +		return -ENOSYS;
> +	if (!media_devnode_is_registered(mdev))
> +		return -ENODEV;
> +	return mdev->fops->get_unmapped_area(filp, addr, len, pgoff, flags);
> +}
> +#endif

Do we really need this? I know it is in v4l2-dev.c, but AFAIK there isn't a
single driver that implements get_unmapped_area. I never understood why it is
in v4l2-dev.c for that matter.

> +
> +static int media_mmap(struct file *filp, struct vm_area_struct *vm)
> +{
> +	struct media_devnode *mdev = media_devnode_data(filp);
> +
> +	if (!mdev->fops->mmap || !media_devnode_is_registered(mdev))
> +		return -ENODEV;
> +	return mdev->fops->mmap(filp, vm);
> +}

Hmm. Do we want mmap support? I wouldn't add this unless there is a driver
that actually needs this.

> +
> +/* Override for the open function */
> +static int media_open(struct inode *inode, struct file *filp)
> +{
> +	struct media_devnode *mdev;
> +	int ret = 0;
> +
> +	/* Check if the media device is available */
> +	mutex_lock(&media_devnode_lock);
> +	mdev = media_devnode_data(filp);
> +	/* return ENODEV if the media device has been removed
> +	   already or if it is not registered anymore. */
> +	if (mdev == NULL || !media_devnode_is_registered(mdev)) {
> +		mutex_unlock(&media_devnode_lock);
> +		return -ENODEV;
> +	}
> +	/* and increase the device refcount */
> +	media_get(mdev);
> +	mutex_unlock(&media_devnode_lock);
> +	if (mdev->fops->open)
> +		ret = mdev->fops->open(filp);
> +
> +	/* decrease the refcount in case of an error */
> +	if (ret)
> +		media_put(mdev);
> +	return ret;
> +}
> +
> +/* Override for the release function */
> +static int media_release(struct inode *inode, struct file *filp)
> +{
> +	struct media_devnode *mdev = media_devnode_data(filp);
> +	int ret = 0;
> +
> +	if (mdev->fops->release)
> +		mdev->fops->release(filp);
> +
> +	/* decrease the refcount unconditionally since the release()
> +	   return value is ignored. */
> +	media_put(mdev);
> +	return ret;
> +}
> +
> +static const struct file_operations media_devnode_fops = {
> +	.owner = THIS_MODULE,
> +	.read = media_read,
> +	.write = media_write,
> +	.open = media_open,
> +	.get_unmapped_area = media_get_unmapped_area,
> +	.mmap = media_mmap,
> +	.unlocked_ioctl = media_ioctl,
> +#ifdef CONFIG_COMPAT
> +/*	.compat_ioctl = media_compat_ioctl32, */
> +#endif

Just remove this.

> +	.release = media_release,
> +	.poll = media_poll,
> +	.llseek = no_llseek,
> +};
> +
> +/**
> + * media_devnode_register - register a media device node
> + * @mdev: media device node structure we want to register
> + * @type: type of device node to register
> + *
> + * The registration code assigns minor numbers and device node numbers based
> + * on the requested type and registers the new device node with the kernel. An
> + * error is returned if no free minor or device node number could be found, or
> + * if the registration of the device node failed.
> + *
> + * Zero is returned on success.
> + *
> + * Note that if the media_devnode_register call fails, the release() callback of
> + * the media_devnode structure is *not* called, so the caller is responsible for
> + * freeing any data.
> + *
> + * Valid types are
> + *
> + * %MEDIA_TYPE_DEVICE - A media device
> + */
> +int __must_check media_devnode_register(struct media_devnode *mdev, int type)

Don't do types. I see no need for that.

> +{
> +	const char *name_base;
> +	int minor_offset = 0;
> +	int minor_cnt = MEDIA_NUM_DEVICES;
> +	void *priv;
> +	int ret;
> +	int nr;
> +	int i;
> +
> +	/* Part 1: check device type. */
> +	name_base = media_devnode_type_name(type);
> +	if (name_base == NULL) {
> +		printk(KERN_ERR "%s called with unknown type: %d\n",
> +		       __func__, type);
> +		return -EINVAL;
> +	}
> +
> +	mdev->type = type;
> +	mdev->cdev = NULL;
> +
> +	/* Part 2: find a free minor and device node number. */
> +
> +	/* Pick a device node number */
> +	mutex_lock(&media_devnode_lock);
> +	nr = devnode_find(mdev, 0, minor_cnt);
> +	if (nr == minor_cnt) {
> +		printk(KERN_ERR "could not get a free device node number\n");
> +		mutex_unlock(&media_devnode_lock);
> +		return -ENFILE;
> +	}
> +
> +	/* The device node number and minor numbers are independent, so we just
> +	 * find the first free minor number.
> +	 */
> +	for (i = 0; i < MEDIA_NUM_DEVICES; i++)
> +		if (media_devnodes[i] == NULL)
> +			break;
> +	if (i == MEDIA_NUM_DEVICES) {
> +		mutex_unlock(&media_devnode_lock);
> +		printk(KERN_ERR "could not get a free minor\n");
> +		return -ENFILE;
> +	}
> +
> +	mdev->minor = i + minor_offset;
> +	mdev->num = nr;
> +	devnode_set(mdev);
> +
> +	/* Should not happen since we thought this minor was free */
> +	WARN_ON(media_devnodes[mdev->minor] != NULL);
> +	mutex_unlock(&media_devnode_lock);
> +
> +	/* Part 3: Initialize the character device */
> +	mdev->cdev = cdev_alloc();
> +	if (mdev->cdev == NULL) {
> +		ret = -ENOMEM;
> +		goto cleanup;
> +	}
> +	mdev->cdev->ops = &media_devnode_fops;
> +	mdev->cdev->owner = mdev->fops->owner;
> +	ret = cdev_add(mdev->cdev, MKDEV(MAJOR(media_dev_t), mdev->minor), 1);
> +	if (ret < 0) {
> +		printk(KERN_ERR "%s: cdev_add failed\n", __func__);
> +		kfree(mdev->cdev);
> +		mdev->cdev = NULL;
> +		goto cleanup;
> +	}
> +
> +	/* Part 4: register the device with sysfs
> +	 *
> +	 * Zeroing struct device will clear the device's drvdata, so make a
> +	 * copy and put it back.
> +	 * */
> +	priv = dev_get_drvdata(&mdev->dev);
> +	memset(&mdev->dev, 0, sizeof(mdev->dev));
> +	dev_set_drvdata(&mdev->dev, priv);
> +	mdev->dev.class = &media_class;
> +	mdev->dev.devt = MKDEV(MAJOR(media_dev_t), mdev->minor);
> +	if (mdev->parent)
> +		mdev->dev.parent = mdev->parent;
> +	dev_set_name(&mdev->dev, "%s%d", name_base, mdev->num);
> +	ret = device_register(&mdev->dev);
> +	if (ret < 0) {
> +		printk(KERN_ERR "%s: device_register failed\n", __func__);
> +		goto cleanup;
> +	}
> +	/* Register the release callback that will be called when the last
> +	   reference to the device goes away. */
> +	mdev->dev.release = media_devnode_release;
> +
> +	/* Part 5: Activate this minor. The char device can now be used. */
> +	set_bit(MEDIA_FLAG_REGISTERED, &mdev->flags);
> +	mutex_lock(&media_devnode_lock);
> +	media_devnodes[mdev->minor] = mdev;
> +	mutex_unlock(&media_devnode_lock);
> +	return 0;
> +
> +cleanup:
> +	mutex_lock(&media_devnode_lock);
> +	if (mdev->cdev)
> +		cdev_del(mdev->cdev);
> +	devnode_clear(mdev);
> +	mutex_unlock(&media_devnode_lock);
> +	return ret;
> +}
> +
> +/**
> + * media_devnode_unregister - unregister a media device node
> + * @mdev: the device node to unregister
> + *
> + * This unregisters the passed device. Future open calls will be met with
> + * errors.
> + *
> + * This function can safely be called if the device node has never been
> + * registered or has already been unregistered.
> + */
> +void media_devnode_unregister(struct media_devnode *mdev)
> +{
> +	/* Check if mdev was ever registered at all */
> +	if (!media_devnode_is_registered(mdev))
> +		return;
> +
> +	mutex_lock(&media_devnode_lock);
> +	clear_bit(MEDIA_FLAG_REGISTERED, &mdev->flags);
> +	mutex_unlock(&media_devnode_lock);
> +	device_unregister(&mdev->dev);
> +}
> +
> +const char *media_devnode_type_name(int type)
> +{
> +	switch (type) {
> +	case MEDIA_TYPE_DEVICE:
> +		return "media";
> +	default:
> +		return NULL;
> +	}
> +}
> +
> +/*
> + *	Initialise media for linux
> + */
> +static int __init media_devnode_init(void)
> +{
> +	int ret;
> +
> +	printk(KERN_INFO "Linux media interface: v0.10\n");
> +	ret = alloc_chrdev_region(&media_dev_t, 0, MEDIA_NUM_DEVICES,
> +				  MEDIA_NAME);
> +	if (ret < 0) {
> +		printk(KERN_WARNING "media: unable to allcoate major\n");
> +		return ret;
> +	}
> +
> +	ret = class_register(&media_class);
> +	if (ret < 0) {
> +		unregister_chrdev_region(media_dev_t, MEDIA_NUM_DEVICES);
> +		printk(KERN_WARNING "media: class_register failed\n");
> +		return -EIO;
> +	}
> +
> +	return 0;
> +}
> +
> +static void __exit media_devnode_exit(void)
> +{
> +	class_unregister(&media_class);
> +	unregister_chrdev_region(media_dev_t, MEDIA_NUM_DEVICES);
> +}
> +
> +module_init(media_devnode_init)
> +module_exit(media_devnode_exit)
> +
> +MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@xxxxxxxxxxxxxxxx>");
> +MODULE_DESCRIPTION("Device node registration for media drivers");
> +MODULE_LICENSE("GPL");
> diff --git a/include/media/media-devnode.h b/include/media/media-devnode.h
> new file mode 100644
> index 0000000..85c890f
> --- /dev/null
> +++ b/include/media/media-devnode.h
> @@ -0,0 +1,97 @@
> +/*
> + * Media device node handling
> + *
> + * Copyright (C) 2010  Laurent Pinchart <laurent.pinchart@xxxxxxxxxxxxxxxx>
> + *
> + * Common functions for media-related drivers to register and unregister media
> + * device nodes.
> + */
> +#ifndef _MEDIA_DEVNODE_H
> +#define _MEDIA_DEVNODE_H
> +
> +#include <linux/poll.h>
> +#include <linux/fs.h>
> +#include <linux/device.h>
> +#include <linux/cdev.h>
> +
> +/* Media device node type. */
> +#define MEDIA_TYPE_DEVICE	0
> +#define MEDIA_TYPE_MAX		1
> +
> +/*
> + * Flag to mark the media_devnode struct as registered. Drivers must not touch
> + * this flag directly, it will be set and cleared by media_devnode_register and
> + * media_devnode_unregister.
> + */
> +#define MEDIA_FLAG_REGISTERED	0
> +
> +struct media_file_operations {
> +	struct module *owner;
> +	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
> +	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
> +	unsigned int (*poll) (struct file *, struct poll_table_struct *);
> +	long (*ioctl) (struct file *, unsigned int, unsigned long);
> +	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
> +	unsigned long (*get_unmapped_area) (struct file *, unsigned long,
> +				unsigned long, unsigned long, unsigned long);
> +	int (*mmap) (struct file *, struct vm_area_struct *);
> +	int (*open) (struct file *);
> +	int (*release) (struct file *);
> +};
> +
> +/**
> + * struct media_devnode - Media device node
> + * @parent:	parent device
> + * @name:	media device node name
> + * @type:	node type, one of the MEDIA_TYPE_* constants
> + * @minor:	device node minor number
> + * @num:	device node number
> + * @flags:	flags, combination of the MEDIA_FLAG_* constants
> + *
> + * This structure represents a media-related device node.
> + *
> + * The @parent is a physical device. It must be set by core or device drivers
> + * before registering the node.
> + *
> + * @name is a descriptive name exported through sysfs. It doesn't have to be
> + * unique.
> + *
> + * The device node number @num is used to create the kobject name and thus
> + * serves as a hint to udev when creating the device node.
> + */
> +struct media_devnode {
> +	/* device ops */
> +	const struct media_file_operations *fops;
> +
> +	/* sysfs */
> +	struct device dev;		/* v4l device */
> +	struct cdev *cdev;		/* character device */
> +	struct device *parent;		/* device parent */
> +
> +	/* device info */
> +	char name[32];
> +	int type;
> +
> +	int minor;
> +	u16 num;
> +	unsigned long flags;		/* Use bitops to access flags */
> +
> +	/* callbacks */
> +	void (*release)(struct media_devnode *mdev);
> +};
> +
> +/* dev to media_devnode */
> +#define to_media_devnode(cd) container_of(cd, struct media_devnode, dev)
> +
> +int __must_check media_devnode_register(struct media_devnode *mdev, int type);
> +void media_devnode_unregister(struct media_devnode *mdev);
> +
> +const char *media_devnode_type_name(int type);
> +struct media_devnode *media_devnode_data(struct file *file);
> +
> +static inline int media_devnode_is_registered(struct media_devnode *mdev)
> +{
> +	return test_bit(MEDIA_FLAG_REGISTERED, &mdev->flags);
> +}
> +
> +#endif /* _MEDIA_DEVNODE_H */
> 

Regards,

	Hans

-- 
Hans Verkuil - video4linux developer - sponsored by TANDBERG, part of Cisco
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux