3. Linux内核DRM显示框架

DRM(Direct Rendering Manager,直接渲染管理器)是Linux内核标准的现代化显示子系统框架,完全替代传统老旧的FrameBuffer(FB)显示驱动架构, 是当前Android、Linux桌面、嵌入式Linux所有平台的主流显示驱动解决方案。

3.1. DRM核心概念

3.1.1. DRM定义与作用

DRM是Linux内核为显卡、LCD、HDMI、MIPI、LVDS等显示设备提供的标准化、组件化、原子化显示驱动框架,解决传统FB驱动的缺陷:画面撕裂、闪屏、多图层管理混乱、无标准化显存管理、不支持动态刷新率、不支持HDR/DSC高清特性、用户态内核态权限混乱等问题。

DRM框架提供标准化显示硬件抽象、原子画面提交、多图层合成管理、显存安全管理(GEM)、IOMMU虚拟内存映射、EDID显示时序自动解析、热插拔检测、电源管理等,全面适配现代高清、高刷、HDR、多屏拼接等显示场景。

3.1.2. DRM相关专业名词

DRM相关专业名词众多,以下解析常见的名词:

  • CRTC(Cathode Ray Tube Controller):显示控制器,对应SOC内部VOP硬件通道,负责显示时序生成、图层合成、画面刷新输出。

  • Encoder(编码器):显示信号编码器,将CRTC输出的通用图像数据,编码为MIPI/HDMI/LVDS/DP专用时序信号。

  • Connector(连接器):显示外设连接器,对应物理显示接口,管理屏幕插拔、EDID读取、外设状态检测。

  • Panel(显示面板):终端显示屏幕实体,是DRM显示链路的最终输出载体,包含LCD、OLED等各类屏幕,驱动中负责屏幕上电、初始化、亮度调节、时序适配、睡眠唤醒等面板控制逻辑。

  • Bridge(显示桥接器):显示信号转接桥芯片/模块,用于衔接SOC原生显示接口与终端Panel,实现信号格式转换、电平转换、时序适配。

  • EDID:显示器扩展标识数据,存储屏幕分辨率、刷新率、HDR、DSC、色彩格式等硬件能力参数。

  • Plane(图层):硬件图层,支持多图层叠加合成,对应UI层、视频层、光标层,实现多画面叠加显示。

  • KMS(Kernel Mode Setting):内核模式设置,由内核统一管理分辨率、刷新率、显示时序、硬件模式切换,替代用户态模式配置,稳定高效。

  • GEM(Graphics Execution Manager):图形内存管理器,DRM标准显存管理组件,负责显存的分配、释放、虚实映射、共享传输。

  • 原子提交(Atomic Commit):DRM核心机制,所有显示参数修改统一打包、一次性写入硬件,避免分步修改导致的画面异常。

  • VRR(Variable Refresh Rate):动态刷新率,根据画面内容动态调整刷新率,降低卡顿、撕裂、功耗。

  • IOTLB:IOMMU地址翻译缓存,缓存显存虚实映射关系,提升DMA访问效率。

  • VSYNC/VBLANK:​垂直消隐期——CRT时代的”回扫间隙”,现代LCD用来做帧边界同步。

  • VOP(Video Output Processor,视频输出处理器):SoC显示硬件IP,等同于其他平台LCDC/LCD控制器,是显示子系统核心引擎,也是DRM框架中CRTC对应的底层硬件实体。

3.1.3. DRM硬件显示通路图

DRM显示系统遵循图层合成 -> 时序生成 -> 信号编码 -> 接口输出 -> 桥接转换 -> 屏幕显示的标准硬件通路,按照以下链路传输渲染。

../_images/subsystem_drm_0.jpg

显示通路逐阶解析:

  1. FrameBuffer 显存帧缓存:画面数据存储载体,FrameBuffer是最终呈现画面的显存缓冲区,用户态GUI、视频、桌面渲染后的最终图像数据,全部绘制、存储在FrameBuffer中。依托GEM显存管理完成空间分配与IOMMU虚实映射,搭配IOTLB缓存加速DMA读取,是整个显示链路的数据源起点。

  2. Plane 图层层:画面合成单元,从FrameBuffer显存中读取UI、视频、光标等多层画面数据,完成图层叠加、透明度混合、缩放裁剪,输出单路合成后的标准图像数据,是画面内容处理的核心单元。

  3. CRTC 控制器:时序驱动单元,接收Plane合成画面,生成标准显示时序(行场同步、刷新率、分辨率时序),控制画面刷新节奏,杜绝画面撕裂、卡顿,是显示时序的核心管控单元。由KMS内核模式设置统一管控分辨率、硬件模式切换,支持VRR动态刷新率自适应帧率。

  4. Encoder 编码器:信号格式转换单元,将CRTC输出的通用数字图像信号,编码为MIPI、HDMI、LVDS、DP等外设的差分信号,适配不同显示接口的电气协议。

  5. Connector 连接器:硬件接口管理单元,对应SOC物理显示接口,负责检测屏幕热插拔状态、读取显示器EDID能力信息,建立硬件通路连接,管理接口电源与状态。

  6. Bridge 桥接器:信号适配拓展单元,针对SOC原生接口与屏幕不匹配场景,完成信号转接、电平转换、时序修正,例如MIPI转HDMI,适配各类拓展显示面板。

  7. Panel 显示面板:最终输出载体,接收适配完成的显示信号,点亮屏幕,完成画面最终成像。

3.2. DRM核心结构体

3.2.1. DRM核心设备结构体

DRM核心设备结构体(struct drm_device)是整个DRM显示子系统的根设备结构体,是所有显示硬件、属性、状态、资源的总容器,一个显示子系统对应一个drm_device实例,所有DRM操作均基于该结构体展开。

drm_device结构体(内核源码/include/drm/drm_device.h)
 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
struct drm_device {
    int if_version;                         /* 接口版本,用于DRM版本兼容适配 */
    struct kref ref;                        /* 设备引用计数,管控设备生命周期 */

    struct device *dev;                     /* 绑定内核总线基础设备结构体 */
    const struct drm_driver *driver;        /* 绑定DRM驱动操作回调集 */

    void *dev_private;                      /* 厂商私有数据 */

    struct drm_minor *primary;              /* DRM主设备节点 */
    struct drm_minor *render;               /* DRM渲染设备节点 */

    bool registered;                        /* 设备是否已注册至内核子系统 */
    bool unplugged;                         /* 设备是否已热插拔离线 */

    struct mutex struct_mutex;              /* 设备核心资源操作锁 */
    struct mutex master_mutex;              /* 设备主权限管控锁 */

    atomic_t open_count;                    /* 上层客户端打开计数 */
    struct mutex filelist_mutex;            /* 客户端链表保护锁 */
    struct list_head filelist;              /* 用户态客户端连接链表 */

    struct drm_vblank_crtc *vblank;         /* CRTC垂直同步中断管理数组 */
    spinlock_t vblank_time_lock;            /* 垂直同步时间更新保护锁 */
    spinlock_t event_lock;                  /* DRM事件投递保护锁 */

    unsigned int num_crtcs;                 /* 硬件有效CRTC通道数(VOP数量) */
    struct drm_mode_config mode_config;     /* 全局显示模式配置信息 */

    struct mutex object_name_lock;          /* GEM对象名称ID管理锁 */
    struct idr object_name_idr;             /* GEM对象ID映射管理树 */
    struct drm_vma_offset_manager *vma_offset_manager; /* 显存虚拟地址偏移管理 */

    struct drm_fb_helper *fb_helper;        /* fbdev兼容模拟层适配指针 */

    /* 其他成员省略 */
};

3.2.2. DRM驱动操作集结构体

DRM驱动操作集结构体(struct drm_driver)是DRM驱动顶层操作集合,存储整套驱动回调函数、版本信息、功能特性,是厂商对接内核DRM框架的核心载体,所有硬件适配、显存操作、用户态交互逻辑均通过该结构体注册挂载。

drm_driver结构体(内核源码/include/drm/drm_drv.h)
 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
struct drm_driver {
    int (*open) (struct drm_device *, struct drm_file *);         /* 用户态打开设备回调 */
    void (*postclose) (struct drm_device *, struct drm_file *);   /* 用户态关闭设备后置回调 */
    void (*lastclose) (struct drm_device *);                      /* 最后一个用户退出回调,用于恢复fbdev、电源管理 */

    void (*debugfs_init)(struct drm_minor *minor);                /* 初始化DRM调试文件系统节点 */

    /* GEM显存对象创建回调,平台显存适配核心接口 */
    struct drm_gem_object *(*gem_create_object)(struct drm_device *dev, size_t size);

    /* PRIME显存共享导出/导入接口,用于多进程、跨设备显存共享 */
    int (*prime_handle_to_fd)(struct drm_device *dev, struct drm_file *file_priv,
                uint32_t handle, uint32_t flags, int *prime_fd);
    int (*prime_fd_to_handle)(struct drm_device *dev, struct drm_file *file_priv,
                int prime_fd, uint32_t *handle);
    struct drm_gem_object * (*gem_prime_import)(struct drm_device *dev, struct dma_buf *dma_buf);
    int (*gem_prime_mmap)(struct drm_gem_object *obj, struct vm_area_struct *vma);

    /* Dumb裸缓冲区创建、映射、销毁,适配基础帧缓存显示 */
    int (*dumb_create)(struct drm_file *file_priv, struct drm_device *dev,
            struct drm_mode_create_dumb *args);
    int (*dumb_map_offset)(struct drm_file *file_priv, struct drm_device *dev,
                uint32_t handle, uint64_t *offset);
    int (*dumb_destroy)(struct drm_file *file_priv, struct drm_device *dev, uint32_t handle);

    /* 驱动版本与名称标识 */
    int major;                                                    /* 主版本号 */
    int minor;                                                    /* 次版本号 */
    int patchlevel;                                               /* 补丁版本号 */
    char *name;                                                   /* 驱动名称 */
    char *desc;                                                   /* 驱动功能描述 */

    u32 driver_features;                                          /* 驱动特性标记位,开启/关闭DRM能力 */
    const struct drm_ioctl_desc *ioctls;                          /* 驱动私有IOCTL接口数组 */
    int num_ioctls;                                               /* 私有IOCTL数量 */
    const struct file_operations *fops;                           /* DRM设备节点文件操作集 */

    /* 其他成员省略 */
};

3.2.3. 显示模式配置结构体

显示模式配置结构体(struct drm_mode_config)存储整个显示子系统的全局模式参数,包含CRTC数量、图层数量、最大分辨率、寻址位数、硬件能力等,是显示模式初始化的核心配置载体。

drm_mode_config结构体(内核源码/include/drm/drm_mode_config.h)
 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
struct drm_mode_config {
    struct mutex mutex;                                    /* 显示模式全局锁,保护核心模式操作 */
    struct drm_modeset_lock connection_mutex;              /* 链路锁,保护Connector-Encoder-Crtc路由状态 */
    struct drm_modeset_acquire_ctx *acquire_ctx;           /* 原子驱动IOCTL全局上下文 */

    struct mutex idr_mutex;                                /* KMS对象ID分配管理锁 */
    struct idr object_idr;                                 /* 统一管理FB/CRTC/Connector等所有KMS对象ID */

    struct mutex fb_lock;                                  /* 帧缓存列表保护锁 */
    int num_fb;                                            /* 当前设备有效帧缓存数量 */
    struct list_head fb_list;                              /* 全局帧缓存对象链表 */

    spinlock_t connector_list_lock;                        /* 连接器链表保护锁 */
    int num_connector;                                     /* 物理连接器总数 */
    struct ida connector_ida;                              /* 连接器ID分配器 */
    struct list_head connector_list;                       /* 全局连接器对象链表 */

    int num_encoder;                                       /* 编码器总数 */
    struct list_head encoder_list;                         /* 全局编码器对象链表 */

    int num_total_plane;                                   /* 硬件总图层数(主图层/光标图层/叠加图层) */
    struct list_head plane_list;                           /* 全局图层对象链表 */

    int num_crtc;                                          /* CRTC控制器数量(对应VOP通道数) */
    struct list_head crtc_list;                            /* 全局CRTC控制器对象链表 */

    struct list_head property_list;                        /* DRM标准属性链表 */
    struct list_head privobj_list;                         /* 驱动私有对象链表 */

    int min_width, min_height;                             /* 显示最小分辨率限制 */
    int max_width, max_height;                             /* 显示最大分辨率限制 */
    const struct drm_mode_config_funcs *funcs;             /* 模式配置核心操作回调集 */

    bool poll_enabled;                                     /* 热插拔轮询总开关 */
    bool poll_running;                                     /* 热插拔轮询运行状态标记 */
    struct delayed_work output_poll_work;                  /* 热插拔延时检测工作队列 */

    struct mutex blob_lock;                                /* 二进制属性数据保护锁 */
    struct list_head property_blob_list;                   /* 全局二进制属性链表 */

    /* 标准原子属性指针 */
    struct drm_property *edid_property;                    /* EDID屏幕信息属性 */
    struct drm_property *dpms_property;                    /* 屏幕电源管理属性 */
    struct drm_property *prop_active;                      /* CRTC使能状态属性 */
    struct drm_property *prop_mode_id;                     /* CRTC显示模式属性 */
    struct drm_property *prop_vrr_enabled;                 /* 动态刷新率开关属性 */

    /* 图层基础原子属性 */
    struct drm_property *prop_src_x, *prop_src_y;           /* 图层源坐标偏移 */
    struct drm_property *prop_src_w, *prop_src_h;           /* 图层源宽高尺寸 */
    struct drm_property *prop_crtc_x, *prop_crtc_y;         /* 图层目标坐标偏移 */
    struct drm_property *prop_crtc_w, *prop_crtc_h;         /* 图层目标宽高尺寸 */
    struct drm_property *prop_fb_id;                        /* 图层绑定帧缓存ID */
    struct drm_property *prop_crtc_id;                      /* 图层绑定CRTC ID */

    uint32_t preferred_depth;                               /* 首选显示色深 */
    bool async_page_flip;                                   /* 是否支持页面异步刷新 */
    bool normalize_zpos;                                    /* 是否自动归一化图层层级 */

    uint32_t cursor_width, cursor_height;                   /* 光标图层最大尺寸 */
    struct drm_atomic_state *suspend_state;                 /* 系统休眠时保存的显示原子状态 */

    /* 其他成员省略 */
};

3.2.4. CRTC显示控制器结构体

CRTC显示控制器结构体(struct drm_crtc)对应SOC内部VOP显示通道,是DRM核心时序控制单元,负责图层合成、显示时序生成、画面刷新、模式切换,为屏幕输出标准图像时序信号。

drm_crtc结构体(内核源码/include/drm/drm_crtc.h)
 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
struct drm_crtc {
    struct drm_device *dev;                         /* 所属DRM核心设备 */
    struct list_head head;                          /* 挂载至mode_config的CRTC链表节点 */
    char *name;                                     /* CRTC控制器名称,用于区分VOP通道 */
    struct drm_modeset_lock mutex;                  /* CRTC状态操作保护锁 */
    struct drm_mode_object base;                    /* KMS通用对象ID管理基类 */

    struct drm_plane *primary;                      /* CRTC绑定的主图层 */
    struct drm_plane *cursor;                       /* CRTC绑定的光标图层 */
    unsigned index;                                 /* CRTC硬件索引(对应VOP通道号) */

    bool enabled;                                   /* CRTC硬件使能状态 */
    struct drm_display_mode mode;                   /* 当前生效的显示时序模式 */
    struct drm_display_mode hwmode;                 /* 硬件最终适配的显示时序 */

    const struct drm_crtc_funcs *funcs;             /* CRTC硬件操作回调函数集 */
    const struct drm_crtc_helper_funcs *helper_private; /* CRTC辅助适配回调集 */
    struct drm_object_properties properties;        /* CRTC原子属性管理 */

    struct drm_crtc_state *state;                   /* CRTC当前原子状态,存储核心配置 */
    struct list_head commit_list;                   /* 原子提交任务队列 */
    spinlock_t commit_lock;                         /* 原子提交队列保护锁 */

    /* 其他成员省略 */
};

3.2.5. 图层管理结构体

图层管理结构体(struct drm_plane)是DRM硬件图层核心管理结构体,对应SOC VOP的各类图层通道(主图层、叠加图层、光标图层),负责图层素材绑定、缩放裁剪、透明度混合、层级叠加、旋转翻转等画面合成核心能力,是多图层显示的基础。

drm_plane结构体(内核源码/include/drm/drm_plane.h)
 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
struct drm_plane {
    struct drm_device *dev;                         /* 所属DRM核心设备 */
    struct list_head head;                          /* 挂载至mode_config的全局图层链表节点 */
    char *name;                                     /* 图层名称,区分主图层/叠加层/光标层 */
    struct drm_modeset_lock mutex;                  /* 图层状态操作保护锁 */
    struct drm_mode_object base;                    /* KMS通用对象ID管理基类 */

    uint32_t possible_crtcs;                        /* 图层可绑定的CRTC通道掩码 */
    uint32_t *format_types;                         /* 图层支持的像素格式数组 */
    unsigned int format_count;                      /* 支持的像素格式数量 */

    enum drm_plane_type type;                       /* 图层类型:主图层/叠加图层/光标图层 */
    unsigned index;                                 /* 图层硬件索引编号 */

    const struct drm_plane_funcs *funcs;            /* 图层硬件操作回调函数集 */
    const struct drm_plane_helper_funcs *helper_private; /* 图层辅助适配回调集 */
    struct drm_object_properties properties;         /* 图层原子属性管理 */

    struct drm_plane_state *state;                  /* 图层当前原子状态,存储核心配置 */

    /* 图层核心自定义原子属性 */
    struct drm_property *alpha_property;            /* 图层透明度属性 */
    struct drm_property *zpos_property;             /* 图层层级属性,控制叠加顺序 */
    struct drm_property *rotation_property;         /* 图层旋转/翻转属性 */
    struct drm_property *blend_mode_property;       /* 图层像素混合模式属性 */
    struct drm_property *scaling_filter_property;   /* 图层缩放滤波算法属性 */

