尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SM750 HDMI DRM驱动解析:Linux显示栈从fbdev到KMS的演进与适配

SM750 HDMI DRM驱动解析:Linux显示栈从fbdev到KMS的演进与适配 SM750 是一颗频繁出现在工控主板、嵌入式一体机、以及部分国产显卡上的显示控制芯片。过去它在 Linux 上主要依赖 sm750fb 这种基于 fbdev 框架的老驱动工作能显示画面但进入 Wayland、现代 Xorg modesetting、以及需要 KMS 的合成器时代后fbdev 的局限性越来越明显。开源社区发布基于 DRM 子系统的 SM750 HDMI 驱动并支持 2048 宽输出与 2560x1080 超宽屏这背后不只是“换了一个驱动框架”而是把 SM750 从“只能点亮屏幕”推进到了“能被现代 Linux 显示体系完整管理”的状态。这篇文章以 Linux 显示链路为起点拆解 SM750 HDMI DRM 驱动的分层结构、模式添加方式、EDID 解析、HPD 热插拔逻辑以及实际验证和排错方法。如果你正在对接 SM750 驱动、准备在新内核中适配 HDMI 输出、或者只是想知道“为什么显示驱动要搞这么多分层”这篇内容可以作为一条不绕路的参考。1. Linux 显示链路SM750 与 DRM 到底在哪个环节工作1.1 从上层渲染到最终像素一条完整的 Linux 显示路径在 Linux 上一张带 HDMI 输出的显卡要真正把画面送到显示器会经过一条典型链路应用程序Qt/GTK/Wayland 客户端 - 合成器/Wayland compositor 或 X Server - DRM 用户空间接口/dev/dri/card0 - DRM 内核子系统KMS - 显卡厂商驱动SM750 DRM 驱动 - HDMI 物理链路 - 显示器这里最容易混淆的是 DRM 与驱动的关系。DRM 不是一个具体的显卡驱动而是 Linux 内核里的显示管理子系统它分成两层DRM 核心层管理设备节点、对象模型、缓冲区分配、原子提交、模式对象、fence 等。KMSKernel Mode Setting层管理显示器输出状态包括连接器Connector、编码器Encoder、CRTC、显示模式Mode、页面翻转等。普通应用不需要关心 KMS但合成器、桌面环境、Xorg 的 modesetting 驱动、Wayland compositor 都要通过 DRM 内核接口来设置分辨率、旋转、缩放和多屏拼接。SM750 的新开源驱动正是落在 KMS 这一层替代旧的 fbdev 路径。1.2 为什么现代图形栈离不开 DRM旧式 fbdev 驱动的做法是直接在内核里画一个帧缓冲然后用fb_ops提供读写和ioctl。它的问题在于缓冲区管理和显示模式设置耦合在一起难以支撑多平面合成。缺少对热插拔、EDID、色彩空间、旋转、缩放等现代显示特性的标准抽象。Wayland 和现代 Xorg 的 modesetting 驱动都以 DRM/KMS 为底层接口fbdev 驱动只能通过兼容层勉强度日。SM750 如果一直停留在 fbdev在 Ubuntu 新版本、Buildroot 的 Wayland 方案、或国产嵌入式发行版上都会越来越难适配。所以这次开源的 DRM 驱动才显得重要它让 SM750 在 Linux 显示子系统里成为一级公民。注意阅读源码前先确认你手上内核的 DRM 版本。不同内核版本的drm_connector_funcs、drm_mode_config等结构体回调数量不同直接照抄老补丁很常见出现编译错误。2. 从 fbdev 到 DRMSM750 显示驱动重构要解决什么2.1 SM750 硬件能力与常见版型SM750 不是传统意义上的独立显卡 GPU它的定位更接近显示控制器内置 2D 图形引擎能做 BitBLT、矩形填充等操作。常见版型通过 PCIe 或 PCI 接口连接 CPU输出端可能引出 VGA、DVI、HDMI 或 LVDS。自带显示内存适合低功耗工控、点歌机、收银一体机、医疗设备、自助终端等场景。因为硬件设计简单驱动开发的核心难点不在“怎么渲染 3D”而在“如何把输出管脚、时钟、模式、热插拔状态完整接进 DRM 框架”。在适配 HDMI 输出时驱动至少要处理四类硬件资源硬件资源作用驱动中的对应对象DDC/I2C 控制器读取显示器 EDIDI2C adapter、DDC 总线HPD 引脚感知 HDMI 线插拔Connector 状态检测TMDS 时钟决定 HDMI 链路带宽mode_valid 限制显示控制器寄存器设置分辨率、同步信号、像素时钟CRTC/Encoder 配置一套 HDMI 驱动如果没有完整覆盖这些资源常见的表现就是“能开机显示画面但插拔显示器后系统不知道屏幕没了”。2.2 新驱动在 DRM 里如何组织对象DRM 驱动需要创建的最小对象集合是 Connector、Encoder、CRTC、Plane。对应到 SM750 HDMI 场景ConnectorHDMI-A 类型连接器负责物理连接状态和 EDID。EncoderTMDS 编码器负责把像素信号转换为 HDMI 信号。CRTC负责扫描输出、同步控制、模式设置。Plane为显示的缓冲区提供映射一般至少一个 primary plane。如果用一句话描述这四个对象的关系一块正在显示的屏幕上Plane 提供画面内容CRTC 按像素时钟逐行扫出Encoder 把 RGB 信号编码成 TMDSConnector 负责让显示器和驱动互相认识。3. 编译环境准备内核源码、工具链和 KMS 配置项3.1 环境检查清单在写代码或加载模块之前先把如下环境确认好检查项建议要求说明Linux 内核源码5.10 或更新的 LTS 内核DRM API 在 5.x 系列持续演进交叉编译工具链arm-linux-gnueabihf 或 aarch64-linux-gnu 等需要和根文件系统匹配U-Boot/设备树确保 PCIe 设备树节点存在SM750 常见为 PCIe 设备根文件系统工具modetest、xrandr、dmesg验证驱动必需显示测量设备HDMI 显示器和 DP 线等不要只依赖远程 SSH 看日志3.2 内核配置项假设驱动以编译进内核或模块方式加载至少需要开启这些项CONFIG_DRMy CONFIG_DRM_KMS_HELPERy CONFIG_DRM_SM750y CONFIG_DRM_LOAD_EDID_FIRMWAREy CONFIG_FBy CONFIG_FB_CMDLINEy CONFIG_DEBUG_FSy其中CONFIG_DRM_LOAD_EDID_FIRMWARE在调试“显示器 EDID 被特殊线材损坏”时很有用可以把 EDID 固件放到/lib/firmware/edid/下再用内核参数强制加载。3.3 编译命令示例手动编译时可以采用如下步骤# 先加载当前内核配置 make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- defconfig # 打开图形配置界面 make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- menuconfig # 确认 DRM 平台驱动路径下出现 SM750 选项 # 编译内核 make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- Image -j8 # 单独编译驱动模块 make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- Mdrivers/gpu/drm/sm750 modules编译完成后把sm750.ko或编译进内核的镜像放到目标板先确认设备树里 PCIe 节点是否枚举到 SM750。4. 核心代码路径从 probe 到 connector 的完整链路4.1 驱动入口与 probe 流程一个典型的 SM750 DRM 驱动probe阶段要做的事包括映射设备 I/O 资源。初始化显示控制器寄存器。创建 DRM 设备对象。注册 Connector、Encoder、CRTC、Plane。注册 IRQ 或轮询处理 HPD。调用drm_dev_register让用户空间看到/dev/dri/card0。简化代码结构如下static int sm750_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct sm750_dev *dev; int ret; dev kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM; /* 映射 PCI 资源 */ ret pcim_enable_device(pdev); if (ret) goto err_free; dev-mmio pcim_iomap(pdev, 0, 0); if (!dev-mmio) { ret -ENOMEM; goto err_free; } /* 初始化显示控制器和输出管脚 */ ret sm750_hw_init(dev); if (ret) goto err_free; /* 注册 DRM 设备与各对象 */ ret sm750_drm_register(dev); if (ret) goto err_free; pci_set_drvdata(pdev, dev); return 0; err_free: kfree(dev); return ret; }probe之后最重要的一步是连接器检测。连接器会回调detect函数用来判断当前是否接入了 HDMI 显示器static enum drm_connector_status sm750_hdmi_detect(struct drm_connector *connector, bool force) { struct sm750_connector *sm750_conn to_sm750_connector(connector); /* 读取 HPD 引脚状态 */ if (gpiod_get_value(sm750_conn-hpd_gpio)) return connector_status_connected; /* 没有 HPD 引脚时可以通过 DDC 探测 */ if (drm_probe_ddc(sm750_conn-ddc_adap)) return connector_status_connected; return connector_status_disconnected; }这里有一个关键取舍HPD 信号在部分嵌入式主板上没有引出驱动就必须回退到 DDC 检测。DDC 检测只在读取 EDID 时比较可靠用户如果开着合成器切到无信号状态时可能不会马上感知。这类细节在实际项目中很容易踩坑。4.2 获取显示器模式EDID 与 get_modes显示器的原生分辨率列表不是驱动拍脑袋写出来的而是连接器通过 DDC 通道读取显示器 EDID 后得到的。get_modes回调负责完成这件事static int sm750_hdmi_get_modes(struct drm_connector *connector) { struct sm750_connector *sm750_conn to_sm750_connector(connector); struct edid *edid; int count 0; edid drm_get_edid(connector, sm750_conn-ddc_adap); if (edid) { drm_connector_update_edid_property(connector, edid); count drm_add_edid_modes(connector, edid); kfree(edid); } /* 部分显示器 EDID 不完整需要补充驱动自定义模式 */ if (count 0) count sm750_hdmi_add_driver_modes(connector); return count; }标准显示器的 2560x1080 超宽模式一般会在 EDID 的详细时序描述块里出现不需要额外处理。但工控显示器、转接头、以及 EDID 被裁剪的情况下经常出现“显示器明明支持超宽但系统模式列表里没有”的问题。这时候驱动侧追加模式是最后的兜底方案。5. 2048 宽输出与 2560x1080 超宽模式怎么让内核接受5.1 模式校验mode_valid 是安全阀连接器把模式列表准备好后DRM 核心在设置模式时还会调用mode_valid回调相当于进入 HDMI 链路前的安全检查。SM750 的mode_valid至少应该检查像素时钟和水平宽度上限static enum drm_mode_status sm750_hdmi_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode) { /* * 这里的上限需要根据具体 SM750 版型确认。 * 有的主板上 TMDS 时钟只能跑到 165MHz有的能到 220MHz。 * 随意填一个值可能导致 4K 显示器都能设置但画面完全无法锁定。 */ if (mode-clock SM750_TMDS_MAX_CLK_KHZ) return MODE_CLOCK_HIGH; if (mode-hdisplay SM750_MAX_H_DISPLAY) return MODE_H_ILLEGAL; if (mode-vdisplay SM750_MAX_V_DISPLAY) return MODE_V_ILLEGAL; return MODE_OK; }2048 宽输出并不是简单地把hdisplay2048放行还需要同步检查像素时钟。比如 2048x153660Hz 的像素时钟通常已经接近 220MHz如果硬件没有足够时钟余量就会出现画面抖动、花屏或直接无信号。5.2 用 drm_cvt_mode 生成模型参数如果显示器 EDID 缺失或者需要绕过显示器的错误时序驱动可以在get_modes里调用drm_cvt_mode来生成标准模式static int sm750_hdmi_add_driver_modes(struct drm_connector *connector) { struct drm_display_mode *mode; mode drm_cvt_mode(connector-dev, 2560, 1080, 60, true, false, false); if (mode) { mode-type | DRM_MODE_TYPE_DRIVER; drm_mode_probed_add(connector, mode); } mode drm_cvt_mode(connector-dev, 2048, 1152, 60, true, false, false); if (mode) { mode-type | DRM_MODE_TYPE_DRIVER; drm_mode_probed_add(connector, mode); } return 0; }drm_cvt_mode会按 CVT 标准生成同步信号、前肩、后肩和极性。这样虽然能用但和真实显示器的详细时序不一定完全一致。生产环境更稳妥的做法是先用 EDID 里的显示模式驱动自定义模式只作为兜底。5.3 用户空间手动添加超宽模式比改内核更快的方法如果驱动已经正确注册大多数“找不到 2560x1080 模式”的问题可以通过用户空间补模式。以 Xorg modesetting 为例# 用 cvt 生成 2560x1080 60Hz 的 modeline cvt 2560 1080 60 # 将生成结果加入 xrandr xrandr --newmode 2560x1080_60.00 185.58 2560 2620 2680 2760 1080 1083 1087 1125 -hsync vsync # 把模式挂到 HDMI 输出上 xrandr --addmode HDMI-1 2560x1080_60.00 # 立即切换 xrandr --output HDMI-1 --mode 2560x1080_60.00如果这一步能成功但画面花屏问题大概率不在模式表而在像素时钟、扫描极性或者 HDMI 转接芯片上。如果这一步报错BadMatch则说明内核侧的mode_valid拒绝了该模式需要回驱动侧查上限。注意cvt生成的 modeline 不一定与显示器内部存储的详细时序一致。在最终量产前建议用edid-decode解析显示器真实时序。6. 驱动验证dmesg、modetest、xrandr 和合成器一起看6.1 先看内核日志驱动是否正常 probe驱动加载成功后dmesg里应该出现类似下面的关键行sm750 0000:01:00.0: SM750 probe ok, rev0x100 sm750 0000:01:00.0: HDMI HPD initial status: connected sm750 0000:01:00.0: [drm] Cannot find any crtc or sizes - going 1024x768 [drm] Initialized sm750 1.0.0 20240101 for 0000:01:00.0 on minor 0如果缺少这些行先检查 PCIe 枚举lspci -nn | grep -i 750常见输出类似01:00.0 VGA compatible controller [0300]: Silicon Motion SM750 [126f:0750]如果没有枚举到设备问题在硬件或 U-Boot/固件设备树而不是驱动本身。6.2 用 modetest 看内核识别到的连接器和模式modetest是 DRM 调试最直接的命令之一它来自libdrm工具集modetest -M sm750 -p-p会打印 connector 状态。正常情况可以看到Connectors: id encoder status name size (mm) modes encoders 1 1 connected HDMI-A-1 600x340 13 1 modes: name refresh (Hz) hdisp hss hse htot vdisp vss vse vtot) 2560x1080 59.97 ... 2048x1152 60.00 ... 1920x1080 60.00 ...如果名字是HDMI-A-1说明 Connector 注册成功。如果 status 一直是disconnected说明 HPD 没有拉高或者 DDC 回退检测失败。6.3 用 xrandr 验证用户空间链路在 Xorg 或 Weston 环境里用xrandr验证最终用户空间看到的信息xrandr --listproviders xrandr --listmonitors # 查看 HDMI 输出 xrandr | grep -A 8 HDMI-1切换到超宽模式后再检查实际输出xrandr --output HDMI-1 --mode 2560x1080 --rate 60 xrandr -q如果屏幕黑掉但 xrandr 仍认为模式成功就要怀疑硬件时钟或 HDMI 线材而不是再改软件。7. 常见问题排查黑屏、花屏、模式缺失的检查路径7.1 排错总表问题现象常见原因检查方式处理建议HDMI 无信号HPD 没有被正确感知cat /sys/class/drm/card0-HDMI-A-1/status检查 HPD 上拉电阻或改用 DDC 检测系统不识别超宽屏EDID 缺失或损坏cat /sys/class/drm/card0-HDMI-A-1/edid | edid-decode手动添加 modeline 或用驱动固定模式兜底2560x1080 设置成功但花屏像素时钟超额查看 dmesg 中时钟初始化行降低刷新率到 50Hz 或检查 HDMI 版本fbcon 正常但 Xorg 无法识别使用的仍然是 fbdev 而非 DRMls /dev/dri/确认已加载 SM750 DRM 驱动并移除 fbdev 驱动插入 HDMI 后状态不变没有 HPD 中断处理dmesg观察 IRQ 日志开启轮询模式或检查 IRQ 注册模式设置返回 -EINVALmode_valid 拒绝modetest -M sm750 -s 1:2560x1080调整模式上限或确认 EDID 中模式参数7.2 一个真实排查顺序示例假设用户反馈“HDMI 插上去黑屏但 VGA 能显示”推荐按下面顺序排查先看连接器状态cat /sys/class/drm/card0-HDMI-A-1/status输出connected则继续输出disconnected则查 HPD。看当前已解析模式modetest -M sm750 -p如果模式列表为空查 EDID。直接强制设置一个低分辨率模式modetest -M sm750 -s 1:1280x720-60如果 1280x720 能亮说明链路和编码器正常问题转向高频模式参数。如果连 720p 都不亮问题可能在 encoder/CRTC 配置或硬件 TMDS 电源。抓图形状态cat /sys/kernel/debug/dri/0/state看connector[1]、crtc[0]、plane[0]是否都已启用以及mode是否真的有值。这条链路可以快速区分“驱动没感知到显示器”和“驱动感知到了但模式设置失败”两类问题。8. 开源驱动的长期价值源码公开只是第一步8.1 为什么开源让 SM750 用户受益SM750 这类显示控制芯片在消费级市场并不耀眼但在工控和嵌入式市场有大量存量设备。厂商假设一个专有驱动没人能一直维护内核升级后驱动失效的问题会反复出现。开源 DRM 驱动从源码层面解决了三个实际问题可移植使用者可以根据自己的内核版本重新编译而不是依赖厂商预编译好的.ko。可回溯出现黑屏、花屏问题时可以用git log和git bisect定位改动引入点。可审计HDMI 输出的 EDID 处理、DPMS、时钟限制都暴露在源码中硬件工程师可以直接判断“是驱动限制还是硬件不够”。8.2 从“能运行”到“可维护”的落地建议虽然开源让代码可见但把它落地到产品上仍要遵守一些工程规则维护一个自己的内核分支把 SM750 驱动作为补丁单独管理避免混入大量无关内核配置变更。在不同 SM750 版型上分别测试不要把一块板子的mode_valid上限复制到所有设备。在生产环境禁用或者严格测试drm_cvt_mode生成的固定模式优先信任 EDID 中的详细时序。为驱动写一个简单的自检脚本在产线阶段自动检查 HPD、EDID 和模式切换是否正常。每次升级内核前用drm-tip或维护者队列里的补丁集做一次兼容性编译尽早发现 API 变更。这种驱动真正适合的下一步扩展方向包括加入 HDMI 音频输出支持、增加电源管理回调、把固定模式改为设备树可配置项、以及支持更多 21:9 显示器的 EDID 覆盖机制。对于开发人员来说先把 HPD、EDID、mode_valid 这三条链路跑通再研究更复杂的功能是最稳妥的路径。
返回列表