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的驱动。

linux驱动管道,Xilinx Linux 如何理解V4L2的管道驱动程序相关推荐

  1. Linux驱动 简单的Linux驱动基础知识

    Linux驱动 简单的Linux驱动基础知识 一.简述         记--Linux驱动学习笔记. Linux驱动程序初始化硬件设备,并提供硬件控制接口给更上一层的应用调用. 例如使用QT应用程序 ...

  2. linux驱动模型开发——linux platform总线机制讲解与实例开发

    1.概述: 通常在Linux中,把SoC系统中集成的独立外设单元(如:I2C.IIS.RTC.看门狗等)都被当作平台设备来处理. 从Linux2.6起,引入了一套新的驱动管理和注册机制:Platfor ...

  3. linux驱动程序设计21 Linux设备驱动的调试

    本章导读 "工欲善其事,必先利其器",为了方便进行Linux设备驱动的开发和调试,建立良好的开发环境很重 要,还要使用必要的工具软件以及掌握常用的调试技巧等. 21.1节讲解了Li ...

  4. linux 驱动 内核模式,Linux内核模块和驱动的编写

    Linux内核是一个整体是结构,因此向内核添加任何东西,或者删除某些功能,都十分困难.为了解决这个问题引入了内核机制.从而可以动态的想内核中添加或者删除模块. 模块不被编译在内核中,因而控制了内核的大 ...

  5. 武汉linux驱动培训转让,linux驱动培训靠谱吗

    作为一名Linux爱好者,在Linux的世界中也算是半个老司机了,从桌面玩到服务器.从ubuntu到centos.从计算机到路由器,各种Linux的花俏玩法都略有体验.作者并非职业Linux选手,我仅 ...

  6. 【Linux驱动编程】Linux中断上半部和下半部

    前言   cpu在执行程序时,如果有外部中断触发时,如定时器中断.串行总线中断等,cpu停止当前任务从而转去响应中断处理.对于中断函数的处理,原则是尽快处理完事务并退出中断,这一点也比较好理解,尽快处 ...

  7. 韦东山嵌入式教程第四篇Linux驱动基础知识学习笔记(1)——Hello驱动程序

    驱动入门--Hello驱动程序 1.怎么写出一个驱动程序? 确定主设备号 定义自己的file_operations结构体 实现对应的open.read.write函数,填入file_operation ...

  8. 【Linux驱动开发】Linux 自带按键驱动

    一.基础 1.以下介绍了 input 子系统的基础,使用的自己编的按键驱动.实际上内核自带了按键驱动(一般默认使能). [Linux驱动开发]INPUT 子系统实验 2.内核自带按键驱动简介 Linu ...

  9. ac3165 linux驱动_为什么Linux系统没有类似Windows上的还原精灵和影子系统?

    很多从Windows转Linux的用户,面临的最大一个问题,就是Linux上没有还原精灵这种软件.众所周知,作为一个操作系统要想普及,绝不可能只针对懂IT的少部分年轻人,而是需要让不同年龄段,不同职业 ...

  10. 嵌入式Linux驱动学习之路(二十六)DM9000C网卡驱动程序

    基于DM9000C的原厂代码修改dm9000c的驱动程序. 首先确认内存的基地址 iobase. 确定中断号码. 打开模块的初始化函数定义. 配置内存控制器的相应时序(结合DM9000C.C的手册). ...

最新文章

  1. Residual Networks
  2. Containerd 的前世今生和保姆级入门教程
  3. 一位非常要好的朋友,零基础转行 Python!
  4. centos(7) 使用yum进行安装lamp环境
  5. 基于NIOS II的液晶显示设计——自定义图形库
  6. 加载顺序_Java的web.xml组件加载顺序
  7. java监听鼠标接口实现_自定义Java鼠标监听器?
  8. 特斯拉Model 3再次停工!这一次还是因为自动化机器人……
  9. eclipse安装OpenExplorer插件--快速打开文件目录
  10. C++ 简单的SQL注入过滤
  11. node.js环境搭建超详细图文教程
  12. 面试 多线程 MFC CSDN
  13. 最新PHP精品在线音乐分享网站源码/UI很好看
  14. 决策树算法总结(下:CART决策树)
  15. ColorPix最好用的屏幕取色器
  16. 山东科技大学计算机学院教师,山东科技大学
  17. 部署基于SAWP的低成本开源CRM系统
  18. 【原创】Mac电脑如何开启Aptx
  19. 2021年建议你要早点进入IT行业
  20. JDBC 学习笔记1

热门文章

  1. 化妆品公司mysql_化妆品网站销售管理系统的设计与实现(SSH,MySQL)(含录像)
  2. HTML中背景透明有阴影,透明PNG在网页有阴影的解决方法
  3. 不花钱,模拟登录古诗中文网
  4. 第七届ArcGIS暨ERDAS用户大会
  5. python 接入百度地图数据包下载_Python爬虫-利用百度地图API接口爬取数据并保存至MySQL数据库...
  6. mac 下载软件无法将程序拖移至app 出现错误
  7. 【渝粤教育】 国家开放大学2020年春季 1308外国文学专题 参考试题
  8. 【原创】无线破解Aircrack-ng套件详解--airmon-ng与airodump-ng
  9. 不可混为一谈 说说数字机顶盒与网络机顶盒
  10. 一文带你盘点市场上主流的BI产品主要有哪些