Camera驱动开发

Camera基础知识

CMOS Sensor

也常被简称为Sensor,它就是个光电转换器件,把光信号转换成电信号

特点:

  • 只是一个裸芯片(die)
  • 输出通常是 Raw Bayer 数据(RGGB/BGGR 等排列)
  • 不含镜头、ISP、电路板等辅助模块
  • 常见规格:分辨率(MP)、尺寸(1/2.3”)、像素尺寸、bit-depth(10/12-bit)

相机模组

将Sensor、镜头、PCB、外壳等封装到一起的摄像头硬件模块,接口可以直接接主控

image-20250820145445828

组成部分:

  • CMOS Sensor → 输出 Raw 或 ISP 后的数据
  • 镜头系统 → 聚焦光线到传感器
  • PCB 电路板 → 控制信号、时钟、I2C/CSI/USB 接口
  • ISP(可选)→ 有些模块自带 ISP,输出 NV12 / RGB / MJPEG
  • 封装/外壳 → 方便安装、保护

网上各种博客对于相机模组的常见叫法比如IMX215,实际指的是模组的sensor

相机

相机是对相机模组的进一步封装,相机模组一般要搭配主控才能使用,而相机可以理解为:内部主控 + 相机模组,相机一般会自带ISP处理,能够直接向后级主控输出标准格式的像素

常见接口

上面提到相机和相机模组实际上也是有区别的,所以他们在硬件上的数据接口一般也不同

相机模组

MIPI CSI

MIPI CSI(Camera Serial Interface)是移动行业处理器接口联盟(MIPI Alliance)制定的摄像头串行接口标准协议栈,目前许多相机模组都用了该协议栈,它的物理层接口D-PHY通常由2部分组成:

PHY通常负责数据的转换,它一般是个独立的硬件模块,比如以太网里的PHY模块