    /* 其他成员省略 */
};

3.2.6. 信号编码器结构体

信号编码器结构体(struct drm_encoder)是DRM信号编码核心组件,承接CRTC输出的通用图像数据流,将其编码为MIPI/HDMI/LVDS/DP等硬件专属电气时序信号,衔接CRTC控制器与Connector接口,是显示信号格式转换的关键中间层。

drm_encoder结构体(内核源码/include/drm/drm_encoder.h)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
struct drm_encoder {
    struct drm_device *dev;                         /* 所属DRM核心设备 */
    struct list_head head;                          /* 挂载至mode_config的编码器全局链表节点 */
    struct drm_mode_object base;                    /* KMS通用对象ID管理基类 */
    char *name;                                     /* 编码器名称,区分不同信号编码通路 */

    int encoder_type;                               /* 编码器类型:HDMI/MIPI/LVDS/DP/DSI等 */
    unsigned index;                                 /* 编码器硬件索引编号 */

    uint32_t possible_crtcs;                        /* 编码器可绑定的CRTC通道掩码 */
    uint32_t possible_clones;                       /* 支持同源复制显示的编码器掩码 */

    struct drm_crtc *crtc;                          /* 当前绑定的CRTC控制器 */
    struct list_head bridge_chain;                  /* 绑定的显示Bridge桥接器链表 */

    const struct drm_encoder_funcs *funcs;          /* 编码器硬件操作回调函数集 */
    const struct drm_encoder_helper_funcs *helper_private; /* 编码器辅助适配回调集 */
};

3.2.7. 显示连接器结构体

显示连接器结构体(struct drm_connector)对应HDMI/MIPI/LVDS/DP物理显示接口,负责屏幕热插拔检测、EDID解析、显示设备能力存储、画面参数配置。

drm_connector结构体(内核源码/include/drm/drm_connector.h)
 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
struct drm_connector {
    struct drm_device *dev;                         /* 所属DRM核心设备 */
    struct list_head head;                          /* 挂载至mode_config的连接器全局链表节点 */
    struct drm_mode_object base;                    /* KMS通用对象ID管理基类 */
    char *name;                                     /* 连接器名称,区分HDMI/MIPI/LVDS/DP接口 */

    unsigned index;                                 /* 连接器硬件索引编号 */
    int connector_type;                             /* 连接器硬件接口类型 */

    /* 显示模式能力支持标记 */
    bool interlace_allowed;                         /* 是否支持隔行扫描模式 */
    bool doublescan_allowed;                        /* 是否支持倍频扫描模式 */
    bool stereo_allowed;                            /* 是否支持立体显示模式 */
    bool ycbcr_420_allowed;                         /* 是否支持YUV420色彩输出 */

    enum drm_connector_status status;               /* 连接器连接状态:已连接/未连接/未知 */
    struct list_head modes;                         /* 连接器支持的显示时序模式链表 */
    struct list_head probed_modes;                  /* EDID探测到的原始显示模式链表 */

    struct drm_display_info display_info;           /* EDID解析后的屏幕硬件信息核心结构体 */
    struct drm_property_blob *edid_blob_ptr;        /* EDID属性数据指针 */

    uint8_t polled;                                 /* 热插拔轮询机制配置:HPD/连接/断开检测 */
    int dpms;                                       /* 屏幕电源管理状态 */

    u32 possible_encoders;                          /* 可绑定的编码器掩码 */
    struct drm_encoder *encoder;                    /* 当前绑定的信号编码器 */

    struct i2c_adapter *ddc;                        /* DDC I2C适配器,用于读取EDID信息 */

    /* 核心原子属性 */
    struct drm_object_properties properties;        /* 连接器通用属性管理 */
    struct drm_property *vrr_capable_property;      /* 动态刷新率支持属性 */
    struct drm_property *colorspace_property;       /* 色域空间配置属性 */
    struct drm_property *max_bpc_property;          /* 最大色深配置属性 */

    const struct drm_connector_funcs *funcs;        /* 连接器硬件操作回调函数集 */
    const struct drm_connector_helper_funcs *helper_private; /* 辅助适配回调集 */

    struct drm_connector_state *state;              /* 连接器当前原子状态,存储核心配置 */
    struct hdr_sink_metadata hdr_sink_metadata;     /* 屏幕HDR能力元数据 */

    /* 其他成员省略 */
};

3.2.8. 原子显示状态结构体

原子显示状态结构体(struct drm_atomic_state)是原子显示机制核心载体,批量存储本次需要修改的所有显示参数,统一提交生效,保证画面更新原子性。

drm_atomic_state结构体(内核源码/include/drm/drm_atomic.h)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
struct drm_atomic_state {
    struct kref ref;                                 /* 原子状态引用计数,管控生命周期 */
    struct drm_device *dev;                          /* 归属的DRM核心设备 */

    bool allow_modeset : 1;                          /* 是否允许本次提交执行完整模式切换 */
    bool async_update : 1;                           /* 是否为异步无阻塞提交 */
    bool duplicated : 1;                             /* 标记是否为复制生成的原子状态 */

    struct __drm_planes_state *planes;               /* 本次变更的所有图层状态数组 */
    struct __drm_crtcs_state *crtcs;                 /* 本次变更的所有CRTC状态数组 */
    struct __drm_connnectors_state *connectors;      /* 本次变更的所有连接器状态数组 */

    struct drm_modeset_acquire_ctx *acquire_ctx;     /* 模式锁申请上下文,保护原子操作锁机制 */

    /* 其他成员省略 */
};

3.2.9. 显示设备信息结构体

显示设备信息结构体(struct drm_display_info)存储EDID解析后的所有显示器硬件能力,包含色彩格式、分辨率、HDR、DSC等核心参数。

drm_display_info结构体(内核源码/include/drm/drm_connector.h)
 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
struct drm_display_info {
    unsigned int width_mm;                         /* 屏幕物理宽度(mm) */
    unsigned int height_mm;                        /* 屏幕物理高度(mm) */
    unsigned int bpc;                              /* 单通道最大色深,HDMI/DP核心参数 */

    enum subpixel_order subpixel_order;            /* LCD子像素排列顺序 */
    int panel_orientation;                         /* 屏幕物理朝向,用于画面旋转补偿 */

    /* 色彩格式宏定义 */
#define DRM_COLOR_FORMAT_RGB444     (1<<0)
#define DRM_COLOR_FORMAT_YCBCR444   (1<<1)
#define DRM_COLOR_FORMAT_YCBCR422   (1<<2)
#define DRM_COLOR_FORMAT_YCBCR420   (1<<3)

    u32 color_formats;                             /* 支持的输出色彩格式:RGB/YUV */
    const u32 *bus_formats;                        /* 总线传输像素格式数组 */
    unsigned int num_bus_formats;                  /* 总线格式数量 */
    u32 bus_flags;                                 /* 总线时序极性等配置标记 */

    int max_tmds_clock;                            /* HDMI最大TMDS传输时钟(kHz) */
    bool is_hdmi;                                  /* 是否为HDMI显示设备 */

    bool rgb_quant_range_selectable;               /* 是否支持RGB量化范围调节 */
    struct drm_hdmi_info hdmi;                     /* HDMI高级特性信息 */

    struct drm_monitor_range_info monitor_range;   /* 屏幕支持的刷新率频率范围 */
    struct drm_luminance_range_info luminance_range;/* 屏幕HDR亮度范围参数 */
    u32 max_dsc_bpp;                               /* DSC压缩最大位率参数 */

    /* 其他成员省略 */
};

3.3. DRM驱动核心函数

3.3.1. 分配DRM核心设备

3.3.1.1. drm_dev_alloc函数

drm_dev_alloc函数用于分配并初始化DRM核心设备结构体,绑定厂商自定义驱动操作集,是DRM设备初始化的第一步。

函数原型:

1
struct drm_device *drm_dev_alloc(const struct drm_driver *driver, struct device *parent);

参数说明:

  • driver:DRM驱动操作集结构体,包含厂商自定义的硬件操作回调。

  • parent:父设备结构体,对应平台设备dev。

返回值:成功返回drm_device指针,失败返回错误指针。

3.3.2. DRM设备上线注册

3.3.2.1. drm_dev_register函数

drm_dev_register函数用于将初始化完成的DRM设备注册到内核子系统,创建设备节点、sysfs节点,设备正式对外提供显示服务。

函数原型:

1
int drm_dev_register(struct drm_device *dev, unsigned long flags);

参数说明:

  • dev:待注册的DRM核心设备结构体。

  • flags:注册标志位,默认传0。

返回值:0 注册成功;负数 注册失败。

3.3.3. DRM设备注销下线

3.3.3.1. drm_dev_unregister函数

drm_dev_unregister函数用于反向注销已注册的DRM设备,销毁设备节点、sysfs、debugfs节点,停止显示硬件服务,是DRM设备卸载、关机、热移除的核心接口,与drm_dev_register成对使用。

函数原型:

1
void drm_dev_unregister(struct drm_device *dev);

参数说明

  • dev:需要注销的DRM核心设备结构体。

3.3.4. 初始化显示模式配置

3.3.4.1. drm_mode_config_init函数

drm_mode_config_init函数用于初始化DRM全局显示模式配置,清零分辨率、硬件数量、时序参数,为后续自定义配置提供干净的初始化环境。

函数原型:

1
int drmm_mode_config_init(struct drm_device *dev);

参数说明:

  • dev:DRM核心设备结构体。

返回值:0 初始化成功;负数 失败。

3.3.5. 重置显示模式配置

3.3.5.1. drm_mode_config_reset函数

drm_mode_config_reset函数用于重置DRM全局显示模式配置与所有KMS组件状态,将CRTC、Encoder、Connector、Plane等所有显示硬件的原子状态恢复为默认初始状态。

函数原型:

1
void drm_mode_config_reset(struct drm_device *dev);

参数说明:

  • dev:DRM核心设备结构体。

3.3.6. 初始化垂直同步中断

3.3.6.1. drm_vblank_init函数

drm_vblank_init函数用于初始化CRTC垂直同步中断,注册画面刷新回调,保障画面同步、杜绝撕裂,是动态刷新率、画面渲染的基础。

函数原型:

1
int drm_vblank_init(struct drm_device *dev, unsigned int num_crtcs);

参数说明:

  • dev:DRM核心设备。

  • num_crtcs:有效CRTC通道数量(VOP硬件通道数)。

返回值:0 成功;负数 失败。

3.3.7. 复制原子状态

3.3.7.1. drm_atomic_helper_duplicate_state函数

drm_atomic_helper_duplicate_state函数用于复制当前系统的显示原子状态,基于原有状态修改参数,避免全局刷新导致的显示异常,常用于默认属性初始化。

函数原型:

1
struct drm_atomic_state *drm_atomic_helper_duplicate_state(struct drm_device *dev, struct drm_modeset_lock *ctx);

参数说明:

  • dev:DRM核心设备。

  • ctx:模式配置锁上下文。

返回值:成功返回原子状态结构体指针,失败返回错误指针。

3.3.8. 提交原子显示配置

3.3.8.1. drm_atomic_commit函数

drm_atomic_commit函数用于批量提交所有原子显示配置,一次性写入硬件寄存器,保证所有显示参数同步生效,无闪屏、无撕裂。

函数原型:

1
int drm_atomic_commit(struct drm_atomic_state *state);

参数说明:

  • state:待提交的原子显示状态结构体。

返回值:0 提交成功;负数 提交失败。

3.3.9. 初始化热插拔轮询

3.3.9.1. drm_kms_helper_poll_init函数

drm_kms_helper_poll_init函数用于初始化显示外设热插拔轮询机制,后台定时检测HDMI、DP等设备的插拔状态,自动适配显示设备上线/下线。

函数原型:

1
void drm_kms_helper_poll_init(struct drm_device *dev);

参数说明:

  • dev:DRM核心设备结构体。

3.3.10. 原子显示框架关机

3.3.10.1. drm_atomic_helper_shutdown函数

drm_atomic_helper_shutdown函数用于DRM原子框架标准关机接口,系统关机、重启、驱动卸载阶段调用。统一关闭所有CRTC显示通道、禁用图层合成、释放原子显示状态、关闭硬件显示输出,复位显示硬件至初始休眠状态,避免关机花屏、硬件寄存器残留、电源异常等问题。

函数原型:

1
void drm_atomic_helper_shutdown(struct drm_device *dev);

参数说明:

  • dev:DRM核心设备结构体。

3.3.11. DRM内存管理器初始化

3.3.11.1. drm_mm_init函数

drm_mm_init函数用于初始化DRM显存内存管理器,是GEM显存管理体系的基础接口。创建一段连续的显存地址空间管理池,初始化空闲内存链表、分配位图与管理锁,为后续显存分片分配、释放、回收、碎片化整理提供基础能力。

函数原型:

1
void drm_mm_init(struct drm_mm *mm, u64 start, u64 size);

参数说明:

  • mm:DRM内存管理器结构体指针,待初始化的管理对象。

  • start:显存管理空间起始地址(物理地址/IOVA虚拟地址)。

  • size:显存管理空间总大小。

3.3.12. DRM内存管理器销毁

3.3.12.1. drm_mm_takedown函数

drm_mm_takedown函数与drm_mm_init成对使用,用于清空显存管理空闲链表、释放所有未回收的管理节点、销毁内存锁与管理资源,彻底释放显存地址空间管理。

函数原型:

1
void drm_mm_takedown(struct drm_mm *mm);

参数说明:

  • mm:待销毁的DRM内存管理器结构体指针。

3.3.13. 全局显示模式上锁

3.3.13.1. drm_modeset_lock_all函数

drm_modeset_lock_all函数用于对DRM设备下所有KMS模式资源执行全局上锁操作,统一锁定CRTC、Encoder、Connector、Plane等所有显示组件的模式锁。主要用于全局显示配置修改、批量参数更新、显示复位等场景,防止多线程并发操作导致的显示状态错乱、资源竞争问题。

函数原型:

1
int drm_modeset_lock_all(struct drm_device *dev);

参数说明:

  • dev:DRM核心设备结构体,对应需要全局加锁的显示设备。

返回值:0 上锁成功;负数 上锁失败。

3.3.14. 全局显示模式解锁

3.3.14.1. drm_modeset_unlock_all函数

drm_modeset_unlock_all函数与 drm_modeset_lock_all 成对使用,解锁DRM设备所有全局模式锁。在批量显示配置操作完成后释放锁资源,避免死锁。

函数原型:

1
void drm_modeset_unlock_all(struct drm_device *dev);

参数说明:

  • dev:DRM核心设备结构体。

3.3.15. 连接器迭代器初始化

3.3.15.1. drm_connector_list_iter_begin函数

drm_connector_list_iter_begin函数用于初始化DRM连接器安全迭代器,为遍历系统所有显示连接器做准备。该迭代机制带锁保护,可安全遍历连接器链表,规避遍历过程中设备热插拔导致的链表崩溃、空指针异常。

函数原型:

1
void drm_connector_list_iter_begin(struct drm_device *dev, struct drm_connector_list_iter *iter);

参数说明:

  • dev:DRM核心设备结构体。

  • iter:连接器迭代器结构体指针,用于存储迭代遍历上下文。

3.3.16. 释放连接器迭代器

3.3.16.1. drm_connector_list_iter_end函数

drm_connector_list_iter_end函数与 drm_connector_list_iter_begin 成对使用,遍历结束后释放迭代器资源、解除链表保护锁,清理迭代上下文,防止锁泄漏与资源占用。

函数原型:

1
void drm_connector_list_iter_end(struct drm_connector_list_iter *iter);

参数说明:

  • iter:需要释放的连接器迭代器结构体指针。

3.3.17. 遍历所有连接器

3.3.17.1. drm_for_each_connector_iter宏

drm_for_each_connector_iter宏是安全遍历宏,配合迭代器遍历DRM设备下所有有效连接器设备。常用于批量检测所有显示接口插拔状态、统一刷新屏幕参数、批量初始化连接器属性,替代传统不安全的链表遍历方式,适配热插拔动态设备变化场景。

宏原型:

1
#define drm_for_each_connector_iter(connector, iter)

参数说明:

  • connector:遍历输出的连接器结构体指针。

  • iter:已初始化的连接器迭代器。

3.3.18. 获取连接器原子状态

3.3.18.1. drm_atomic_get_connector_state函数

drm_atomic_get_connector_state函数用于从当前原子状态事务中获取指定连接器的原子状态副本,自动标记该连接器状态为待修改。是原子更新连接器参数的核心接口,所有连接器属性的原子修改均需通过该接口获取状态后再赋值,保证修改的原子性。

函数原型:

1
2
3
struct drm_connector_state *
drm_atomic_get_connector_state(struct drm_atomic_state *state,
                struct drm_connector *connector);

参数说明:

  • state:当前待提交的原子状态结构体。

  • connector:需要获取状态的目标连接器。

返回值:成功返回连接器原子状态指针,失败返回NULL。

3.3.19. 释放原子状态引用

3.3.19.1. drm_atomic_state_put函数

drm_atomic_state_put函数用于原子提交完成、状态拷贝废弃、异常退出场景,主动释放原子状态资源,杜绝内存泄漏。

函数原型:

1
void drm_atomic_state_put(struct drm_atomic_state *state);

参数说明:

  • state:需要释放引用的原子状态结构体。

3.3.20. 清理uboot帧缓存

3.3.20.1. drm_aperture_remove_framebuffers函数

drm_aperture_remove_framebuffers函数用于清理uboot阶段遗留的帧缓存画面,解决内核启动初期内核DRM显示与uboot画面冲突、花屏、闪屏问题。

函数原型:

1
int drm_aperture_remove_framebuffers(const struct drm_driver *drv);

参数说明:

  • drv:当前DRM驱动操作集。

返回值:0 清理成功;负数 清理失败。

3.3.21. 注册组件主设备

3.3.21.1. component_master_add_with_match函数

component_master_add_with_match函数用于注册DRM组件化架构的主控制器设备,绑定主设备操作回调与匹配规则。用于统一管理VOP、MIPI、HDMI等所有显示子组件,是DRM组件化按需绑定、自动适配的核心入口。

函数原型:

1
2
struct device *component_master_add_with_match(struct device *dev,
    const struct component_master_ops *ops, void *match_data);

参数说明:

  • dev:主设备结构体,显示子系统平台设备。

  • ops:组件主设备操作回调集,包含匹配、绑定、解绑核心回调。

  • match_data:组件匹配私有数据,用于精准匹配各类显示子组件。

返回值:成功返回主设备指针,失败返回NULL。

3.3.22. 批量绑定所有子组件

3.3.22.1. component_bind_all函数

component_bind_all函数用于遍历所有匹配成功的显示子组件,批量执行组件绑定操作,完成VOP、编码器、连接器、Bridge等子设备的关联初始化。 所有子组件初始化完成后统一绑定生效,保障DRM显示链路完整性,是组件化架构核心收尾接口。

函数原型:

1
int component_bind_all(struct device *master_dev, void *data);

参数说明:

  • master_dev:组件主设备指针,与component_master_add_with_match注册的主设备对应。

  • data:组件绑定私有参数,传递给子组件绑定回调。

返回值:0 全部绑定成功;负数 子组件绑定失败。

3.4. 瑞芯微DRM驱动源码解析

瑞芯微DRM驱动是瑞芯微SOC专用的Linux显示驱动,基于内核标准DRM子系统实现,采用组件化分层架构,适配HDMI、MIPI、LVDS等各类显示外设。 驱动包含显示模式管理、GEM显存管理、IOMMU虚拟内存映射、EDID显示参数解析、HDR/DSC高清特性、调试日志、热插拔、电源管理、异常监控等。

瑞芯微DRM驱动实现流程为:内核驱动加载 -> 子驱动批量注册 -> 平台设备匹配探测 -> 设备树解析硬件配置 -> 组件匹配绑定所有显示子设备 -> DRM核心初始化 -> 各类子模块(IOMMU/属性/调试/事件)初始化 -> 设备注册上线 -> 运行显示 -> 设备卸载、资源释放。

以下以 瑞芯微6.1.99内核版本 结合源码函数逐一分析。

3.4.1. 模块入口与子驱动注册

宏定义辅助

宏定义辅助(位于内核源码/drivers/gpu/drm/rockchip/rockchip_drm_drv.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/* 定义瑞芯微显示子驱动最大支持数量 */
#define MAX_ROCKCHIP_SUB_DRIVERS 16
/* 定义平台驱动指针数组 */
static struct platform_driver *rockchip_sub_drivers[MAX_ROCKCHIP_SUB_DRIVERS];
/* 子驱动有效计数变量 */
static int num_rockchip_sub_drivers;

/* 子驱动批量注册宏函数 */
#define ADD_ROCKCHIP_SUB_DRIVER(drv, cond) { \
    if (IS_ENABLED(cond) && \
        !WARN_ON(num_rockchip_sub_drivers >= MAX_ROCKCHIP_SUB_DRIVERS)) \
        rockchip_sub_drivers[num_rockchip_sub_drivers++] = &drv; \
}

关键说明:

  • 第2行:定义子驱动最大数量上限16,限制子驱动数组容量,防止驱动注册溢出。

  • 第4行:创建全局驱动指针数组,用于统一收纳所有显示子驱动,为后续批量注册提供数据源。

  • 第6行:全局计数变量,实时统计已收录的有效子驱动数量,作为数组索引和注册数量依据。

  • 第9-13行:定义批量注册核心宏,实现条件式驱动收录,避免注册代码重复。

    • 第10行:通过内核标准IS_ENABLED宏,判断当前内核配置是否开启对应外设驱动。

    • 第11行:添加数组越界保护,子驱动数量超上限时打印内核警告。

    • 第12行:合法驱动存入数组末尾,同时计数器自增,完成单个子驱动的收录。

模块入口与子驱动注册

rockchip_drm_init函数通过ADD_ROCKCHIP_SUB_DRIVER宏,根据内核配置项,动态注册VOP、MIPI、HDMI、LVDS、DP等所有显示子驱动,实现模块化适配不同显示硬件。

模块入口与子驱动注册(位于内核源码/drivers/gpu/drm/rockchip/rockchip_drm_drv.c)
 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/* 模块初始化入口函数 */
static int __init rockchip_drm_init(void)
{
    int ret;

    /* 判断系统是否仅加载固件驱动,若是则直接退出 */
    if (drm_firmware_drivers_only())
        return -ENODEV;

    /* 初始化子驱动计数器,清零待注册子驱动数量 */
    num_rockchip_sub_drivers = 0;

    /* 根据内核配置判断是否启用VVOP架构 */
#if IS_ENABLED(CONFIG_DRM_ROCKCHIP_VVOP)
    /* VVOP架构下仅注册VVOP平台驱动 */
    ADD_ROCKCHIP_SUB_DRIVER(vvop_platform_driver, CONFIG_DRM_ROCKCHIP_VVOP);
#else
    /* 传统VOP架构,批量注册各类显示外设子驱动 */
    ADD_ROCKCHIP_SUB_DRIVER(vop_platform_driver, CONFIG_ROCKCHIP_VOP);
    ADD_ROCKCHIP_SUB_DRIVER(vop2_platform_driver, CONFIG_ROCKCHIP_VOP2);
    /* VCONN电压控制驱动 */
    ADD_ROCKCHIP_SUB_DRIVER(vconn_platform_driver, CONFIG_ROCKCHIP_VCONN);
    /* LVDS显示接口驱动 */
    ADD_ROCKCHIP_SUB_DRIVER(rockchip_lvds_driver,
            CONFIG_ROCKCHIP_LVDS);
    /* 模拟DP显示接口驱动 */
    ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver,
            CONFIG_ROCKCHIP_ANALOGIX_DP);
    /* CDN方案DP显示接口驱动 */
    ADD_ROCKCHIP_SUB_DRIVER(cdn_dp_driver, CONFIG_ROCKCHIP_CDN_DP);
    /* 通用DW架构HDMI驱动 */
    ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_rockchip_pltfm_driver,
            CONFIG_ROCKCHIP_DW_HDMI);
    /* 第一代DW架构MIPI-DSI显示驱动 */
    ADD_ROCKCHIP_SUB_DRIVER(dw_mipi_dsi_rockchip_driver,
            CONFIG_ROCKCHIP_DW_MIPI_DSI);
    /* 第二代DW架构MIPI-DSI显示驱动 */
    ADD_ROCKCHIP_SUB_DRIVER(dw_mipi_dsi2_rockchip_driver,
            CONFIG_ROCKCHIP_DW_MIPI_DSI2);
    /* 自研INNO架构HDMI驱动 */
    ADD_ROCKCHIP_SUB_DRIVER(inno_hdmi_driver, CONFIG_ROCKCHIP_INNO_HDMI);
    /* RK3066芯片专用HDMI驱动 */
    ADD_ROCKCHIP_SUB_DRIVER(rk3066_hdmi_driver,
            CONFIG_ROCKCHIP_RK3066_HDMI);
    /* RGB并行显示接口驱动 */
    ADD_ROCKCHIP_SUB_DRIVER(rockchip_rgb_driver, CONFIG_ROCKCHIP_RGB);
    /* TVE电视编码输出驱动 */
    ADD_ROCKCHIP_SUB_DRIVER(rockchip_tve_driver, CONFIG_ROCKCHIP_DRM_TVE);
    /* DW架构DP显示接口驱动 */
    ADD_ROCKCHIP_SUB_DRIVER(dw_dp_driver, CONFIG_ROCKCHIP_DW_DP);
#endif

    /* 批量注册所有已收录的显示子驱动 */
    ret = platform_register_drivers(rockchip_sub_drivers,
                    num_rockchip_sub_drivers);
    if (ret)
        return ret;

    /* 注册Rockchip DRM主平台驱动 */
    ret = platform_driver_register(&rockchip_drm_platform_driver);
    if (ret)
        goto err_unreg_drivers;

    /* 读取DDR内存硬件参数,为GEM显存分配、DMA映射提供基础数据 */
    rockchip_gem_get_ddr_info();

    return 0;

err_unreg_drivers:
    /* 批量卸载所有已注册的子驱动,清理资源 */
    platform_unregister_drivers(rockchip_sub_drivers,
        num_rockchip_sub_drivers);
    return ret;
}

/* 模块退出卸载函数 */
static void __exit rockchip_drm_fini(void)
{
    /* 优先卸载主平台驱动 */
    platform_driver_unregister(&rockchip_drm_platform_driver);
    /* 批量卸载所有显示子驱动 */
    platform_unregister_drivers(rockchip_sub_drivers,
        num_rockchip_sub_drivers);
}

/* 驱动加载策略配置:开启镜像翻转功能则使用fs_initcall优先加载,否则使用标准模块加载 */
#ifdef CONFIG_VIDEO_REVERSE_IMAGE
fs_initcall(rockchip_drm_init);
#else
module_init(rockchip_drm_init);
#endif

/* 注册模块卸载入口函数 */
module_exit(rockchip_drm_fini);

关键说明:

  • 第7行:调用内核DRM通用接口,判断当前系统运行模式,固件驱动模式下跳过硬件驱动初始化。

  • 第11行:清零子驱动计数器,保证每次驱动加载时子驱动统计数量从零开始,避免脏数据干扰。

  • 第14行:通过内核宏判断VVOP架构配置,差异化适配新旧两种显示架构的驱动注册逻辑。

  • 第19-50行:传统VOP架构下,根据内核编译配置,动态收录VOP、各类显示接口、辅助功能子驱动。

  • 第54-55行:批量注册所有收录的子驱动,统一完成显示外设驱动的内核注册。

  • 第60行:注册DRM核心主驱动,用于匹配设备树显示子系统节点,是整个驱动的核心载体。

  • 第65行:读取DDR容量、位宽、地址区间等硬件信息,为后续显存管理、DMA寻址提供参数依据。

  • 第77-84行:模块卸载回调函数,按照“先主后次”的反向顺序卸载驱动,资源释放。

  • 第87-91行:差异化加载策略,镜像翻转场景优先加载驱动。

3.4.2. 平台设备匹配与设备树解析

设备树解析

rockchip_drm_platform_of_probe函数解析显示子系统设备树节点,遍历VOP端口、检测IOMMU配置、初始化硬件特性参数。

设备树解析(位于内核源码/drivers/gpu/drm/rockchip/rockchip_drm_drv.c)
 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/* 设备树解析函数 */
static int rockchip_drm_platform_of_probe(struct device *dev)
{
    /* 获取当前设备的设备树节点指针 */
    struct device_node *np = dev->of_node;
    /* 定义端口节点指针,用于遍历VOP显示端口 */
    struct device_node *port;
    /* 标记是否检测到有效可用的VOP端口 */
    bool found = false;
    /* 循环遍历索引变量 */
    int i;

    /* 无设备树节点直接返回错误 */
    if (!np)
        return -ENODEV;

    /* 循环遍历设备树中ports属性对应的所有VOP端口 */
    for (i = 0;; i++) {
        /* 定义IOMMU节点指针,用于解析端口IOMMU配置 */
        struct device_node *iommu;

        /* 按索引解析单个VOP端口节点 */
        port = of_parse_phandle(np, "ports", i);
        if (!port)  /* 无更多端口,结束遍历 */
            break;

        /* 跳过设备树中状态为禁用的端口设备 */
        if (!of_device_is_available(port->parent)) {
            of_node_put(port);
            continue;
        }

        /* 解析当前端口对应的IOMMU配置节点 */
        iommu = of_parse_phandle(port->parent, "iommus", 0);
        if (!iommu || !of_device_is_available(iommu)) {
            DRM_DEV_DEBUG(dev,
                "no iommu attached for %pOF, using non-iommu buffers\n",
                port->parent);
            is_support_iommu = false;   /* 无有效IOMMU设备,使用非IOMMU缓冲区 */
        }

        /* 标记存在有效VOP端口 */
        found = true;
        /* 读取IOMMU预映射配置标记,用于4G内存预映射初始化 */
        iommu_reserve_map |= of_property_read_bool(iommu, "rockchip,reserve-map");

        /* 释放设备树节点引用,避免内存泄漏 */
        of_node_put(iommu);
        of_node_put(port);
    }

    /* 未遍历到任何ports属性,设备树配置异常 */
    if (i == 0) {
        DRM_DEV_ERROR(dev, "missing 'ports' property\n");
        return -ENODEV;
    }

    /* 无任何可用VOP端口,显示硬件初始化失败 */
    if (!found) {
        DRM_DEV_ERROR(dev,
            "No available vop found for display-subsystem.\n");
        return -ENODEV;
    }

    return 0;
}

关键说明:

  • 第18-50行:循环遍历所有VOP端口,自动跳过设备树禁用的无效硬件端口。

    • 第34-40行:解析端口IOMMU配置,无有效IOMMU则使用非IOMMU缓冲区。

    • 第43-45行:标记有效端口,读取IOMMU预映射配置,为后续内存初始化赋值。

  • 第53-56行:校验ports属性合法性,缺失则初始化失败。

  • 第59-63行:校验有效VOP端口,无可用显示硬件则初始化失败。

平台设备匹配、移除与关闭

设备匹配成功后执行probe函数,完成设备树解析、组件匹配、DMA配置、主组件注册; 设备卸载时执行remove函数,反向清理组件匹配关系、注销主组件; 系统关机时执行shutdown函数,关闭显示原子会话,防止花屏、残留画面。

平台设备匹配、移除与关闭(位于内核源码/drivers/gpu/drm/rockchip/rockchip_drm_drv.c)
 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/* 平台设备探测函数 */
static int rockchip_drm_platform_probe(struct platform_device *pdev)
{
    /* 获取设备通用结构体指针 */
    struct device *dev = &pdev->dev;
    /* 定义组件匹配结构体,用于绑定所有显示子组件依赖关系 */
    struct component_match *match = NULL;
    /* 定义返回值 */
    int ret;

    /* 执行设备树解析、IOMMU、VOP端口硬件检测 */
    ret = rockchip_drm_platform_of_probe(dev);

    /* 非VVOP架构下,设备树解析失败直接返回错误,终止初始化 */
#if !IS_ENABLED(CONFIG_DRM_ROCKCHIP_VVOP)
    if (ret)
        return ret;
#endif

    /* 遍历所有子驱动,创建显示组件依赖匹配关系 */
    match = rockchip_drm_match_add(dev);
    if (IS_ERR(match))
        return PTR_ERR(match);

    /* 设置64位DMA寻址掩码,支持大内存DDR寻址 */
    ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(64));
    if (ret)
        goto err;

    /* 注册DRM主组件控制器,绑定组件绑定/解绑回调 */
    ret = component_master_add_with_match(dev, &rockchip_drm_ops, match);
    if (ret < 0)
        goto err;

    return 0;

err:
    /* 初始化失败后清理组件资源 */
    rockchip_drm_match_remove(dev);
    return ret;
}

/* 平台设备移除函数 */
static int rockchip_drm_platform_remove(struct platform_device *pdev)
{
    /* 注销DRM主组件控制器,解绑所有子组件 */
    component_master_del(&pdev->dev, &rockchip_drm_ops);
    /* 清理组件匹配依赖关系,释放资源 */
    rockchip_drm_match_remove(&pdev->dev);
    return 0;
}

/* 平台设备关机收尾函数 */
static void rockchip_drm_platform_shutdown(struct platform_device *pdev)
{
    /* 获取DRM设备私有数据 */
    struct drm_device *drm = platform_get_drvdata(pdev);
    /* 设备有效则关闭原子显示会话 */
    if (drm)
        drm_atomic_helper_shutdown(drm);
}

/* 定义DRM显示子系统设备树匹配表 */
static const struct of_device_id rockchip_drm_dt_ids[] = {
    { .compatible = "rockchip,display-subsystem", },
    { },
};
/* 导出设备匹配表 */
MODULE_DEVICE_TABLE(of, rockchip_drm_dt_ids);

/* 定义Rockchip DRM核心平台驱动结构体 */
static struct platform_driver rockchip_drm_platform_driver = {
    .probe = rockchip_drm_platform_probe,
    .remove = rockchip_drm_platform_remove,
    .shutdown = rockchip_drm_platform_shutdown, /* 设备关机时回调函数,防止显示异常 */
    .driver = {
        .name = "rockchip-drm",
        .of_match_table = rockchip_drm_dt_ids,
        .pm = &rockchip_drm_pm_ops,             /* 绑定电源管理操作集,适配休眠/唤醒场景 */
    },
};

关键说明:

  • 第12行:调用设备树解析接口,完成硬件配置检测与参数初始化。

  • 第15-18行:传统VOP架构严格校验设备树解析结果,异常直接终止初始化。

  • 第21行:创建组件匹配链路,关联VOP、HDMI、MIPI等所有子设备。

  • 第26行:配置64位DMA寻址,适配大内存平台。

  • 第31行:注册主组件控制器,绑定bind/unbind核心回调,为组件统一初始化做准备。

  • 第44-51行:移除函数,按顺序注销主组件、清理匹配链路,规范资源释放。

  • 第54-61行:关机收尾函数,关闭原子显示会话,规避关机花屏问题。

  • 第64-69行:定义DRM显示子系统设备树匹配表,用于平台设备与设备树节点绑定。

  • 第72-81行:定义Rockchip DRM核心平台驱动结构体,绑定相关回调与配置。

3.4.3. 设备绑定与解绑

组件操作结构体

组件操作结构体(位于内核源码/drivers/gpu/drm/rockchip/rockchip_drm_drv.c)
1
2
3
4
5
6
7
/* DRM组件主操作结构体 */
static const struct component_master_ops rockchip_drm_ops = {
    /* 所有子组件匹配完成后,统一执行绑定初始化 */
    .bind = rockchip_drm_bind,
    /* 设备卸载时,统一执行组件解绑与资源释放 */
    .unbind = rockchip_drm_unbind,
};

关键说明:

  • 第4行:bind回调,所有子组件就绪后触发,执行完整DRM设备初始化流程。

  • 第6行:unbind回调,设备卸载时触发,有序释放所有显示组件资源。

设备绑定

所有子组件匹配完成后执行rockchip_drm_bind函数,完成DRM设备全流程初始化、硬件上线。

