当前位置: 首页 > news >正文

Linux驱动之USB、MIPI摄像头驱动

一、USB摄像头内部逻辑结构

一个USB摄像头必定有一个VideoControl接口,用于控制。有0个或多个Videostreaming接口,用于传输视频。在VideoControl内部,有多个Unit或Terminal,上一个Unit或Terminal的数据,流向下一个Unit或Terminal,多个Unit或Terminal组成一个完整的UVC功能设备。

之后我们的驱动程序主要是来解析这些描述符的信息,来完善各种功能。

二、UVC驱动框架

2.1 流程分析

一般有几个OT就有几个/dev/video*设备节点。在USB摄像头驱动中,一个USB摄像头 对应一个struct uvc_device结构体,uvc_device中含有usb_device 并且含有一个struct list_head entities链表,这个链表中含有每一个uvc_entity结构体(一般一个termal/unit对应一个entity结构体)。而一个videoStreaming interface 中包含摄像头所支持的格式,每种格式下支持多少的frame。一个videoStreaming interface 在Linux 中会构建出一个uvc_streaming结构体。

那么大致的流程就是 1、处理entity:解析entity,构造entity结构体,放入entity链表。2、处理videostreaming:解析videostreaming(获取format / frame),构造uvc_streaming结构体,放入streams链表。3、对于每个OT,找到完整的chain,去注册video_device。

2.2 源码分析

Linux源码路径:drivers/media/usb/uvc/uvc_driver.c

2.2.1 设备枚举过程

首先先进入到init函数-->在init函数中注册了usb_driver: usb_register(&uvc_driver.driver)-->一旦接入的摄像头设备含有videocontrol interface就会调用该driver中的probe函数。

probe函数主要做了下面几件事,注意这边是按照probe函数的流程:

a. 分配设置注册uvc_device。

b. 解析videocontrol interface,uvc_parse_control-->uvc_parse_standard_control,在这个执行期间,会为每一个entity创建一个uvc_entity结构体,并且将其插入uvc_device的entities链表中,同时一旦解析到UVC_VC_HEADER这一类的entity,会使用uvc_parse_streaming解析videostream interface中的数据,构建,设置uvc_streaming,并将其放到uvc_device的streams链表中。

c. 注册v4l2 device起辅助作用。

d. 使用uvc_ctrl_init_device解析每一个entity,根据entity中的bmcontrols来确定通过该entity可以对设备进行哪些控制。

e. 使用uvc_scan_device扫描这个设备,扫描出一条完整的视频链路chains。如果这个链路可以到达videostreaming,就表示是一条完整的链路,将其放入uvc_device的chains链表。

f. 使用uvc_register_chains函数,对于一个完整的链路,将会注册一个/dev/video*设备节点,过程如下:uvc_register_video-->uvc_register_video_device-->video_register_device

┌──────────────────────────────┐
│        struct uvc_device     │
│──────────────────────────────│
│ entities → [uvc_entity list] │───┐  (控制单元/终端)
│ streams  → [uvc_stream list] │───┤  (视频流接口)
│ chains   → [uvc_chain list]  │───┘  (完整视频链路)
└──────────────────────────────┘│├─ v4l2_device_register()├─ uvc_ctrl_init_device()├─ uvc_scan_device()└─ uvc_register_chains() → /dev/videoX

2.2.2 设备控制过程

我们先来看一下控制的调用过程,首先会对每一个创建的entity进行比较,取出每一个的bmControls(应该是一个16位的),bmControls中的每一位会生成一个struct uvc_control controls。

随后如果想设置硬件,需要指定四项内容,1. entity(哪一个entity的能力) 2.selector(什么能力) 3.size 4.offsize(寄存器相关设置)。

在内核中,uvc_control 结构体包含一个 uvc_control_info 成员,用于描述该控制项的详细信息,例如其所属的 entity(功能单元)以及对应的 selector
uvc_control_info 内部,还包含一个 uvc_control_mapping 结构体,该结构体记录了与寄存器操作相关的参数,如 sizeoffset 等,同时还定义了一个 id 字段。

这个 id 值正是用户态程序在 ioctl 调用时传入的控制标识符,用于在内核中定位对应的控制项。因此,用户态只需提供控制的 idvalue,即可通过内核匹配到相应的 entity 并修改其功能参数。

uvc_probeuvc_parse_control // 得到PU的entityuvc_ctr1_init_device //初始化Pu的control//从描述符里得到关键数据bmControls =entity->processing.bmControls;//比如0x7F,0x15bControlsize =entity->processing.bcontrolsize;//根据bmControls分配多个uvc_contro1结构体entity->controls=kcalloc(ncontrols,sizeof(*ctr1), GFP_KERNEL);// 对于每个controlfor(i=0;i<bcontrolsize*8;++i){    ctrl->entity=entity;ctrl->index=i;uvc_ctrl_init_ctrl(dev, ctr1);}