image-20250820151411740
  • CSI Transmiter:主机处理器与摄像头模块之间的高速串行接口(传输图像数据)

    • 数据通道(Data Lanes)

      • 数量:1、2 或 4 对差分线(最常见的是 4 Lane,即 4 对差分线)

      • 作用:传输图像数据(像素数据、同步信号等)

      • 命名:通常标记为 D0+/D0-D1+/D1-D2+/D2-D3+/D3-

      • 带宽:每对 Lane 的带宽取决于速率(如 1.5 Gbps per Lane),4 Lane 可提供更高吞吐量(适用于高分辨率传感器)

    • 时钟通道(Clock Lane)

      • 数量:1 对差分线(必需)

      • 作用:提供同步时钟信号(与数据 Lane 同步)

      • 命名:通常标记为 CLK+/CLK-

  • CCI Slave:摄像头控制接口

    • I2C:用于传感器配置(如设置分辨率、曝光时间等),通常需要:

      • SCL(I2C 时钟)、SDA(I2C 数据)
    • GPIO 控制信号(可选):

      • 传感器复位(RESET

      • 电源使能(PWDN

    • 帧同步(FSYNC

注意,CSI是个协议栈(类似TCP/IP),并不是像IIC那样的一个具体的接口,它也分为多层,上面提到的他在硬件上有这几种引脚,实际上是由该协议的PHY Layer决定的

image-20250820150757623 image-20250820150837579

实际上MIPI协议栈定义了几种通用物理层接口:D-PHY、C-PHY、M-PHY来传递数据,不管是用来传递摄像头、显示器还是别的什么的数据,物理层面都可以用这几种接口

image-20250820151044363

实例分析:

image-20250820152903822

  1. MIPI DPHY提供了4 Lane的RX接口,由Sensor提供Clock,并通过四条数据Lane输入图像数据
  2. DPHY与CSI-2 Host Contrller之间通过PPI(PHY-Protocol Interface)相连,该接口包括了控制, 数据,时钟等多条信号
  3. CSI-2 Host Contrller通过PPI接口收到数据后进行解析,完成后通过IDI(Image Data Interface)或者 IPI(Image Pixel Interface)输出到SoC的其他模块(VICAP或ISP,rk3568是送至VICAP模块)
  4. ISP将处理过的图片输出到MP主通道或SP自身通道,SP一般用来预览图片,SP图片的最大分辨率比 MP低

DVP

DVP(Digital Video Port,摄像头数据并口传输协议)

  • 一种并口协议,提供8-bit或10-bit并行传输数据线、HSYNC(Horizontal sync)行同步线、VSYNC(Vertical sync)帧同步线和PCLK(Pixel Clock)时钟同步线

相机

  • USB
  • GigE Vison
  • Camera Link
  • CXP

疑问:

1.CSI摄像头是通用的吗(引脚数量和排列是一样的吗)

  • 不是,各家CSI摄像头的引脚顺序和数量都是它们自己决定的,即使2Lane也可能引出10几个引脚(大部分都接地罢了…)
  • 一般CSI摄像头都是和开发板绑定的,因为CSI接口一般用FPC排线,无法像杜邦线那样自己调整顺序。网上卖的CSI摄像头都会说适配xxx开发板,如果是自己设计的开发板要想用这些摄像头的话,CSI接口引脚顺序不能随便决定

ISP

ISP(Image Signal Processor),即图像信号处理器,用于处理图像信号传感器输出的原始图像信号。 它在相机系统中占有核心主导的地位,是构成相机的重要设备

瑞芯微rk3568平台的ISP2.1 处理图像数据的基本流程如下:

image-20250820153941872

一般抓图的顺序:

  1. 摄像头的初始化(输出格式、分辨率、输出速率)
  2. 使能摄像头接入主控板卡中的物理通道
  3. 使能主控板卡中的ISP(图像信号处理模块)、并让ISP知道当前有效接入的摄像头是哪一个(因为可以多个接入,但只能一个有效)
  4. 告诉ISP输进来的数据如何处理(颜色空间转换、缩放、裁剪、旋转等)、经由那个通道输出到内存/显存(MP主通道、SP自身通道)
  5. 输出到内存

V4L2驱动框架

v4l2_device

作用:v4l2_device可以理解为一个V4L2设备的顶层抽象容器,它把一个摄像头或者视频采集卡整体抽象出来,是一个逻辑上的“总控”。他会管理底下的多个子设备,比如一个摄像头设备,就会包含sensor、ISP、I2C控制器等多个子设备

特点:

  • 驱动里通常先注册 v4l2_device,再往里面挂 video_devicev4l2_subdev
  • 主要负责管理、统一资源(例如注销时统一清理)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
struct v4l2_device {
struct device *dev; // 对应的物理设备(通常是PCI/USB等)
struct media_device *mdev; // 可选,V4L2与media framework集成时用
struct list_head subdevs; // 维护所有挂载的 v4l2_subdev
spinlock_t lock; // 保护 subdevs 链表
char name[V4L2_DEVICE_NAME_SIZE]; // 设备名
void (*notify)(struct v4l2_subdev *sd, unsigned int notification, void *arg);
void *priv;
void (*release)(struct v4l2_device *v4l2_dev);
};

// 注册一个 v4l2_device,初始化内部成员并绑定到底层 struct device
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);
// 注销 v4l2_device,清理已注册的子设备和资源
void v4l2_device_unregister(struct v4l2_device *v4l2_dev);

// 向 v4l2_device 注册一个 subdev,建立主设备与子设备关联
int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev, struct v4l2_subdev *sd);
// 从 v4l2_device 注销一个 subdev,解除绑定关系
void v4l2_device_unregister_subdev(struct v4l2_subdev *sd);

// 为 v4l2_device 下的每个 subdev 创建对应的 /dev/v4l-subdevX 节点
int v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev);

// 在驱动卸载或异常时断开 v4l2_device 与底层设备的连接
void v4l2_device_disconnect(struct v4l2_device *v4l2_dev);

// 设置 v4l2_device 的唯一名字,用于区分多个相似设备
int v4l2_device_set_name(struct v4l2_device *v4l2_dev, const char *basename, atomic_t *instance);