设备绑定(位于内核源码/drivers/gpu/drm/rockchip/rockchip_drm_drv.c)
  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
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/* DRM设备组件绑定函数 */
static int rockchip_drm_bind(struct device *dev)
{
    /* 标准DRM设备结构体 */
    struct drm_device *drm_dev;
    /* 瑞芯微DRM私有数据 */
    struct rockchip_drm_private *private;
    /* 函数返回值 */
    int ret;

    /* 清除uboot遗留的帧缓存,避免内核初始化阶段显示冲突、花屏 */
    ret = drm_aperture_remove_framebuffers(&rockchip_drm_driver);
    if (ret) {
        DRM_DEV_ERROR(dev,
            "Failed to remove existing framebuffers - %d.\n",
            ret);
        return ret;
    }

    /* 分配并初始化标准DRM设备,绑定瑞芯微自定义驱动操作集 */
    drm_dev = drm_dev_alloc(&rockchip_drm_driver, dev);
    if (IS_ERR(drm_dev))
        return PTR_ERR(drm_dev);

    /* 绑定设备私有数据与DRM设备,实现数据关联 */
    dev_set_drvdata(dev, drm_dev);

    /* 分配瑞芯微私有数据内存 */
    private = devm_kzalloc(drm_dev->dev, sizeof(*private), GFP_KERNEL);
    if (!private) {
        ret = -ENOMEM;
        goto err_free;
    }

    /* 初始化图层操作锁、PSR锁、提交锁 */
    mutex_init(&private->ovl_lock);
    drm_dev->dev_private = private;
    INIT_LIST_HEAD(&private->psr_list);
    mutex_init(&private->psr_list_lock);
    mutex_init(&private->commit_lock);

    /* 获取HDMI TMDS时钟源,用于高清视频时序生成 */
    private->hdmi_pll.pll = devm_clk_get_optional(dev, "hdmi-tmds-pll");
    if (PTR_ERR(private->hdmi_pll.pll) == -EPROBE_DEFER) {
        ret = -EPROBE_DEFER;
        goto err_free;
    } else if (IS_ERR(private->hdmi_pll.pll)) {
        dev_err(dev, "failed to get hdmi-tmds-pll\n");
        ret = PTR_ERR(private->hdmi_pll.pll);
        goto err_free;
    }

    /* 获取VOP默认基础时钟,为显示时序提供基础时钟支持 */
    private->default_pll.pll = devm_clk_get_optional(dev, "default-vop-pll");
    if (PTR_ERR(private->default_pll.pll) == -EPROBE_DEFER) {
        ret = -EPROBE_DEFER;
        goto err_free;
    } else if (IS_ERR(private->default_pll.pll)) {
        dev_err(dev, "failed to get default vop pll\n");
        ret = PTR_ERR(private->default_pll.pll);
        goto err_free;
    }

    /* 初始化DRM标准显示模式配置结构体 */
    ret = drmm_mode_config_init(drm_dev);
    if (ret)
        goto err_free;

    /* 初始化瑞芯微自定义显示模式参数 */
    rockchip_drm_mode_config_init(drm_dev);
    /* 创建瑞芯微专属DRM自定义属性 */
    rockchip_drm_create_properties(drm_dev);

    /* 批量绑定所有显示子组件 */
    ret = component_bind_all(dev, drm_dev);
    if (ret)
        goto err_mode_config_cleanup;

    /* 绑定连接器自定义属性,适配各类显示外设特性 */
    rockchip_attach_connector_property(drm_dev);

    /* 初始化垂直同步中断,适配刷新率、画面同步机制 */
    ret = drm_vblank_init(drm_dev, drm_dev->mode_config.num_crtc);
    if (ret)
        goto err_unbind_all;

    /* 重置显示模式为默认配置 */
    drm_mode_config_reset(drm_dev);
    /* 设置显示属性默认参数 */
    rockchip_drm_set_property_default(drm_dev);

    /* 旧版DRM架构兼容,开启中断默认使能 */
#if IS_ENABLED(CONFIG_DRM_LEGACY)
    drm_dev->irq_enabled = true;
#endif

    /* 初始化显示热插拔轮询机制,检测外设插拔状态 */
    drm_kms_helper_poll_init(drm_dev);

    /* 初始化IOMMU虚拟内存映射 */
    ret = rockchip_drm_init_iommu(drm_dev);
    if (ret)
        goto err_unbind_all;

    /* 初始化GEM显存内存池,管理显存分配与释放 */
    rockchip_gem_pool_init(drm_dev);

    /* 解析设备树预留内存,初始化专用显存空间 */
    ret = of_reserved_mem_device_init(drm_dev->dev);
    if (ret)
        DRM_DEBUG_KMS("No reserved memory region assign to drm\n");

    /* 注册DRM设备至内核子系统,设备正式上线 */
    ret = drm_dev_register(drm_dev, 0);
    if (ret)
        goto err_kms_helper_poll_fini;

    /* 开机显示logo画面 */
    rockchip_drm_show_logo(drm_dev);

    /* 初始化fbdev兼容层,适配传统帧缓存应用 */
    ret = rockchip_drm_fbdev_init(drm_dev);
    if (ret)
        goto err_drm_dev_unregister;

    /* 初始化sysfs调试节点,提供用户态配置接口 */
    ret = rockchip_drm_sysfs_init(drm_dev);
    if (ret)
        goto err_drm_fbdev_fini;

    /* 初始化显示错误事件监控线程 */
    rockchip_drm_error_event_init(drm_dev);

    return 0;

/* 逐层异常资源清理分支,遵循先开后关原则 */
err_drm_fbdev_fini:
    rockchip_drm_fbdev_fini(drm_dev);
err_drm_dev_unregister:
    drm_dev_unregister(drm_dev);
err_kms_helper_poll_fini:
    rockchip_gem_pool_destroy(drm_dev);
    drm_kms_helper_poll_fini(drm_dev);
    rockchip_iommu_cleanup(drm_dev);
err_unbind_all:
    component_unbind_all(dev, drm_dev);
err_mode_config_cleanup:
    drm_mode_config_cleanup(drm_dev);
err_free:
    drm_dev->dev_private = NULL;
    dev_set_drvdata(dev, NULL);
    drm_dev_put(drm_dev);
    return ret;
}

关键说明:

  • 第12行:清理uboot残留帧缓存,避免内核启动初期显示错乱问题。

  • 第21行:分配标准DRM设备,接入内核DRM子系统框架。

  • 第29行:分配私有数据内存,为平台专属配置、锁机制提供存储空间。

  • 第36-40行:初始化各类互斥锁,保障多线程显示操作、寄存器操作安全。

  • 第43-51行:获取HDMI时钟资源,失败则延迟探测或返回错误。

  • 第54-62行:获取VOP基础时钟资源,为显示时序输出提供时钟。

  • 第65行:初始化DRM标准模式配置,为分辨率、刷新率配置奠定基础。

  • 第70-72行:初始化自定义显示模式与专属属性,扩展DRM能力。

  • 第75行:批量绑定所有子组件,完成外设驱动统一初始化。

  • 第83行:初始化垂直同步中断,保障画面刷新同步、杜绝撕裂。

  • 第88-90行:重置默认显示参数,初始化亮度、对比度等基础配置。

  • 第98行:开启热插拔轮询,自动检测HDMI、DP等外设插拔。

  • 第101行:初始化IOMMU虚拟内存,实现显存高效管理。

  • 第106行:初始化GEM显存池,统一管理显示内存分配。

  • 第109行:解析设备树预留内存,开辟专用显存空间。

  • 第114行:DRM设备注册上线,正式纳入内核设备管理。

  • 第119行:开机logo显示,完成开机画面渲染。

  • 第122-129行:初始化兼容层与调试节点,适配上层应用与调试需求。

  • 第132行:启动异常监控线程,实时监听显示硬件错误。

设备解绑

设备卸载、驱动退出时执行rockchip_drm_unbind函数,反向释放所有初始化资源。

设备解绑(位于内核源码/drivers/gpu/drm/rockchip/rockchip_drm_drv.c)
 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
static void rockchip_drm_unbind(struct device *dev)
{
    /* 获取绑定的DRM设备私有数据 */
    struct drm_device *drm_dev = dev_get_drvdata(dev);

    /* 停止异常事件监控线程 */
    rockchip_drm_error_event_fini(drm_dev);
    /* 注销sysfs调试与配置节点 */
    rockchip_drm_sysfs_fini(drm_dev);
    /* 释放fbdev兼容层资源 */
    rockchip_drm_fbdev_fini(drm_dev);
    /* 从内核注销DRM设备 */
    drm_dev_unregister(drm_dev);
    /* 销毁GEM显存内存池 */
    rockchip_gem_pool_destroy(drm_dev);
    /* 关闭热插拔轮询机制 */
    drm_kms_helper_poll_fini(drm_dev);
    /* 关闭原子显示会话,收尾硬件状态 */
    drm_atomic_helper_shutdown(drm_dev);
    /* 批量解绑所有显示子组件 */
    component_unbind_all(dev, drm_dev);
    /* 清理显示模式配置资源 */
    drm_mode_config_cleanup(drm_dev);
    /* 释放IOMMU虚拟内存映射资源 */
    rockchip_iommu_cleanup(drm_dev);

    /* 清空私有数据绑定关系,防止野指针 */
    drm_dev->dev_private = NULL;
    dev_set_drvdata(dev, NULL);
    /* 释放DRM设备结构体内存 */
    drm_dev_put(drm_dev);
}

3.4.4. IOMMU虚拟内存管理

IOMMU初始化

rockchip_drm_init_iommu函数分配IOMMU域名空间、初始化显存内存管理池、注册故障回调、预映射4G物理内存,为DRM显存提供虚拟地址映射能力,解决大内存寻址、显存碎片化问题。

IOMMU初始化(位于内核源码/drivers/gpu/drm/rockchip/rockchip_drm_drv.c)
 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
/* IOMMU虚拟内存初始化核心函数 */
static int rockchip_drm_init_iommu(struct drm_device *drm_dev)
{
    struct rockchip_drm_private *private = drm_dev->dev_private;
    struct iommu_domain_geometry *geometry;
    u64 start, end;
    int ret = 0;
    /* 无有效IOMMU硬件设备,直接跳过初始化 */
    if (IS_ERR_OR_NULL(private->iommu_dev))
        return 0;
    /* 分配IOMMU域名空间,用于管理显存虚实地址映射关系 */
    private->domain = iommu_domain_alloc(private->iommu_dev->bus);
    if (!private->domain)
        return -ENOMEM;
    /* 获取IOMMU域的寻址空间起止范围 */
    geometry = &private->domain->geometry;
    start = geometry->aperture_start;
    end = geometry->aperture_end;
    /* 打印IOMMU寻址区间 */
    DRM_DEBUG("IOMMU context initialized (aperture: %#llx-%#llx)\n",
        start, end);
    /* 初始化DRM内存管理池,基于IOMMU地址域管理显存分配与回收 */
    drm_mm_init(&private->mm, start, end - start + 1);
    /* 初始化内存操作互斥锁,防止多线程显存操作冲突 */
    mutex_init(&private->mm_lock);
    /* 注册IOMMU硬件异常故障回调函数 */
    iommu_set_fault_handler(private->domain, rockchip_drm_fault_handler,
                drm_dev);
    /* 开启4G内存预映射配置,适配32位系统寻址限制 */
    if (iommu_reserve_map) {
        /* 分段映射0-2G物理内存,赋予读写权限 */
        ret = iommu_map(private->domain, 0, 0, (size_t)SZ_2G,
                IOMMU_WRITE | IOMMU_READ | IOMMU_PRIV);
        if (ret)
            dev_err(drm_dev->dev, "failed to create 0-2G pre mapping\n");
        /* 分段映射2G-4G物理内存,完成4G全空间预映射 */
        ret = iommu_map(private->domain, SZ_2G, SZ_2G, (size_t)SZ_2G,
                IOMMU_WRITE | IOMMU_READ | IOMMU_PRIV);
        if (ret)
            dev_err(drm_dev->dev, "failed to create 2G-4G pre mapping\n");
    }
    return ret;
}

关键说明:

  • 第9行:校验IOMMU设备有效性,无有效IOMMU硬件则直接退出初始化。

  • 第12行:分配IOMMU域名空间,用于管理显存虚拟地址与物理地址映射。

  • 第16-18行:获取IOMMU域的寻址空间起止地址,确定显存虚拟内存映射范围。

  • 第23行:初始化DRM内存管理池(mm),依托IOMMU地址域管理显存分配与回收。

  • 第25行:初始化内存操作互斥锁,防止多线程显存分配冲突。

  • 第27-28行:注册IOMMU故障回调函数,映射异常时触发错误处理与日志打印。

  • 第30-41行:开启预映射配置时,分段映射0-2G、2G-4G物理内存,适配32位系统4G寻址限制。

IOMMU故障处理与限流

IOMMU故障处理与限流(位于内核源码/drivers/gpu/drm/rockchip/rockchip_drm_drv.c)
 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
46
47
48
49
50
51
52
53
/* 定义IOMMU故障日志限流规则:10秒内最多输出1条日志,防止高频报错刷屏 */
static DEFINE_RATELIMIT_STATE(fault_handler_rate, 10 * HZ, 1);

/* 故障日志限流判断函数 */
static int fault_handler_rate_limit(void)
{
    return __ratelimit(&fault_handler_rate);
}

/* 重置IOMMU故障限流状态 */
void rockchip_drm_reset_iommu_fault_handler_rate_limit(void)
{
    /* 清空计时与打印计数,恢复日志打印能力 */
    fault_handler_rate.begin = 0;
    fault_handler_rate.printed = 0;
}

/* IOMMU映射故障处理回调函数 */
static int rockchip_drm_fault_handler(struct iommu_domain *iommu,
                    struct device *dev,
                    unsigned long iova, int flags, void *arg)
{
    struct drm_device *drm_dev = arg;
    struct rockchip_drm_private *priv = drm_dev->dev_private;
    struct drm_crtc *crtc;
    bool handled = false;
    /* 打印故障标志位与累计故障次数 */
    DRM_ERROR("iommu fault handler flags: 0x%x: count: %lld\n",
        flags, ++priv->iommu_fault_count);
    /* 触发限流则直接退出,避免日志刷屏 */
    if (!fault_handler_rate_limit())
        return 0;
    /* 遍历所有CRTC显示通道,逐个处理异常 */
    drm_for_each_crtc(crtc, drm_dev) {
        int pipe = drm_crtc_index(crtc);
        /* 调用对应通道的IOMMU故障恢复接口 */
        if (priv->crtc_funcs[pipe] &&
            priv->crtc_funcs[pipe]->iommu_fault_handler &&
            !handled) {
            priv->crtc_funcs[pipe]->iommu_fault_handler(crtc, iommu);
            handled = true;
        }
        /* 打印当前通道VOP硬件寄存器信息,定位异常根源 */
        if (priv->crtc_funcs[pipe] &&
            priv->crtc_funcs[pipe]->regs_dump)
            priv->crtc_funcs[pipe]->regs_dump(crtc, NULL);
        /* 输出通道调试日志 */
        if (priv->crtc_funcs[pipe] &&
            priv->crtc_funcs[pipe]->debugfs_dump)
            priv->crtc_funcs[pipe]->debugfs_dump(crtc, NULL);
    }
    return 0;
}

关键说明:

  • 第2行:定义限流规则,10秒内最多打印1条故障日志,避免高频报错刷屏。

  • 第5-7行:限流判断函数,校验当前是否允许打印故障日志。

  • 第11-16行:限流状态重置函数,清空计时与打印计数,恢复日志打印能力。

  • 第31-32行:触发限流则直接退出,不重复打印日志。

  • 第34-51行:遍历所有CRTC通道,调用对应通道的IOMMU故障恢复函数,完成异常处理。

IOMMU资源释放与设备绑定解绑

IOMMU资源释放与设备绑定解绑(位于内核源码/drivers/gpu/drm/rockchip/rockchip_drm_drv.c)
 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
46
47
48
49
50
51
52
53
54
55
/* IOMMU资源清理释放函数 */
static void rockchip_iommu_cleanup(struct drm_device *drm_dev)
{
    struct rockchip_drm_private *private = drm_dev->dev_private;
    /* 无有效IOMMU域,无需清理直接返回 */
    if (!private->domain)
        return;
    /* 预映射模式下,分段解除4G物理内存映射 */
    if (iommu_reserve_map) {
        iommu_unmap(private->domain, 0, (size_t)SZ_2G);
        iommu_unmap(private->domain, SZ_2G, (size_t)SZ_2G);
    }
    /* 销毁DRM显存内存管理池 */
    drm_mm_takedown(&private->mm);
    /* 释放IOMMU域名空间,还原硬件初始状态 */
    iommu_domain_free(private->domain);
}

/* DMA设备挂载IOMMU域函数 */
int rockchip_drm_dma_attach_device(struct drm_device *drm_dev,
                struct device *dev)
{
    struct rockchip_drm_private *private = drm_dev->dev_private;
    int ret;
    /* 无IOMMU域直接返回成功 */
    if (!private->domain)
        return 0;
    /* ARM架构下先解绑原有DMA映射,防止冲突 */
    if (IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU)) {
        struct dma_iommu_mapping *mapping = to_dma_iommu_mapping(dev);
        if (mapping) {
            arm_iommu_detach_device(dev);
            arm_iommu_release_mapping(mapping);
        }
    }
    /* 将设备挂载到DRM专属IOMMU域 */
    ret = iommu_attach_device(private->domain, dev);
    if (ret) {
        DRM_DEV_ERROR(dev, "Failed to attach iommu device\n");
        return ret;
    }
    return 0;
}

/* DMA设备解绑IOMMU域函数 */
void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
                    struct device *dev)
{
    struct rockchip_drm_private *private = drm_dev->dev_private;
    /* 无有效IOMMU域无需解绑 */
    if (!private->domain)
        return;
    /* 解除设备IOMMU映射绑定 */
    iommu_detach_device(private->domain, dev);
}

