【干货分享】Xilinx Linux V4L2视频管道(Video Pipeline)驱动程序分析

FPGA开发圈 2020-04-30 00:00

作者:付汉杰,hankf@xilinx.com,

文章转载自:赛灵思中文社区论坛


概述


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.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.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.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() 回调函数就会被调用:


后续任务


还有很多代码可以分析,比如HDMI RX的驱动、TPG的驱动、m2m的驱动。

关注我们

FPGA开发圈 这里介绍、交流、有关FPGA开发资料(文档下载,技术解答等),提升FPGA应用能力。
评论
  • 每日可见的315MHz和433MHz遥控模块,你能分清楚吗?众所周知,一套遥控设备主要由发射部分和接收部分组成,发射器可以将控制者的控制按键经过编码,调制到射频信号上面,然后经天线发射出无线信号。而接收器是将天线接收到的无线信号进行解码,从而得到与控制按键相对应的信号,然后再去控制相应的设备工作。当前,常见的遥控设备主要分为红外遥控与无线电遥控两大类,其主要区别为所采用的载波频率及其应用场景不一致。红外遥控设备所采用的射频信号频率一般为38kHz,通常应用在电视、投影仪等设备中;而无线电遥控设备
    华普微HOPERF 2025-01-06 15:29 164浏览
  • 大模型的赋能是指利用大型机器学习模型(如深度学习模型)来增强或改进各种应用和服务。这种技术在许多领域都显示出了巨大的潜力,包括但不限于以下几个方面: 1. 企业服务:大模型可以用于构建智能客服系统、知识库问答系统等,提升企业的服务质量和运营效率。 2. 教育服务:在教育领域,大模型被应用于个性化学习、智能辅导、作业批改等,帮助教师减轻工作负担,提高教学质量。 3. 工业智能化:大模型有助于解决工业领域的复杂性和不确定性问题,尽管在认知能力方面尚未完全具备专家级的复杂决策能力。 4. 消费
    丙丁先生 2025-01-07 09:25 116浏览
  • 村田是目前全球量产硅电容的领先企业,其在2016年收购了法国IPDiA头部硅电容器公司,并于2023年6月宣布投资约100亿日元将硅电容产能提升两倍。以下内容主要来自村田官网信息整理,村田高密度硅电容器采用半导体MOS工艺开发,并使用3D结构来大幅增加电极表面,因此在给定的占位面积内增加了静电容量。村田的硅技术以嵌入非结晶基板的单片结构为基础(单层MIM和多层MIM—MIM是指金属 / 绝缘体/ 金属) 村田硅电容采用先进3D拓扑结构在100um内,使开发的有效静电容量面积相当于80个
    知白 2025-01-07 15:02 141浏览
  • By Toradex 秦海1). 简介嵌入式平台设备基于Yocto Linux 在开发后期量产前期,为了安全以及提高启动速度等考虑,希望将 ARM 处理器平台的 Debug Console 输出关闭,本文就基于 NXP i.MX8MP ARM 处理器平台来演示相关流程。 本文所示例的平台来自于 Toradex Verdin i.MX8MP 嵌入式平台。  2. 准备a). Verdin i.MX8MP ARM核心版配合Dahlia载板并
    hai.qin_651820742 2025-01-07 14:52 106浏览
  •  在全球能源结构加速向清洁、可再生方向转型的今天,风力发电作为一种绿色能源,已成为各国新能源发展的重要组成部分。然而,风力发电系统在复杂的环境中长时间运行,对系统的安全性、稳定性和抗干扰能力提出了极高要求。光耦(光电耦合器)作为一种电气隔离与信号传输器件,凭借其优秀的隔离保护性能和信号传输能力,已成为风力发电系统中不可或缺的关键组件。 风力发电系统对隔离与控制的需求风力发电系统中,包括发电机、变流器、变压器和控制系统等多个部分,通常工作在高压、大功率的环境中。光耦在这里扮演了
    晶台光耦 2025-01-08 16:03 58浏览
  • 「他明明跟我同梯进来,为什么就是升得比我快?」许多人都有这样的疑问:明明就战绩也不比隔壁同事差,升迁之路却比别人苦。其实,之间的差异就在于「领导力」。並非必须当管理者才需要「领导力」,而是散发领导力特质的人,才更容易被晓明。许多领导力和特质,都可以通过努力和学习获得,因此就算不是天生的领导者,也能成为一个具备领导魅力的人,进而被老板看见,向你伸出升迁的橘子枝。领导力是什么?领导力是一种能力或特质,甚至可以说是一种「影响力」。好的领导者通常具备影响和鼓励他人的能力,并导引他们朝着共同的目标和愿景前
    优思学院 2025-01-08 14:54 61浏览
  • 本文介绍编译Android13 ROOT权限固件的方法,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。关闭selinux修改此文件("+"号为修改内容)device/rockchip/common/BoardConfig.mkBOARD_BOOT_HEADER_VERSION ?= 2BOARD_MKBOOTIMG_ARGS :=BOARD_PREBUILT_DTB
    Industio_触觉智能 2025-01-08 00:06 92浏览
  • 故障现象一辆2017款东风风神AX7车,搭载DFMA14T发动机,累计行驶里程约为13.7万km。该车冷起动后怠速运转正常,热机后怠速运转不稳,组合仪表上的发动机转速表指针上下轻微抖动。 故障诊断 用故障检测仪检测,发动机控制单元中无故障代码存储;读取发动机数据流,发现进气歧管绝对压力波动明显,有时能达到69 kPa,明显偏高,推断可能的原因有:进气系统漏气;进气歧管绝对压力传感器信号失真;发动机机械故障。首先从节气门处打烟雾,没有发现进气管周围有漏气的地方;接着拔下进气管上的两个真空
    虹科Pico汽车示波器 2025-01-08 16:51 69浏览
  • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
    华普微HOPERF 2025-01-06 17:23 202浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球无人机锂电池产值达到2457百万美元,2024-2030年期间年复合增长率CAGR为9.6%。 无人机锂电池是无人机动力系统中存储并释放能量的部分。无人机使用的动力电池,大多数是锂聚合物电池,相较其他电池,锂聚合物电池具有较高的能量密度,较长寿命,同时也具有良好的放电特性和安全性。 全球无人机锂电池核心厂商有宁德新能源科技、欣旺达、鹏辉能源、深圳格瑞普和EaglePicher等,前五大厂商占有全球
    GIRtina 2025-01-07 11:02 119浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