// 在 v4l2_device 管理的子设备列表中查找指定名称的 subdev
struct v4l2_subdev *v4l2_device_find_subdev(struct v4l2_device *v4l2_dev, const char *name, bool search_all);

video_device

作用:将v4l2_device以字符设备的形式暴露给应用层,让应用层能通过IO操作与V4L2设备进行交互

特点:

  • 它有个cdev的成员变量,所以/dev/videoX节点实际由该类产生
  • 一个 v4l2_device 下面可以有多个 video_device 节点,例如:
    • /dev/video0 → 原始采集数据
    • /dev/video1 → ISP 输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// v4l2-dev.h
struct video_device {
#if defined(CONFIG_MEDIA_CONTROLLER)
struct media_entity entity;
#endif
struct v4l2_device *v4l2_dev; // 归属的 v4l2_device
const struct v4l2_file_operations *fops; // 应用层 open/read/ioctl
const struct v4l2_ioctl_ops *ioctl_ops; // V4L2 ioctl 接口
struct device dev; // 内核的 device 结构
struct cdev cdev; // 字符设备,注册 /dev/videoX
struct vb2_queue *queue; // 维护buffer

char name[32]; // 节点名
int minor; // 次设备号 (/dev/videoX 中的 X)
...
};

// 分配一个空的 video_device 结构体,用于驱动初始化
struct video_device *video_device_alloc(void);
// 释放 video_device 内存,一般与 video_device_alloc 配对使用
void video_device_release(struct video_device *vdev);

// 向内核注册 video_device,创建设备节点 /dev/videoX
int video_register_device(struct video_device *vdev, enum vfl_devnode_type type, int nr);
// 注销 video_device,删除 /dev/videoX 节点
void video_unregister_device(struct video_device *vdev);

// 设置 video_device 的私有数据指针,供驱动内部使用
void video_set_drvdata(struct video_device *vdev, void *data);
// 获取 video_device 的私有数据指针
void *video_get_drvdata(struct video_device *vdev);

v4l2_subdev

作用:表示一个子设备,一般是整个图像pipeline中的独立的一个模块,比如:

  • 摄像头 Sensor (OV5640, IMX219)
  • ISP外设
  • 视频编解码器芯片
  • HDMI 接收器

特点:

  • 不直接暴露 /dev/videoX 节点(一般不会被应用层直接 open
  • 通过 sub-device API(v4l2_subdev_ops)和上层驱动交互
  • 常常通过 I²C 或 SPI 注册进来(sensor 驱动就是这种)
  • 在 Media Controller 框架里,每个 subdev 会变成一个 entity,和别的 entity 组成 pipeline

举例:在 CSI 摄像头中

  • ov5640 驱动注册为一个 v4l2_subdev
  • CSI 控制器驱动(video_device)会通过 subdev API 来调用 sensor 的设置,比如分辨率、帧率、曝光
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
struct v4l2_subdev {
struct media_entity entity;
struct list_head list;
struct module *owner;
bool owner_v4l2_dev;
u32 flags;
struct v4l2_device *v4l2_dev;
const struct v4l2_subdev_ops *ops;
const struct v4l2_subdev_internal_ops *internal_ops;
struct v4l2_ctrl_handler *ctrl_handler;
char name[V4L2_SUBDEV_NAME_SIZE];
u32 grp_id;
void *dev_priv;
void *host_priv;
struct video_device *devnode;
struct device *dev;
struct fwnode_handle *fwnode;
struct list_head async_list;
struct v4l2_async_subdev *asd;
struct v4l2_async_notifier *notifier;
struct v4l2_async_notifier *subdev_notifier;
struct v4l2_subdev_platform_data *pdata;
struct mutex *state_lock;
};

// 注册一个 subdev 到 v4l2_device 中,建立主设备与子设备的关联
int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev, struct v4l2_subdev *sd);
// 从 v4l2_device 注销一个 subdev,解除绑定关系
void v4l2_device_unregister_subdev(struct v4l2_subdev *sd);