关键说明:

  • 第6-12行:无有效域名空间则直接返回,预映射模式下分段解除4G内存映射。

  • 第14行:销毁DRM显存内存管理池,释放内存管理资源。

  • 第16行:释放IOMMU域名空间,还原硬件初始状态。

  • 第29-35行:设备挂载函数,ARM架构下先解绑原有DMA映射,避免映射冲突。

  • 第37行:将显示子设备挂载到统一IOMMU域,实现显存共享映射。

  • 第46-55行:设备解绑函数,设备卸载时解除子设备与IOMMU域的绑定,释放资源。

3.4.5. DRM自定义属性配置

rockchip_drm_create_properties函数用于创建平台专属原子属性、枚举属性、只读硬件属性,扩展标准DRM能力,支持色域、分屏、异步刷新、色彩校准等定制功能。

属性创建

属性创建(位于内核源码/drivers/gpu/drm/rockchip/rockchip_drm_drv.c)
 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/* 瑞芯微DRM自定义属性创建函数 */
static int rockchip_drm_create_properties(struct drm_device *dev)
{
    struct drm_property *prop;
    struct rockchip_drm_private *private = dev->dev_private;

    /* 创建EOTF色域映射档位属性,0-5档位原子可调 */
    prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC,
                    "EOTF", 0, 5);
    if (!prop)
        return -ENOMEM;
    private->eotf_prop = prop;

    /* 创建异步提交开关属性,0关闭/1开启,解决闪屏问题 */
    prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC,
                    "ASYNC_COMMIT", 0, 1);
    if (!prop)
        return -ENOMEM;
    private->async_commit_prop = prop;

    /* 创建画面共享ID属性,用于多图层、多画面资源共享 */
    prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC,
                    "SHARE_ID", 0, UINT_MAX);
    if (!prop)
        return -ENOMEM;
    private->share_id_prop = prop;

    /* 创建只读连接器ID,作为显示外设唯一硬件标识 */
    prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_IMMUTABLE,
                    "CONNECTOR_ID", 0, 0xf);
    if (!prop)
        return -ENOMEM;
    private->connector_id_prop = prop;

    /* 创建分屏模式枚举属性,适配屏幕拼接、分屏显示场景 */
    prop = drm_property_create_enum(dev, DRM_MODE_PROP_ENUM, "SPLIT_AREA",
                    split_area,
                    ARRAY_SIZE(split_area));
    private->split_area_prop = prop;

    /* 创建只读SOC硬件标识属性,用于硬件适配与日志定位 */
    prop = drm_property_create_object(dev,
                    DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_IMMUTABLE,
                    "SOC_ID", DRM_MODE_OBJECT_CRTC);
    private->soc_id_prop = prop;

    /* 创建只读PORT端口标识属性,区分不同显示输出端口 */
    prop = drm_property_create_object(dev,
                    DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_IMMUTABLE,
                    "PORT_ID", DRM_MODE_OBJECT_CRTC);
    private->port_id_prop = prop;

    /* 创建显示时钟ACLK自定义可调属性 */
    private->aclk_prop = drm_property_create_range(dev, 0, "ACLK", 0, UINT_MAX);
    /* 创建画面背景色自定义属性 */
    private->bg_prop = drm_property_create_range(dev, 0, "BACKGROUND", 0, UINT_MAX);
    /* 创建行标志位自定义属性 */
    private->line_flag_prop = drm_property_create_range(dev, 0, "LINE_FLAG1", 0, UINT_MAX);

    /* 创建LUT调色参数二进制属性,支持自定义色彩校准 */
    private->cubic_lut_prop = drm_property_create(dev, DRM_MODE_PROP_BLOB, "CUBIC_LUT", 0);
    /* 创建LUT尺寸只读属性,标识调色参数大小 */
    private->cubic_lut_size_prop = drm_property_create_range(dev, DRM_MODE_PROP_IMMUTABLE,
                                "CUBIC_LUT_SIZE", 0, UINT_MAX);

    /* 初始化TV通用属性:亮度、对比度、饱和度、色调 */
    return drm_mode_create_tv_properties(dev, 0, NULL);
}

关键说明:

  • 第8-12行:创建EOTF色域映射属性,支持0-5档位调节,为原子操作属性。

  • 第15-19行:创建异步提交属性,0/1开关控制,支持显示配置异步刷新,杜绝闪屏。

  • 第22-26行:创建画面共享ID属性,用于多图层、多画面共享渲染资源。

  • 第29-33行:创建只读连接器ID属性,标识不同显示外设,硬件唯一标识。

  • 第36-39行:创建分屏模式枚举属性,适配多尺寸分屏、拼接显示场景。

  • 第42-51行:创建SOC、PORT只读硬件标识属性,用于硬件适配与日志定位。

  • 第54-58行:创建时钟、背景色、行标志自定义数值属性,支撑显示时序与底色配置。

  • 第61-63行:创建LUT调色参数属性与尺寸属性,支持自定义色彩校准。

  • 第67行:初始化TV通用亮度、对比度等基础属性,兼容电视显示场景。

属性默认值配置

rockchip_drm_set_property_default函数基于DRM原子框架,批量初始化显示基础画质参数默认值,保证设备开机显示正常,无偏色、过亮、过暗等问题。

属性默认值配置(位于内核源码/drivers/gpu/drm/rockchip/rockchip_drm_drv.c)
 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
46
47
48
49
50
51
52
53
/* 显示属性默认值初始化配置函数 */
static void rockchip_drm_set_property_default(struct drm_device *drm)
{
    struct drm_connector *connector;
    struct drm_mode_config *conf = &drm->mode_config;
    struct drm_atomic_state *state;
    int ret;
    struct drm_connector_list_iter conn_iter;

    /* 加全局显示锁,防止配置过程中参数被篡改 */
    drm_modeset_lock_all(drm);
    /* 复制当前原子状态,基于现有状态修改,避免全局刷新异常 */
    state = drm_atomic_helper_duplicate_state(drm, conf->acquire_ctx);
    if (IS_ERR(state)) {
        DRM_ERROR("failed to alloc atomic state\n");
        goto err_unlock;
    }
    /* 绑定状态上下文,保证原子配置上下文统一 */
    state->acquire_ctx = conf->acquire_ctx;

    /* 遍历所有显示连接器设备 */
    drm_connector_list_iter_begin(drm, &conn_iter);
    drm_for_each_connector_iter(connector, &conn_iter) {
        struct drm_connector_state *connector_state;
        /* 获取当前连接器的属性配置状态 */
        connector_state = drm_atomic_get_connector_state(state,
                                connector);
        if (IS_ERR(connector_state)) {
            DRM_ERROR("Connector[%d]: Failed to get state\n", connector->base.id);
            continue;
        }
        /* 初始化画质基础参数为中间默认值,保证开机画面正常 */
        connector_state->tv.brightness = 50;
        connector_state->tv.contrast = 50;
        connector_state->tv.saturation = 50;
        connector_state->tv.hue = 50;
    }
    /* 结束连接器遍历,释放迭代资源 */
    drm_connector_list_iter_end(&conn_iter);

    /* 原子批量提交所有默认属性配置 */
    ret = drm_atomic_commit(state);
    /* 检测死锁异常 */
    WARN_ON(ret == -EDEADLK);
    if (ret)
        DRM_ERROR("Failed to update properties\n");

    /* 释放原子状态内存资源 */
    drm_atomic_state_put(state);
err_unlock:
    /* 解锁全局显示锁,完成初始化配置 */
    drm_modeset_unlock_all(drm);
}

3.4.6. 错误事件监控

内核线程循环监听显示硬件异常事件捕获IOMMU故障、时序异常、硬件报错,推送事件至用户态。

错误事件监控(位于内核源码/drivers/gpu/drm/rockchip/rockchip_drm_drv.c)
 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
/* 显示异常事件监控内核线程 */
static int rockchip_drm_error_event_thread(void *data)
{
    struct drm_device *drm_dev = data;
    struct rockchip_drm_private *priv = drm_dev->dev_private;
    struct rockchip_drm_error_event *error_event = &priv->error_event;
    struct drm_event_vblank *e;
    int ret = 0;
    int cnt = 0;

    /* 线程循环,未收到停止信号持续运行 */
    while (!kthread_should_stop()) {
        e = &error_event->event;
        /* 重置异常状态,等待新异常触发 */
        error_event->error_state = false;
        /* 阻塞等待异常事件,响应中断信号 */
        ret = wait_event_interruptible(error_event->wait, error_event->error_state);
        /* 捕获到显示异常事件 */
        if (!ret) {
            /* 通过sysfs向用户态推送异常通知 */
            sysfs_notify(&drm_dev->dev->kobj, NULL, "error_event");
            /* 打印异常类型与累计次数,统计故障频次 */
            drm_info(drm_dev, "rockchipdrm send_error_event_type: 0x%x, count:%d\n",
                e->base.type, ++cnt);
        }
    }
    return 0;
}

3.4.7. 瑞芯微DRM驱动测试

注解

编写C程序测试瑞芯微DRM可参考板卡配套的 <<Linux基础与应用开发实战指南>>的DRM相关章节 进行测试。

以下使用LubanCat2板卡连接HDMI和5.5寸MIPI屏幕为例,通过modetest工具对DRM进行简单测试。

3.4.7.1. 查看Connector

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#查看Connector
modetest -M rockchip -c

#信息打印如下
Connectors:
id      encoder status          name            size (mm)       modes   encoders
175     174     connected       HDMI-A-1        600x340         44      174
    modes:
            index name refresh (Hz) hdisp hss hse htot vdisp vss vse vtot
    #0 1920x1080 60.00 1920 2008 2052 2200 1080 1084 1089 1125 148500 flags: phsync, pvsync; type: preferred, driver
    #1 4096x2160 60.00 4096 4184 4272 4400 2160 2168 2178 2250 594000 flags: phsync, pvsync; type: driver
    #2 4096x2160 59.94 4096 4184 4272 4400 2160 2168 2178 2250 593407 flags: phsync, pvsync; type: driver

    ....

191     190     connected       DSI-1           0x0             1       190
    modes:
            index name refresh (Hz) hdisp hss hse htot vdisp vss vse vtot
    #0 1080x1920 60.00 1080 1090 1100 1120 1920 1930 1935 1955 131376 flags: nhsync, nvsync; type: preferred, driver

表头含义如下:

  • id:Connector编号。

  • encoder:当前绑定的 Encoder ID。

  • status:connected表示已接屏幕,disconnected表示未接屏幕。

  • name:接口类型。

  • size (mm):屏幕物理尺寸。

  • modes:支持的分辨率个数。

  • encoders:可绑定的Encoder ID列表。

可以确认当前系统有两个物理显示接口,分别是HDMI-A-1和DSI-1,其中:

  • Connectors id=175 :HDMI-A-1,绑定Encoder ID为174,支持44种分辨率,连接状态为connected,首选分辨率(preferred)为1920x1080 60.00Hz

  • Connectors id=191 :DSI-1,绑定Encoder ID为190,只支持1中分辨率连接状态为connected,首选分辨率(preferred)为1080x1920 60.00Hz

3.4.7.2. 查看Plane

 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#查看Plane
modetest -M rockchip -p

#Planes信息打印如下
Planes:
id      crtc    fb      CRTC x,y        x,y     gamma size      possible crtcs
56      72      217     0,0             0,0     0               0x00000001
    formats: XR24 AR24 XB24 AB24 RG24 BG24 RG16 BG16
    props:
            8 type:
                    flags: immutable enum
                    enums: Overlay=0 Primary=1 Cursor=2
                    value: 1
            ...
            64 NAME:
                    flags: immutable bitmask
                    values: Smart0-win0=0x1
                    value: 1
    ...


80      96      220     0,0             0,0     0               0x00000002
    formats: XR24 AR24 XB24 AB24 RG24 BG24 RG16 BG16
    props:
            8 type:
                    flags: immutable enum
                    enums: Overlay=0 Primary=1 Cursor=2
                    value: 1
            ...
            88 NAME:
                    flags: immutable bitmask
                    values: Smart1-win0=0x2
                    value: 2
    ...


104     0       0       0,0             0,0     0               0x00000002
    formats: XR24 AR24 XB24 AB24 RG24 BG24 RG16 BG16 NV12 NV16 NV24 NV15 NV20 NV30 YVYU VYUY
    props:
            8 type:
                    flags: immutable enum
                    enums: Overlay=0 Primary=1 Cursor=2
                    value: 0
            ...
            112 NAME:
                    flags: immutable bitmask
                    values: Esmart1-win0=0x4
                    value: 4
    ...


120     0       0       0,0             0,0     0               0x00000001
    formats: XR24 AR24 XB24 AB24 RG24 BG24 RG16 BG16 NV12 NV16 NV24 NV15 NV20 NV30 YVYU VYUY
    props:
            8 type:
                    flags: immutable enum
                    enums: Overlay=0 Primary=1 Cursor=2
                    value: 0
            ...
            128 NAME:
                    flags: immutable bitmask
                    values: Esmart0-win0=0x8
                    value: 8
    ...


136     0       0       0,0             0,0     0               0x00000001
    formats: XR30 XB30 XR24 AR24 XB24 AB24 RG24 BG24 RG16 BG16 YU08 YU10 YUYV Y210
    props:
            8 type:
                    flags: immutable enum
                    enums: Overlay=0 Primary=1 Cursor=2
                    value: 0
            ...
            144 NAME:
                    flags: immutable bitmask
                    values: Cluster0-win0=0x10
                    value: 16
    ...


152     0       0       0,0             0,0     0               0x00000002
    formats: XR30 XB30 XR24 AR24 XB24 AB24 RG24 BG24 RG16 BG16 YU08 YU10 YUYV Y210
    props:
            8 type:
                    flags: immutable enum
                    enums: Overlay=0 Primary=1 Cursor=2
                    value: 0
            ...
            160 NAME:
                    flags: immutable bitmask
                    values: Cluster1-win0=0x40
                    value: 64
    ...

表头含义如下:

  • id:Plane 硬件唯一编号。

  • crtc:当前绑定到哪个CRTC,0表示未绑定任何屏幕。

  • fb:绑定的显存ID;0表示没有加载画面。

  • CRTC x,y:这个图层在屏幕上的显示起始坐标,比如 0,0 就是屏幕左上角。

  • x,y:图层原始图像的裁剪起始坐标,一般用 0,0。

  • gamma size:伽马校正尺寸;0表示此图层不支持伽马调节。

  • possible crtcs:这个图层能绑定哪些CRTC,位掩码:此处0x1表示只能绑HDMI,0x2表示只能绑DSI。

额外关键字段:

  • formats:图层支持的像素格式。

  • type:图层类型 Primary主图层/Overlay叠加层。

  • NAME:瑞芯微硬件图层分组名称(Smart/Esmart/Cluster)。

从打印信息可看到当前设备一共6个硬件图层,分成2个主图层 + 4个叠加图层,其中:

  1. Plane id=56

1
2
3
4
56      72      217     0,0             0,0     0       0x00000001
formats: 只有XR24/AR24/XB24等RGB格式
type: value=1 -> Primary
NAME: Smart0-win0
  • 绑定:CRTC 72(HDMI 屏幕)

  • 类型:Primary 主图层

  • 作用:HDMI屏幕的桌面/UI主画面图层,当前正在工作,有fb=217

  1. Plane id=80

1
2
3
4
80      96      220     0,0             0,0     0       0x00000002
formats: 只有XR24/AR24/XB24等RGB格式
type: value=1 -> Primary
NAME: Smart1-win0
  • 绑定:CRTC 96(DSI 屏幕)

  • 类型:Primary 主图层

  • 作用:DSI屏幕的桌面/UI主画面图层,当前正在工作,有fb=220

注解

56、80是两块屏主图层,只负责普通UI桌面,不做视频解码。

  1. Plane id=104

1
2
3
4
104     0       0       0,0             0,0     0       0x00000002
formats: RGB + NV12/NV16/YUYV 等标准视频格式
type: value=0 -> Overlay
NAME: Esmart1-win0
  • 绑定:当前未绑定(crtc=0表示空闲,0x2表示仅能绑定DSI屏幕)

  • 类型:Overlay 视频叠加层

  • 作用:给DSI屏幕做视频、摄像头硬件播放

  1. Plane id=120

1
2
3
4
120     0       0       0,0     0,0     0   0x00000001
formats: RGB + NV12/NV16/YUYV 等标准视频格式
type: value=0 -> Overlay
NAME: Esmart0-win0
  • 绑定:当前未绑定(crtc=0表示空闲,0x1表示仅能绑定HDMI屏幕)

  • 类型:Overlay 视频叠加层

  • 作用:给HDMI屏幕做视频、摄像头硬件播放

  1. Plane id=136

1
2
3
4
136     0       0       0,0     0,0     0   0x00000001
formats: 支持XR30/XB30(30bit高色深) + YU08/YU10/Y210(HDR视频)
type: Overlay=0
NAME: Cluster0-win0
  • 绑定:当前未绑定(crtc=0表示空闲,0x1表示仅能绑定HDMI屏幕)

  • 类型:专用压缩图层:仅支持AFBC硬件压缩格式,不支持普通线性RGB格式,支持播放30bit高色深、HDR视频、高清流媒体视频

  • 作用:HDMI屏幕播放4K/HDR/10bit高清视频

  1. Plane id=152

1
2
3
4
152     0       0       0,0     0,0     0   0x00000002
formats: 支持XR30/XB30(30bit高色深) + YU08/YU10/Y210(HDR视频)
type: Overlay=0
NAME: Cluster1-win0
  • 绑定:当前未绑定(crtc=0表示空闲,0x2表示仅能绑定DSI屏幕)

  • 类型:专用压缩图层:仅支持AFBC硬件压缩格式,不支持普通线性RGB格式,支持播放30bit高色深、HDR视频、高清流媒体视频

  • 作用:DSI屏幕播放4K/HDR/10bit高清视频

3.4.7.3. 查看Encoder

1
2
3
4
5
6
7
8
9
#查看Encoder
modetest -M rockchip -e

#信息打印如下
Encoders:
id      crtc    type    possible crtcs  possible clones
168     0       Virtual 0x00000003      0x00000001
174     72      TMDS    0x00000001      0x00000002
190     96      DSI     0x00000002      0x00000004

表头含义如下:

  • id:Encoder 硬件编号

  • crtc:当前已经绑定的CRTC编号,0表示未绑定空闲

  • type:编码器类型:

    • TMDS:HDMI专用编码器

    • DSI:MIPI屏专用编码器

    • Virtual:虚拟编码器

  • possible crtcs:允许绑定哪些CRTC(掩码)

  • possible clones:支持双屏同显,可克隆输出到哪个接口

当前系统有3个Encoder,其中:

  1. Encoder id=168

1
168     0       Virtual 0x00000003      0x00000001
  • 绑定:crtc=0,没有绑定任何显示通道

  • 类型:Virtual 虚拟编码器

  • possible crtcs=0x03:同时兼容CRTC 72、CRTC 96

  • 作用:虚拟显示、投屏、软件合成,不对应物理屏幕

  1. Encoder id=174

1
174     72      TMDS    0x00000002
  • 绑定:crtc=72,和HDMI显示通道绑定

  • 类型:TMDS 标准HDMI/DVI编码器

  • possible crtcs=0x00000001:只能给 CRTC 72 使用

  • 对应物理口:HDMI-A-1

  1. Encoder id=190

1
190     96      DSI     0x00000002      0x00000004
  • 绑定:crtc=96,和DSI屏幕通道绑定

  • 类型:DSI MIPI屏专用编码器

  • possible crtcs=0x00000002:只能给 CRTC 96 使用

  • 对应物理口:DSI-1

3.4.7.4. 刷屏测试

可以通过modetest -s设置屏幕显示模式,命令参数如下:

1
modetest -M <DRM驱动名称> -s <Connector ID>@<CRTC ID>:<分辨率>-<刷新率>

结合前面获取到的屏幕Connector id、crtc、分辨率等信息,得到命令如下:

1
2
3
4
5
#刷HDMI屏幕,根据实际修改,此处Connector id=175、crtc=72,分辨率1920x1080,刷新率60Hz
modetest -M rockchip -s 175@72:1920x1080-60

#刷DSI屏幕,根据实际修改,此处Connector id=191、crtc=96,分辨率1080x1920,刷新率60Hz
modetest -M rockchip -s 191@96:1080x1920-60

执行后可以在屏幕上看到彩条,效果如下图:

../_images/subsystem_drm_1.jpg

3.5. 瑞芯微DRM Direct Show接口

瑞芯微DRM驱动提供了DRM直显的轻量化功能,基于标准DRM框架开发,脱离Linux标准原子显示提交流程,提供低延迟、高适配的底层直接显示接口。 可用于开机Logo、底层裸屏直刷、实时视频预览、无桌面系统显示等轻量化场景,无需上层Compositor合成,可直接操作DRM图层、申请显存、提交画面,极大降低显示延迟与系统资源占用。

Direct Show接口驱动位于: 内核源码/drivers/gpu/drm/rockchip/rockchip_drm_direct_show.c

以下以 瑞芯微6.1.99内核版本 结合源码函数逐一分析。

3.5.1. 获取DRM设备

rockchip_drm_get_dev函数用于获取DRM设备结构体,通过遍历系统所有DRM设备,匹配瑞芯微DRM设备,为后续显存申请、画面提交、图层操作提供设备句柄,是所有直显功能的前置基础。

rockchip_drm_get_dev函数(位于内核源码/drivers/gpu/drm/rockchip/rockchip_drm_direct_show.c)
 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
/* 获取瑞芯微DRM设备结构体 */
struct drm_device *rockchip_drm_get_dev(void)
{
    int i;
    /* 目标驱动名称 */
    char *name = "rockchip";

    /* 遍历0~63号DRM主设备号,覆盖所有可能的DRM设备 */
    for (i = 0; i < 64; i++) {
        struct drm_minor *minor;

        /* 获取对应序号的DRM主设备次级设备 */
        minor = drm_minor_acquire(i + DRM_MINOR_PRIMARY);

        /* 获取设备失败,跳过当前序号 */
        if (IS_ERR(minor))
            continue;

        /* 校验设备、驱动、驱动名称合法性 */
        if (!minor->dev || !minor->dev->driver ||
            !minor->dev->driver->name)
            continue;

        /* 匹配瑞芯微驱动名称,匹配成功返回DRM主控设备 */
        if (!name)
            return minor->dev;
        if (!strcmp(name, minor->dev->driver->name))
            return minor->dev;
    }

    /* 未匹配到设备 */
    return NULL;
}

3.5.2. 申请内存

rockchip_drm_direct_show_alloc_buffer函数用于申请直显专用显存缓冲区,通过计算显存大小、对齐行间距、创建GEM显存、映射虚拟/物理地址、导出DMA-BUF,实现显存从申请、初始化、地址映射、句柄导出流程。

rockchip_drm_direct_show_alloc_buffer函数(位于内核源码/drivers/gpu/drm/rockchip/rockchip_drm_direct_show.c)
 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
/* 申请直显专用显存缓冲区 */
int rockchip_drm_direct_show_alloc_buffer(struct drm_device *drm,
                                        struct rockchip_drm_direct_show_buffer *buffer)
{
        u32 min_pitch;
        const struct drm_format_info *format_info;
        struct drm_mode_create_dumb args;
        struct rockchip_gem_object *rk_obj;
        struct drm_gem_object *obj;
        struct dma_buf *dmabuf;
        int dmabuf_fd;

        /* 赋值画面宽高参数 */
        args.width = buffer->width;
        args.height = buffer->height;
        /* 获取像素格式详细信息 */
        format_info = drm_format_info(buffer->pixel_format);
        /* 计算当前格式像素位宽 */
        args.bpp = rockchip_drm_get_bpp(format_info);

        /* 计算最小行间距,并按64字节对齐 */
        min_pitch = args.width * DIV_ROUND_UP(args.bpp, 8);
        args.pitch = ALIGN(min_pitch, 64);
        /* 计算基础显存大小 */
        args.size = args.pitch * args.height;
        /* 继承缓冲区标识位 */
        args.flags = buffer->flag;

        /* 适配YUV格式,追加UV层显存大小 */
        if (format_info->is_yuv) {
                int bpp = 0;
                /* 获取UV分量位宽 */
                bpp = format_info->cpp[1] * 8;
                min_pitch = args.width * DIV_ROUND_UP(bpp, 8);
                min_pitch = ALIGN(min_pitch, 64);
                /* 根据YUV子采样比例,计算UV层显存并累加总大小 */
                args.size += min_pitch * args.height / format_info->hsub / format_info->vsub;
        }

        /* 创建瑞芯微GEM显存对象 */
        rk_obj = rockchip_gem_create_object(drm, args.size, true, args.flags);
        if (IS_ERR(rk_obj)) {
                DRM_DS_ERR("create rk_obj failed\n");
                return -ENOMEM;
        }
        obj = &rk_obj->base;

        /* 回填显存基础信息到缓冲区结构体 */
        buffer->bpp = args.bpp;
        buffer->pitch[0] = args.pitch;
        buffer->vir_addr[0] = rk_obj->kvaddr;    /* 内核虚拟地址,供CPU读写 */
        buffer->phy_addr[0] = rk_obj->dma_handle;/* 物理DMA地址,供硬件显示 */
        buffer->rk_gem_obj = rk_obj;

        /* YUV格式二层地址偏移配置 */
        if (format_info->is_yuv) {
                buffer->vir_addr[1] = buffer->vir_addr[0] + buffer->width * buffer->height;
                buffer->pitch[1] = buffer->pitch[0];
                buffer->phy_addr[1] = buffer->phy_addr[0] + buffer->width * buffer->height;
        }

        /* 基于GEM显存创建标准DRM帧缓冲区 */
        rockchip_drm_direct_show_alloc_fb(drm, buffer);

        /* 加锁导出DMA-BUF,保障多线程安全 */
        mutex_lock(&drm->object_name_lock);
        /* 将GEM显存导出为DMA-BUF,支持跨进程、硬件共享 */
        dmabuf = obj->funcs->export(obj, 0);
        if (IS_ERR(dmabuf)) {
                mutex_unlock(&drm->object_name_lock);
                goto err_gem_free;
        }
        /* 绑定DMA-BUF到GEM对象,增加引用计数 */
        obj->dma_buf = dmabuf;
        get_dma_buf(obj->dma_buf);
        drm_gem_dmabuf_release(obj->dma_buf);
        mutex_unlock(&drm->object_name_lock);

        /* 将DMA-BUF转换为用户态可操作的文件描述符 */
        dmabuf_fd = dma_buf_fd(dmabuf, 0);
        if (dmabuf_fd < 0) {
                DRM_DS_ERR("failed dma_buf_fd, ret %d\n", dmabuf_fd);
                goto err_free_dmabuf;
        }
        /* 回填DMA-BUF文件句柄 */
        buffer->dmabuf_fd = dmabuf_fd;

        DRM_DS_DBG("alloc buffer: 0x%p, dma buf fd:%d, args.pitch:%d\n", buffer->rk_gem_obj, dmabuf_fd, args.pitch);
        return 0;

        /* 异常资源释放分支 */
err_free_dmabuf:
        dma_buf_put(dmabuf);
err_gem_free:
        drm_gem_object_put(&rk_obj->base);
        return -ENOMEM;
}

3.5.3. 获取crtc

rockchip_drm_direct_show_get_crtc函数用于获取激活的CRTC显示通道,通过遍历CRTC通道,筛选正在工作、已激活的显示通道。

rockchip_drm_direct_show_get_crtc函数(位于内核源码/drivers/gpu/drm/rockchip/rockchip_drm_direct_show.c)
 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
/* 获取激活的CRTC显示通道 */
struct drm_crtc *rockchip_drm_direct_show_get_crtc(struct drm_device *drm, const char *name)
{
    struct drm_crtc *crtc = NULL;
    bool crtc_active = false;

    /* 遍历所有CRTC显示通道 */
    drm_for_each_crtc(crtc, drm) {
        /* 名称为空:自动匹配任意处于激活状态的CRTC */
        if (name == NULL) {
            if (crtc->state && crtc->state->active) {
                crtc_active = true;
                break;
            }
        } else {
            /* 指定名称:精准匹配名称且状态激活的CRTC通道 */
            if (crtc->state && crtc->state->active &&
                !strncmp(crtc->name, name, DRM_PROP_NAME_LEN)) {
                crtc_active = true;
                break;
            }
        }
    }

    /* 未检索到有效激活CRTC */
    if (crtc_active == false) {
            DRM_DS_ERR("failed to find active crtc\n");
            return NULL;
    }
    DRM_DS_DBG("get crtc[%s] success\n", crtc->name);

    return crtc;
}

3.5.4. 获取plane

rockchip_drm_direct_show_get_plane函数用于根据名称获取DRM图层,通过遍历所有DRM图层,根据名称精准匹配目标图层。

rockchip_drm_direct_show_get_plane函数(位于内核源码/drivers/gpu/drm/rockchip/rockchip_drm_direct_show.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/* 根据名称匹配DRM图层 */
struct drm_plane *rockchip_drm_direct_show_get_plane(struct drm_device *drm, const char *name)
{
    struct drm_plane *plane;

    /* 遍历DRM设备下所有注册的显示图层 */
    drm_for_each_plane(plane, drm) {
        /* 匹配图层名称,匹配成功则终止遍历 */
        if (!strncmp(plane->name, name, DRM_PROP_NAME_LEN))
            break;
    }

    /* 遍历结束未找到对应图层,打印错误日志并返回空 */
    if (!plane) {
        DRM_DS_ERR("failed to find plane:%s!\n", name);
        return NULL;
    }

    DRM_DS_DBG("get plane[%s] success\n", plane->name);
    return plane;
}

3.5.5. 提交显示

rockchip_drm_direct_show_commit函数用于直显画面提交渲染,跳过标准DRM原子提交流程,直接调用硬件图层更新接口,大幅降低显示延迟。可支持置顶图层、自定义源/目标坐标等显示需求。

rockchip_drm_direct_show_commit函数(位于内核源码/drivers/gpu/drm/rockchip/rockchip_drm_direct_show.c)
 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
46
47
48
49
50
51
52
53
/* 直显画面提交渲染 */
int rockchip_drm_direct_show_commit(struct drm_device *drm,
                    struct rockchip_drm_direct_show_commit_info *commit_info)
{
    int ret = 0;
    struct drm_plane *plane = commit_info->plane;
    struct drm_crtc *crtc = commit_info->crtc;
    struct drm_framebuffer *fb = commit_info->buffer->fb;
    struct drm_mode_config *conf = &drm->mode_config;
    struct drm_property *zpos_prop;

    /* 查找图层层级属性zpos */
    zpos_prop = rockchip_drm_direct_show_find_prop(drm, &plane->base, "zpos");
    if (!zpos_prop)
        DRM_DS_ERR("failed to find plane zpos prop, ret:%d\n", ret);

    /* 加全局显示锁,防止画面刷新冲突 */
    drm_modeset_lock_all(drm);

    /* 配置图层为最高层级,置顶显示,覆盖其他图层 */
    if (commit_info->top_zpos && zpos_prop) {
        ret = rockchip_drm_direct_show_set_property_value(&plane->base,
                                zpos_prop,
                                zpos_prop->values[1]);
        if (ret)
            DRM_DS_ERR("failed to set plane zpos prop, ret:%d\n", ret);
        plane->state->zpos = zpos_prop->values[1];
    }

    /* 直接调用硬件图层更新接口,提交画面到硬件 */
    ret = plane->funcs->update_plane(plane, crtc, fb,
                    commit_info->dst_x, commit_info->dst_y,
                    commit_info->dst_w, commit_info->dst_h,
                    /* DRM 16.16定点小数坐标格式 */
                    commit_info->src_x << 16,
                    commit_info->src_y << 16,
                    commit_info->src_w << 16,
                    commit_info->src_h << 16,
                    conf->acquire_ctx);

    /* 解锁显示模式 */
    drm_modeset_unlock_all(drm);
    if (ret)
        return ret;

    DRM_DS_DBG("commit success: plane[%s], crtc[%s], src[%dx%d@%dx%d], dst[%dx%d@%dx%d]\n",
        plane->name, crtc->name,
        commit_info->src_w, commit_info->src_h,
        commit_info->src_x, commit_info->src_y,
        commit_info->dst_w, commit_info->dst_h,
        commit_info->dst_x, commit_info->dst_y);
    return ret;
}

3.5.6. 关闭图层

rockchip_drm_direct_show_disable_plane函数用于关闭指定显示图层,通过锁定显示设备,调用硬件接口关闭图层输出。

rockchip_drm_direct_show_disable_plane函数(位于内核源码/drivers/gpu/drm/rockchip/rockchip_drm_direct_show.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/* 关闭指定显示图层 */
int rockchip_drm_direct_show_disable_plane(struct drm_device *drm, struct drm_plane *plane)
{
    int ret = 0;
    struct drm_mode_config *conf = &drm->mode_config;

    DRM_DS_DBG("disable plane: %s\n", plane->name);
    drm_modeset_lock_all(drm);
    /* 调用硬件接口禁用图层 */
    ret = plane->funcs->disable_plane(plane, conf->acquire_ctx);
    drm_modeset_unlock_all(drm);
    return ret;
}

3.5.7. 释放内存

rockchip_drm_direct_show_free_buffer函数用于释放直显显存缓冲区资源,通过反向释放DMA-BUF、GEM显存资源,清零句柄,防止内存泄漏。

rockchip_drm_direct_show_free_buffer函数(位于内核源码/drivers/gpu/drm/rockchip/rockchip_drm_direct_show.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* 释放直显显存缓冲区资源 */
void rockchip_drm_direct_show_free_buffer(struct drm_device *drm,
                    struct rockchip_drm_direct_show_buffer *buffer)
{
    struct drm_gem_object *obj = &buffer->rk_gem_obj->base;

    DRM_DS_DBG("free buffer: 0x%p\n", buffer->rk_gem_obj);

    /* 加锁,安全释放DMA-BUF资源,避免多线程资源竞争 */
    mutex_lock(&drm->object_name_lock);
    if (obj->dma_buf) {
        /* 递减DMA-BUF引用计数,释放跨进程共享句柄 */
        dma_buf_put(obj->dma_buf);
        /* 清空DMA-BUF指针,防止野指针、重复释放 */
        obj->dma_buf = NULL;
    }
    /* 解锁 */
    mutex_unlock(&drm->object_name_lock);

    /* 递减GEM显存对象引用计数,内核自动回收空闲显存内存 */
    drm_gem_object_put(obj);
}

3.6. DRM双缓冲动态彩条绘制实验

本实验驱动基于瑞芯微DRM Direct Show接口开发的内核态动态彩条绘制驱动,采用双缓冲绘图机制,无需用户层应用参与,纯内核线程自动刷新画面,实现8色循环滚动彩条效果。 同时提供sysfs配置节点,支持用户动态开关显示、切换显示图层(Plane)与显示通道(CRTC),适配多屏幕、多图层场景。

本章的示例代码目录为: linux_driver/37_drm_color_bar

3.6.1. 驱动代码详解

核心定义与数据结构

核心定义与数据结构(位于linux_driver/37_drm_color_bar/drm_color_bar.c)
 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
46
47
48
49
50
51
52
53
54
55
56
#include <linux/init.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/jiffies.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/kobject.h>
#include <drm/drm_device.h>
#include <drm/drm_crtc.h>
#include "rockchip_drm_drv.h"
#include "rockchip_drm_direct_show.h"

/* 定义显示缓冲区宽度 */
#define BUFFER_WIDTH        640
/* 定义显示缓冲区高度 */
#define BUFFER_HEIGHT       480
/* 定义像素格式 */
#define BUFFER_FORMAT       DRM_FORMAT_ARGB8888
/* 定义缓冲区数量 */
#define BUFFER_COUNT        2
/* 定义彩条总段数 */
#define BAR_TOTAL           8
/* 定义彩条偏移更新间隔 */
#define UPDATE_MS           1000

/* 默认PLANE、CRTC配置值 */
#define DEFAULT_PLANE_NAME     "Esmart0-win0"
#define DEFAULT_CRTC_INDEX     0

#define COLOR_WHITE        0xFFFFFFFF   /* 白色 */
#define COLOR_YELLOW       0xFFFFFF00   /* 黄色 */
#define COLOR_CYAN         0xFF00FFFF   /* 青色 */
#define COLOR_GREEN        0xFF00FF00   /* 绿色 */
#define COLOR_MAGENTA      0xFFFF00FF   /* 粉色 */
#define COLOR_RED          0xFFFF0000   /* 红色 */
#define COLOR_BLUE         0xFF0000FF   /* 蓝色 */
#define COLOR_BLACK        0xFF000000   /* 黑色 */

/* 驱动私有数据结构体 */
struct colorbar_drv_data {
    struct drm_device *ddev;            /* DRM设备句柄 */
    struct drm_crtc *crtc;              /* DRM显示控制器 */
    struct drm_plane *plane;            /* DRM图层 */
    struct rockchip_drm_direct_show_buffer *buf[BUFFER_COUNT];  /* Direct Show缓冲 */
    struct task_struct *thread;         /* 内核线程句柄 */
    int bar_offset;                     /* 彩条偏移量 */
    int front_buf;                      /* 显示缓冲索引 */
    int enable;                         /* 彩条显示使能开关 */
    char plane_name[32];                /* Plane名称 */
    int crtc_index;                     /* CRTC索引 */
    struct kobject *kobj;               /* sysfs kobject节点 */
    int hw_ready;                       /* 硬件绑定就绪标记 */
};

/* 定义全局驱动私有数据对象 */
static struct colorbar_drv_data drv_data;

关键说明:

  • 第10-11行:引用瑞芯微DRM驱动和Direct Show接口头文件。

  • 第14-18行:固定测试分辨率640*480,使用ARGB8888像素格式。

  • 第20-24行:定义缓冲区数量、彩条总段数和刷新间隔。

  • 第27-28行:默认绑定Esmart0-win0图层、CRTC通道索引为0。

  • 第40-56行:定义全局驱动私有数据结构体,包含所有运行状态与硬件句柄。

注解

此处crtc_index是CRTC索引编号,不是真实的CRTC ID,如根据前面“瑞芯微DRM驱动测试”小节数据,crtc_index=0 -> 内核遍历第1个CRTC -> 硬件真实CRTC ID=72,crtc_index=1 -> 内核遍历第2个CRTC -> 硬件真实CRTC ID=96。而plane_name的取值也在“瑞芯微DRM驱动测试”小节讲解过了,Esmart0-win0就是Plane id=120的图层,为Overlay 视频叠加层。

Sysfs配置节点

提供enable、plane_name、crtc_index三个sysfs接口用来控制彩条刷新、图层名称、crtc索引号。

Sysfs配置节点(位于linux_driver/37_drm_color_bar/drm_color_bar.c)
 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/* sysfs读取彩条使能状态 */
static ssize_t enable_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
    return scnprintf(buf, PAGE_SIZE, "%d\n", drv_data.enable);
}

/* sysfs设置彩条使能状态 */
static ssize_t enable_store(struct kobject *kobj, struct kobj_attribute *attr,
                            const char *buf, size_t count)
{
    int val;
    if (kstrtoint(buf, 10, &val) < 0)
        return -EINVAL;

    /* 仅允许0/1配置 */
    drv_data.enable = (val ? 1 : 0);
    return count;
}

/* sysfs读取当前绑定的Plane名称 */
static ssize_t plane_name_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
    return scnprintf(buf, PAGE_SIZE, "%s\n", drv_data.plane_name);
}

/* sysfs修改绑定的Plane名称 */
static ssize_t plane_name_store(struct kobject *kobj, struct kobj_attribute *attr,
                                const char *buf, size_t count)
{
    /* 限制名称长度,防止溢出 */
    strncpy(drv_data.plane_name, buf, sizeof(drv_data.plane_name) - 1);
    /* 去除换行符 */
    drv_data.plane_name[strcspn(drv_data.plane_name, "\n")] = '\0';
    /* 修改配置后清空硬件就绪标记,下次使能重新绑定 */
    drv_data.hw_ready = 0;
    return count;
}

/* sysfs读取当前CRTC索引 */
static ssize_t crtc_index_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
    return scnprintf(buf, PAGE_SIZE, "%d\n", drv_data.crtc_index);
}

/* sysfs设置CRTC索引 */
static ssize_t crtc_index_store(struct kobject *kobj, struct kobj_attribute *attr,
                            const char *buf, size_t count)
{
    int val;
    if (kstrtoint(buf, 10, &val) < 0)
        return -EINVAL;

    /* 限制支持所有大于0的数字索引 */
    if (val >= 0)
        drv_data.crtc_index = val;
    /* 修改配置后清空硬件就绪标记,下次使能重新绑定 */
    drv_data.hw_ready = 0;
    return count;
}

/* 定义sysfs属性文件 */
static struct kobj_attribute enable_attr = __ATTR(enable, 0660, enable_show, enable_store);
static struct kobj_attribute plane_name_attr = __ATTR(plane_name, 0660, plane_name_show, plane_name_store);
static struct kobj_attribute crtc_index_attr = __ATTR(crtc_index, 0660, crtc_index_show, crtc_index_store);

关键说明:

  • 第16行:校验enable输入参数,非法输入直接返回参数错误。

  • 第31-33行:Plane名称字符串去除换行符,防止数据错误导致图层匹配失败。

  • 第35行:修改图层名称后清空硬件就绪标记,下次刷新重绑硬件。

  • 第54-55行:限制输入的CRTC索引值需大于0。

  • 第57行:修改CRTC索引后清空硬件就绪标记,下次刷新重绑硬件。

  • 第62-64行:创建0660权限节点,支持用户态读写。

彩条绘制函数

draw_color_bar函数(位于linux_driver/37_drm_color_bar/drm_color_bar.c)
 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
/*
* 函数功能:绘制带偏移量的彩条图案
* @buf: 指向要绘制的显示缓冲区
* @offset: 彩条水平偏移量
*/
static void draw_color_bar(struct rockchip_drm_direct_show_buffer *buf, int offset)
{
    u32 *pix = (u32 *)buf->vir_addr[0];      /* 映射显存32位像素指针 */
    u32 w = buf->width;                      /* 获取缓冲区宽度 */
    u32 h = buf->height;                     /* 获取缓冲区高度 */
    u32 pitch = buf->pitch[0] / 4;           /* 计算行跨度(4字节/像素) */
    u32 bar_width = w / BAR_TOTAL;           /* 计算单段彩条宽度 */
    int x, y;                                /* 像素坐标遍历变量 */
    u32 color;                               /* 当前像素颜色值 */

    /* 判断像素格式是否匹配,不匹配则直接返回 */
    if (buf->pixel_format != BUFFER_FORMAT)
        return;

    /* 遍历所有行 */
    for (y = 0; y < h; y++) {
        /* 遍历所有列 */
        for (x = 0; x < w; x++) {
            /* 计算当前像素对应彩条索引 */
            int idx = (x / bar_width + offset) % BAR_TOTAL;
            /* 根据索引选择对应颜色 */
            switch (idx) {
                case 0: color = COLOR_WHITE;   break;      /* 索引0:白色 */
                case 1: color = COLOR_YELLOW;  break;      /* 索引1:黄色 */
                case 2: color = COLOR_CYAN;    break;      /* 索引2:青色 */
                case 3: color = COLOR_GREEN;   break;      /* 索引3:绿色 */
                case 4: color = COLOR_MAGENTA; break;      /* 索引4:粉色 */
                case 5: color = COLOR_RED;     break;      /* 索引5:红色 */
                case 6: color = COLOR_BLUE;    break;      /* 索引6:蓝色 */
                default: color = COLOR_BLACK;  break;      /* 默认:黑色 */
            }
            /* 将颜色写入显存对应位置 */
            pix[y * pitch + x] = color;
        }
    }
}

关键说明:

  • 第8行:直接操作显存虚拟地址vir_addr,内核态直接写显存,提高效率。

  • 第11行:pitch为硬件返回的行字节数,ARGB8888单像素占4字节,除以4换算为单行像素个数。

  • 第12行:根据分辨率均分8段彩条宽度,保证色彩均匀。

  • 第17行:格式校验,防止显存格式不匹配导致花屏、色彩错乱。

  • 第21-36行:滚动算法:坐标+偏移量取模,实现彩条整体平移循环滚动。

  • 第38行:直接写入显存像素点,完成一帧画面绘制。

双缓冲申请与释放

双缓冲申请与释放(位于linux_driver/37_drm_color_bar/drm_color_bar.c)
 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
46
47
48
49
50
51
52
53
54
/* 分配双显示缓冲区 */
static int alloc_bufs(struct colorbar_drv_data *data)
{
    int i, ret;

    /* 循环分配2个缓冲区 */
    for (i = 0; i < BUFFER_COUNT; i++) {
        /* 分配Direct Show缓冲区结构体内存 */
        data->buf[i] = kzalloc(sizeof(*data->buf[i]), GFP_KERNEL);
        if (!data->buf[i]) {
            printk(KERN_ERR "rk-colorbar: alloc buf[%d] failed\n", i);
            return -ENOMEM;
        }

        /* 设置缓冲区宽度 */
        data->buf[i]->width = BUFFER_WIDTH;
        /* 设置缓冲区高度 */
        data->buf[i]->height = BUFFER_HEIGHT;
        /* 设置缓冲区像素格式 */
        data->buf[i]->pixel_format = BUFFER_FORMAT;
        /* 设置缓冲区为连续物理内存 */
        data->buf[i]->flag = ROCKCHIP_BO_CONTIG;

        /* 调用Direct Show接口分配硬件缓冲区 */
        ret = rockchip_drm_direct_show_alloc_buffer(data->ddev, data->buf[i]);
        if (ret) {
            printk(KERN_ERR "rk-colorbar: alloc buf[%d] failed %d\n", i, ret);
            kfree(data->buf[i]);
            data->buf[i] = NULL;
            return ret;
        }
    }

    return 0;
}

/* 释放双显示缓冲区 */
static void free_bufs(struct colorbar_drv_data *data)
{
    int i;

    /* 循环释放2个缓冲区 */
    for (i = 0; i < BUFFER_COUNT; i++) {
        /* 判断缓冲区是否存在 */
        if (data->buf[i]) {
            /* 调用Direct Show接口释放硬件缓冲区 */
            rockchip_drm_direct_show_free_buffer(data->ddev, data->buf[i]);
            /* 释放结构体内存 */
            kfree(data->buf[i]);
            /* 清空指针 */
            data->buf[i] = NULL;
        }
    }
}

关键说明:

  • 第22行:标记显存为连续物理内存,满足DRM显示硬件DMA寻址要求。

  • 第25行:调用瑞芯微direct_show接口申请硬件显存、FB、DMA-BUF资源。

  • 第43-53行:成对释放资源,先调用瑞芯微direct_show接口释放硬件显存,再释放内核结构体内存。

硬件CRTC/PLANE绑定

硬件CRTC/PLANE绑定(位于linux_driver/37_drm_color_bar/drm_color_bar.c)
 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/* 遍历获取指定索引的CRTC,并等待其激活 */
static int get_crtc(struct colorbar_drv_data *data)
{
    /* CRTC临时指针 */
    struct drm_crtc *crtc;
    /* CRTC索引计数器 */
    int index = 0;

    /* 遍历DRM设备下所有CRTC */
    drm_for_each_crtc(crtc, data->ddev) {
        /* 判断是否为目标CRTC索引 */
        if (index == data->crtc_index) {
            /* 等待CRTC硬件激活 */
            while (!crtc->state->active) {
                /* 休眠100ms,释放CPU */
                schedule_timeout_interruptible(msecs_to_jiffies(100));
            }
            /* 保存激活的CRTC到私有数据 */
            data->crtc = crtc;

            /* 打印CRTC获取成功日志 */
            printk(KERN_INFO "rk-colorbar: get CRTC%d", data->crtc_index);

            return 0;
        }
        /* CRTC索引自增 */
        index++;
    }

    /* 未找到目标CRTC,返回无设备错误 */
    return -ENODEV;
}

/* 等待所有显示硬件(DRM/Plane/CRTC)就绪 */
static int wait_hw_ready(struct colorbar_drv_data *data)
{
    /* 循环等待硬件就绪 */
    while (1) {
        /* 获取Rockchip DRM设备句柄 */
        data->ddev = rockchip_drm_get_dev();
        /* 判断DRM设备是否获取成功 */
        if (!data->ddev) {
            /* 休眠100ms,等待DRM设备初始化 */
            schedule_timeout_interruptible(msecs_to_jiffies(100));

            /* 继续循环 */
            continue;
        }

        /* 获取指定名称的Plane图层 */
        data->plane = rockchip_drm_direct_show_get_plane(data->ddev, data->plane_name);
        /* 获取指定索引的CRTC,并判断Plane/CRTC是否全部就绪 */
        if (!get_crtc(data) && data->plane) {
            /* 打印硬件就绪日志 */
            printk(KERN_INFO "rk-colorbar: VP all hardware ready!");

            /* 返回成功 */
            return 0;
        }

        /* 硬件未就绪,休眠200ms后重试 */
        schedule_timeout_interruptible(msecs_to_jiffies(200));
    }
}

关键说明:

  • 第10行:drm_for_each_crtc按照内核DRM设备链表从上至下遍历,确定索引对应关系。

  • 第12行:crtc_index是索引编号,而非硬件CRTC ID,内核通过链表遍历顺序一一映射,对应硬件CRTC ID。

  • 第14-17行:等待目标CRTC硬件激活,解决开机CRTC初始化晚于驱动加载的时序问题。

  • 第38行:循环轮询,解决开机DRM硬件初始化晚于驱动加载的时序问题。

  • 第40行:调用瑞芯微direct_show接口获取瑞芯微全局DRM设备句柄。

  • 第42-48行:DRM设备未初始化完成则休眠100ms重试,不占用CPU资源。

  • 第51行:调用瑞芯微direct_show接口,根据配置的Plane名称,匹配获取目标图层句柄。

  • 第53行:校验CRTC获取成功和Plane有效,确保显示链路完全就绪。

  • 第62行:硬件未完全就绪则休眠200ms重试。

硬件动态重新绑定

rebind_hardware函数是驱动动态配置更新的核心函数,用户态修改Sysfs的plane_name、crtc_index后,hw_ready标记置0,主线程触发该函数完成硬件重绑,无需修改默认配置并重新编译驱动即可生效新显示配置。

