Xilinx Linux 如何理解V4L2的管道驱动程序

概述
xilinx提供了完整的v4l2的驱动程序,xilinx v4l2 driver。处于最顶层的驱动程序是v4l2框架的视频管道(video pipeline)驱动程序,也叫桥驱动程序(bridge driver),主要代码在文件xilinx-vipp.c中。在v4l2框架中,整个视频管道(video pipeline)可以通过媒体设备(/dev/media)配置,流媒体可以通过视频设备(/dev/video)控制。这两种设备,都是在视频管道(video pipeline)驱动程序里创建的。所以,理解v4l2的管道(pipeline)驱动程序是理解xilinx所有video ip 在linux下工作情况的基础。
3. 文件
3.1. c文件
xilinx的v4l2的管道(pipeline)驱动程序在下面四个文件中。
1. drivers/media/platform/xilinx/xilinx-vipp.c
2. drivers/media/platform/xilinx/xilinx-vipp.h
3. drivers/media/platform/xilinx/xilinx-dma.c
4. drivers/media/platform/xilinx/xilinx-dma.h
3.2.设备树(devicetree)
设备树(devicetree)里含有整个视频管道(video pipeline)的配置,对应的文档在documentation/devicetree/bindings/media/xilinx/xlnx,video.txt。
下面是一个设备树(devicetree)的例子。
axi_video_cap {
compatible = xlnx,axi-video;
dmas = , ;
dma-names = port0, port1;
ports {
#address-cells = ;
#size-cells = ;
port@0 {
reg = ;
direction = input;
vcap0_in0: endpoint {
remote-endpoint = ;
};
};
port@1 {
reg = ;
direction = input;
vcap0_in1: endpoint {
remote-endpoint = ;
};
};
};
};
3.3. 函数调用关系图
xvipp 函数调用关系图
4.主要函数
4.1. 函数xvip_composite_probe()
函数xvip_composite_probe是整个驱动的入口,主要工作是初始化驱动的数据结构xvip_composite_device里的通用数据,包括lock、list(entities和dmas),再调用了xvip_composite_v4l2_init()和xvip_graph_init(),最后调用platform_set_drvdata设置平台设备platform_device里的当前设备的数据指针。
4.2. 函数xvip_composite_v4l2_init()
函数xvip_composite_v4l2_init做的事情比较简单,只是初始化了struct media_device,设置了设备版本和model名称、dev/mdev指针,就调用了v4l2_device_register()注册v4l2设备。
xvip_composite_v4l2_init的关键代码如下:
xdev->media_dev.dev = xdev->dev;
strlcpy(xdev->media_dev.model, xilinx video composite device,
sizeof(xdev->media_dev.model));
xdev->media_dev.hw_revision = 0;
media_device_init(&xdev->media_dev);
xdev->v4l2_dev.mdev = &xdev->media_dev;
ret = v4l2_device_register(xdev->dev, &xdev->v4l2_dev);
4.3. 函数xvip_graph_init
函数xvip_graph_init是最重要的函数,函数调用层次也最深。它首先调用xvip_graph_dma_init根据设备树(devicetree)里的port信息初始化dma通道,创建一个dma的列表;再调用xvip_graph_parse在设备树(devicetree)里分析子设备节点,根据设备树里remote-endpoint属性创建一个entity的列表;最后调用v4l2_async_notifier_register注册异步处理函数。系统发现各个子设备(subdev)后,调用异步处理函数xvip_graph_notify_bound获取子设备信息。所有子设备(subdev)都被发现后,调用xvip_graph_notify_complete,为每个entity创建link和v4l2子设备,并注册media设备。
xvip_graph_init的关键代码如下:
/* init the dma channels. */
ret = xvip_graph_dma_init(xdev);
/* parse the graph to extract a list of subdevice dt nodes. */
ret = xvip_graph_parse(xdev);
/* register the subdevices notifier. */
num_subdevs = xdev->num_subdevs;
subdevs = devm_kcalloc(xdev->dev, num_subdevs, sizeof(*subdevs), gfp_kernel);
xdev->notifier.subdevs = subdevs;
xdev->notifier.num_subdevs = num_subdevs;
xdev->notifier.ops = &xvip_graph_notify_ops;
ret = v4l2_async_notifier_register(&xdev->v4l2_dev, &xdev->notifier);
4.4. 函数xvip_graph_dma_init
xvip_graph_dma_init()自身比较简单,先找到第一个ports子节点,再找其中的所有port节点,并为每个port子节点执行xvip_graph_dma_init_one(),从而将每个port子节点对应的dma添加到链表dmas中。
xvip_graph_dma_init的关键代码如下:
ports = of_get_child_by_name(xdev->dev->of_node, ports);
for_each_child_of_node(ports, port) {
ret = xvip_graph_dma_init_one(xdev, port);
}
4.5. 函数xvip_graph_dma_init_one
xvip_graph_dma_init_one()根据设备树(devicetree)的port子节点的配置,找到dma,并添加到链表xdev->dmas中。
xvip_graph_dma_init_one先读取port子节点的属性direction和reg属性。devicetre的port节点中,要含有属性“direction”和reg属性;如果没有属性“direction”,会返回错误;如果没有reg属性,代码会继续,但是功能会出错。属性direction的值是input或者output。
xvip_graph_dma_init_one还为每个port分配struct xvip_dma,再执行xvip_dma_init()。
接下来,xvip_graph_dma_init_one把struct xvip_dma加入到队列xdev->dmas。
xvip_graph_dma_init_one还根据direction的值是input或者output,以及xvip_is_mplane的设置,选择buffer类型。
xvip_graph_dma_init_one的关键代码如下:
// read direction and reg properties
ret = of_property_read_string(node, direction, &direction);
of_property_read_u32(node, reg, &index);
dma = devm_kzalloc(xdev->dev, sizeof(*dma), gfp_kernel);
ret = xvip_dma_init(xdev, dma, type, index);
list_add_tail(&dma->list, &xdev->dmas);
4.6. 函数xvip_dma_init
xvip_dma_init是核心的函数,完成了最关键的任务:初始化buffer队列、申请dma设备、注册video设备。
函数xvip_dma_init()先初始化struct xvip_dma的数据成员,包括dma->lock、dma->pipe.lock、dma->queued_bufs、dma->queued_lock;再根据buffer类型,初始化v4l2_format里的像数点格式,struct v4l2_pix_format pix 或者struct v4l2_pix_format_mplane pix_mp。接下来设置pad.flags为media_pad_fl_sink或者media_pad_fl_source,调用media_entity_pads_init初始化初始化media entity。
函数xvip_dma_init()然后继续初始化video_device的各种成员和操作函数,包括fops、v4l2_dev、queue、vfl_type、vfl_dir、lock、和ioctl_ops。video_device的fops被设置为xvip_dma_fops,ioctl_ops被设置为xvip_dma_ioctl_ops。
函数xvip_dma_init()再接着初始化buffer队列struct vb2_queue queue,其中ops被设置为xvip_dma_queue_qops,mem_ops被设置为vb2_dma_contig_memops,执行vb2_queue_init。
函数xvip_dma_init()再接着执行dma_request_chan申请dma设备,这是一个复杂和核心的函数。dma_request_chan()的第二个参数是dma通道的名称,名字是格式是port%u,比如port0,也就是设备树的属性dma-names里的字符串。其中of_dma_request_slave_channel()调用的of_find_property(np, dmas, null),通过属性dmas,取得了dma。of_property_count_strings()计算属性dma-names里的字符串个数,也就是dma的个数。然后根据dma的个数,为每一个dma执行of_dma_match_channel()。of_dma_match_channel (dev->of_node, port%u, )取出dma-names的字符串,再和port%u对比,这是检查名字是否对应,如果一致,就用对应的dma,取得struct of_phandle_args dma_spec,再把struct of_phandle_args dma_spec转换为struct of_dma *ofdma。of_dma_xlate接下来转换为struct dma_chan。of_dma_request_slave_channel使用了字符串dmas。of_dma_match_channel()使用了字符串dmas和dma-names。所以xilinx-vipp.c实现的xlnx,video设备的设备树里的dmas和dma-names,port0是必须有的固定字符串,不能更改;只有dmas后面的dma phandle才可以更改。
函数xvip_dma_init()最后执行video_register_device注册video设备。
4.7. 函数xvip_graph_parse
函数xvip_graph_parse先调用xvip_graph_parse_one,找到直接的remote_port;然后再为每一个找到的remote_port执行一次xvip_graph_parse_one。
4.8. 函数xvip_graph_parse_one
xvip_graph_parse_one()里先调用of_graph_get_next_endpoint(node, ep)取得下一个endpoint,然后再调用of_graph_get_remote_port_parent(ep) 得到remote_port的父节点。of_graph_get_next_endpoint(node, ep)会先尝试找子节点ports,再找到子节点port,然后再在子节点port里找endpoint;读取路径是 { ports { port {endpoint } } },其中ports是可选的。endpoint是port的子节点,不管其内部的属性名称,所以也可以用属性名称remote-endpoint。of_graph_get_remote_port_parent(ep)通过调用of_graph_get_remote_endpoint()得到remote_port,再调用of_graph_get_port_parent()得到父节点。of_graph_get_remote_endpoint()里读取了设备树里的remote-endpoint属性。
xvip_graph_parse_one()再调用xvip_graph_find_entity(),在xdev->entities里检查是否已经包含对应的entity,如果已经包含,则跳过后续处理;如果没没有,则找到的remote_port的父节点,存放到链表xdev->entities,并执行操作xdev->num_subdevs++。
最后xdev->entities里包含了所有entity。后来xvip_graph_notify_complet会根据xdev->entities,为每个entity创建一个设备。
4.9. 函数xvip_graph_notify_bound
系统发现子设备(subdev)后,调用异步处理函数xvip_graph_notify_bound获取子设备信息。xvip_graph_notify_bound根据设备树节点,匹配实体(entity)和子设备(subdev);匹配成功后,再把子设备(subdev)的指针保存到实体(entity)。
xvip_graph_notify_bound的关键代码如下:
entity->entity = &subdev->entity;
entity->subdev = subdev;
4.10. 函数xvip_graph_notify_complete
系统所有子设备(subdev)都被发现后,linux会调用xvip_graph_notify_complete。xvip_graph_notify_complete调用xvip_graph_build_one为每个entity创建link;调用xvip_graph_build_dma做create links for dma channels;并为每个实体(entity)注册v4l2子设备v4l2_device_register_subdev_nodes ;最后调用media_device_register注册media设备。
4.11. 函数xvip_graph_build_one
函数xvip_graph_build_dma在每个entity及其远端模块之间创建link。
函数xvip_graph_build_one先调用函数of_graph_get_next_endpoint从设备的设备树里找到下一个endpoint(struct device_node)。与之前描述一样,of_graph_get_next_endpoint的读取路径是 { ports { port {endpoint} } },endpoint是port的子节点,不管其内部的属性名称,所以也可以用属性名称remote-endpoint。函数xvip_graph_build_dma接着调用v4l2_fwnode_parse_link,得到对应的link(struct v4l2_fwnode_link)。接着根据link中的端口号(local_port)取得pad信息,判断出是否是目的端(sink)端口;如果是目的端(sink)端口,则忽略。另外还忽略dma,它由xvip_graph_build_dma处理。接下来再调用函数xvip_graph_find_entity,根据link中的远端节点(remote_node),得到远端的entity。最后调用media_create_pad_link,当前entity作为源端(source),远端entity作为目的端(sink)的media_entity和media_pad,创建媒体pad的链接(link)
4.12. 函数xvip_graph_build_dma
函数xvip_graph_build_dma为dma模块及其远端模块之间创建link。
函数xvip_graph_build_dma先调用函数of_graph_get_next_endpoint从设备的设备树里找到下一个endpoint(struct device_node)。函数xvip_graph_build_dma接着调用v4l2_fwnode_parse_link,得到对应的link(struct v4l2_fwnode_link),根据link中的端口号(local_port),找到对应的dma(struct xvip_dma)。接下来再调用函数xvip_graph_find_entity,根据link中的远端节点(remote_node),得到远端的entity。然后设置源端(source)/目的端(sink)的media_entity和media_pad,最后调用media_create_pad_link创建媒体pad的链接(link)。
4.13. 函数xvip_graph_find_dma
函数xvip_graph_build_dma根据指定的port号,在xdev->dmas里找dma,如果找到,就返回对应的的struct xvip_dma的指针。
4.14. 函数xvip_graph_find_entity
函数xvip_graph_find_entity根据指定的设备节点(struct device_node),在xdev->entities里找entity,如果找到,就返回对应的的struct xvip_graph_entity的指针。
5. 关键数据
5.1. subdevs
其中subdevs是一个指针,指向(struct v4l2_async_subdev *)的数组(是指针数组),包含了这个设备下的所有subdev的指针,根据遍历xdev->entities填满这个数组。xdev->entities由processedxvip_graph_parse_one()根据设备树找到相关的子设备填充。
5.2. group id
组id(struct v4l2_subdev里有成员grp_id。v4l2_subdev_init()里把其初始化为0。xilinx没有设置grp_id,所以都是0.)
5.3. xvip_graph_notify_ops
xvip_graph_notify_ops指向bound和complete函数,如果成功匹配设备,.bound()回调函数将会被调用,当所有的子设备全部被加载完毕之后,.complete() 回调函数就会被调用:
6. 后续任务
还有很多代码可以分析,比如hdmi rx的驱动、tpg的驱动、m2m的驱动。