// 异步注册 subdev,用于异步 probe 场景
int v4l2_async_register_subdev(struct v4l2_subdev *sd);
// 注销异步注册的 subdev
void v4l2_async_unregister_subdev(struct v4l2_subdev *sd);
// 将 subdev 添加到 v4l2_device 的异步子设备列表
int v4l2_async_register_subdev_sensor(struct v4l2_subdev *sd);

// 初始化 subdev 内部结构(name、ops 等),驱动 probe 时调用
void v4l2_subdev_init(struct v4l2_subdev *sd, const struct v4l2_subdev_ops *ops);

// 设置 subdev 的唯一名字(通常用于异步匹配)
int v4l2_subdev_set_name(struct v4l2_subdev *sd, const char *basename, struct v4l2_device *v4l2_dev);

// 设置 subdev 所属的 media entity pads(Media Controller 相关)
int v4l2_subdev_init_entity(struct v4l2_subdev *sd, u16 num_pads, struct media_pad *pads);

subdev的probe中一般用v4l2_async_register_subdev将设备异步注册到V4L2的异步管理链表中,因为probe函数的执行顺序难以通过代码确定,为了满足不同设备的依赖关系,所以引入了异步注册的机制:当某个subdev被异步注册时,V4L2会扫描 notifier 的 subdev list,检查依赖是否满足,如果满足则会把该subdev注册给依赖它的设备

v4l2_subdev 就是摄像头模块中的某个具体功能块,可以认为 subdev 是 “插件化模块”,让驱动更易扩展,每个 subdev 提供一套 ops,比如:

  • core_ops :控制开关、电源管理
  • video_ops:视频流输入输出
  • pad_ops:媒体 pad 链接
1
2
3
4
5
6
7
8
9
10
struct v4l2_subdev_ops {
const struct v4l2_subdev_core_ops *core;
const struct v4l2_subdev_tuner_ops *tuner;
const struct v4l2_subdev_audio_ops *audio;
const struct v4l2_subdev_video_ops *video;
const struct v4l2_subdev_vbi_ops *vbi;
const struct v4l2_subdev_ir_ops *ir;
const struct v4l2_subdev_sensor_ops *sensor;
const struct v4l2_subdev_pad_ops *pad;
};

vb2_queue

定义:是 V4L2 框架中视频帧缓冲管理的核心结构体,是videobuf2(VB2)框架的核心数据结构。主要负责视频帧缓冲区(buffer)的分配、队列管理、映射和DMA交互,是连接驱动层与应用层 mmap/read/streaming 操作的关键桥梁,V4L2的各种数据相关的ioctl,底层都会操作vb2_queue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct vb2_queue {
enum v4l2_buf_type type; // 缓冲区类型,如 V4L2_BUF_TYPE_VIDEO_CAPTURE
unsigned int io_modes; // 支持的 I/O 模式(MMAP、USERPTR、DMABUF)
unsigned int buf_struct_size; // 驱动私有 buffer 结构体大小

const struct vb2_ops *ops; // 驱动提供的回调函数集合
const struct vb2_mem_ops *mem_ops; // 内存操作函数(dma-contig, vmalloc 等)
const struct vb2_buf_ops *buf_ops; // 针对单个 buffer 的操作
struct device *dev; // 设备指针(DMA 操作用)

struct mutex *lock; // 队列访问锁
spinlock_t done_lock; // 保护 done_list

struct list_head queued_list; // 等待处理的 buffer 队列
struct list_head done_list; // 已完成的 buffer 队列
struct vb2_buffer *bufs[VB2_MAX_FRAME]; // 所有 buffer 的指针数组

unsigned int num_buffers; // 缓冲区数量
unsigned int min_buffers_needed; // 启动流所需最小 buffer 数
unsigned int num_queued; // 已入队 buffer 数

enum vb2_queue_state state; // 队列状态(UNINITIALIZED, STREAMING...)
struct v4l2_fh *owner; // 当前使用队列的文件句柄
void *drv_priv; // 驱动私有数据
...
};

3个关键的回调接口