关键函数:

2.2.3 传输过程

这个过程涉及到两个重要的结构体。该部分需要借鉴到上一章的内容,才好理解。

在初始化vb2_queue的时候传入了对应的vb2_ops和mem_ops,mem_ops主要是做一些分配内存相关的操作,vb2_ops主要是一些与硬件相关的操作。现在让我们看下uvc_start_streaming开启传输时候进行的操作。

uvc_start_streaminguvc_video_enable(stream, 1);uvc_init_videouvc_init_video_isoc //申请URB buffer等usb_alloc_urburb->complete = uvc_video_complete; //当硬件传输完urb之后会调用这个                uvc_video_complete函数usb_submit_urb //启动usb传输

当驱动程序接受完一整帧的数据之后,uvc_video_complete被调用

uvc_video_completestream->decode(uvc_urb, buf, buf_meta); 解析urb,将其内容放入uvc_buffer中的bufferuvc_video_decode_isoc    uvc_video_next_buffersuvc_queue_next_bufferusb_submit_urb 重新提交urb

从硬件相关的irqqueue取出buffer,将urb中的数据放入buffer,把当前buffer从硬件相关的irqqueue移除放入done_list,重新提交urb,应用程序从done_list中取出buffer,并且将buffer从queued_list、done_list中移除。处理完数据之后会重新将buffer放入queued_list和irqqueue。

三、MIPI摄像头硬件

对于摄像头接口,常用的是串行协议CSI,分为两类CSI-2,CSI-3,CSI-2对应的物理接口有D-PHY、C-PHY。

下图为显示接口DBI和DSI。

D-PHY接口图如下(一路信号由一对差分引脚来表示):

对于摄像头,D-PHY接口仅仅是用来传递数据:摄像头发送数据,它被称为:CSI Transmitter
主控接收数据,它被称为:CSI Receiver。
主控通过I2C接口发送控制命令,它被称为:CCIMaster(CCl名为Camera Control Interface)
摄像头接收控制命令,它被称为:CCISlave。

四、subdev和media子系统

4.1 subdev子系统

在 UVC 摄像头驱动中,确实会涉及到 subdev(sub-device)机制。每一个 uvc_entity 结构体内部都包含一个 struct v4l2_subdev subdev 成员,用于与 V4L2 框架中的子设备接口保持一致。然而,在实际的 UVC 实现中,这些 subdevops(操作函数集)通常为空,即并未注册任何具体的回调函数。这意味着虽然 UVC 驱动在结构上支持 subdev 概念,但其功能实现是空的或占位的。

需要注意的是,只有当内核配置项 CONFIG_MEDIA_CONTROLLER 被启用时,UVC 驱动才会真正创建并注册这些 subdev 实例,以便与媒体控制器框架(Media Controller Framework)交互。如果该选项未启用,则不会执行 subdev 的初始化过程。

此外,在 UVC 摄像头中,各个 uvc_entity(如处理单元、选择器单元、终端等)的功能区分性较弱,且大多通过标准化的 UVC 协议接口进行统一控制。因此,没有必要为每一个 entity 都单独生成对应的 subdev 设备并提供独立的驱动程序支持。

下图展示了一个典型的 MIPI 摄像头系统结构。该系统由多个关键模块共同组成,包括 MIPI 接口摄像头传感器硬件(Sensor)数据解析模块(Parser)图像信号处理器(ISP) 等部分。这些组件协同工作,从图像采集、数据传输到图像处理的整个流程,共同构成一个完整的摄像头系统。

MIPI 摄像头不像 UVC 那样把所有功能都集成在一个 USB 设备里。它通常由多个独立硬件模块组成,例如:

  1. Sensor(图像传感器):负责图像采集;

  2. Lens/AF 驱动器:控制镜头或对焦;

  3. Parser(CSI-2 接口解析模块):解析 MIPI CSI-2 数据流;

  4. ISP(Image Signal Processor):负责去噪、白平衡、色彩校正等;

  5. Scaler / Output 节点:输出最终图像流。

这些模块往往由不同的驱动程序控制,有的属于外部 I2C 器件(比如 Sensor),有的集成在 SoC 内部(比如 ISP)。所以说MIPI摄像头需要使用 v4l2_subdev 来分别表示各个模块(Sensor、Parser、ISP 等),并通过 Media Controller 建立完整的数据流拓扑。

因此,对于每一个 subdev 驱动而言,只需专注于实现该功能单元自身的控制与数据处理逻辑,而不需要直接关心与其他模块的连接关系。

模块之间的连接关系(即数据流向、控制路径)由 Media Controller 框架 统一管理,并通过 media graph 描述。这样可以实现模块化、可复用和灵活的系统设计。