为什么他却说“深度学习框架之争,现在谈结果为时尚早”?
同样是拍片子,X线、CT、B超、核磁有哪些不同?
2019防晒产品排行榜出炉,第一名是你绝对想不到的!
无人值守地面传感器成为探测时敏目标的重要手段
全球首款8K口袋全景相机QooCam 8K正式发布
Xilinx Linux 如何理解V4L2的管道驱动程序
SIP网络音频模块-SIP网络播放音频模块(带2*15W功放输出)
5G在室内有烦恼?面向5G时代如何做好室内深度覆盖
2019年1-9月河南省手机产量同比下降6.95%,还将持续增长
TI最高效率集成Qi、PMA 双模无线电源接收器
微软Xbox One X怎么样值得买吗?看完这些理由再考虑
华为Mate 9到P10,华为12月还有新旗舰?
人工智能包含哪些
74ls161管脚图引脚图及功能表
业内专家预测到2050年将出现在航空航天领域的创新蓝图
电子调光镇流控制器(上)
引渡孟晚舟最新消息 孟晚舟律师指美方证据明显不可靠
MCU产业站上风口 中移芯昇接受《中国电子报》采访畅谈MCU
华为Mate8升级EMUI5.0运行彪升,再不升级就OUT了
合肥长鑫DRAM正式投片,国产存储跨出重要一步