接口结构体 作用 示例
vb2_ops 队列级操作(流控制、队列管理) queue_setup, start_streaming, buf_queue
vb2_mem_ops 内存操作(分配、映射、释放) vb2_dma_contig_memops, vb2_vmalloc_memops
vb2_buf_ops 针对单个 buffer 的回调.主要用来实现应用层(v4l2_buf)和驱动层(vb2_buffer)的数据转换 通常默认使用框架实现

vb2_ops一般每个多媒体设备都要自己实现,而其他2个,一般使用内核实现的

image-20251005160527109

vb2_buffer

定义:保存单个视频缓冲区的信息,应用层通过ioctl申请的v4l2_buffer,在内核中实际上对应vb2_buffer结构体,他们可以互相转换。vb2_queue中维护了一个vb2_buffer数组代表所有缓冲区,并通过2个链表进行管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct vb2_buffer {
struct v4l2_buffer v4l2_buf;
struct vb2_queue *vb2_queue; // 指向所属队列
unsigned int index; // 缓冲区索引号(0,1,2...)
unsigned int type; // V4L2_BUF_TYPE_VIDEO_CAPTURE 等
unsigned int num_planes; // 多平面格式(YUV有多plane)
struct vb2_plane planes[VB2_MAX_PLANES]; // 每个plane的内存信息
unsigned int state; // 缓冲区状态:DEQUEUED/QUEUED/DONE
struct list_head queued_entry; // 链入 queued_list
struct list_head done_entry; // 链入 done_list
struct timespec64 timestamp; // 时间戳(帧捕获时间)
void *priv; // 驱动私有数据
...
};

vb2_plane

定义:描述 “每一帧视频缓冲区中的一个平面(Plane)” 的结构体,每个 vb2_buffer 都由一个或多个 vb2_plane 组成,它是帧数据的最小单位。在用户空间中,对应v4l2_plane结构体,2者可以相互转换

为什么一帧数据会被分成多个plane

有些像素格式(比如 RGB24)把所有颜色分量存在一个内存块中,这种格式叫单平面格式。而像 YUV420、NV12 等格式则会将亮度(Y)和色度(UV)分开存放,这就需要多平面格式

1
2
3
4
5
6
7
8
9
struct vb2_plane {
void *mem_priv; // 内存分配实现的私有数据
unsigned long bytesused; // 实际使用的字节数
unsigned long length; // 缓冲区总长度
unsigned long data_offset; // 数据起始偏移(用于多平面格式)
struct dma_buf *dbuf; // DMA-BUF 对象(用于 DMABUF 模式)
dma_addr_t dma_addr; // 物理地址(DMA 用)
void __user *userptr; // 用户空间指针(USERPTR 模式)
};

初始化流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──────────────────────────────┐
│ Kernel boot │
│ ├── initcalls ... │
│ ├── media subsystem loaded │
│ └──→ v4l2_init() │
│ ↓
│ class_register(video_class)
│ ↓
│ /sys/class/video4linux/
│ ↓
│ waits for video drivers
└──────────────────────────────┘

→ Driver probe()
→ vb2_queue_init()
→ v4l2_device_register()
→ video_register_device()
→ /dev/video0

系统调用流程

1.普通的系统调用(非ioctl)

1
2
3
4
应用层调用系统调用
-> 核心层注册的file_operations
-> vedieo_device的v4l2_file_operations
.open/.close...之类的函数需要由每个设备的驱动独立实现

2.ioctl

1
2
3
4
5
6
7
8
9
应用层调用ioctl
-> 核心层注册的file_operations
-> vedieo_device的v4l2_file_operations的.unlocked_ioctl(核心层提供)
-> video_ioctl2()
-> __video_do_ioctl()
-> 内核通过 _IOC_NR(cmd)在 v4l2_ioctls[]中查找对应的 v4l2_ioctl_info
-> 根据 flags决定如何调用处理函数:
- INFO_FL_STD:从 v4l2_ioctl_ops中按偏移量找到 vidioc_querycap
- INFO_FL_FUNC:直接调用 v4l_querycap

