1. DRM 架构介绍
DRM是Linux目前主流的图形显示框架,相比FB架构,DRM更能适应当前日益更新的显示硬件。比如FB原生不支持多层合成,不支持VSYNC,不支持DMA-BUF,不支持异步更新,不支持fence机制等等,而这些功能DRM原生都支持。同时DRM可以统一管理GPU和Display驱动,使得软件架构更为统一,方便管理和维护。 DRM从模块上划分,可以简单分为3部分:libdrm、KMS、GEM。
1.1libdrm
Libdrm是对底层接口进行封装,向上层提供通用的API接口,主要是对各种IOCTL接口进行封装。
1.2KMS Kernel ModeSet ting,所谓Mode setting:更新画面和设置显示参数。
- 更新画面:显示buffer的切换,多图层的合成方式,以及每个图层的显示位置。
- 设置显示参数:包括分辨率、刷新率、电源状态(休眠唤醒)等。
1.3GEM GraphicExecution Manager,主要负责显示buffer的分配和释放,也是GPU唯一用到DRM的地方。当然kms也会使用GEM中分配的buffer来进行显示,后面会涉及到相关处理。
1.4基本元素
DRM框架涉及到的元素很多,大致如下: KMS:CRTC,ENCODER,CONNECTOR,PLANE,FB,VBLANK,propertyGEM:DUMB、PRIME、fence 元素 | 说明 | CRTC | 对显示buffer进行扫描,并产生时序信号的硬件模块,通常指Display Controller | ENCODER | 负责将CRTC输出的timing时序转换成外部设备所需要的信号的模块,如HDMI转换器或DSI Controller | CONNECTOR | 连接物理显示设备的连接器,如HDMI、DisplayPort、DSI总线,通常和Encoder驱动绑定在一起 | PLANE | 硬件图层,有的Display硬件支持多层合成显示,但所有的Display Controller至少要有1个plane | FB | Framebuffer,单个图层的显示内容,唯一一个和硬件无关的基本元素 | VBLANK | 软件和硬件的同步机制,RGB时序中的垂直消影区,软件通常使用硬件VSYNC来实现 | property | 任何你想设置的参数,都可以做成property,是DRM驱动中最灵活、最方便的Mode setting机制 |
2. IMX8MM DRM 上面是从理论上或者说在宏观上来介绍DRM,我的学习方法一直理论与实践相结合,对于我们程序员来说,啥是实践?就是代码,不论什么知识,单纯的理论很难记忆深刻,至少自己是这样,如果将理论和代码联系起来就比较容易记住,容易理解,记忆深刻不容易忘记。 上面提到DRM按照模块可以分为三部分,我觉得对于某个具体的SOC的DRM一定程度上可以分为下面4个部分:
- drm_drv
- imx_drm
- Display-interface
- GPU-interface
2.1源代码路径2.1.1驱动文件夹说明drivers/gpu/imx/ --imx lcdif common drivers/gpu/drm / -- drm core、prime、mode_config和vblank等文件 drivers/gpu/drm/imx/ --imx drm core drivers/gpu/drm/bridge/ --hdmi drivers/gpu/drm/panel/ --panel drivers/phy/ 2.1.2驱动文件说明 模块 | 文件名 | Drm core | Drm-drv.c | Imx drm core | Imx-drm-core.c | Framebuffer | Imx-drm-core.c drm_fb_helper.c | GEM | Drm_gem.c drm_gem_cma_helper.c | LVDS | imx-ldb.c | HDMI | dw_hdmi-imx.c | INNO HDMI | dw_hdmi-imx.c | eDP | cdn-mhdp-imx8qm.c | DP | cdn-mhdp-imx8qm.c | MIPI | sec_mipi_dsim-imx.c | Panel | parallel-display.c | TVE | imx-tve.c |
2.2Device Treearch/ ARM64/boot/dts/myir文件夹下,具体哪个文件呢? 开发板启动后输入命令:fw_printenv,找到对应Device Tree文件, fdt_file=mys-imx8mm-lt8912-hontron-7.dtb 但是mys-imx8mm-lt8912-hontron-7.dts,还需要include其他文件,所以直接使用dtc工具转化一下编译号的dtb文件时一个不错的方法。 dtc -I dtb -O dts -o imx8mm.dtsmys-imx8mm-lt8912-hontron-7.dtb 2.3驱动加载顺序
2.3.1 imx8mm中加载顺序2.3.1.1 imx_lcdif_probe lcdif-common.c,与下面dts文件内容呼应,启动probe函数。
注意DRIVER_NAME好像是无用代码,没有对应代码。
- struct platform_driver imx_lcdif_driver = {
- .probe = imx_lcdif_probe,
- .remove = imx_lcdif_remove,
- .driver = {
- .name = DRIVER_NAME,
- .of_match_table = imx_lcdif_dt_ids,
- .pm = &imx_lcdif_pm_ops,
- },
- };
- module_platform_driver(imx_lcdif_driver);
- static const struct of_device_id imx_lcdif_dt_ids[] = {
- { .compatible = "fsl,imx8mm-lcdif", .data = &imx8mm_pdata, },
- { .compatible = "fsl,imx8mn-lcdif", .data = &imx8mm_pdata, },
- { /* sentinel */ }
- };
复制代码
- lcdif@32e00000 {
- #address-cells = <0x1>;
- #size-cells = <0x0>;
- compatible = "fsl,imx8mm-lcdif";
- reg = <0x32e00000 0x10000>;
- clocks = <0x2 0x6b 0x2 0xcd 0x2 0xce>;
- clock-names = "pix", "disp-axi", "disp-apb";
- assigned-clocks = <0x2 0x6b 0x2 0x55 0x2 0x56>;
- assigned-clock-parents = <0x2 0x28 0x2 0x41 0x2 0x38>;
- assigned-clock-rate = <0x2367b880 0x1dcd6500 0xbebc200>;
- interrupts = <0x0 0x5 0x4>;
- lcdif-gpr = <0x45>;
- resets = <0x46>;
- power-domains = <0xe>;
- status = "okay";
- max-res = <0x400 0x258>;
- port@0 {
- reg = <0x0>;
- phandle = <0x44>;
- endpoint {
- remote-endpoint = <0x47>;
- phandle = <0x4a>;
- };
- };
- };
复制代码
2.3.1.2 drm_core_init drm_drv.c,module_init加载此驱动模块,module_init和module_platform_driver函数加载的模块,在kernel里面哪个先加载呢?
- static int __init drm_core_init(void)
- {
- int ret;
- DRM_INFO("[%s %d] drm_core_initn", __FILE__, __LINE__);
- drm_connector_ida_init();
- DRM_INFO("[%s %d] drm_core_initn", __FILE__, __LINE__);
- idr_init(&drm_minors_idr);
- DRM_INFO("[%s %d] drm_core_initn", __FILE__, __LINE__);
- ret = drm_sysfs_init();
- if (ret < 0) {
- DRM_ERROR("Cannot create DRM class: %dn", ret);
- goto error;
- }
- DRM_INFO("[%s %d] drm_core_initn", __FILE__, __LINE__);
- drm_debugfs_root = debugfs_create_dir("dri", NULL);
- ret = register_chrdev(DRM_MAJOR, "drm", &drm_stub_fops);
- if (ret < 0)
- goto error;
- DRM_INFO("[%s %d] drm_core_initn", __FILE__, __LINE__);
- drm_core_init_complete = true;
- DRM_INFO("[%s %d] drm_core_initn", __FILE__, __LINE__);
- DRM_DEBUG("Initializedn");
- return 0;
- error:
- drm_core_exit();
- return ret;
- }
- module_init(drm_core_init);
复制代码
2.3.1.3 drm_vblank_init 被imx_drm_bind函数调用
2.3.1.4 imx_drm_platform_probe
imx-drm-core.c,与dts文件的内容呼应启动probe函数。
- static const struct of_device_id imx_drm_dt_ids[] = {
- { .compatible = "fsl,imx-display-subsystem", },
- { /* sentinel */ },
- };
- MODULE_DEVICE_TABLE(of, imx_drm_dt_ids);
- static struct platform_driver imx_drm_pdrv = {
- .probe = imx_drm_platform_probe,
- .remove = imx_drm_platform_remove,
- .driver = {
- .name = "imx-drm",
- .pm = &imx_drm_pm_ops,
- .of_match_table = imx_drm_dt_ids,
- },
- };
- module_platform_driver(imx_drm_pdrv);
复制代码
- display-subsystem {
- compatible = "fsl,imx-display-subsystem";
- ports = <0x44>;
- };
复制代码
2.3.1.5 lcdif_crtc_probe
lcdif-crtc.c,注意这个模块不是dts方式启动,而是通过imx-lcdif-crtc名字呼应的方式启动,另外一个名字在lcdif-common.c中,她被函数lcdif_add_client_devices调用,启动时机则涉及到component机制,下面会统一介绍。
- static int lcdif_crtc_probe(struct platform_device *pdev)
- {
- int ret = 0;
- struct device *dev = &pdev->dev;
- dev_dbg(&pdev->dev, "%s:%d lcdif crtc probe beginn", __func__, __LINE__);
- DRM_INFO("[%s %d] lcdif_crtc_proben", __FILE__, __LINE__);
- if (!dev->platform_data) {
- dev_err(dev, "no platform datan");
- return -EINVAL;
- }
- DRM_INFO("[%s %d] lcdif_crtc_proben", __FILE__, __LINE__);
- ret = component_add(dev, &lcdif_crtc_ops);
- DRM_INFO("[%s %d] lcdif_crtc_proben", __FILE__, __LINE__);
- return ret;
- }
- static int lcdif_crtc_remove(struct platform_device *pdev)
- {
- component_del(&pdev->dev, &lcdif_crtc_ops);
- return 0;
- }
- static struct platform_driver lcdif_crtc_driver = {
- .probe = lcdif_crtc_probe,
- .remove = lcdif_crtc_remove,
- .driver = {
- .name = "imx-lcdif-crtc",
- },
- };
- module_platform_driver(lcdif_crtc_driver);
复制代码
- static int lcdif_add_client_devices(struct lcdif_soc *lcdif)
- {
- int ret = 0, i;
- struct device *dev = lcdif->dev;
- struct platform_device *pdev = NULL;
- struct device_node *of_node;
- dev_info(dev, "[%s %d] lcdif_add_client_devicesn", __FILE__, __LINE__);
- for (i = 0; i < ARRAY_SIZE(client_reg); i++) {
- of_node = of_graph_get_port_by_id(dev->of_node, i);
- if (!of_node) {
- dev_info(dev, "no port@%d node in %sn",
- i, dev->of_node->full_name);
- continue;
- }
- of_node_put(of_node);
- pdev = platform_device_alloc(client_reg[i].name, i);
- if (!pdev) {
- dev_err(dev, "Can't allocate port pdevn");
- ret = -ENOMEM;
- goto err_register;
- }
- pdev->dev.parent = dev;
- client_reg[i].pdata.of_node = of_node;
- dev_info(dev, "[%s %d] lcdif_add_client_devicesn", __FILE__, __LINE__);
- ret = platform_device_add_data(pdev, &client_reg[i].pdata,
- sizeof(client_reg[i].pdata));
- dev_info(dev, "[%s %d] lcdif_add_client_devicesn", __FILE__, __LINE__);
- if (!ret)
- ret = platform_device_add(pdev);
- dev_info(dev, "[%s %d] lcdif_add_client_devicesn", __FILE__, __LINE__);
- if (ret) {
- platform_device_put(pdev);
- dev_info(dev, "[%s %d] lcdif_add_client_devicesn", __FILE__, __LINE__);
- goto err_register;
- }
- pdev->dev.of_node = of_node;
- }
- if (!pdev)
- return -ENODEV;
- return 0;
- err_register:
- platform_device_unregister_children(to_platform_device(dev));
- return ret;
- }
复制代码
2.3.1.6 imx_sec_dsim_probe sec_mipi_dsim-imx.c,与dts中的内容呼应启动此probe函数。
- struct platform_driver imx_sec_dsim_driver = {
- .probe = imx_sec_dsim_probe,
- .remove = imx_sec_dsim_remove,
- .driver = {
- .name = DRIVER_NAME,
- .of_match_table = imx_sec_dsim_dt_ids,
- .pm = &imx_sec_dsim_pm_ops,
- },
- };
- module_platform_driver(imx_sec_dsim_driver);
- static const struct of_device_id imx_sec_dsim_dt_ids[] = {
- {
- .compatible = "fsl,imx8mm-mipi-dsim",
- .data = &imx8mm_mipi_dsim_plat_data,
- },
- {
- .compatible = "fsl,imx8mn-mipi-dsim",
- .data = &imx8mm_mipi_dsim_plat_data,
- },
- {
- .compatible = "fsl,imx8mp-mipi-dsim",
- .data = &imx8mm_mipi_dsim_plat_data,
- },
- { /* sentinel */ }
- };
复制代码
- mipi_dsi@32e10000 {
- #address-cells = <0x1>;
- #size-cells = <0x0>;
- compatible = "fsl,imx8mm-mipi-dsim";
- reg = <0x32e10000 0x400>;
- clocks = <0x2 0x8e 0x2 0x8f>;
- clock-names = "cfg", "pll-ref";
- assigned-clocks = <0x2 0x8e 0x2 0x8f>;
- assigned-clock-parents = <0x2 0x36 0x2 0x28>;
- assigned-clock-rates = <0xfdad680 0x2367b880>;
- interrupts = <0x0 0x12 0x4>;
- dsi-gpr = <0x45>;
- resets = <0x48>;
- power-domains = <0x49>;
- status = "okay";
- port@0 {
- endpoint {
- remote-endpoint = <0x4a>;
- phandle = <0x47>;
- };
- };
- port@1 {
- compatible = "lontium,lt8912";
- endpoint {
- remote-endpoint = <0x4b>;
- attach-bridge;
- phandle = <0x31>;
- };
- };
- };
复制代码
2.3.2加载顺序说明 上述加载顺序是目前imx8mm系统上电启动的顺序,其实上面的几个模块初始化的顺序并不是固定的,可以任意调换其加载顺序,当然那些明显的调用和被调用函数顺序除外。
上面的运行log遵循下面三个原则:
- module_init和module_platform_driver等函数的加载顺序,这个跟函数定义等级有关;
- imx-drm-core.c与lcdif-common.c等文件在makefile中编译顺序有关;
- drm框架中运行的几个模块的加载顺序要遵循component框架相关规定,master先注册,component设备都准备好后在依次加载。
3.modetest和modest bmp功能分析
3.1简单modetes功能测试
modetest -M imx-drm -D 0 -s 35@33:1920x1080 -P 31@33:1920x1080 -Ftiles
上述命令可以显示一个Gamma图像,而且是RGB彩色Gramm图像。由于大自然中的颜色是连续的,而在计算机中的颜色点是离散的。所以,设备所显示的图片颜色,其实是在一定的波长段内,寻找有限个点,来进行一种近似地颜色表示。那么,可以想见,bit数越大,层次越多,切割越细,色彩过渡就会越均匀流畅。反之,就会在图像中存在比较明显的色块、色彩跳跃现象。人类的视觉对亮度感知能力并不是呈现线性关系,所以需要Gamma值来进行一个合理的值与值映射,这样来纠正亮度感知变化与实际亮度值变化不统一的问题。
modetest.c中main函数有对后续参数的各种解析,这里不详细说明。
这里只是重点说一下“imx-drm”,其实我们可以从驱动找到“imx-drm”,而且可以确定它是应用层可以访问的设备节点,那modetest如何知道的呢?
kms.c文件中与如下定义,这下都可以明白了,其他代码就不一一罗列。假如我们开发了一款显卡,显然我们可以把我们的kms的module的定义加到这里面,“xdx-drm”可能就是一个不错的选择。
- static const char * const modules[] = {
- "i915",
- "amdgpu",
- "radeon",
- "nouveau",
- "vmwgfx",
- "omapdrm",
- "exynos",
- "tilcdc",
- "msm",
- "sti",
- "tegra",
- "imx-drm",
- "rockchip",
- "atmel-hlcdc",
- "fsl-dcu-drm",
- "vc4",
- "virtio_gpu",
- "mediatek",
- "meson",
- "pl111",
- "stm",
- "sun4i-drm",
- "armada-drm",
- };
复制代码
3.2modetes显示bmp图片
简单的gramm显示彩色,可能不是那么有说服力,我们来显示一张bmp图片吧。
代码太长就放到附件里面,编译,运行如下:
这里简单说明一下,自己遇到的难点,即FB是如何与实现设备(比如HDMI)关联起来的? app中调用ioctl+DRM_IOCTL_MODE_ADDFB,调用到drm驱动中,调用到drm_framebuffer.c文件中的drm_internal_framebuffer_create函数,调用drm_gem_framebuffer_helper.c文件中的drm_gem_fb_create函数;
- aarch64-linux-gnu-gcc -o modeset-single-buffer xf86drmHash.c xf86drmRandom.c xf86drm.c xf86drmMode.c modeset-single-buffer.c bmp.c -I . -I./include/drm -lm
- ./modeset-single-buffer universe.bmp
复制代码
4.component框架介绍
当一个system由多个设备组成,而且每个设备加载时机不确定,就需要一个框架来管理整个system多个设备的加载顺序,确保多个设备加载和使用时不出问题。Linux内核引入component框架是对多个模块加载顺序进行统一管理。 component框架从功能上可以分为4部分: 1.master设备初始化
ret = drm_of_component_probe_with_match(&pdev->dev, match, compare_of, &imx_drm_ops);此函数是master设备初始化的关键函数,注意此函数通过解析dts文件内容明确有几个component设备,try_to_bring_up_master是最终实现函数,注意这里调用此函数第二个参数为NULL。 2.component设备初始化
根据dts和makefile的顺序,每个component设备依次加载,component_add函数初始化component设备,try_to_bring_up_master也是最终实现函数,但是此时调用此函数第二个参数非NULL,而且try_to_bring_up_master函数对第二个参数做了两个判断,这也就是component设备可以比master设备早创建的原因。
- /*
- * Try to bring up a master. If component is NULL, we're interested in
- * this master, otherwise it's a component which must be present to try
- * and bring up the master.
- *
- * Returns 1 for successful bringup, 0 if not ready, or -ve errno.
- */
- static int try_to_bring_up_master(struct master *master,
- struct component *component)
- {
- int ret;
- dev_dbg(master->dev, "trying to bring up mastern");
- if (find_components(master)) {
- dev_dbg(master->dev, "master has incomplete componentsn");
- return 0;
- }
- if (component && component->master != master) {
- dev_dbg(master->dev, "master is not for this component (%s)n",
- dev_name(component->dev));
- return 0;
- }
- if (!devres_open_group(master->dev, NULL, GFP_KERNEL))
- return -ENOMEM;
- /* Found all components */
- ret = master->ops->bind(master->dev);
- if (ret < 0) {
- devres_release_group(master->dev, NULL);
- dev_info(master->dev, "master bind failed: %dn", ret);
- return ret;
- }
- master->bound = true;
- return 1;
- }
复制代码
3.master和component设备之间节点匹配
master和component设备之间节点匹配功能实现的函数也是try_to_bring_up_master,正式因为此函数可以正确地进行节点匹配,所以master和component设置才可以实现等待所有的component设备都准备好后在统一加载。 4.cmaster和component设备实现bind功能
bind功能是指所有的component设备都已经准备好,首先进行master设备bind,然后对每个component设备进行bind。
try_to_bring_up_master->imx_drm_bind->component_bind_all,下面会一次调用lcdif_crtc_bind和imx_sec_dsim_bind,另外每个bind完成的具体内容这里不在详细介绍,想了解细节需要看代码和附件中的运行log。
5.总结 上面就是自己总结的drm框架的KMS功能的大概内容,其实drm框架还有另外一大块内容GEM,或者说GPU功能相关的东西,因为时间关系不再涉及。
吐槽一下yocto编译一次内核需要时间太长,一旦出错需要重新开始。研究内核相关功能花费时间太多,还是搞上层应用开发简单一些。
附件
0
|
|
|
|