rebind_hardware函数(位于linux_driver/37_drm_color_bar/drm_color_bar.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* 重新绑定Plane和CRTC */
static void rebind_hardware(struct colorbar_drv_data *data)
{
    /* 关闭旧图层 */
    if (data->ddev && data->plane) {
        rockchip_drm_direct_show_disable_plane(data->ddev, data->plane);
        printk(KERN_INFO "rk-colorbar: disable old plane before rebind\n");
    }

    /* 清空旧的硬件绑定 */
    data->plane = NULL;
    data->crtc = NULL;

    /* 重新获取最新配置的Plane + CRTC */
    wait_hw_ready(data);

    /* 标记硬件就绪 */
    data->hw_ready = 1;

    printk(KERN_INFO "rk-colorbar: rebind hardware success (plane:%s, crtc:%d)",
        data->plane_name, data->crtc_index);
}

关键说明:

  • 第5-8行:重绑前调用瑞芯微direct_show接口主动关闭旧图层,防止多图层叠加、画面残留、显示冲突。

  • 第11-12行:清空旧硬件句柄,防止野指针、旧缓存配置干扰新绑定。

  • 第15行:调用硬件就绪函数,重新加载用户最新修改的Plane名称、CRTC索引配置。

  • 第18行:刷新硬件就绪标记,告知主线程无需重复绑定。

  • 第20-21行:打印当前生效的硬件配置,方便确认切换结果。

滚动彩条内核主线程

colorbar_thread_func函数(位于linux_driver/37_drm_color_bar/drm_color_bar.c)
 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/* 彩条显示核心线程 */
static int colorbar_thread_func(void *arg)
{
    /* 转换参数为驱动私有数据指针 */
    struct colorbar_drv_data *data = (struct colorbar_drv_data *)arg;
    /* 定义Direct Show提交信息结构体 */
    struct rockchip_drm_direct_show_commit_info info = {0};
    /* 函数返回值变量 */
    int ret;

    /* 等待显示硬件全部初始化完成 */
    wait_hw_ready(data);
    /* 初始化硬件就绪标志 */
    data->hw_ready = 1;

    /* 分配双显示缓冲区 */
    ret = alloc_bufs(data);
    if (ret)
        /* 分配失败,退出线程 */
        return ret;

    /* 清空提交信息结构体 */
    memset(&info, 0, sizeof(info));
    /* 绑定目标CRTC */
    info.crtc    = data->crtc;
    /* 绑定目标Plane */
    info.plane   = data->plane;
    /* 设置源区域X坐标 */
    info.src_x   = 0;
    /* 设置源区域Y坐标 */
    info.src_y   = 0;
    /* 设置源区域宽度 */
    info.src_w   = BUFFER_WIDTH;
    /* 设置源区域高度 */
    info.src_h   = BUFFER_HEIGHT;
    /* 设置目标显示X坐标 */
    info.dst_x   = 0;
    /* 设置目标显示Y坐标 */
    info.dst_y   = 0;
    /* 设置目标显示宽度 */
    info.dst_w   = BUFFER_WIDTH;
    /* 设置目标显示高度 */
    info.dst_h   = BUFFER_HEIGHT;

    /* 初始化彩条偏移量为0 */
    data->bar_offset = 0;
    /* 初始化缓冲索引为0 */
    data->front_buf = 0;
    /* 绘制初始偏移的彩条到第一块缓冲区 */
    draw_color_bar(data->buf[data->front_buf], data->bar_offset);

    /* 打印彩条启动日志 */
    printk(KERN_INFO "rk-colorbar: VP start, 1s scroll\n");

    /* 线程主循环:监听停止信号,持续显示 */
    while (!kthread_should_stop()) {
        if (data->enable) {
            /* 配置变更/首次启动:重新绑定Plane和CRTC */
            if (!data->hw_ready) {
                rebind_hardware(data);
                /* 更新提交信息的硬件绑定 */
                info.crtc  = data->crtc;
                info.plane = data->plane;
            }

            /* 绑定当前缓冲区 */
            info.buffer = data->buf[data->front_buf];
            /* 提交画面到硬件显示 */
            rockchip_drm_direct_show_commit(data->ddev, &info);

            /* 更新彩条偏移量,循环8段 */
            data->bar_offset = (data->bar_offset + 1) % BAR_TOTAL;
            /* 切换缓冲区 */
            data->front_buf = !data->front_buf;
            /* 绘制新偏移的彩条到切换后的缓冲区 */
            draw_color_bar(data->buf[data->front_buf], data->bar_offset);
        } else {
            /* 调用官方接口关闭Plane */
            if (data->ddev && data->plane) {
                rockchip_drm_direct_show_disable_plane(data->ddev, data->plane);
                printk(KERN_INFO "rk-colorbar: plane disabled by enable=0\n");
                /* 关闭后清空指针,防止重复操作 */
                data->plane = NULL;
                data->crtc = NULL;
            }
            /* 关闭使能时,清空硬件就绪标记,允许下次重新绑定 */
            data->hw_ready = 0;
        }

        /* 休眠1秒,实现每秒更新一次 */
        schedule_timeout_interruptible(msecs_to_jiffies(UPDATE_MS));
    }
    return 0;
}

关键说明:

  • 第12行:启动前等待硬件就绪,解决开机驱动加载时序问题。

  • 第23-43行:初始化全屏显示参数,源窗口、目标窗口均为全屏。

  • 第46-50行:初始化彩条偏移量和缓冲索引,绘制初始偏移的彩条到第一块缓冲区。

  • 第59-64行:检测到配置变更,重绑硬件。

  • 第67-69行:绑定当前缓冲区,调用瑞芯微direct_show接口提交画面到硬件显示。

  • 第72行:偏移量自增取模,实现8色循环滚动。

  • 第74行:缓冲区切换,0/1索引翻转,交替渲染。

  • 第76行:切换后的缓冲区预绘制下一帧画面。

  • 第77-88行:关闭使能时主动关闭图层,清空句柄,释放显示资源。

  • 第91行:1秒休眠,控制滚动帧率,降低CPU占用。

注解

开始流程为绘制第一块缓冲区,绑定第一块缓冲区提交画面到硬件显示,再后台绘制第二块缓冲区,休眠1s,更新绑定到第二块缓冲区提交画面到硬件显示,再后台重新绘制第一块缓冲区,休眠1s,更新绑定到第一块缓冲区提交画面到硬件显示,以此循环,实现双缓冲绘制和显示。

驱动初始化与退出

驱动初始化与退出(位于linux_driver/37_drm_color_bar/drm_color_bar.c)
 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/* 驱动模块初始化函数 */
static int __init colorbar_driver_init(void)
{
    int ret;
    /* 清空全局私有数据结构体 */
    memset(&drv_data, 0, sizeof(drv_data));

    /* 初始化默认配置 */
    drv_data.enable = 0;
    strncpy(drv_data.plane_name, DEFAULT_PLANE_NAME, sizeof(drv_data.plane_name)-1);
    drv_data.crtc_index = DEFAULT_CRTC_INDEX;
    drv_data.hw_ready = 0; /* 初始化硬件未就绪 */

    /* 创建sysfs根节点 /sys/kernel/rk_colorbar/ */
    drv_data.kobj = kobject_create_and_add("rk_colorbar", kernel_kobj);
    if (!drv_data.kobj) {
        printk(KERN_ERR "rk-colorbar: create kobject failed\n");
        return -ENOMEM;
    }
    /* 创建sysfs属性文件 */
    ret = sysfs_create_file(drv_data.kobj, &enable_attr.attr);
    ret |= sysfs_create_file(drv_data.kobj, &plane_name_attr.attr);
    ret |= sysfs_create_file(drv_data.kobj, &crtc_index_attr.attr);
    if (ret) {
        printk(KERN_ERR "rk-colorbar: create sysfs file failed\n");
        kobject_put(drv_data.kobj);
        drv_data.kobj = NULL;
        return ret;
    }

    /* 创建彩条显示内核线程 */
    drv_data.thread = kthread_run(colorbar_thread_func, &drv_data, "rk-colorbar");
    /* 线程创建失败判断 */
    if (IS_ERR(drv_data.thread)) {
        /* 打印错误日志 */
        printk(KERN_ERR "rk-colorbar: create thread failed\n");
        /* 初始化失败释放sysfs资源 */
        sysfs_remove_file(drv_data.kobj, &enable_attr.attr);
        sysfs_remove_file(drv_data.kobj, &plane_name_attr.attr);
        sysfs_remove_file(drv_data.kobj, &crtc_index_attr.attr);
        kobject_put(drv_data.kobj);
        drv_data.kobj = NULL;
        /* 返回线程创建错误码 */
        return PTR_ERR(drv_data.thread);
    }

    /* 打印初始化日志 */
    printk(KERN_INFO "rk-colorbar: driver init, wait VP...\n");

    return 0;
}

/* 驱动模块退出函数 */
static void __exit colorbar_driver_exit(void)
{
    /* 退出前关闭图层 */
    if (drv_data.ddev && drv_data.plane) {
        rockchip_drm_direct_show_disable_plane(drv_data.ddev, drv_data.plane);
    }

    /* 判断内核线程是否存在 */
    if (drv_data.thread) {
        /* 停止内核线程 */
        kthread_stop(drv_data.thread);
        /* 清空线程指针 */
        drv_data.thread = NULL;
    }

    /* 释放双显示缓冲区 */
    free_bufs(&drv_data);

    /* 清空CRTC指针 */
    drv_data.crtc = NULL;
    /* 清空Plane指针 */
    drv_data.plane = NULL;
    /* 清空DRM设备指针 */
    drv_data.ddev = NULL;

    /* 销毁sysfs节点及属性 */
    if (drv_data.kobj) {
        sysfs_remove_file(drv_data.kobj, &enable_attr.attr);
        sysfs_remove_file(drv_data.kobj, &plane_name_attr.attr);
        sysfs_remove_file(drv_data.kobj, &crtc_index_attr.attr);
        kobject_put(drv_data.kobj);
        drv_data.kobj = NULL;
    }

    printk(KERN_INFO "rk-colorbar: driver exit\n");
}

/* 注册驱动为子系统同步初始化调用 */
subsys_initcall_sync(colorbar_driver_init);
/* 注册驱动模块退出函数 */
module_exit(colorbar_driver_exit);

关键说明:

  • 第9-12行:初始化默认配置,默认关闭彩条、绑定默认硬件参数。

  • 第15行:创建Sysfs节点:/sys/kernel/rk_colorbar/。

  • 第21-29行:批量创建三个配置节点,失败自动回滚资源。

  • 第32行:创建彩条显示内核线程,实现彩条刷新逻辑。

  • 第57-59行:退出优先关闭图层,避免残留画面。

  • 第62-67行:主动停止内核线程,防止内核线程乱跑。

  • 第70行:统一释放双缓冲显存资源,避免内存泄漏。

  • 第92行:同步子系统初始化,跟随DRM驱动同步加载,解决时序问题。

3.6.2. 添加驱动进内核

显然,实验驱动依赖瑞芯微DRM框架以及Direct Show接口,所以只能把实验驱动放到和瑞芯微DRM驱动相同目录下进行编译,不能和之前实验一样独立于内核源码。

以下以 瑞芯微6.1.99内核版本 为例。

3.6.2.1. 添加驱动文件到内核源码

将drm_color_bar.c驱动放到 内核源码/drivers/gpu/drm/rockchip/ 目录下,与瑞芯微DRM驱动相同目录。

3.6.2.2. 修改Kconfig

内核源码/drivers/gpu/drm/rockchip/Kconfig 的 if DRM_ROCKCHIP … endif 判断内添加以下内容:

1
2
3
4
5
config DRM_COLOR_BAR
    bool "DRM color bar test"
    select ROCKCHIP_DRM_DIRECT_SHOW
    help
      This is a DRM test driver.

添加完成后如下图:

../_images/subsystem_drm_2.jpg

配置含义说明如下:

  • config DRM_COLOR_BAR:编译一个独立的内核驱动,对应配置为CONFIG_DRM_COLOR_BAR。

  • bool “DRM color bar test”:只能两种选择状态,不编译或编译进内核。

  • select ROCKCHIP_DRM_DIRECT_SHOW:自动依赖并开启RK的Direct Show驱动,即选择编译CONFIG_DRM_COLOR_BAR会自动将CONFIG_ROCKCHIP_DRM_DIRECT_SHOW添加编译。

  • help:配置提示信息。

3.6.2.3. 修改Makefile

根据Kconfig的配置在 内核源码/drivers/gpu/drm/rockchip/Makefile 添加编译项,内容如下:

1
rockchipdrm-$(CONFIG_DRM_COLOR_BAR) += drm_color_bar.o

添加完成后如下图:

../_images/subsystem_drm_3.jpg

配置含义说明如下:

  • 内核规定的格式:模块名-编译类型

  • rockchipdrm:瑞芯微给自家DRM显示驱动起的官方模块名。

  • -$(CONFIG_DRM_COLOR_BAR):当CONFIG_DRM_COLOR_BAR被选择时,$(CONFIG_DRM_COLOR_BAR)的值为y,取消选择时值则为n。

所以,当CONFIG_DRM_COLOR_BAR被选择时,配置行就变成rockchipdrm-y += drm_color_bar.o,将驱动编译进内核。

3.6.2.4. 修改defconfig

内核配置项都保存在对应的defconfig文件中,可以通过menuconfig配置界面修改配置项然后保存到新的defconfig文件,在内核源码顶层目录执行以下命令:

1
2
#这里以rk356x系列6.1.99内核配置文件为例,打开menuconfig配置界面
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- lubancat_linux_rk356x_defconfig menuconfig

提示

其余系列板卡参考 驱动章节实验环境搭建章节的编译内核 小节确定板卡配置文件。

打开的界面如下:

../_images/subsystem_drm_4.jpg

按下键盘的“/”按键打开搜索框,然后输入我们前面添加的CONFIG_DRM_COLOR_BAR配置项:

../_images/subsystem_drm_5.jpg

搜索框输入完后选择下方“ok”然后再按下回车就会打开搜索结果,如下图,只有一个搜索结果,可以看到配置项当前状态(为n未被选择)、描述、路径、和依赖情况。

../_images/subsystem_drm_6.jpg

按下键盘的数字“1”键就会跳转到CONFIG_DRM_COLOR_BAR配置项处,在配置项处按下空格即可选中配置项,[ ]变为[*]代表配置项的值从n变为y:

../_images/subsystem_drm_7.jpg

然后通过键盘方向键选择下方“Save”,再按下回车进行保存,默认保存到.config即可。

../_images/subsystem_drm_8.jpg

最后选择“Exit”,再按下回车退出界面。默认保存的.config包含了全部的配置项,不利于我们观察配置,需要生成精简的配置文件然后覆盖原来的配置文件,在内核源码顶层目录执行以下命令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#保存defconfig精简的配置文件
make savedefconfig ARCH=arm64

#覆盖原来的配置文件,这里以rk356x系列6.1.99内核配置文件为例
cp -f defconfig arch/arm64/configs/lubancat_linux_rk356x_defconfig

#查看新添加的配置项
cat arch/arm64/configs/lubancat_linux_rk356x_defconfig | grep CONFIG_DRM_COLOR_BAR

#信息打印如下
CONFIG_DRM_COLOR_BAR=y

可以看到defconfig配置文件的CONFIG_DRM_COLOR_BAR=y,那么编译的时候Makefile里面配置实际就变成了rockchipdrm-y += drm_color_bar.o,从而将驱动编译进内核。

3.6.3. 编译内核并替换

3.6.3.1. 编译内核

将drm_color_bar.c驱动放进内核源码指定目录并添加配置将驱动设置为编译进内核后,在内核源码顶层目录执行以下命令编译内核:

1
2
3
4
5
6
7
8
#清除之前生成的所有文件和配置
make mrproper

# 这里以rk356x系列6.1.99内核配置文件为例
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- lubancat_linux_rk356x_defconfig

# 编译内核,指定平台,指定交叉编译工具,使用8线程进行编译,线程可根据电脑性能自行确定
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j8

提示

其余系列板卡参考 驱动章节实验环境搭建章节的编译内核 小节进行编译。

编译完成后在 内核源码/arch/arm64/boot/ 目录生成的 Image 文件就是内核文件。

3.6.3.2. 替换内核

将内核先传到板卡,再拷贝到板卡的/boot/目录下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#先传输到板卡

#查看板卡原先内核
ls /boot/Image* -l

#信息打印如下,可以确认当前板卡实际内核就是/boot/Image-6.1.99-rk356x
lrwxrwxrwx 1 root root       19  5月19日 10:15 /boot/Image -> Image-6.1.99-rk356x
-rw-r--r-- 1 root root 42213888  5月19日 10:15 /boot/Image-6.1.99-rk356x

#替换内核,根据实际内核名字而定
sudo cp -f Image /boot/Image-6.1.99-rk356x

#重启系统
sudo reboot

#查看内核版本
cat /proc/version

#信息打印如下
Linux version 6.1.99-rk356x (guest@dev107) (aarch64-none-linux-gnu-gcc (GNU Toolchain for the A-profile Architecture 10.3-2021.07 (arm-10.29)) 10.3.1 20210621, GNU ld (GNU Toolchain for the A-profile Architecture 10.3-2021.07 (arm-10.29)) 2.36.1.20210621) #64 SMP Sat May 30 09:37:34 UTC 2026

可以从编译的主机名和编译时间确认内核是否替换成功,如以上信息中“guest@dev107”就是作者的服务器主机,“Sat May 30 09:37:34 UTC 2026”就是作者编译内核的UTC世界时间,说明内核替换成功。

3.6.4. 程序运行结果

如出现 Permission denied 或类似字样,请注意用户权限,大部分操作硬件外设的功能,几乎都需要root用户权限,简单的解决方案是在执行语句前加入sudo或以root用户运行程序。

3.6.4.1. 实验操作

注解

作者此处测试板卡为LubanCat2,连接了HDMI屏幕和5.5寸MIPI屏幕,crtc_index=0,plane_name=Esmart0-win0对应vop0的HDMI屏幕,crtc_index=1,plane_name=Esmart1-win0对应vop1的DIS0 MIPI屏幕,其他板卡根据实际而定。

确认内核替换成功并连接屏幕后,使用以下命令测试:

1
2
3
4
5
6
7
8
9
#查看驱动加载情况
dmesg | grep colorbar

#信息打印如下
[    2.021553] rk-colorbar: driver init, wait VP...
[    3.258247] rk-colorbar: get CRTC0
[    3.258254] rk-colorbar: VP all hardware ready!
[    3.278310] rk-colorbar: VP start, 1s scroll
[    3.872980] rk-colorbar: plane disabled by enable=0

可以看到系统开机时默认加载了rk-colorbar驱动,成功获取到CRTC和Plane,默认显示处于关闭状态。

 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
#进入sysfs节点
cd /sys/kernel/rk_colorbar/

#查看节点目录属性
ls -l

#信息输出如下
-rw-rw---- 1 root root 4096  6月 1日 14:47 crtc_index
-rw-rw---- 1 root root 4096  6月 1日 14:47 enable
-rw-rw---- 1 root root 4096  6月 1日 14:47 plane_name

#查看默认参数
sudo cat *

#信息输出如下,默认CRTC索引为0,图层为Esmart0-win0
0
0
Esmart0-win0

#开启显示
sudo sh -c "echo 1 > enable"

#信息打印如下
[  986.939894] rk-colorbar: get CRTC0
[  986.939916] rk-colorbar: VP all hardware ready!

###关闭显示
sudo sh -c "echo 0 > enable"

###信息打印如下
[  986.939944] rk-colorbar: rebind hardware success (plane:Esmart0-win0, crtc:0)
[ 1009.241228] rk-colorbar: plane disabled by enable=0

使能显示后在vop0的屏幕左上角可以看到彩条动态滚动,如下图:

../_images/subsystem_drm_9.jpg

注意

若没有显示需要确认当前屏幕连接的接口,如LubanCat2板卡的vop0对应板卡HDMI和DSI1接口,vop1对应板卡DSI0接口。

如果板卡支持双屏异显并且接上了对应的屏幕,可切换至vop1进行显示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#关闭显示
sudo sh -c "echo 0 > enable"

#切换crtc索引
sudo sh -c "echo 1 > crtc_index"

#切换图层
sudo sh -c "echo Esmart1-win0 > plane_name"

#开启显示
sudo sh -c "echo 1 > enable"

#信息打印如下
[ 1153.127784] rk-colorbar: get CRTC1
[ 1153.127801] rk-colorbar: VP all hardware ready!

使能显示后在vop1的屏幕左上角可以看到彩条动态滚动,如下图:

../_images/subsystem_drm_10.jpg

同理可输出到主图层,如设置crtc_index=0、plane_name=Smart0-win0,但不能输出到专用压缩图层,如Cluster0-win0。

3.6.5. 实验注意事项

  • 内核依赖:本实验依赖瑞芯微DRM Direct Show接口,必须将驱动放到内核源码瑞芯微DRM驱动目录进行编译,否则会编译失败。

  • 图层名称匹配:默认图层为Esmart0-win0,不同板卡接口可用图层不同,配置错误会导致找不到Plane、显示失败。

  • 双缓冲机制不可省略:禁止改为单缓冲测试,单缓冲会出现严重画面撕裂,无法观察流畅动画效果。

  • 硬件重绑定机制:修改plane_name、crtc_index后必须清空hw_ready标记。

  • 线程安全与休眠:线程循环必须使用休眠延时,无休眠会导致CPU占用率100%,造成系统卡顿。

  • 资源释放规范:驱动卸载必须依次停止线程、释放显存、关闭图层、销毁sysfs节点,否则会造成内核内存泄漏、DRM硬件占用。