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 内部中断使能两个阶段:

  1. 平台中断号获取: 通过 platform_get_irq(pdev, 0) 从设备树获取 VOP 中断号。
  2. 中断处理函数注册: 调用 devm_request_irq(dev, irq, vop_isr, IRQF_SHARED, "vop", vop) 注册共享中断处理函数 vop_isr。
  3. VOP 内部中断使能: 在 vop_enable() 或 crtc->enable() 回调中,通过 VOP_INTR_SET_TYPE(vop, INTR, LINE_FLAG_INTR, 1) 等宏分别使能 VSYNC、LINE_FLAG 和 BUFFER_UNDERRUN 中断。
  4. 中断屏蔽寄存器: 每个中断类型对应一个使能位和一个状态位,驱动通过写 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 架构图

下图展示了从硬件中断触发到驱动各处理模块的完整调用链路:

RK3399 VOP 中断处理流程 VOP 中断处理流程架构 VOP 硬件中断 GIC 中断控制器 vop_isr() 读 INTR_STATUS LINE_FLAG 处理 VSYNC 处理 UNDERRUN 处理 写 INTR_CLR 清除 返回 IRQ_HANDLED 说明:LINE_FLAG 优先级最高,保证行级 DMA 预取不被阻塞;VSYNC 用于帧同步;BUFFER_UNDERRUN 用于异常监控。

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() 中通常执行:

  1. 递增 vop->underrun_count 统计计数器。
  2. 通过 drm_dev->dev->driver->debugfs 暴露统计值供调试。
  3. 若连续多次触发,触发 DRM_IOCTL_MODE_CURSOR 回退或降低显示分辨率。
性能指标: 在 4K@60fps 输出下,VOP 的像素时钟约为 594MHz,每像素 32bit,所需内存带宽约 2.4GB/s。若系统 DDR 带宽低于此值,BUFFER_UNDERRUN 将频繁触发。