这些模块被抽象为subdev,原因有2:

1. 屏蔽硬件操作的细节,有些模块是I2C接口的,有些模块是SPI接口的,它们都被封装为subdev

2. 方便内核使用、APP使用:可以在内核里调用subdev的函数,也可以在用户空间调用subdev的函数:很多厂家不愿意公开ISP的源码,只能在驱动层面提供最简单的subdev,然后通过APP调用subdev的基本读写函数进行复杂的设置。 

使用media来描述不同subdev之间的联系关系。 在media子系统中,每一个实体被称为一个media_entity,entity跟外界的联系被称为pad(可以认为是端口),端口和端口之间的连接称为media_link。 完成的信号通道称为pipeline。

4.1.1 subdev结构体

编写subdev驱动程序的时候,核心就是实现各类ops结构体的函数。其中重点是v4l2_subdev_core_ops、v4l2_subdev_video_ops、v4l2_subdev_pad_ops。 

一个摄像头驱动程序中只有一个v4l2_device,其结构体中含有一个subdevs的链表,用来管理多个注册的subdev之间的关系,v4l2_subdev中的media_entity 就是为了和media子系统挂上关系,进行管理。

4.1.2 media子系统的数据结构

每一个subdev中都含有一个media_entity结构体,这个结构体是用来描述各个subdev之间的关系,现在我们来看一下这个结构体中的信息有哪些?首先num_pads表示有几个端口(输出输入端口),num_links表示有多少个连接,然后有多少个端口其media_pads结构体数组就会有几项media_pad结构体,这个media_pad结构体中含有其的flag(是输出还是吸入pad),pad数组的索引,以及属于哪一个entity。

为了给两个subdev或者说是media_entity建立连接(由另外一个驱动程序决定),需要创建一个media_link结构体,media_link中的struct media_pad * source 指向输出的source pad , 而struct media_pad * sink指向输出的pad,建立好连接之后会将这个结构体放入media_entity 的links链表。调用关系如下图所示。这些关系均会被放入media_device结构体中。

4.1.3 subdev的注册和使用

4.1.3.1 注册

subdev管理可以使用v4l2_device,将其注册进内核,并可以选择是否将接口暴露给应用程序,如果将接口程序暴露给应用程序,需要将其注册成字符设备/设备节点。

在内核中,subdev的注册过程分为两步:

1、v4l2_device_register_subdev:把subdev放入v4l2_device的链表

int v4l2_device_register_subdev(struct v4l2_deivce *v4l2_dev , struct v4l2_subdev *sd);

2、v4l2_device_register_subdev_nodes:遍历v4l2_device链表中的各个subdev,如果它想暴露给应用程序,就把它注册成一个普通的字符设备。

int v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev)

4.1.3.2 使用

使用可以分为两种,一种是直接在内核态使用,第二种是应用程序调用

内核态使用

在内核态可以直接调用subdev里的操作函数,也可以使用下面的宏:其主要的方法是去判断subdev中的ops.pad的function函数。

 */
#define v4l2_subdev_call(sd, o, f, args...)				\({								\int __result;						\if (!(sd))						\__result = -ENODEV;				\else if (!((sd)->ops->o && (sd)->ops->o->f))		\__result = -ENOIOCTLCMD;			\else							\__result = (sd)->ops->o->f((sd), ##args);	\__result;						\})
应用程序调用过程(看暴露给用户程序的逻辑)

调用过程这一部分需要和前面注册的流程联系起来理解,否则容易弄不清为什么会这样调用。
在调用 v4l2_device_register_subdev_nodes() 时,系统会为每个 subdev 创建对应的字符设备节点 /dev/v4l-subdevX,并将其 cdevfile_operations 设置为 v4l2_fops
当用户空间程序打开 /dev/v4l-subdevX 节点时,内核会根据该节点的 file_operations 调用相应的操作函数。执行 ioctl 时,就会进入到 video_device 结构体中定义的 ops 操作集,该结构体类型为 v4l2_file_operations
ioctl 函数经过一系列调用后,最终会进入 subdev_do_ioctl()。在这个函数中,系统会先判断应用层传入的命令 cmd 是否被当前驱动支持,如果匹配,就会进一步调用 subdev 内部注册的 ops,其类型为 v4l2_subdev_ops
这样一来,用户空间发出的 ioctl 调用最终就能落到对应子设备的具体实现函数上,实现从应用层到 subdev 的完整调用链。

