3. RKISP驱动核心数据结构
好,咱们今天来啃一块硬骨头——RKISP驱动里的三个核心数据结构。说实话,我刚接触RKISP驱动时,也被这三个结构体绕得晕头转向。它们就像三兄弟,长得像,但各司其职。搞懂了它们,你就掌握了ISP驱动的半壁江山。
我个人习惯把这三个结构体比作一个摄影团队:rkisp_device是导演,rkisp_pipeline是摄影组,rkisp_stream是具体的摄像机。这样想,是不是清晰多了?
3.1 rkisp_device:整个ISP的“大管家”
先看老大——rkisp_device。它代表一个完整的ISP硬件设备。在RK3399、RK3288这些芯片上,每个ISP硬件单元对应一个这样的结构体。
struct rkisp_device {
struct device *dev;
struct v4l2_device v4l2_dev;
struct media_device media_dev;
struct rkisp_pipeline pipeline;
struct rkisp_stream stream[RKISP_MAX_STREAM];
void __iomem *base_addr;
int irq;
struct clk *clks[RKISP_MAX_CLKS];
struct rkisp_match_data *match_data;
spinlock_t slock;
struct mutex lock;
// ... 还有不少成员
};
这个结构体里,我重点说几个关键字段:
- dev:指向Linux设备模型中的device结构体。说白了,就是告诉内核“嘿,我在这儿呢”。
- v4l2_dev和media_dev:这是V4L2和Media Controller框架的接口。没有它们,用户空间就找不到这个设备。
- pipeline:这个很重要!它管理着ISP内部的数据流管道。
- stream:一个数组,每个元素代表一个视频流。为什么是数组?因为一个ISP可以同时处理多个流,比如主路和子路。
- base_addr:寄存器的基地址。所有硬件操作都通过它来读写。
核心要点:rkisp_device是全局唯一的。一个ISP硬件对应一个实例。它负责管理所有子模块的生命周期。
我记得第一次调试时,发现中断一直不触发。查了半天,原来是irq号没注册对。嗯,这种低级错误,犯过一次就记住了。
3.2 rkisp_pipeline:数据流的“调度中心”
接下来是老二——rkisp_pipeline。它描述的是ISP内部的数据处理流水线。你想想看,图像数据从传感器进来,要经过DPCC(坏点校正)、BLS(黑电平)、LSC(镜头阴影校正)……这一整套流程,就是pipeline来管理的。
struct rkisp_pipeline {
struct media_pipeline pipe;
struct rkisp_device *dev;
struct rkisp_stream *stream;
struct v4l2_subdev *sensor;
struct v4l2_subdev *mipi_csi;
struct rkisp_isp_subdev isp_sd;
struct rkisp_params_subdev params_sd;
struct rkisp_stats_subdev stats_sd;
bool streaming;
// ...
};
这里有几个关键点:
- sensor、mipi_csi:指向实际的硬件子设备。pipeline负责把它们串联起来。
- isp_sd、params_sd、stats_sd:这三个是ISP内部的核心子设备。分别处理图像数据、参数配置、统计信息。
- streaming:一个布尔值,标记当前是否在推流。我曾经踩过一个坑:在streaming为true时修改了pipeline配置,结果系统直接挂了。所以,改配置前一定要检查这个标志位。
个人经验:调试pipeline时,我习惯先检查streaming状态。如果发现流已经启动,但图像出不来,多半是pipeline里的某个子设备没有正确链接。用media-ctl工具打印一下拓扑图,一目了然。
3.3 rkisp_stream:用户看到的“视频流”
最后是小弟——rkisp_stream。这个结构体直接面向用户空间。应用程序通过V4L2接口打开一个video节点,背后就是这个结构体在工作。
struct rkisp_stream {
struct rkisp_device *dev;
struct video_device vdev;
struct vb2_queue buf_queue;
struct rkisp_buffer *curr_buf;
struct list_head buf_list;
enum rkisp_stream_mode mode;
struct v4l2_pix_format_mplane pixm;
struct v4l2_rect crop;
// ...
};
重点字段解析:
- vdev:V4L2视频设备节点。用户空间通过
/dev/videoX操作的就是它。 - buf_queue:缓冲区队列。应用程序通过
QBUF/DQBUG来管理缓冲区,底层就是操作这个队列。 - curr_buf:当前正在处理的缓冲区。中断处理时,我们会把数据填到这个缓冲区里。
- mode:流模式。是RAW数据还是YUV?是主路还是子路?这个字段决定了后续的处理逻辑。
- crop:裁剪区域。用户可以通过V4L2的
S_SELECTION来设置,只输出感兴趣的区域。
避坑指南:我曾经遇到过一个问题——应用程序设置了裁剪区域,但输出的图像还是全尺寸的。查了两天才发现,crop的值虽然设置了,但pipeline里的scaler模块没有同步更新。所以,修改stream参数时,一定要确保pipeline里的所有相关模块都同步了。
3.4 三个结构体的“三角关系”
好了,三个结构体都介绍完了。但它们之间到底怎么配合的?我画了一张图,帮你理清关系。
从这张图可以看出:
- rkisp_device是顶层容器。它创建并管理pipeline和stream。
- rkisp_pipeline负责数据流的内部处理。它把sensor、MIPI CSI、ISP子设备串联成一条完整的处理链路。
- rkisp_stream是用户空间的接口。应用程序通过它来配置参数、获取图像数据。
- 数据流向是:sensor → pipeline处理 → stream输出给用户。
关键理解:一个device可以有多个stream(比如主路和子路),但只有一个pipeline。所有stream共享同一个pipeline的处理能力。这就是为什么修改pipeline配置时,要确保所有stream都处于空闲状态。
3.5 实际项目中的“三角关系”
我在一个项目中遇到过这样的场景:客户要求同时输出RAW和YUV两个流。RAW用于算法处理,YUV用于预览显示。
当时我配置了两个rkisp_stream实例:
- stream[0]:模式设为
RKISP_STREAM_RAW,输出RAW12数据。 - stream[1]:模式设为
RKISP_STREAM_YUV,输出YUV420数据。
但问题来了——两个stream共享同一个pipeline。pipeline里的ISP模块只能以一种配置运行。RAW流需要关闭一些图像增强模块,而YUV流需要开启。怎么办?
我的解决方案是:在pipeline里增加一个mode字段,根据当前活跃的stream动态切换配置。当只有RAW流时,pipeline走“直通”模式;当YUV流加入时,pipeline切换到“完整处理”模式。
小技巧:处理多stream时,我建议在rkisp_pipeline里维护一个active_streams位图。每次stream启动或停止时,更新这个位图,然后根据位图动态调整pipeline配置。这样既灵活又安全。
3.6 总结一下
这三个结构体,说白了就是ISP驱动的骨架:
| 结构体 | 角色 | 核心职责 | 关键字段 |
|---|---|---|---|
| rkisp_device | 导演 | 管理整个ISP硬件,生命周期控制 | dev, v4l2_dev, pipeline, stream[] |
| rkisp_pipeline | 摄影组 | 数据流调度,子设备串联 | sensor, isp_sd, params_sd, streaming |
| rkisp_stream | 摄像机 | 用户接口,缓冲区管理 | vdev, buf_queue, mode, crop |
嗯,这三个结构体搞明白了,RKISP驱动的框架你就掌握了七七八八。剩下的就是各个子模块的具体实现了。下次我们聊ISP子设备的时候,你会发现,它们都是在为这三个核心结构体服务的。
最后提醒一句:写驱动时,千万别把这三个结构体的生命周期搞混了。device在probe时创建,remove时销毁。pipeline和stream在open时初始化,close时清理。我曾经见过一个驱动,在remove之后还在访问pipeline,结果内核直接oops了。血的教训啊。
可可精品资料库