ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

4. MTK DRM驱动架构:mtk_drm_drv.c分析、平台驱动注册、组件化框架与bind/unbind机制

4. MTK DRM驱动架构:mtk_drm_drv.c分析、平台驱动注册、组件化框架与bind/unbind机制 4.1 平台驱动注册——一切从这里开始先看最基础的部分。MTK的DRM驱动是通过Linux平台驱动框架注册到内核里的。你想想看系统怎么知道该调用哪个驱动来处理显示靠的就是这个平台驱动匹配机制。static const struct of_device_id mtk_drm_of_ids[] { { .compatible mediatek,mt8678-drm, }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, mtk_drm_of_ids); static struct platform_driver mtk_drm_platform_driver { .probe mtk_drm_probe, .remove mtk_drm_remove, .driver { .name mtk-drm, .of_match_table mtk_drm_of_ids, .pm mtk_drm_pm_ops, }, }; module_platform_driver(mtk_drm_platform_driver);这里有个细节我提醒你注意module_platform_driver这个宏它其实展开后就是module_init和module_exit。我个人习惯在写新驱动时如果不需要特殊初始化顺序直接用这个宏最省事。我在项目中遇到过一个问题设备树里compatible写成了mediatek,mt8678-drm但驱动里写的是mediatek,mt8678-drm-v1结果驱动死活不加载。嗯这种低级错误排查起来最浪费时间。4.2 probe函数——驱动的心脏当内核匹配到设备后就会调用mtk_drm_probe。这个函数干了三件大事分配并初始化DRM设备结构体注册各个显示子组件如OVL、DSI、HDMI等调用component_master_add_with_match启动组件化绑定流程static int mtk_drm_probe(struct platform_device *pdev) { struct device *dev pdev-dev; struct mtk_drm_private *private; struct component_match *match NULL; int ret; private devm_kzalloc(dev, sizeof(*private), GFP_KERNEL); if (!private) return -ENOMEM; private-drm drm_dev_alloc(mtk_drm_driver, dev); if (IS_ERR(private-drm)) return PTR_ERR(private-drm); /* 收集所有子组件 */ ret mtk_drm_component_match_add(dev, match); if (ret) goto err_free; /* 注册为组件化master */ component_master_add_with_match(dev, mtk_drm_component_ops, match); dev_set_drvdata(dev, private); return 0; err_free: drm_dev_put(private-drm); return ret; }你可能会问为什么要搞这么复杂直接一次性初始化不行吗答案在于MTK的显示架构是高度模块化的。OVL图层叠加、DSI显示串行接口、HDMI、DP等模块它们可能在不同的时间点才准备好。组件化框架就是为了解决这种“异步依赖”问题。核心要点probe函数只负责“搭架子”真正的硬件初始化要等到所有组件都bind完成后才进行。4.3 组件化框架——分而治之的艺术组件化框架是Linux内核里一套很巧妙的设计。它把一个大驱动拆成多个小组件每个组件可以独立probe等所有组件都ready了再统一进行bind操作。在MTK DRM里组件分为两类组件类型代表模块职责Master主控mtk_drm_drv协调所有组件管理DRM设备Component子组件ovl, dsi, hdmi, dp各自管理硬件模块提供接口每个子组件需要实现两个关键回调bind和unbind。我刚开始接触时觉得这两个名字起得真好——bind就是“绑到一起”unbind就是“解绑”。static const struct component_ops mtk_ovl_component_ops { .bind mtk_ovl_bind, .unbind mtk_ovl_unbind, }; static int mtk_ovl_probe(struct platform_device *pdev) { /* 初始化硬件资源 */ /* ... */ /* 把自己注册为组件 */ return component_add(dev, mtk_ovl_component_ops); }避坑指南我曾经在调试时发现某个子组件的probe成功了但bind没被调用。排查了半天原来是设备树里该节点的status写成了disabled。记住组件化框架只对status为okay的设备进行bind。4.4 bind/unbind机制——从零到一的组装过程当所有子组件都完成probe后内核会调用master的bind函数。这个函数才是真正的“开工信号”。static int mtk_drm_bind(struct device *dev) { struct mtk_drm_private *private dev_get_drvdata(dev); struct drm_device *drm private-drm; int ret; /* 1. 绑定所有子组件 */ ret component_bind_all(dev, drm); if (ret) return ret; /* 2. 初始化DRM框架 */ ret drm_dev_register(drm, 0); if (ret) goto err_unbind; /* 3. 创建CRTC、Encoder、Connector等 */ ret mtk_drm_kms_init(drm); if (ret) goto err_unregister; /* 4. 创建Framebuffer和显示平面 */ ret mtk_drm_fbdev_init(drm); if (ret) goto err_kms_deinit; return 0; err_kms_deinit: mtk_drm_kms_deinit(drm); err_unregister: drm_dev_unregister(drm); err_unbind: component_unbind_all(dev, drm); return ret; }这里有个顺序问题值得注意component_bind_all必须在drm_dev_register之前调用。为什么因为子组件在bind时可能会注册自己的encoder或connector如果DRM设备还没注册这些资源就没地方挂载。unbind函数是bind的逆过程顺序要反过来static void mtk_drm_unbind(struct device *dev) { struct mtk_drm_private *private dev_get_drvdata(dev); struct drm_device *drm private-drm; mtk_drm_fbdev_fini(drm); mtk_drm_kms_deinit(drm); drm_dev_unregister(drm); component_unbind_all(dev, drm); }重要提醒unbind函数在系统休眠、驱动卸载或热插拔时都会被调用。我曾经见过一个bugunbind里没有正确释放中断导致下次bind时申请中断失败。所以写unbind时一定要“打扫干净屋子再请客”。4.5 多屏显示的关键——组件化如何支持多路输出MTK8678支持多屏显示说白了就是可以有多个CRTC和多个Encoder。组件化框架怎么支持这个答案在于每个显示通道如主屏、副屏、HDMI都可以看作一个独立的组件组。设备树里会定义多个节点display-subsystem { compatible mediatek,mt8678-display-subsystem; ovl14000000 { compatible mediatek,mt8678-ovl; /* 主屏图层 */ }; ovl14001000 { compatible mediatek,mt8678-ovl; /* 副屏图层 */ }; dsi14010000 { compatible mediatek,mt8678-dsi; /* 主屏DSI */ }; hdmi14020000 { compatible mediatek,mt8678-hdmi; /* HDMI输出 */ }; };在mtk_drm_component_match_add函数里驱动会遍历这些节点为每个节点创建一个组件匹配项。当所有节点都probe完成后bind函数就会一次性把它们全部初始化。我个人觉得这种设计最妙的地方在于添加一个新的显示接口比如DP只需要在设备树里加一个节点然后实现对应的component_ops完全不需要修改核心的bind/unbind逻辑。这就是组件化框架的魅力——高内聚、低耦合。4.6 调试技巧与常见问题最后分享几个我在调试MTK DRM驱动时积累的经验查看组件绑定状态cat /sys/kernel/debug/component可以列出所有已注册的组件和它们的绑定状态。如果某个组件显示not bound说明它还没准备好。模拟unbind/bind通过echo 0 /sys/bus/platform/drivers/mtk-drm/unbind可以手动解绑再echo 1 .../bind重新绑定。这在调试热插拔场景时特别有用。日志过滤在mtk_drm_bind和各个子组件的bind函数里加dev_dbg然后通过echo file mtk_drm_drv.c p /sys/kernel/debug/dynamic_debug/control动态开启日志。嗯关于mtk_drm_drv.c的分析就到这里。这个文件虽然只有几百行但它承载了整个多屏显示架构的骨架。理解了它你就掌握了MTK DRM驱动的“总开关”。下一章我们会深入分析各个子组件的具体实现看看OVL、DSI这些模块是怎么在bind/unbind框架下工作的。
返回列表