文章目录
- 1、声明
- 2、subdev和media子系统
1、声明
本文是在学习韦东山《驱动大全》V4L2子系统时,为梳理知识点和自己回看而记录,全部内容高度复制粘贴。
韦老师的《驱动大全》:商品详情
其对应的讲义资料:https://e.coding.net/weidongshan/linux/doc_and_source_for_drivers.git
subdevmedia_8">2、subdev和media子系统
subdev_9">2.1、subdev的引入
在v4l2最开始的学习中,我们编写了一个虚拟的摄像头驱动程序,其整个程序的核心就是一个video_device结构体。
后面学习uvc摄像头驱动程序框架,因为usb摄像头内部有各类Unit或Terminal,但是这些Unit或Terminal的独立性不强,并且它们遵守UVC规范,没必要单独为它们编写驱动程序。在UVC驱动程序里,每一个Unit或Terminal都有一个"struct uvc_entity"结构体,每一个"struct uvc_entity"里面也都有一个"struct v4l2_subdev":
但是,这些subdev没有什么作用,它们的ops函数都是空的:
但本节我们将讨论mipi摄像头驱动,之所以在mipi摄像头驱动章节才引入subdev和media子系统,正是因为mipi摄像头涉及的内部结构或内部流程很复杂,如下图是v853 mipi摄像头的内部结构:
内部涉及的模块非常多,这些模块相对独立,有必要单独给这些模块编写驱动程序。
这些模块被抽象为subdev,原因有2:
- 屏蔽硬件操作的细节,有些模块是I2C接口的,有些模块是SPI接口的,它们都被封装为subdev。
- 方便内核使用、APP使用:可以在内核里调用subdev的函数,也可以在用户空间调用subdev的函数。很多厂家不愿意公开ISP的源码,只能在驱动层面提供最简单的subdev,然后通过APP调用subdev的基本读写函数进行复杂的设置。
subdev结构体如下:
里面有各类ops结构体,比如core、tuner、audio、video等等。编写subdev驱动程序时,核心就是实现各类ops结构体的函数。
以上图中第1个模块gc2053为例,它的代码为<font style="color:rgb(51, 51, 51);">"drivers\media\platform\sunxi-vin\modules\sensor\gc2053_mipi.c"</font>
,核心结构体如下:
2.2、media子系统的引入
即使我们给每一个子模块都编写了驱动程序,都构造了subdev,但是它们之间的相互关系怎么表示?如下图:
- 每一个模块都构造了subdev
- 但是,框图里黑色箭头所表示的关系如何描述?
- 比如,从"格式解析"模块出来的数据,到底是输出到"ISP0"还是"ISP1"?用户如何选择?
- 当我们指定"格式解析"模块输出的数据格式后,是否也要同步指定ISP0或ISP1模块的输入数据格式?
这就需要引入media子系统,简化如下:
怎么操作上图的各个对象?使用subdev。subdev里含有各个对象的操作函数。
怎么表示上图的各个对象之间的关系?使用media子系统。在media子系统里:
- 每个对象都是一个media_entity。
- media_entity有media_pad,可以认为是端点(不能简单认为是硬件引脚),有source pad(输出数据),sink pad(输入数据)。
- media_entity之间的连接被称为media_link。
- media_link仅仅表示两个media_entity之间的连接,要构成一个完整的数据通道,需要多个系列的连接,这被称为pipeline。
- media子系统的作用就在于管理"拓扑关系",就是各个对象的连接关系。
把V853的拓扑图精简一下:
怎么表示上图的各个对象?使用media子系统,media子系统的作用在于管理"拓扑关系",就是各个对象的连接关系。
怎么操作上图的各个对象?使用subdev。subdev里含有各个对象的操作函数。
命令示例:
media-ctl -v -l '"gc2053_mipi":0->"sunxi_mipi.0":0[1]'
v4l2-ctl -D -d /dev/v4l-subdev0
subdev_82">2.3、subdev概览和数据结构
2.3.1、实例
以<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">drivers\media\platform\sunxi-vin\modules\sensor\gc2053_mipi.c</font>
为例:
sensor_probe/* 分配sensor_info结构体,里面含有v4l2_subdev */struct v4l2_subdev *sd; struct sensor_info *info;info = kzalloc(sizeof(struct sensor_info), GFP_KERNEL);sd = &info->sd;cci_dev_probe_helper(sd, client, &sensor_ops, &cci_drv[sensor_dev_id++]);v4l2_i2c_subdev_init(sd, client, sensor_ops);v4l2_subdev_init(sd, ops);
2.3.2、数据结构
一个模块对应一个subdev,怎么操作这个模块?subdev里面有ops:
怎么管理多个subdev?必定是放入一个链表里:
2.4、media子系统概览和数据结构
2.4.1、拓扑结构
对于V853精简的拓扑图:
在media子系统里,怎么表示上面的各个entity的关系?以最左边2个entity为例,结构体如下:
每个v4l2_subdev结构体都会包含一个media_entity结构体,media_entity结构体会描述这个entity有多少个pad,多少个link,同时包含有media_pad和media_link结构体。其中media_pad结构体的index成员用来描述自己是第几个pad,成员flag表示是source pad还是sink pad。media_link结构体用来表示每两个entity之间的连接关系。而entity之间用media_device结构体来联系。
2.4.2、数据结构
一个media子系统使用"struct media_device"来表示,结构体如下:
media_device里有多个entity,它使用"struct media_entity"来表示:
media_entity里有多个pad,它使用"struct media_pad"来表示:
entity通过pad跟其他entity相连,这被称为link,它使用"media_link"来表示:
两个entity之间的连接,被称为link;多个已经使能的link构成一条完整的数据通道,这被称为pipeline。驱动程序在进行streamon操作时,有些驱动会调用"media_entity_pipeline_start"函数,类似下面的代码:
ret = media_entity_pipeline_start(sensor, camif->m_pipeline);
第1个参数是第1个entity,第2个参数就是pipeline(它在media_entity_pipeline_start函数内部构造)。
media_entity_pipeline_start的目的,是从第1个entity开始,遍历所有的link,把已经使能的link都执行"link_validate"。
media_entity_pipeline_start内部,是按照深度优先来操作entity的,以下图为例:
- 有两条pipeline:e0->e1->e2->e3,e0->e1->e4->e5
- 先遍历e0->e1->e2->e3这条pipeline,调用e3、e2的"link_validate"
- 再遍历e0->e1->e4->e5这条pipeline,调用e5、e4的"link_validate"
- 接着处理e1,调用它的"link_validate"
- 最后处理e0,它没有sink pad,无需调用它的"link_validate"
subdev_160">2.4.3、media子系统和subdev的关系
内核空间,v4l2_subdev里含有media_entity,两者都很容易找到对方:
从entity找到subdev,使用以下代码:
#define media_entity_to_v4l2_subdev(ent) \container_of(ent, struct v4l2_subdev, entity)
用户空间里,怎么从entity找到对应的/dev/v4l-subdevX?参考<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">v4l-utils-1.20.0\utils\media-ctl\libmediactl.c:</font>
// 1. open /dev/media0
media->fd = open(media->devnode, O_RDWR);// 2.循环调用ioctl(MEDIA_IOC_ENUM_ENTITIES), 得到每一个entity的信息
entity = &media->entities[media->entities_count];
memset(entity, 0, sizeof(*entity));
entity->fd = -1;
entity->info.id = id | MEDIA_ENT_ID_FLAG_NEXT;
entity->media = media;// entity->info类型为: struct media_entity_desc info;
// 里面含有major, minor
ret = ioctl(media->fd, MEDIA_IOC_ENUM_ENTITIES, &entity->info);// 3. 使用media_get_devname_sysfs函数获得entity对应的设备节点
// 根据主设备号、次设备号在/sys/dev/char目录下找到类似这样的文件: /sys/dev/char/81:2
// 它是一个连接文件,连接到:../../devices/platform/soc/5800800.vind/video4linux/v4l-subdev0
// /dev/v4l-subdev0就是这个entity对应的设备节点
// APP的media_entity.devname[32]里就是"/dev/v4l-subdev0"
static int media_get_devname_sysfs(struct media_entity *entity);
subdev_198">2.5、subdev的注册和使用
2.5.1、内核注册过程
subdev里含有模块的操作函数,谁调用这些函数?
- 内核调用:subdev完全可以不暴露给用户,在摄像头驱动程序内部"偷偷地"调用subdev的函数,用户感觉不到subdev的存在。
- APP调用:对于比较复杂的硬件,驱动程序应该"让用户有办法调节各类参数",比如ISP模块几乎都是闭源的,对它的设置只能通过APP进行。这类subdev的函数,应该暴露给用户,用户可以调用它们。
在内核里,subdev的注册过程为分为2步:
int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,struct v4l2_subdev *sd);// 核心代码
list_add_tail(&sd->list, &v4l2_dev->subdevs);
int v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev);// 调用过程如下
v4l2_device_register_subdev_nodesstruct video_device *vdev;vdev->fops = &v4l2_subdev_fops;err = __video_register_device(vdev, VFL_TYPE_SUBDEV, -1, 1,sd->owner);name_base = "v4l-subdev";vdev->cdev->ops = &v4l2_fops;ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
v4l2_device_register_subdev_nodes的注册过程涉及2个结构体:
- file_operations:
- v4l2_file_operations:
注册subdev后,内核里结构体如下:
subdev_247">2.5.2、内核态使用subdev
可以直接调用subdev里的操作函数,也可以使用下面的宏:
#define v4l2_subdev_call(sd, o, f, args...) \(!(sd) ? -ENODEV : (((sd)->ops->o && (sd)->ops->o->f) ? \(sd)->ops->o->f((sd), ##args) : -ENOIOCTLCMD))
示例:drivers\media\platform\blackfin\bfin_capture.c
subdev的ops示例:
subdev_264">2.5.3、用户态使用subdev
APP对/dev/v4l-subdev*这类的设备进行ioctl操作时,内核里流程如下:
App: ioctl(fd, cmd, arg)
--------------
kernel:
v4l2_fops.unlocked_ioctl, 即v4l2_ioctlret = vdev->fops->unlocked_ioctl(filp, cmd, arg);v4l2_subdev_fops.unlocked_ioctl, 即subdev_ioctlvideo_usercopy(file, cmd, arg, subdev_do_ioctl);
APP可以打开设备文件/dev/v4l-subdevX,执行ioctl调用驱动里面的各个ops函数,调用关系如下:
v4l2_subdev_core_opsfont_281">2.5.3.1、v4l2_subdev_core_ops
- VIDIOC_SUBSCRIBE_EVENT:APP订阅感兴趣的事件,导致core->subscribe_event被调用
- VIDIOC_UNSUBSCRIBE_EVENT:APP取消兴趣,导致core->unsubscribe_event被调用
- VIDIOC_DBG_G_REGISTER:调试作用,读寄存器,导致core->g_register被调用
- VIDIOC_DBG_S_REGISTER:调试作用,写寄存器,导致core->s_register被调用
- VIDIOC_LOG_STATUS:调试作用,打印调试信息,导致core->log_status被调用
v4l2_subdev_video_opsfont_288">2.5.3.2、v4l2_subdev_video_ops
- VIDIOC_SUBDEV_G_FRAME_INTERVAL:获得帧间隔,导致video->g_frame_interval被调用
- VIDIOC_SUBDEV_S_FRAME_INTERVAL:设置帧间隔,导致video->s_frame_interval被调用
- VIDIOC_SUBDEV_QUERY_DV_TIMINGS:导致video->query_dv_timings被调用
- VIDIOC_SUBDEV_G_DV_TIMINGS:导致video->g_dv_timings被调用
- VIDIOC_SUBDEV_S_DV_TIMINGS:导致video->s_dv_timings被调用
v4l2_subdev_pad_opsfont_295">2.5.3.3、v4l2_subdev_pad_ops
- VIDIOC_SUBDEV_G_FMT:导致pad->get_fmt被调用
- VIDIOC_SUBDEV_S_FMT:导致pad->set_fmt被调用
- VIDIOC_SUBDEV_G_CROP/VIDIOC_SUBDEV_G_SELECTION:导致pad->get_selection被调用
- VIDIOC_SUBDEV_S_CROP/VIDIOC_SUBDEV_S_SELECTION:导致pad->set_selection被调用
- VIDIOC_SUBDEV_ENUM_MBUS_CODE:导致pad->enum_mbus_code被调用
- VIDIOC_SUBDEV_ENUM_FRAME_SIZE:导致pad->enum_frame_size被调用
- VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL:导致pad->enum_frame_interval被调用
- VIDIOC_G_EDID:导致pad->get_edid被调用
- VIDIOC_S_EDID:导致pad->set_edid被调用
- VIDIOC_SUBDEV_DV_TIMINGS_CAP:导致pad->dv_timings_cap被调用
- VIDIOC_SUBDEV_ENUM_DV_TIMINGS:导致pad->enum_dv_timings被调用
2.5.3.4、驱动内部调用
2.6、media子系统的注册与使用
2.6.1、内核注册过程
总图如下:
media子系统的注册分为2个层次,4个步骤:
2个层次:
- 描述自己的media_entity:各个subdev里含有media_entity,但是多个media_entity之间的关系由更上层的驱动决定。
- 描述media_entity之间的联系:更上层的、统筹的驱动。它知道各个subdev即各个media_entity之间的联系:link。
4个步骤:
media子系统的注册分为4个步骤:
- 描述自己:各个底层驱动构造subdev时,顺便初始里面的media_entity:比如这个media_entity有哪些pad
// 示例
// drivers\media\platform\sunxi-vin\modules\sensor\gc2053_mipi.c: cci_dev_probe_helper
// drivers\media\platform\sunxi-vin\vin-cci\cci_helper.c: cci_media_entity_init_helper
media_entity_pads_init(&sd->entity, SENSOR_PAD_NUM, si->sensor_pads);
- 注册自己:底层或上层注册subdev时,顺便注册media_entity:把media_entity记录在media_device里
// 示例
// drivers\media\platform\sunxi-vin\vin.c
vin_md_register_entitiesv4l2_device_register_subdevstruct media_entity *entity = &sd->entity;err = media_device_register_entity(v4l2_dev->mdev, entity);
- 和别人建立联系:subdev之上的驱动程序决定各个media_entity如何连接:比如调用media_create_pad_link创建连接
// 示例
// drivers\media\platform\sunxi-vin\vin.c
ret = media_create_pad_link(source, SCALER_PAD_SOURCE,sink, VIN_SD_PAD_SINK,MEDIA_LNK_FL_ENABLED);
- 暴露给APP使用:subdev之上的驱动程序注册media_device: media_device里已经汇聚了所有的media_entity
// 示例
// drivers\media\platform\sunxi-vin\vin.c
vin_proberet = media_device_register(&vind->media_dev);
在内核里,media子系统的注册过程为:
media_device_register__media_device_registerstruct media_devnode *devnode;devnode->fops = &media_device_fops;ret = media_devnode_register(mdev, devnode, owner);cdev_init(&devnode->cdev, &media_devnode_fops);ret = cdev_add(&devnode->cdev, MKDEV(MAJOR(media_dev_t), devnode->minor), 1);
上述注册过程涉及2个结构体:
- file_operations:
- media_file_operations:
2.6.2、APP使用media子系统
APP使用media子系统时,除了open之外,就只涉及5个ioctl:
- MEDIA_IOC_DEVICE_INFO
- MEDIA_IOC_ENUM_ENTITIES
- MEDIA_IOC_ENUM_LINKS
- MEDIA_IOC_SETUP_LINK
- MEDIA_IOC_G_TOPOLOGY
这5个ioctl将导致内核中如下函数被调用(drivers\media\media-device.c):
APP对/dev/media0这类的设备进行ioctl操作时,内核里流程如下:
App: ioctl
--------------
kernel:
media_devnode_fops.unlocked_ioctl, 即media_ioctl__media_ioctl(filp, cmd, arg, devnode->fops->ioctl);media_device_ioctlconst struct media_ioctl_info *info;info = &ioctl_info[_IOC_NR(cmd)];ret = info->arg_from_user(karg, arg, cmd);ret = info->fn(dev, karg);ret = info->arg_to_user(arg, karg, cmd);
核心在于ioctl_info:
2.6.2.1、MEDIA_IOC_DEVICE_INFO
这个ioctl被用来获得/dev/mediaX的设备信息,设备信息结构体如下
APP代码如下:
memset(&media->info, 0, sizeof(media->info)); // struct media_device_info info;ret = ioctl(media->fd, MEDIA_IOC_DEVICE_INFO, &media->info);
驱动中对应核心代码如下:
2.6.2.2、MEDIA_IOC_ENUM_ENTITIES
这个ioctl被用来获得指定entity的信息,APP参考代码为:
驱动中对应核心代码如下:
2.6.2.3、MEDIA_IOC_ENUM_LINKS
这个ioctl被用来枚举enity的link,APP参考代码为:
驱动中对应核心代码如下:
2.6.2.4、MEDIA_IOC_SETUP_LINK
这个ioctl被用来设置link,把源pad、目的pad之间的连接激活(active),或者闲置(inactive),APP示例代码为:
APP传入的flags的取值有2种:
- 0:闲置link
- 1:激活link
内核里link的flag有如下取值:
如果这个link的flag不是MEDIA_LINK_FL_IMMUTABLE的话,就可以去更改它的bit0状态:激活或闲置。
驱动中对应核心代码如下:
2.6.2.5、MEDIA_IOC_G_TOPOLOGY
这个ioctl被用来获得整体的拓扑图,包括entities、interfaces、pads、links的信息,示例代码如下:
驱动中对应核心代码如下,只要APP提供了对应的buffer,它就可以复制对应的信息,比如entities、interfaces、pads、links的信息: