☰
DRM驱动架构与U-Boot显示链路解析:嵌入式Linux显示开发必读
2026/10/3 11:08:20 网站建设 项目流程

搞嵌入式显示开发的人,应该都绕不开这三个字母:DRM。它是Direct Rendering Manager,Linux内核里管显示设备最核心的驱动框架。很多人第一次接触DRM是在内核侧看drm_probe_helper、drm_panel这些代码,后来发现U-Boot阶段开机logo也跟它有关系,再往后连设备树里一个mipi dsi节点怎么配置也离不开对DRM的理解。这篇文章是“DRM驱动架构浅析”的上篇,先把DRM框架在解决什么问题讲清楚,再把U-Boot阶段显示链路如何借用DRM的思路从零启动拆开。适合刚接触显示驱动、或者已经在调U-Boot logo但还没系统看过DRM的嵌入式Linux工程师,读完至少能把“DRM是个什么玩意儿”“U-Boot里为什么也会出现panel/backlight节点”这两个问题彻底弄明白。

1. 先给DRM画个像:它到底解决什么问题

1.1 从fbdev到DRM:同样是显存,差别在哪

想理解DRM,最好的办法是先看它之前的fbdev。fbdev就是framebuffer设备,Linux内核里它把显示控制器抽象成一个简单的显存设备,用户程序打开/dev/fb0,mmap一块内存,往里面填像素,显示控制器就把它刷到屏幕上。这套机制在早期嵌入式Linux里用得很多,现在很多老方案还在用。

但fbdev有个绕不过去的问题:它的抽象粒度太粗。显示控制器其实不是一坨“显存”,它内部有多个图层、扫描时序、编码器、物理接口,还有垂直同步、缓冲区切换这些机制。fbdev把这些细节全部藏起来,用户态里的游戏、浏览器、视频播放器就只有“往一个内存里画图”这一条路。多个应用同时画,谁画谁不画、哪个界面带撕裂、刷新节奏怎么跟显示屏对齐,内核完全没能力做精细调度。

DRM的出现就是为了解决这堆问题。本质上,DRM把显示控制器拆成几个内核对象,每个对象管一段职责,同时提供给用户态一套完整的命令接口,让上层画图的时候可以一次性地告诉内核“我要用哪个图层、显示在哪个区域、按什么刷新率、从哪块内存取数”。这套机制就是KMS,Kernel Mode Setting。配合GEM(Graphics Execution Manager)管理显存,组成了现代Linux显示驱动的底座。今天Android的SurfaceFlinger、桌面Linux的Wayland合成器、机顶盒的图形界面,底层全部挂在DRM框架上。

1.2 DRM不是“显示驱动”的全部,它只是内核侧那半层

这里要先把边界划清楚:DRM通常指内核侧那套框架,但实际显示链路远不止它。一个完整的显示路径大概是这样:GPU或CPU往显存里写内容,显示控制器里的DMA引擎按设定的时序从显存取数,数据经过MIPI DSI/HDMI/DP/eDP这类接口的编码器转换成物理信号,最终到达面板。DRM管的重点是从显存到编码器这段,以及和用户态的交互。面板本身通常由drm_panel驱动来管理,背光由backlight驱动来管理,这部分也是DRM体系里常用的搭档。

还有一个容易混淆的点:网上搜索时经常看到有人说“DRM数字激励器”,那个东西跟Linux DRM一点关系都没有。真正的Linux DRM在显示领域已经用了二十年,叫Direct Rendering Manager,和音频设备厂商用“DRM/DAM”做产品后缀完全是两码事,后面我会专门说这个名称混用的问题。总之,只要你在看内核驱动、设备树、显示调试,遇到DRM就是在说Linux的显示驱动框架,不是其他任何东西。

1.3 为什么U-Boot阶段也要聊DRM

很多人一开始不理解:U-Boot不是只负责引导内核吗?怎么也要处理显示?实际上现代嵌入式产品早就不是“出一个命令行就行”,U-Boot阶段要显示开机logo、充电图标、恢复模式菜单,甚至有些设备还搞动态开机动画。这些功能都落在Linux内核接管之前,而U-Boot自己的显示子系统,设计和DRM有着很深的血缘关系。

U-Boot的video子系统虽然不叫DRM,但它使用了和DRM一致的思路:设备树节点驱动模型、显示输出与panel分离、面板驱动负责时序和初始化命令、背光单独管理。尤其是我后面要讲的drivers/video/drm目录,U-Boot把内核里的一些DP/eDP桥接芯片驱动移植过来直接用,这样两边的compatible匹配逻辑一致,驱动代码复用的收益非常大。理解了DRM基础,再去看U-Boot的video驱动,会少走很多弯路。

2. DRM框架的核心对象与设备模型

2.1 四个关键对象:CRTC、Encoder、Connector、Plane

DRM框架对显示控制器的抽象大概可以分成四类对象。把理解这四类对象,基本就理解整个DRM的骨架。

第一是CRTC。CRTC不是“阴极射线管”那个老掉牙的东西延续下来的概念,它代表的是显示控制器里的主要时序控制模块。它的职责是决定“什么时候从内存取数据、按什么像素时钟把行场信号发出去”。你可以把CRTC理解成一台带节拍器的流水线:数据进来后被送到哪条输出通道、刷新是多少Hz,都由它控制。一个CRTC通常对应一个显示输出通道。

第二是Encoder。Encoder负责把CRTC送出来的并行RGB信号,转成物理链路需要的格式。比如HDMI编码器、MIPI DSI控制器、DisplayPort的发送器,都是Encoder。注意,Encoder不等于物理接口本身,它是接口的“驱动侧”。

第三是Connector。Connector代表的是物理连接点,比如HDMI插座、DSI面板连接器、eDP排线。在内核里Connector上挂着状态检测、EDID数据、以及用户态能看到的属性,比如背光亮度、连接状态、是否支持某个分辨率。对HDMI这类可热插拔接口,Connector的检测逻辑很关键;对DSI这种固定接面板的场合,Connector更多是一个逻辑概念,实际初始化逻辑都走到panel驱动里。

第四是Plane。Plane是DRM比较晚才正式抽象出来的概念,它代表显示控制器里的硬件图层。一个显示控制器通常有主层、光标层,甚至多个叠加层。用户态可以把一张带透明通道的图片放到某个Plane上,由显示控制器硬件完成混叠,而不是在软件里做alpha混合。很多SoC的显示引擎支持多个Plane,这直接决定了UI能不能做到顺滑的悬浮窗、弹幕、HDR叠加效果。

2.2 Framebuffer与Mode Setting:DRM模式设置的每次交互

在DRM里,“设置显示模式”不是往寄存器里写一堆值那么简单。用户态通过drmModeSetCrtc、drmModeSetPlane这类ioctl告诉内核:我要把某个framebuffer显示到某个CRTC上,输出的时序是哪一组。内核拿到请求后,要做完整的状态检查,看看当前CRTC、Encoder、Connector、Plane能不能组成一条合法的显示链路,确认没问题才提交。

这个过程引入了DRM的“原子模式设置”概念,英文叫Atomic Modeset。以前改显示参数是“逐个对象改寄存器”,中间态就可能出现花屏、撕裂。原子模式设置把所有要改的对象打包成一个状态提交,内核先检查再一次性切换,这样才能保证画面切换的过程中不出现半残状态。这也是为什么DRM驱动调试时经常能看到drm_atomic_commit这个调用链。

Framebuffer这一层本身也做了一层抽象。它不直接管理物理内存,而是引用一个GEM对象,GEM负责具体的显存分配、映射、同步。早期fbdev时代,所有应用共享一块显存,改分辨率就得全屏重来。DRM里一个进程可以创建自己的GEM对象,多个framebuffer绑定到不同对象,显示层在切换时通过硬件“换地址指针”的方式实现真正的无撕裂显示。

2.3 Probe流程:从设备树节点到内核对象的绑定

DRM驱动通常是一个平台驱动,在设备树里对应到一个display controller节点。拿常见的MIPI DSI来说,设备树里的结构大概是:SoC的DSI控制器节点下面挂一个panel节点,内核启动时,DSI驱动probe成功后会扫描自己的子节点,找到compatible匹配的panel驱动,调用mdp/mipi_dsi_attach之类接口把面板挂在display链路里。

这套probe流程中,最核心的是drm_bridge和drm_panel这两个抽象。桥接芯片,比如HDMI转eDP、MIPI DSI转HDMI,会被抽象成drm_bridge,连接在Encoder和Connector之间。面板则被抽象成drm_panel,提供prepare、enable、disable、unprepare这几个回调函数。所有初始化和关断逻辑都放在这些回调里,驱动框架在合适的时间点调用。理解了这个结构,后面再看U-Boot里的panel驱动,会发现它们几乎复刻了同一套回调设计,只是函数名简化成了单板版本。

设备树中compatible匹配是整个绑定过程的关键。内核启动时,driver core会根据设备树节点的compatible字段去驱动表里找匹配的of_device_id。如果节点被绑定成功,日志里通常能看到“OF: panel: probed”或“drm_panel init”,如果没匹配上,最常见的错误就是“no panel driver found”之类的提示。所以调试DRM显示问题时,第一步永远都是确认设备树里的compatible和驱动里的match表是否对得上。

