多媒体设备涉及到多个设备之间的数据链接和数据流控制,对其进行管理比较复杂,需要通过media子系统进行统一管理,以将这些媒体设备形成的数据通路上的各个设备建立拓扑关系,便于实现各个设备之间的链接控制和数据传输管理。
v4l2框架中,媒体设备的注册主要在驱动程序在v4l2_dev的注册以及video_device的注册时进行注册的,注册完成后会出现/dev/mediaX 设备。
媒体设备相关数据结构
媒体设备的主体media_device:
struct media_device {
/* dev->driver_data points to this struct. */
struct device *dev;
struct media_devnode *devnode;
char model[32];
char driver_name[32];
char serial[40];
char bus_info[32];
u32 hw_revision;
u64 topology_version;
u32 id;
struct ida entity_internal_idx;
int entity_internal_idx_max;
struct list_head entities;
struct list_head interfaces;
struct list_head pads;
struct list_head links;
/* notify callback list invoked when a new entity is registered */
struct list_head entity_notify;
/* Serializes graph operations. */
struct mutex graph_mutex;
struct media_graph pm_count_walk;
void *source_priv;
int (*enable_source)(struct media_entity *entity,
struct media_pipeline *pipe);
void (*disable_source)(struct media_entity *entity);
const struct media_device_ops *ops;
};
媒体设备media_device所属的三件套:media_entity 、media_pad、media_link,媒体设备之间的拓扑关系都是靠这几个媒体构件来建立和维系的,三者能够建立两两相互包含的关系。在注册各个子设备v4l2_subdev时,v4l2_subdev将会作为一个 entity。
/*
media_gobj 是media_entity media_pad media_link共有的数据结构
所以单独定义成一个数据结构,嵌入在media_entity media_pad media_link这几个媒体构件中。
mdev: 指向拥有嵌入本数据结构的媒体构件的&media_device。
list: 根据嵌入的媒体构件的类型,挂接在media_device中相应的链表上。
*/
struct media_gobj {
struct media_device *mdev;
u32 id;
struct list_head list;
};
struct media_entity {
struct media_gobj graph_obj; /* must be first field in struct */
const char *name;
enum media_entity_type obj_type;//实现media_entity的对象的类型
u32 function;//entity功能
unsigned long flags;
u16 num_pads;//entity拥有的pad数
u16 num_links;//链接数,包括前向链接和背向链接,使能和未使能的链接
u16 num_backlinks;//背向链接总数
int internal_idx;//entity内部唯一id,entity被注销后可以被重复利用
struct media_pad *pads;//pad数组,存放media_entity的所有pad,这里的空间被定义在驱动程序定义的结构体中。
struct list_head links;//结合stack_push函数和media_graph_walk_iter函数可知,links挂载着属于本media_entity的media_link
const struct media_entity_operations *ops;//entity相关的函数操作集
int stream_count;//开流次数
int use_count;
struct media_pipeline *pipe;//本entity属于的media_pipeline
union {
struct {
u32 major;
u32 minor;
} dev;//设备节点的设备号,Kept just for backward compatibility.
} info;
};
struct media_pad {
struct media_gobj graph_obj; /* must be first field in struct */
struct media_entity *entity;//本media_pad所属的media_entity
u16 index;//本media_pad在media_entity.pads数组中的索引
unsigned long flags;
};
struct media_link {
struct media_gobj graph_obj;
struct list_head list;//media_entity.links挂载着属于media_entity的media_link.list
union {
struct media_gobj *gobj0;
struct media_pad *source;//输出meida_entity的media_pad
struct media_interface *intf;
};
union {
struct media_gobj *gobj1;
struct media_pad *sink;//输入meida_entity的media_pad
struct media_entity *entity;
};
struct media_link *reverse;//如果本链接media_link为背向链接,则指向正向链接,否则指向背向链接
unsigned long flags;
bool is_backlink;//本链接media_link为背向链接时为true
};
源码分析
在注册媒体设备前,需要先使用media_device_init函数初始化media_device,源码如下:
void media_device_init(struct media_device *mdev)
{
INIT_LIST_HEAD(&mdev->entities);
INIT_LIST_HEAD(&mdev->interfaces);
INIT_LIST_HEAD(&mdev->pads);
INIT_LIST_HEAD(&mdev->links);
INIT_LIST_HEAD(&mdev->entity_notify);
mutex_init(&mdev->graph_mutex);
ida_init(&mdev->entity_internal_idx);//初始化基数树,用于id分配和查询
dev_dbg(mdev->dev, "Media device initialized\n");
}
static inline void ida_init(struct ida *ida)
{
INIT_RADIX_TREE(&ida->ida_rt, IDR_RT_MARKER | GFP_NOWAIT);
}
#define INIT_RADIX_TREE(root, mask) \
do { \
(root)->gfp_mask = (mask); \
(root)->rnode = NULL; \
} while (0)
media_device_init中主要初始化entity、pad、link等链表,以及初始化相应的锁和基数树。media_device_init函数完成初始化后,使用media_device_register进行媒体设备的注册,
该函数的源码如下:
struct media_devnode {
struct media_device *media_dev;
/* device ops */
const struct media_file_operations *fops;
/* sysfs */
struct device dev; /* media device */
struct cdev cdev; /* character device */
struct device *parent; /* device parent */
/* device info */
int minor;
unsigned long flags; /* Use bitops to access flags */
/* callbacks */
void (*release)(struct media_devnode *devnode);
};
#define media_device_register(mdev) __media_device_register(mdev, THIS_MODULE)
int __must_check __media_device_register(struct media_device *mdev,
struct module *owner)
{
struct media_devnode *devnode;
int ret;
devnode = kzalloc(sizeof(*devnode), GFP_KERNEL);
if (!devnode)
return -ENOMEM;
/* Register the device node. */
mdev->devnode = devnode;
devnode->fops = &media_device_fops;
devnode->parent = mdev->dev;
devnode->release = media_device_release;
/* Set version 0 to indicate user-space that the graph is static */
mdev->topology_version = 0;
ret = media_devnode_register(mdev, devnode, owner);//下面继续分析
if (ret < 0) {
/* devnode free is handled in media_devnode_*() */
mdev->devnode = NULL;
return ret;
}
ret = device_create_file(&devnode->dev, &dev_attr_model);//注册设备的属性节点,用户层可直接通过该节点访问设备某些参数属性
if (ret < 0) {
/* devnode free is handled in media_devnode_*() */
mdev->devnode = NULL;
media_devnode_unregister_prepare(devnode);
media_devnode_unregister(devnode);
return ret;
}
dev_dbg(mdev->dev, "Media device registered\n");
return 0;
}
//这个函数中的几个步骤英文注释得挺清楚的
int __must_check media_devnode_register(struct media_device *mdev,
struct media_devnode *devnode,
struct module *owner)
{
int minor;
int ret;
/* Part 1: Find a free minor number */
mutex_lock(&media_devnode_lock);
/*
查找位图media_devnode_nums中查找从0开始,第一个为0的位的位置。为0表示该位空闲
将返回的该位的位置作为设备节点的minor
*/
minor = find_next_zero_bit(media_devnode_nums, MEDIA_NUM_DEVICES, 0);
if (minor == MEDIA_NUM_DEVICES) {
mutex_unlock(&media_devnode_lock);
pr_err("could not get a free minor\n");
kfree(devnode);
return -ENFILE;
}
set_bit(minor, media_devnode_nums);
mutex_unlock(&media_devnode_lock);
devnode->minor = minor;
devnode->media_dev = mdev;
/* Part 1: Initialize dev now to use dev.kobj for cdev.kobj.parent
在系统启动时调用media_devnode_init中完成media_bus_type设备总线的注册
和media_dev_t媒体设备号的申请
*/
devnode->dev.bus = &media_bus_type;
devnode->dev.devt = MKDEV(MAJOR(media_dev_t), devnode->minor);
devnode->dev.release = media_devnode_release;
if (devnode->parent)
devnode->dev.parent = devnode->parent;
dev_set_name(&devnode->dev, "media%d", devnode->minor);//设置devnode->dev.object.name
device_initialize(&devnode->dev);//初始化devnode->dev
/* Part 2: Initialize the character device
初始化字符设备,主要效果devnode->cdev->ops = &media_devnode_fops
注意,这里的media_devnode_fops与前面的devnode->fops = &media_device_fops
有联系,这个放在结尾分析
*/
cdev_init(&devnode->cdev, &media_devnode_fops);
devnode->cdev.owner = owner;
/* Part 3: Add the media and char device
注册字符设备到devnode->dev
*/
ret = cdev_device_add(&devnode->cdev, &devnode->dev);
if (ret < 0) {
pr_err("%s: cdev_device_add failed\n", __func__);
goto cdev_add_error;
}
/* Part 4: Activate this minor. The char device can now be used.
*/
set_bit(MEDIA_FLAG_REGISTERED, &devnode->flags);
return 0;
cdev_add_error:
mutex_lock(&media_devnode_lock);
clear_bit(devnode->minor, media_devnode_nums);
devnode->media_dev = NULL;
mutex_unlock(&media_devnode_lock);
put_device(&devnode->dev);
return ret;
}
这里再分析一下media_devnode_fops和media_device_fops的关系,媒体设备的注册__media_device_register函数中,devnode->fops = &media_device_fops,media_devnode_register函数中,cdev_init(&devnode->cdev, &media_devnode_fops),即devnode->cdev->fops = media_device_fops。media_device_fops的实现如下:
static const struct file_operations media_devnode_fops = {
.owner = THIS_MODULE,
.read = media_read,
.write = media_write,
.open = media_open,
.unlocked_ioctl = media_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = media_compat_ioctl,
#endif /* CONFIG_COMPAT */
.release = media_release,
.poll = media_poll,
.llseek = no_llseek,
};
static ssize_t media_read(struct file *filp, char __user *buf,
size_t sz, loff_t *off)
{
struct media_devnode *devnode = media_devnode_data(filp);
if (!devnode->fops->read)
return -EINVAL;
if (!media_devnode_is_registered(devnode))
return -EIO;
//devnode->fops就是__media_device_register函数中,devnode->fops = &media_device_fops
return devnode->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 *devnode = media_devnode_data(filp);
if (!devnode->fops->write)
return -EINVAL;
if (!media_devnode_is_registered(devnode))
return -EIO;
//如上,devnode->fops为media_device_fops
return devnode->fops->write(filp, buf, sz, off);
}
static long media_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct media_devnode *devnode = media_devnode_data(filp);
//如上,devnode->fops为media_device_fops
return __media_ioctl(filp, cmd, arg, devnode->fops->ioctl);
}
__media_ioctl(struct file *filp, unsigned int cmd, unsigned long arg,
long (*ioctl_func)(struct file *filp, unsigned int cmd,
unsigned long arg))
{
struct media_devnode *devnode = media_devnode_data(filp);
if (!ioctl_func)
return -ENOTTY;
if (!media_devnode_is_registered(devnode))
return -EIO;
//ioctl_func为传入参数,即devnode->fops->ioctl
return ioctl_func(filp, cmd, arg);
}
static long media_compat_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct media_devnode *devnode = media_devnode_data(filp);
//如上,devnode->fops为media_device_fops
return __media_ioctl(filp, cmd, arg, devnode->fops->compat_ioctl);
}
由以上代码可见,cdev->ops的函数会去调用devnode->fops中的函数,这就是相当于
devnode是个壳,套着cdev字符设备,并将devnode本身的操作集通过cdev的操作集
间接暴露给用户空间。
字符设备节点的注册、设备节点属性文件节点和媒体设备总线的注册涉及到虚拟文件系统的东西,这些都很久之前看过挺多相关的源码和资料,印象已经不是很深刻,等以后有空复习再更新一下。
关于媒体设备相关的注册,还有entity、pad、link等的添加和注册,后续再更新。