
简介RK ISP 驱动代码包面向嵌入式Linux下Rockchip图像信号处理器ISP的驱动开发与移植场景适合内核驱动工程师和学习V4L2框架的开发者。资源以rk-isp11为例重点展示设备树匹配使用的of_device_id并基于device/media/platform/rk-cif/cif_cif10_v4l2.c进行讲解可帮助理解ISP与Camera子系统在同一驱动框架下的协作方式。压缩包共含17个文件以7个C源文件、8个头文件为主配套1个Makefile与1个Kconfig代码量约96KB各模块包括平台初始化、图像源注册、v4l2-subdev对接以及寄存器定义等结构清晰便于按模块精读。现有1763人学习下载。读者可直接获得整套rkisp11驱动源码结合V4L2与子设备模型可快速定位设备树匹配、图像数据通路及平台驱动接口为后续适配新Sensor或调试ISP管线提供具体参考。1. rkisp驱动代码不是单个文件先把它当一套ISP媒体栈看待做Rockchip平台相机方案的人迟早会打开一个叫rkisp的驱动代码目录刚开始容易以为里面就一个.c文件顶多几百行。实际翻进去会发现drivers/media/platform/rockchip/isp/下躺着十来个源文件probe入口、ISP主控、MIPI CSI2接收、resizer、stats统计回传、params参数下发各自分工背后还依赖内核的v4l2_subdev和media controller框架。这套代码直接影响三个视频节点能不能出帧、画面黑不黑、颜色对不对。适合两种人一是要移植或调试Rockchip ISP的新手二是想把数据流和buffer时序彻底理清、以后能自己改帧率改分辨率的熟手。先把它当一套驱动栈看后面才不会迷路。2. rkisp驱动代码的三层骨架platform driver、v4l2_subdev与video设备2.1 从probe入口看rkisp的注册顺序拿到源码后先别急着搜寄存器操作先把probe流程看明白。不同SDK的目录名可能是isp、isp1或isp2但结构基本一致核心文件大致是这么分工的文件职责rkisp_dev.c平台驱动入口、v4l2_device注册、media device初始化rkisp_isp.cISP主控子设备负责sensor数据流控制和link配置rkisp_csi.cMIPI CSI2接收子设备完成时序同步与虚拟通道解析rkisp_resizer.c缩放通路负责mainpath/selfpath输出分辨率调整rkisp_stream.c三个视频通路的stream状态机与buffer管理rkisp_stats.cISP统计信息上报给3A算法用rkisp_params.c3A参数回灌AWB/AE等参数的向下传递probe那段代码是整个驱动的总开关注册顺序直接决定你开机后看到的设备节点顺序。示意代码大致长这样// rkisp_dev.c 的 probe 流程示意 static int rkisp_plat_drv_probe(struct platform_device *pdev) { struct rkisp_device *isp kzalloc(sizeof(*isp), GFP_KERNEL); /* 1. media device 必须先注册后续 link 建立在它之上 */ media_device_init(isp-media_dev, rkisp); /* 2. v4l2_device 是所有 subdev 和 video 节点的父容器 */ v4l2_device_register(pdev-dev, isp-v4l2_dev); /* 3. 依次注册 ISP 主控、MIPI CSI2、resizer 子设备 */ rkisp_register_isp_subdev(isp); rkisp_register_csi_subdev(isp); rkisp_register_resizer_subdev(isp); /* 4. 注册 mainpath/selfpath/dmapath 三个 video 节点 */ rkisp_register_stream_video(isp); /* 5. 注册 stats 与 params 两个辅助节点 */ rkisp_register_stats_video(isp); rkisp_register_params_video(isp); /* 6. media link 必须最后建link 依赖前面已注册的 entity */ rkisp_create_media_links(isp); return 0; }这里register顺序不是随手写的。v4l2_device必须先于subdev和video节点存在media link又必须放到最后因为link引用的entity句柄要等所有子设备注册完才能拿到。stats节点要先做event上报初始化params节点则要先准备默认参数缓冲否则应用层一打开节点就会读到空参数组。你调试时发现设备节点顺序和板子文档对不上多半是SDK改过probe顺序不是什么玄学。2.2 MainPath、SelfPath、DmaPath三个视频节点各自干嘛rkisp驱动代码里最常被问的就是“为什么有两个video0/video1/video2”。这三条通路不是复制粘贴的关系用途差别很大通路特点典型用途MainPath完整ISP处理链支持缩放、裁剪、3A统计主预览、拍照、录像SelfPath带宽较小独立参数配置画中画、双路预览、低分辨率第二路DmaPath相对接近DMA裸数据通路调试、特殊数据采集场景MainPath能拿到的格式和分辨率范围最大SelfPath在多数SDK里宽度受限DmaPath则不一定经过完整ISP链路出图颜色可能是raw状态。应用层一般只开MainPath需要第二路稳定输出时才考虑SelfPath。调试时如果发现SelfPath帧率一直上不去先看带宽配置而不是怀疑驱动有bug。2.3 media controller链路sensor到video之间不是直连rkisp驱动里最常见的新手误区是以为sensor输出直接进ISP的video节点。实际在media controller框架下链路是分段建立的sensor subdev - mipi csi2 subdev - isp subdev - resizer subdev - mainpath video每个subdev都有自己的pad相邻pad之间必须有media link相连并且在应用层打开video节点之前这条链路要处于enable状态。硬件上焊了线不代表驱动里默认就通了链路没建好STREAMON会直接报EPIPE。所以拿到一份新SDK第一步一定是打开media-ctl -p看实体列表media-ctl -d /dev/media0 -p输出里会列出所有entity比如rockchip-mipi-csi2、rkisp-isp-subdev、rkisp_mainpath它们之间的连接关系用-表示。链路是否enable决定了你后续open节点和stream on能不能成功。这一步看不懂后面所有排查都容易变成瞎试。3. 让rkisp驱动代码跑起来Kconfig、设备树与开机证据3.1 把驱动编进内核而不是编成模块rkisp驱动代码在大部分SDK里默认是模块化编译但我刚上手时吃了一次亏insmod顺序错了sensor模块先加载结果匹配不到后加载的csi2实体probe直接defer日志里只有一条不痛不痒的deferred probe提示。后来我拿到新板子一律先编成y少一层加载顺序的烦恼。make ARCHarm64 rockchip_defconfig make menuconfig # 进入 Device Drivers Multimedia support V4L platform devices # 找到 Rockchip ISP driver按 Y 编入内核 grep CONFIG_VIDEO_ROCKCHIP_ISP .config这里有个细节menuconfig里勾选的是CONFIG_VIDEO_ROCKCHIP_ISP而sensor、csi2、isp是互相依赖的编成y时内核会在启动阶段按probe顺序自动处理依赖。编成m时加载顺序要自己控制常见做法是写一个modules.order或者在rcS里按sensor、csi2、isp的顺序依次modprobe。少一个模块后面所有节点都是空的。3.2 设备树里匹配什么rkisp驱动代码能起来设备树节点至少要提供compatible、reg、interrupts、clocks这几样。compatible要和驱动里的of_match_table对得上reg是寄存器基地址interrupts是ISP中断号clocks少了任何一个probe都可能挂在clock_get阶段。rkisp { status okay; compatible rockchip,rk3568-rkisp; /* 示例值以你的SDK为准 */ reg 0x0 0xfdff0000 0x0 0x10000; /* 示例值以板级dts为准 */ interrupts GIC_SPI 62 IRQ_TYPE_LEVEL_HIGH; clocks cru ACLK_ISP, cru CLK_ISP; clock-names aclk_isp, hclk_isp; power-domains power RK3568_PD_VI; };写设备树最容易翻车的是clocks和power-domains。时钟名必须在驱动代码里能找到对应顺序不一致没关系因为驱动按clock-names字符串查找。power-domain没就绪时probe会返回-EPROBE_DEFER内核日志里出现deferred probe字样。遇到这种情况先查父级power-domain的status是不是okay而不是急着改驱动代码。3.3 开机后怎么确认驱动真的起来了驱动probe成功不代表链路就是好的开机后第一步先看内核日志和设备节点是否存在dmesg | grep -i rkisp ls -l /dev/video0 /dev/video1 /dev/video2 v4l2-ctl --list-devices media-ctl -d /dev/media0 -pdmesg里能看到rkisp的probe成功打印通常包含rkisp driver probe success或类似的字样。ls能看到video节点存在说明video_register_device执行过了。v4l2-ctl --list-devices会按驱动名分组列出设备rkisp (platform:fdff0000.rkisp): /dev/video0 /dev/video1 /dev/video2media-ctl -p是判断链路是否完整的关键。输出里如果只有rkisp自己的entity没有sensor或csi2实体说明上游链路压根没注册进来问题不在isp驱动而在sensor那边。如果entity齐全但link状态是[0]而不是[1]说明链路没enable先用media-ctl手动建一下链接再继续。4. 顺一遍rkisp数据流stream on、buffer与stats/params4.1 打开节点先定格式S_FMT后要读回bytesperlinerkisp的video节点多数是multi-plane接口格式协商要用V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE这一点和普通USB camera不一样。打开节点后先做S_FMT但不要盲目假设你设置的宽度就是驱动最终对齐后的值。int fd open(/dev/video0, O_RDWR); struct v4l2_format fmt {0}; fmt.type V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; fmt.fmt.pix_mp.width 1920; fmt.fmt.pix_mp.height 1080; fmt.fmt.pix_mp.pixelformat V4L2_PIX_FMT_NV12; fmt.fmt.pix_mp.field V4L2_FIELD_NONE; fmt.fmt.pix_mp.num_planes 1; if (ioctl(fd, VIDIOC_S_FMT, fmt) 0) { perror(S_FMT); return -1; } /* 驱动可能为了16字节对齐把 bytesperline 改了 */ printf(bytesperline%u sizeimage%u\n, fmt.fmt.pix_mp.plane_fmt[0].bytesperline, fmt.fmt.pix_mp.plane_fmt[0].sizeimage);驱动在对齐方面非常死板尤其NV12这类格式宽度不是16的倍数时bytesperline会被强制往上对齐。你如果按192010803/2去算sizeimage大概率比驱动实际要求的buffer小后面QBUF会报EINVAL。正确姿势是S_FMT后把plane_fmt里的bytesperline和sizeimage原样读出来后续申请buffer全按这个值来。4.2 REQBUFS、QBUF、STREAMON的顺序一个都不能乱rkisp的stream状态机对调用顺序很敏感最常见的崩溃就是没建media link就直接REQBUFS或者QBUF前没做QUERYBUF。标准流顺序应该这样走struct v4l2_requestbuffers req {0}; req.count 4; req.type V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; req.memory V4L2_MEMORY_MMAP; ioctl(fd, VIDIOC_REQBUFS, req); struct v4l2_buffer buf {0}; struct v4l2_plane planes[1] {0}; buf.type req.type; buf.memory V4L2_MEMORY_MMAP; buf.index 0; buf.length 1; buf.m.planes planes; /* QUERYBUF 拿到 mmap 的 offset 和长度 */ ioctl(fd, VIDIOC_QUERYBUF, buf); void *map mmap(NULL, planes[0].length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, planes[0].m.mem_offset); /* 入队之后才能 STREAMON */ ioctl(fd, VIDIOC_QBUF, buf); ioctl(fd, VIDIOC_STREAMON, req.type);REQBUFS的count建议先按4个申请太少容易在帧率波动时丢帧太多则浪费内存。QUERYBUF那一步很多人偷懒跳过直接拿固定地址mmap结果驱动写了内核分配的另一块内存画面永远花。QBUF是入队归还buffer给驱动的动作STREAMON必须在至少一个buffer入队之后执行否则驱动没有可用的内存写入目标会卡在等待队列里。4.3 stats与paramsrkisp不同于普通USB camera的关键之处rkisp驱动代码里最不好理解的部分就是stats和params两个辅助节点。普通USB camera只要打开节点就能出图rkisp不一样ISP处理完一帧后会把统计信息通过stats节点上报给应用层应用层算完3A参数再通过params节点写回驱动。这个闭环如果没跑起来画面永远是默认参数下的结果暗光下会黑到你怀疑人生。/* 打开 stats 节点通常是 /dev/video1 */ int fd_stats open(/dev/video1, O_RDWR); struct v4l2_event_subscription sub {0}; sub.type V4L2_EVENT_ISP_STATS; /* 以SDK头文件定义为准 */ ioctl(fd_stats, VIDIOC_SUBSCRIBE_EVENT, sub); /* 打开 params 节点通常是 /dev/video2 */ int fd_params open(/dev/video2, O_RDWR); struct rkisp_params_cfg cfg; memset(cfg, 0, sizeof(cfg)); ioctl(fd_params, RKISP_CMD_SET_PARAMS, cfg);stats事件是ISP中断触发后由驱动上报的订阅动作一定要在STREAMON之前完成否则前几帧的统计信息会被丢掉AE会在一段时间内不收敛。params下发的结构体在不同SDK里差异很大驱动代码里通常有RKISP_API_VERSION之类的宏来约束版本应用层和内核态结构体版本不一致时参数会被错误解析表现就是画面颜色诡异AWB怎么调都偏色。常见做法是先在应用层把params结构体完整清零只设需要的模块位别一上来就填满。5. rkisp驱动踩坑记录media链路、格式对齐与事件订阅rkisp这套驱动代码说实话调试起来最折磨人的不是算法复杂而是那些链路和buffer层面的隐性约束。我整理了几条自己翻过车的记录每一行都是实际烧过时间的地方。5.1 链路类问题sensor找不见、STREAMON报EPIPE现象media-ctl -d /dev/media0 -p输出里只有rkisp自己的entitysensor完全看不到像是被凭空吞掉了。原因sensor驱动没有成功probe。最常见是i2c地址不对或者sensor供电的regulator没在dts里打开sensor芯片根本没上电读不到chip idprobe直接返回-ENODEV。解决先用dmesg | grep -i sensor看有没有sensor注册失败的打印再检查i2c总线i2cdetect -y bus如果能扫到地址说明硬件OK问题在dts或驱动匹配表扫不到就顺着供电和时钟排查别一上来就怀疑rkisp驱动代码有问题。现象media-ctl -p里链路都有了但应用层调用STREAMON直接返回EPIPE连个多余日志都没有。原因media link没有被enable。链路存在是两码事链路状态是[0]还是[1]才决定数据能不能流过去。尤其在换了sensor或改了dts之后某些SDK的link默认状态会被重置。解决手动把链路建起来media-ctl -d /dev/media0 -l sensor 0-0036:0 - rockchip-mipi-csi2:0 [1] media-ctl -d /dev/media0 -l rockchip-mipi-csi2:0 - rkisp-isp-subdev:0 [1]entity名字必须和media-ctl -p里打印的一字不差粘贴复制最保险。链路enable后如果还报EPIPE再去查resizer和mainpath之间的linkrkisp的链路不止一段。5.2 格式与buffer类问题EINVAL、sizeimage对不上现象QBUF报EINVAL或者打开了节点但一入队就失败日志里没有任何内核错误看起来像是驱动故意不给人用。原因S_FMT返回后驱动把bytesperline和sizeimage改了你没读回来直接按自己计算的数值去申请buffer。NV12在非16对齐宽度下非常容易触发这个问题还有一个隐藏坑是multi-plane接口的buf.length要赋成plane数量而不是buffer大小。解决S_FMT之后强制把plane_fmt[0].bytesperline和sizeimage打印出来所有buffer申请都用驱动给的值。如果发现驱动把宽度从1920对齐到了1920但bytesperline从1920变成了2048说明内部有额外对齐不要试图用V4L2_PIX_FMT把格式改成RGB去绕过绕不过去的。现象v4l2-ctl抓帧成功文件大小看起来正常但播放出来画面整体偏移或者后半部分出现条纹。原因sizeimage比实际数据需要的空间大驱动在buffer尾部留了padding。应用层把整个sizeimage当作有效数据拿去解码解码器把padding当成了数据。解决解析帧数据时每帧有效行数按height算但每行长度要用bytesperline不能用宽度乘像素字节数。如果bytesperline是2048而宽度是1920NV12下UV平面的起始地址也要基于bytesperline计算否则色度信息全偏。5.3 事件与参数类问题黑屏、stats事件丢失现象mainpath出帧正常帧率稳定但画面全黑曝光值完全不动感觉ISP根本没在做自动曝光。原因params通道没有任何数据下发ISP部分模块处于未初始化状态或者stats事件没有被消费3A闭环根本没建立。很多调试板子只打开了mainpath忽略了与ISP配套的stats和params节点。解决至少用空结构体走一次RKISP_CMD_SET_PARAMS再订阅stats事件。如果条件允许先接一支基础3A库跑通闭环再换裸驱动调试。一味只看mainpath出黑帧容易误判成sensor问题浪费半天时间。现象stats事件订阅了但read返回超时或者事件频率远低于帧率AE反应迟钝。原因订阅发生在STREAMON之后前几帧统计已经丢了另一种可能是在多个fd上混用了stats和params节点不同实例的上下文错位。解决严格按open stats - SUBSCRIBE_EVENT - REQBUFS - STREAMON的顺序初始化并且stats和mainpath要用同一个rkisp实例下的节点不同videoX之间不要混搭。把订阅时机提前到STREAMON之前问题基本就消失了。6. 我排查rkisp问题的固定动作media拓扑存档与ioctl回放接手一块新的Rockchip板子我不急着改任何代码先把环境状态固化下来再动一行驱动代码。第一件事是dump一份media拓扑存档v4l2-ctl --list-devices /tmp/devices_$(date %m%d).txt media-ctl -d /dev/media0 -p /tmp/media0_$(date %m%d).txt dmesg | grep -iE rkisp|csi2|sensor /tmp/isp_dmesg.txt这三个文件就是一个可对比的基线。后面改sensor dts、更新驱动代码、换内核都能用diff /tmp/media0_旧.txt /tmp/media0_新.txt快速看出链路变化。很多rkisp问题是被SDK更新悄悄改掉了entity命名或link默认状态对比存档比翻git log快得多。第二件事是抓一帧原始数据验证基本通路v4l2-ctl -d /dev/video0 \ --set-fmt-videowidth1920,height1080,pixelformatNV12 \ --stream-mmap --stream-count1 --stream-to/tmp/frame.raw ls -l /tmp/frame.raw文件大小如果和S_FMT后返回的sizeimage一致说明格式协商正常如果不一致基本就是链路里某一段没对齐。这一步跑通了才能继续谈颜色、曝光和3A。第三件事是把应用层的调用顺序固定成一个回放脚本每次改驱动后按相同顺序跑一遍。从open开始到subscribe event、S_FMT、REQBUFS、QUERYBUF、mmap、QBUF、STREAMON最后再RKISP_CMD_SET_PARAMS投递一次空参数。顺序不能乱乱一次就是一次翻车。从那以后我拿到任何一块新板子第一件事就是先把media拓扑dump一份存档再动驱动代码。这个动作救过我很多次也省下了好几个排查到深夜的下午。希望帮到你。本文还有配套的精品资源点击获取