3. U-Boot阶段DRM驱动的具体实现与流程

3.1 U-Boot为什么坚持“轻量版”显示栈

U-Boot里显示子系统的定位跟内核完全不同。内核要同时服务多个进程、多窗口、图形加速,所以DRM做得复杂。U-Boot只需要在引导阶段把Logo、界面、图标画到屏幕上,说白了就是单buffer单平面的静态显示场景,所以它没必要引入完整的CRTC/Plane/Atomic那一套。

但“轻量”不代表“胡乱写”。U-Boot的显示栈仍然做到了设备树驱动、分层解耦。它把显示路径拆成几个部分:video uclass负责framebuffer和基本显示接口,panel驱动负责初始化面板和时序,backlight驱动负责背光使能。分层逻辑和DRM一脉相承,只是接口精简到只有set_mode、enable、disable、backlight_set这几个回调。这种设计的好处是明显的:同一个panel,厂商可以在U-Boot里写一个简化版驱动,内核里写一个正式版DRM驱动,两边的设备树compatible尽量保持一致,后续维护时改设备树的一个面板参数,两边都生效。

顺便说一下,现在U-Boot里已经有drivers/video/drm这个目录,主要放一些从内核移植过来的显示桥接芯片驱动,例如常见的eDP到HDMI桥、MIPI DSI到eDP桥。这些驱动直接复用了内核DRM中drm_bridge那套数据结构,在U-Boot里配合video桥接框架工作。如果遇到“U-Boot下HDMI输出没有信号,但内核下正常”的案例,多半就要到这里面找对应芯片的初始化函数。

3.2 从DM模型到panel驱动:一次完整的U-Boot显示初始化

U-Boot现在的设备驱动框架叫DM,Driver Model。它跟内核的driver model非常像,核心对象是udevice,核心接口是probe/remove。想要U-Boot启动时就能显示,至少需要以下几个环节按顺序配合。

首先是设备树准备阶段。U-Boot会在早期阶段把设备树从固定地址或者存储介质里读进来,经过fdt解析生成设备列表。如果项目里logo显示是在SPL阶段就做,那设备树里还需要给相关节点加上“u-boot,dm-pre-reloc”属性,否则U-Boot在relelocate之前不会去probe这个设备。很多人设置的“SPL阶段就出画面”总是不生效,检查点就是这里。

其次是video uclass的初始化。U-Boot启动过程中会执行video_init(),它会遍历所有video设备,为每个设备分配framebuffer内存。这段内存是从U-Boot的reserved memory里预留的,分配完成后,U-Boot会调用video_probe设备驱动,然后执行video_ops里的回调。对于带面板的显示设备,视频驱动的probe里会完成:解析设备树timing、设置显示控制器时序、初始化panel、使能背光,最后返回framebuffer地址。

关键阶段我以mipi dsi屏为例概括一下流程。U-Boot先probe DSI主机控制器,配置lane数、时钟、视频模式还是命令模式。然后遍历子节点找到panel,panel驱动里的probe会把reset gpio先拉低再拉高,让面板完成上电复位,接着通过mipi dsi命令通道发送初始化序列,初始化完再等待若干毫秒,之后使能DSI的视频流输出。最后U-Boot才往framebuffer里填logo的像素数据。这一个串行过程,任何一步时序不对都可能黑屏或者花屏。

写成代码逻辑大概是这样的流程:

