4、RK3399显示中断:VOP中断类型与处理流程
RK3399 的 VOP(Video Output Processor)显示控制器通过中断机制向内核驱动报告帧同步、行边界及异常事件。理解中断类型、注册方式与处理流程,是实现高效显示刷新、避免撕裂及诊断传输异常的基础。
4.1 VOP 中断类型与触发条件
VOP 模块在硬件层面支持三类核心中断,分别对应不同的显示时间点与异常状态:
| 中断类型 | 硬件信号 | 触发条件 | 典型用途 |
|---|---|---|---|
| VSYNC | 帧同步脉冲 | 每帧 VBlank 起始时刻(垂直消隐区开始) | 帧计数器更新、Fence 信号释放、用户空间 vsync 回调 |
| LINE_FLAG | 行计数匹配 | 当前扫描行达到预设的行号寄存器值 | 提前唤醒、DMA 预取、局部刷新窗口控制 |
| BUFFER_UNDERRUN | FIFO 空标志 | VOP 读取数据时 FIFO 已空,无法输出有效像素 | 错误统计、DMA 带宽调整、驱动告警 |
关键时序点: VSYNC 中断发生在垂直消隐区起始,此时所有扫描线已输出完毕,是切换帧缓冲最安全的窗口。LINE_FLAG 中断可在帧内任意行触发,常用于提前准备下一行数据或实现分区刷新。
4.2 中断注册与硬件使能路径
在 RK3399 显示驱动(drivers/gpu/drm/rockchip/rockchip_drm_vop.c)中,中断注册分为平台设备中断申请与 VOP 内部中断使能两个阶段:
- 平台中断号获取: 通过
platform_get_irq(pdev, 0)从设备树获取 VOP 中断号。 - 中断处理函数注册: 调用
devm_request_irq(dev, irq, vop_isr, IRQF_SHARED, "vop", vop)注册共享中断处理函数vop_isr。 - VOP 内部中断使能: 在
vop_enable()或crtc->enable()回调中,通过VOP_INTR_SET_TYPE(vop, INTR, LINE_FLAG_INTR, 1)等宏分别使能 VSYNC、LINE_FLAG 和 BUFFER_UNDERRUN 中断。 - 中断屏蔽寄存器: 每个中断类型对应一个使能位和一个状态位,驱动通过写
INTR_EN寄存器控制使能,通过读INTR_STATUS寄存器获取中断源。
/* 中断使能示例(简化自 rockchip_drm_vop.c) */
static void vop_enable_irqs(struct vop *vop)
{
uint32_t mask = 0;
mask |= VOP_INTR_MASK(VSYNC_INTR);
mask |= VOP_INTR_MASK(LINE_FLAG_INTR);
mask |= VOP_INTR_MASK(BUFFER_UNDERRUN_INTR);
VOP_INTR_SET_TYPE(vop, INTR, mask, 1);
}
4.3 中断处理核心流程
当中断触发时,CPU 跳转到 vop_isr() 函数。该函数首先读取中断状态寄存器,然后按优先级依次处理各中断源:
中断处理优先级(驱动内顺序): LINE_FLAG > VSYNC > BUFFER_UNDERRUN。LINE_FLAG 优先处理可保证行级 DMA 预取不被帧同步事件阻塞。
static irqreturn_t vop_isr(int irq, void *data)
{
struct vop *vop = data;
uint32_t intr_status;
spin_lock(&vop->irq_lock);
intr_status = VOP_INTR_GET_STATUS(vop, INTR);
/* 1. 处理 LINE_FLAG 中断 */
if (intr_status & LINE_FLAG_INTR) {
vop_line_flag_handler(vop);
}
/* 2. 处理 VSYNC 中断 */
if (intr_status & VSYNC_INTR) {
vop_vsync_handler(vop);
}
/* 3. 处理 BUFFER_UNDERRUN 中断 */
if (intr_status & BUFFER_UNDERRUN_INTR) {
vop_underrun_handler(vop);
}
/* 清除已处理的中断状态 */
VOP_INTR_CLR_STATUS(vop, INTR, intr_status);
spin_unlock(&vop->irq_lock);
return IRQ_HANDLED;
}
4.4 中断处理流程 SVG 架构图
下图展示了从硬件中断触发到驱动各处理模块的完整调用链路:
4.5 中断处理中的关键数据结构
驱动使用 struct vop 中的中断相关字段管理状态与回调:
| 字段 | 类型 | 作用 |
|---|---|---|
irq_lock |
spinlock_t | 保护中断状态寄存器读写,防止与主线程竞争 |
is_enabled |
bool | 标记 VOP 是否已使能,中断处理中用于过滤虚假中断 |
vop->data->intr_regs |
const struct vop_intr_regs * | 保存中断使能、状态、清除寄存器的偏移地址 |
vop->line_flag_cb |
void (*)(struct vop *) | LINE_FLAG 中断回调函数指针,可被 DMA 预取模块注册 |
中断上下文注意事项: VOP 中断处理函数运行在 IRQ 上下文,不可调用可能导致睡眠的函数(如
mutex_lock、kmalloc(GFP_KERNEL))。所有与用户空间或主线程的通信应通过 workqueue 或 tasklet 延后处理。
4.6 LINE_FLAG 中断与 DMA 预取协同
LINE_FLAG 中断在显示优化中扮演关键角色。驱动可设置行号寄存器,使 VOP 在扫描到指定行时触发中断,从而提前启动下一帧数据的 DMA 传输:
/* 设置 LINE_FLAG 触发行号 */
static void vop_set_line_flag(struct vop *vop, uint32_t line)
{
VOP_REG_SET(vop, LINE_FLAG, line_num, line);
}
/* LINE_FLAG 中断处理中触发 DMA 预取 */
static void vop_line_flag_handler(struct vop *vop)
{
if (vop->line_flag_cb)
vop->line_flag_cb(vop); /* 回调到 DMA 预取模块 */
}
通过合理配置 LINE_FLAG 触发位置(通常设置在 VBlank 结束前若干行),驱动可以在 VSYNC 到来之前完成数据准备,消除因 DMA 延迟导致的 BUFFER_UNDERRUN。
4.7 BUFFER_UNDERRUN 中断与调试
BUFFER_UNDERRUN 中断表示 VOP 的输入 FIFO 已经空,无法继续输出像素。常见原因包括:
- DMA 带宽不足: 多图层叠加或高分辨率下,DMA 传输速率低于 VOP 消耗速率。
- 内存访问延迟: 系统总线繁忙或 DRAM 控制器处于低功耗状态。
- 帧缓冲地址错误: 驱动设置了无效的内存地址,导致 DMA 无法获取数据。
驱动在 vop_underrun_handler() 中通常执行:
- 递增
vop->underrun_count统计计数器。 - 通过
drm_dev->dev->driver->debugfs暴露统计值供调试。 - 若连续多次触发,触发
DRM_IOCTL_MODE_CURSOR回退或降低显示分辨率。
性能指标: 在 4K@60fps 输出下,VOP 的像素时钟约为 594MHz,每像素 32bit,所需内存带宽约 2.4GB/s。若系统 DDR 带宽低于此值,BUFFER_UNDERRUN 将频繁触发。