V4L2的ioctl可以分为2类:

  • INFO_FL_STD:绑定到设备驱动实现的函数,相关的操作每个驱动可能有不同的实现
  • INFO_FL_FUNC:绑定到V4L2核心层实现的函数,相关操作可能需要参数验证、格式转换,把这部分公用的逻辑放到核心层实现

参考链接

Media Controller驱动框架

作用:他是V4L2的pipeline管理层,把多媒体硬件系统抽象成一个可编程的图,来描述硬件之间的拓扑关系。图中的每个节点是一个功能模块(entity),边是模块间的连接(link)

早期的 V4L2 设备(比如 USB 摄像头)结构简单,数据流只有一条直线,所以一个 /dev/videoX 就能完成采集、控制:

1
Sensor → Video Node (/dev/video0)

但后来嵌入式 SoC 多媒体系统变复杂了,比如:

1
Sensor → MIPI-CSI Receiver → ISP → Scaler → Video Node (/dev/video0)

这些模块可能:

  • 属于不同驱动
  • 通过不同总线(I2C、CSI、MMDC)通信
  • 可以被多个上层设备共享
  • 能通过软件动态切换通路(例如多摄像头切换、双路输出)

传统 V4L2 模型(单个 /dev/videoX)已经没法表示这么复杂的“模块互联”关系,所以引入了Media Controller驱动框架

结构体 描述
media_device 表示整个媒体设备(如一颗 SoC 的摄像头系统)
media_entity 表示功能单元(sensor、CSI、ISP、video node 等)
media_pad entity 的输入输出端口
media_link pad 与 pad 之间的连接关系

与V4L2的关系:

层次 框架 作用
上层 Media Controller 描述模块拓扑与连接关系
中层 V4L2 Core 管理视频设备与 subdev
底层 具体驱动 驱动 sensor、ISP、CSI 等模块

Jetson Nano平台下的多媒体pipeline拓扑示意图:

media-ctl -p

Device topology
- entity 1: nvcsi--2 (2 pads, 0 link)
            type V4L2 subdev subtype Unknown flags 0
            device node name /dev/v4l-subdev0
        pad0: Sink
        pad1: Source

- entity 4: nvcsi--1 (2 pads, 2 links)
            type V4L2 subdev subtype Unknown flags 0
            device node name /dev/v4l-subdev1
        pad0: Sink
                <- "imx219 8-0010":0 [ENABLED]
        pad1: Source
                -> "vi-output, imx219 8-0010":0 [ENABLED]

- entity 7: imx219 8-0010 (1 pad, 1 link)
            type V4L2 subdev subtype Sensor flags 0
            device node name /dev/v4l-subdev2
        pad0: Source
                [fmt:SRGGB10_1X10/3264x2464 field:none colorspace:srgb]
                -> "nvcsi--1":0 [ENABLED]

- entity 9: vi-output, imx219 8-0010 (1 pad, 1 link)
            type Node subtype V4L flags 0
            device node name /dev/video0
        pad0: Sink
                <- "nvcsi--1":1 [ENABLED]

media_device

定义:它是多媒体设备的全局抽象对象,用于统一管理摄像头、解码器、编码器、ISP 等多模块之间的连接关系(运行时的数据流的管理)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct media_device {
struct device *dev; // 对应的底层物理设备(如 USB/PCI/Platform)
struct media_entity *entity; // 设备内的实体节点(subdev、video_device)
struct list_head entities; // 所有已注册的 entity 链表
struct list_head pads; // 所有 pad(输入/输出端口)
struct list_head links; // 所有 entity 之间的 link(连接关系)

struct mutex graph_mutex; // 保护拓扑图操作的互斥锁
u32 id; // 全局唯一标识符
const char *model; // 设备型号(如 "uvcvideo")
char serial[64]; // 序列号(可选)
char bus_info[32]; // 总线信息(如 "usb-0000:01:00.0-1")
u32 hw_revision; // 硬件版本号

struct media_device_ops *ops; // 操作函数集,如 link_validate()
};

media_entity