┌───────────────────────────────┐
│ 用户空间                      │
│ ┌───────────────────────────┐ │
│ │ open("/dev/v4l-subdev0")  │ │
│ │ ioctl(fd, CMD, &arg)      │ │
│ └───────────┬───────────────┘ │
└─────────────┼─────────────────┘│▼
┌────────────────────────────────────┐
│ 内核空间(V4L2 层)               │
│ file_operations.v4l2_fops          │
│   └─ video_ioctl2()                │
│       └─ subdev_do_ioctl()         │
│           └─ v4l2_subdev_call()    │
│               └─ sd->ops->core->ioctl() │
└────────────────────────────────────┘│▼
┌──────────────────────────────┐
│ 驱动层(subdev 实现)       │
│ struct v4l2_subdev_ops       │
│   └─ .core/.video/.pad 等子集│
│        → 具体函数(sensor_ioctl等) │
└──────────────────────────────┘

4.2、媒体media子系统的注册过程和使用

4.2.1 注册过程

4.2.1.1 两个层次

media子系统的注册分为2个层次(底层描述自己,上层描述不同media_entity之间的关系):

  • 各个subdev里含有media_entity,但是多个media_entity之间的关系由更上层的驱动决定。
  • 更上层的、统筹的驱动:它知道各个subdev即各个media_entity之间的联系:link。
4.2.1.2 四个步骤

media子系统的注册分为4个步骤:

  1. 描述自己:各个底层驱动构造subdev时,顺便初始化里面的media_entity,比如这个entity有哪些pad
    media_entity_pads_init(&sd->entity, SENSOR_PAD_NUM, si->sensor_pads);
  2. 注册自己:底层或者上层注册subdev时,顺便注册media_entity记录在media_device里
    v4l2_device_register_subdev()
    media_device_register_entity(v4l2_dev->mdev, entity);
  3. 和别人建立联系:subdev之上的驱动程序决定各个media_entity如何连接,比如调用media_create_pad_link
    media_create_pad_link(source, SCALER_PAD_SOURCE, sink, VIN_SD_PAD_SINK , MEDIA_LNK_FL_ENABLED);
  4. 暴露给应用程序使用:subdev之上的驱动程序注册media_device:media_device里汇聚了所有的media_entity。注册的时候会去分配设置一个media_devnode,设置其中的->fops。然后设置cdev,再设置fops,cdev的fops会是一个中转作用,必定会调用到media_devnode的fops。这里会创建/dev/media*节点。
    media_device_register(&vind->media_dev)

4.2.2、media子系统的使用

应用程序打开/dev/media*设备节点,按照上述的注册流程来说,相当于打开了media_devnode节点,如果调用ioctl,就会使用到这个devnode的cdev提供的file_operations结构体中的ioctl函数,这个函数是一个中转函数,会调用到devnode中的media_file_operation结构体。

列举所有的link

设置link状态

获取整体的拓扑图

最近实验室出了些状况,我的源码SDK服务器停了,看不了相关的源码了,G

http://www.dtcms.com/a/516538.html

相关文章:

  • TypeScript 面试题及详细答案 100题 (71-80)-- 模块与命名空间
  • 元组练习题
  • 【文献分享】Cell Decode:利用多尺度可解释深度学习进行细胞身份解码
  • H6843 DC-DC升压恒压芯片 支持3.3V转5V升压12V升压24V升压36V4A大电流电源芯片 低功耗
  • 4399页游网站第二课强登陆网站新型智库建设的意见
  • 企业网站模板下载网址东莞建网站哪家强
  • 北京住总第三开发建设有限公司网站广州万户网络技术有限公司招聘
  • gr00t机器人数据录制,通过遥操作的方式,操作isaacsim录制仿真数据的方法,HDF5格式秒变LeRobot标准数据集(数据采集一)
  • 织梦 网站公告陕西省住建厅网站官网
  • 23.C++11(四)
  • Leetcode 31
  • 手机 iOS 系统全解析,生态优势、开发机制与跨平台应用上架实践指南
  • 在线做动漫图的网站网站开发用什么技术asp
  • React Native 使用 react-native-credentials-manager 接入谷歌登录教程
  • 从零起步学习MySQL || 第七章:初识索引底层运用及性能优化(结合底层数据结构讲解)
  • CVPR2025 | OPS | 通过假设空间增强提升对抗迁移性
  • 自己做的网站怎么才能在百度上查找郑州定制网站推广工具产品
  • 如何从小白变成rust糕手
  • 注册一个网站多少钱?哪个网站可以免费建站
  • GCC与Makefile常用基础知识
  • 类装饰器
  • 什么网站可以直接做word如何在外管局网站做付汇延期
  • Dify从入门到精通 第22天 利用分支与判断构建智能路由客服机器人
  • 网站底备案号链接代码商丘建设厅网站首页
  • 【C++】手搓AVL树
  • 【完整源码+数据集+部署教程】【天线&其他】月球表面状况检测系统源码&数据集全套:改进yolo11-unireplknet
  • Flutter---弹窗
  • 从零开始学习RabbitMQ
  • 台州市住房和城乡建设局网站做美容美发学校网站公司
  • [答疑]考虑复用,尺度应该怎样把握