static int mipi_panel_probe(struct udevice *dev) { struct panel_priv *priv = dev_get_priv(dev); /* 1. 解析reset/backlight等GPIO信息 */ priv->reset_gpio = devm_gpiod_get(dev, "reset"); /* 2. 面板上电复位 */ dm_gpio_set_value(&priv->reset_gpio, 0); mdelay(10); dm_gpio_set_value(&priv->reset_gpio, 1); mdelay(20); /* 3. 发送面板初始化命令 */ mipi_dsi_dcs_write(priv->dsi, 0x11, data, len); // exit sleep mdelay(120); mipi_dsi_dcs_write(priv->dsi, 0x29, NULL, 0); // display on return 0; }

这里有个很容易踩的坑:面板初始化命令发送可能不是一次就能成功的,有些屏对时序非常敏感,芯片刚退出睡眠模式立刻发命令就会丢。实际调试时,往往需要在mipi_dsi_dcs_write之后加足够的延时,具体加多久要看面板手册。U-Boot的做法是每个命令周期做一个整体延时,宁多勿少,反正U-Boot阶段不追求命令速度。

3.3 U-Boot显示参数的解析与传递

U-Boot里面板时序的解析依赖设备树里的display-timings节点。一个标准timing节点大概是这样的:

display-timings { timing0 { clock-frequency = <148500000>; hactive = <1920>; vactive = <1080>; hfront-porch = <88>; hback-porch = <148>; hsync-len = <44>; vfront-porch = <4>; vback-porch = <36>; vsync-len = <5>; }; };

这里每个参数的含义,实际上就是DRM中drm_display_mode的对应字段。U-Boot的video framework会把timing节点解析成struct display_timing,然后交给显示控制器驱动计算具体的寄存器值。注意这里有一个和内核不太一样的地方:内核的DRM驱动会在connector的get_modes阶段把timing整理成drm_display_mode列表,而U-Boot不需要那么多模式,它只认设备树里那一个timing,启动时直接用。

所以调试U-Boot显示时,如果发现比例不对、刷新率不对,首先要回去看设备树里的timing。很多项目改分辨率只改了hactive/vactive,忘了把hfront-porch、hsync-len这些参数一起改,结果U-Boot阶段能用,进内核后屏幕抖动或者边缘偏移,就是这个原因。

3.4 U-Boot与内核交接:logo闪现的根因

嵌入式设备常见的现象是:U-Boot阶段logo正常,内核跑起来瞬间屏幕黑一下或者闪一下,再显示内核的console。这个问题的根源,在于U-Boot和内核是两套完全独立的驱动,它们各自初始化各自的硬件寄存器。U-Boot把显示控制器配好了,内核DRM驱动probe的时候会重新复位整个显示通道,于是面板就会经历一次断电重开,表现就是闪屏。

想消除这个闪屏,常见做法有几个。最省事的做法是让U-Boot和内核用完全相同的设备树timing和panel初始化序列,这样内核启动后虽然重新初始化了,但屏的状态几乎没变,闪烁感会轻很多。更彻底的做法是使用内核的simplefb机制,设备树里保留U-Boot配置好的framebuffer地址,内核不重新初始化显示控制器,而是直接沿用U-Boot留下的显存内容和寄存器状态,这样U-Boot的logo能无缝过渡到内核console。

不过要注意,simplefb只适用于不带复杂图形加速的场景。只要内核里还要跑GPU、跑Wayland/X11,最终还是要切换到完整DRM驱动。这时候可以采用“U-Boot不关闭显示控制器、内核驱动先沿用当前配置再按需切模式”的思路,也就是DRM驱动里做“fastboot”支持。很多厂商的显示驱动里都有一份“bootloader传递display状态”的代码,就是为了减少这个切换阶段的黑屏时间。

4. 实操中躲不开的几个坑

4.1 竖屏改横屏:mipi DSI下,别急着改分辨率

网上有个高频需求是“mipi dsi drm竖屏改横屏显示”,很多项目是拿竖屏模组放在横屏结构里,或者反过来。先说结论:竖屏改横屏不是把设备树里的hactive和vactive对调那么简单,它牵扯到面板扫描方式、DSI时序、甚至显示控制器的图层旋转能力。

DRM内核框架里通常会给Plane提供rotation属性,支持ROTATE_90/ROTATE_180这类角度旋转。这个旋转是显示控制器硬件图层变换实现的,不需要重新初始化面板,只需要在用户态或内核里设置plane属性。但前提是SoC的显示引擎必须支持硬件旋转。如果硬件不支持,就只能靠GPU合成器在软件层把画面旋转后输出,性能开销会明显增加。

U-Boot阶段就没有这么丰富的选项了,因为U-Boot没有完整的Plane抽象,也没有GPU可用。所以U-Boot里做竖屏改横屏,一般只能走“改面板初始化序列+改display-timings”这条路。具体来说,要去看面板控制IC的初始化寄存器,很多DSI面板IC支持MADCTL或类似命令来设置色彩显示方向、行扫描方向、列扫描方向。通过这个寄存器把面板内部的GRAM扫描方向改成横屏方向,屏幕内部的数据排布才会正确。与此同时,设备树里的display-timings也需要把hactive/vactive对调,并且按对调后的像素时钟重新计算porch参数。如果这两边不同步,就会出“画面比例对了但颜色偏转”或者“显示区域偏移”这类奇怪症状。

实操建议:拿到一个面板先在U-Boot阶段用小批量驱动验证基础显示,再进内核验证DRM正常,最后才考虑旋转问题。旋转问题不要一上来就改GPL代码,先看系统有没有现成的rotation属性可用,再看面板IC能不能支持扫描方向切换,最后才是改timing。优先级反了很容易越调越乱。

4.2 花屏、黑屏、背光不亮:排查顺序很重要

显示问题一般分三种:画面完全黑、屏幕亮但没有内容、有内容但花屏。根据我多年的调试经验,排查顺序基本是固定的。

先看背光。背光不亮和显示通路是两套东西,如果屏幕背光没打开,你后面查再多时序都看不到图像。检查设备树里的backlight节点,确认pwm或者gpio配置正确,测量背光供电是否升压成功。很多屏的背光IC需要EN脚先拉高,供电再建立,顺序反了灯就不亮。

再确认面板的reset和上电时序。MIPI DSI屏对reset时序极其敏感,reset释放后要等延迟,过早发初始化命令面板会忽略。之前遇到一个项目,U-Boot下偶尔黑屏,查了一个星期,最后用示波器抓发现reset拉高后t6时序不足,初始化命令发出去面板根本没进工作状态。这类问题光看代码是看不出来的,必须量波形。

然后查DSI链路。包括lane数是否和面板匹配,每个lane的差分电压、时钟频率。DSI时钟和显示timing里的像素时钟有换算关系,lane速率不够的时候就表现在“低分辨率能显示、高分辨率花屏”。内核对这块报错比较明显,U-Boot阶段经常直接没画面,所以U-Boot先跑通一个小分辨率再往高分辨率调是务实的路径。

最后才查显示控制器寄存器。CRTC的H/V总大小、porch,任何一个参数不对,输出出来的图像可能是整体偏移、右移、下方有条纹。这类问题往往在“从U-Boot到内核切换后”才暴露,因为U-Boot用的timing和内核drm mode未必一致,两边需要逐一比对。

4.3 关于“DRM/DAM数字激励器”名称混用的一点澄清

搜“DRM驱动架构”的时候,很可能搜到“DRM数字激励器和DAM数字激励器的区别”这类内容。给出一个明确的判断:在嵌入式Linux、显示驱动、U-Boot这些语境下,DRM只有一个含义,就是Direct Rendering Manager,内核里的显示驱动框架。它不是数字激励器,也不存在所谓“DRM数字激励器”的细分概念。

DAM,在音频设备圈子里常被用作某个音频处理模块的名字,跟显示、内核驱动完全不搭边。搜索平台把这些词搅在一起,只是因为缩写撞车。所以如果你在找显示驱动资料,看到“数字激励器”之类的内容可以直接跳过。反过来,如果你是在找音频产品,那也不要拿Linux DRM的文章去理解。两者毫无关联,硬套必然看不懂。

这种多义缩写其实在很多领域都存在,比如DMA是直接内存访问,同时也是数字媒体适配器的缩写。区分的关键就看上下文:出现drm_panel、drm_bridge、drm_atomic_commit,那一定是显示框架;出现DSP、均衡器、音效处理,那就是音频方向。掌握这个判断逻辑,以后遇到缩写混用就不会被误导。

4.4 内核与U-Boot面板参数不一致导致“内核阶段黑屏”

最后再补充一个非常典型的排查案例:U-Boot下显示一切正常,内核起来黑屏。这类问题很大概率出在U-Boot和内核的设备树不一致。常见情况是U-Boot用的dts和内核用的dts在公司仓库里分成了两份,早期版本中panel的compatible被改过,比如U-Boot里写的“panel-abc”而内核驱动里兼容列表是“panel-xyz”,于是内核DRM驱动probe时找不到panel,整个显示链路建不起来。

另一个容易忽略的点是U-Boot和内核里panel init sequence不一样。有些项目先U-Boot初始化了面板,接着内核又一次初始化,如果内核初始化序列在某条命令上卡住或者等待时间不够,面板就停在半初始化状态。这时候建议在内核驱动里做一个“跳过初始化”的开关:如果U-Boot已经初始化成功,内核启动后只需要把framebuffer地址填上去就能显示,没有必要重复发送一整套面板初始化序列。

我在实际调试中的体会是,显示链路的问题,90%都能归到三个地方:设备树compatible匹配不上、面板电源时序不对、timing参数和物理面板不匹配。DRM这套框架本身逻辑是清晰的,它把复杂显示控制器拆成对象去管理,U-Boot虽然没叫DRM,但设计思路大量沿用DRM的分层思想。这篇上篇把基础框架和U-Boot阶段讲完,内核侧drm_plane、drm_panel完整probe流程以及atomic commit的细节,可以留到下一篇继续拆,先把U-Boot到内核这条链路上该踩的坑多踩几遍,再回来看DRM内部实现会轻松很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询