在 Media Controller 框架中,每一个可以处理或传输媒体数据的模块(如 sensor、CSI、ISP、video node 等)都被抽象为一个 media_entity,可以理解为它是pipeline中的一个节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
struct media_entity {
struct media_gobj graph_obj; // 媒体图对象基础
const char *name; // 实体名称
enum media_entity_type type; // 实体类型(比如MEDIA_ENT_T_V4L2_SUBDEV_SENSOR)
u32 function; // 功能标识(如 MEDIA_ENT_F_CAM_SENSOR)
unsigned int num_pads; // pad数量
struct media_pad *pads; // pad数组
unsigned int num_links; // 链接数量
struct media_link *links; // 指向所有link
const struct media_entity_operations *ops; // 操作集
void *priv; // 驱动私有数据
};

// 初始化一个 media_entity 的 pads 数组
int media_entity_pads_init(struct media_entity *entity,unsigned int num_pads,struct media_pad *pads);

// 释放 entity 的 pad 资源(与 pads_init 配对使用)
void media_entity_cleanup(struct media_entity *entity);

// 在两个 entity 的 pad 之间创建连接(link)
int media_create_pad_link(struct media_entity *source, u16 source_pad,
struct media_entity *sink, u16 sink_pad,
u32 flags);

// 启用或禁用某个 media_link(激活拓扑关系)
int media_entity_setup_link(struct media_link *link, u32 flags);

// 根据 pad 找到与其相连的另一端 pad(通常用于上游/下游遍历)
struct media_pad *media_entity_remote_pad(const struct media_pad *pad);
// 查找两个 pad 之间的链接对象(若存在)
struct media_link *media_entity_find_link(struct media_pad *source,
struct media_pad *sink);

// 遍历媒体图中的所有实体,初始化遍历状态
int media_graph_walk_init(struct media_graph *graph,
struct media_device *mdev);

// 获取遍历中的下一个 entity
struct media_entity *media_graph_walk_next(struct media_graph *graph);

// 停止 entity 所属的 pipeline
void media_entity_pipeline_stop(struct media_entity *entity);

每个media_entity都有若干个pad(数据传输接口),pad之间通过link链接

1
2
3
4
5
6
7
8
9
10
11
struct media_pad {
struct media_entity *entity; // 所属实体
u16 index; // pad编号
u16 flags; // sink或source
};

struct media_link {
struct media_pad *source; // 源pad
struct media_pad *sink; // 目标pad
u32 flags; // 是否已激活
};

面试问题

1.sensor驱动的作用是什么

  • sensor的驱动主要负责通过D-PHY的CCI对sensor的控制,比如硬件上开启sensor的数据输出、对sensor进行一些分辨率的配置什么的,不负责软件上将sensor的数据传输给下游。模组输出的数据的直接传给了CSI控制器的D-PHY,之后会触发它的中断来完成数据的读取

2.数据是如何在不同子设备之间传输的

  • 之前有个误区,以为数据是通过media框架描述出的pipeline在不同模块之间传输的,实际上不是的。虽然media框架通过media_entitymedia_padmedia_link等结构体构建了一个有向图,它描述的只是子设备之间的拓扑(连接)关系,用于实现应用层的一些控制的自动传输,比如我要开启数据传输,需要同步启动多个子设备,通过构建pipeline V4L2就知道该如何控制了
  • 数据的传输主要通过DDR,开启数据流后,RAW数据首先被CSI控制器读取,处理后就直接放到DDR,下游硬件直接从DDR读取

3.v4l2_subdevmedia_entity的区别是什么

  • v4l2_subdev主要用于封装对子设备逻辑管理、回调调用,主设备只知道有哪些subdev但不知道他们之间的拓扑关系,比如主机只能知道有ISP、VI、sensor这几个硬件,但是不知道数据应该从sensor输出,经过ISP再到VI
  • media_entity主要靠media_padmedia_link描述子设备之间的拓扑关系

4.如何调试

  • 拓扑关系:使用media-ctl查看
  • 查看V4L2设备的具体能力:使用v4l2-ctl查看