前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux DRM 框架与实例分析

Linux DRM 框架与实例分析

作者头像
Jasonangel
发布2024-04-15 12:45:07
1180
发布2024-04-15 12:45:07
举报

1、DRM 框架

Linux 图像子系统涉及 GUI、3D application、DRM/KMS、hardware 等:

在 Linux display 驱动开发时,通常关注 FBDEV(Framebuffer Device),DRM/KMS 子系统。在 FrameBuffer Device 驱动框架下,我们能够快速开发出可供简单使用的显示驱动。

但是随着芯片显示外设的性能逐渐增强、3D 渲染及 GPU 的引入,FrameBuffer 框架就落伍了,显示覆盖 (菜单层级)、GPU 加速、硬件光标等功能并不能得到很好得支持,并且 FrameBuffer 将底层的显存通过 /dev/fb 暴露给用户空间,容易导致不同的应用程序在操作显存时产生访问冲突,不安全。因此,需要一个现代的图形显示框架来解决这些问题,DRM(Direct Rendering Manager,直接图形管理器) 诞生。

DRM 将现代显示领域中会涉及的一些操作进行分层并使这些模块独立,如果上层应用想操作显存、显示效果、GPU,都必须在一些框架的约束下进行。

我们可以从用户空间、内核空间的两个角度去了解 DRM 框架:

用户空间 (libdrm driver):

  • Libdrm(DRM 框架在用户空间的 Lib)

内核空间 (DRM driver):

  • KMS(Kernel Mode Setting,内核显示模式设置)
  • GEM(Graphic Execution Manager,图形执行管理器)

通常用 DRM/KMS 来指代整个 DRM subsystem,但是 KMS 和 DRM driver 只是整个 DRM subsystem 的其中 2 个部分。

Libdrm

DRM 框架在用户空间提供的 Libdrm,对底层接口进行封装,主要是对各种 IOCTL 接口进行封装,向上层提供通用的 API 接口,用户或应用程序在用户空间调用 libdrm 提供的库函数,即可访问到显示的资源,并对显示资源进行管理和使用。

这样通过 libdrm 对显示资源进行统一访问,libdrm 将命令传递到内核最终由 DRM 驱动接管各应用的请求并处理,可以有效避免访问冲突。

KMS(Kernel Mode Setting)

KMS 属于 DRM 框架下的一个大模块,主要负责两个功能:显示参数设置及显示画面控制。这两个基本功能可以说是显示驱动必须基本的能力,在 DRM 框架下,为了将这两部分适配得符合现代显示设备逻辑,又分出了几部分子模块配合框架。

1、DRM FrameBuffer

DRM FrameBuffer 是一个软件抽象,硬件无关的基本元素,描述了图层显示内容的信息 (width, height, pixel_format,pitch 等)。

2、Planes

平面,图层的意思。基本的显示控制单位,每个图像拥有一个 Planes,Planes 的属性控制着图像的显示区域、图像翻转、色彩混合方式等,最终图像经过 Planes 并通过 CRTC 组件,得到多个图像的混合显示或单独显示的等功能。

3、CRTC

CRTC:Cathode Ray Tube Controller,负责把要显示图像,转化为底层硬件层面上的具体时序要求,还负责着帧切换、电源控制、色彩调整等,可以连接多个 Encoder ,实现复制屏幕功能。

4、Encoder

编码器,转换输出器,负责电源管理、显然输出需要不同的信号转换器,将内存的像素转换成显示器需要的信号。

5、Connector

连接器,负责硬件设备的接入,比如 HDMI,VGA 等,可以获取到设备 EDID , DPMS 连接状态等等。

上述的这些组件,最终完成了一个完整的 DRM 显示控制过程,如下图所示:

上面 CRTC、Planes、Encoder、Connector 这些组件是对硬件的抽象,即使没有实际的硬件与之对应,在软件驱动中也需要实现这些,否则 DRM 子系统无法正常运行。

GEM(generic DRM memory-management)

GEM 负责对 DRM 使用的内存 (如显存) 进行管理, 是一个软件抽象。

GEM 框架提供的功能包括:

  1. 内存分配和释放
  2. 命令执行
  3. 执行命令时的管理

2、RK 平台 DRM 实现

显示功能的驱动一般由芯片厂商 rockchip 来负责实现,完成一个 DRM-Host,主机驱动代码一般位于 drivers/gpu/drm/xxx/ 目录下,这里 xxx 代指芯片厂商。

在驱动中 rockchip 的显示驱动使用 component 框架,显示驱动为 master,显示驱动下的设备称为 component。

在 display_subsystem 设备节点中的 ports 节点就是关联的 component,实际指向 vopl_out 和 vopb_out 节点,(VOP 是各种输出图像的接口),在该节点下有多个 port,可以同时输出多个视频信号。

rk3399.dtsi

代码语言:javascript
复制
display_subsystem: display-subsystem {
 status = "okay";
 compatible = "rockchip,display-subsystem";
 ports = <&vopl_out>, <&vopb_out>;
 clocks = <&cru PLL_VPLL>, <&cru PLL_CPLL>;
 clock-names = "hdmi-tmds-pll", "default-vop-pll";
 devfreq = <&dmc>;
 logo-memory-region = <&drm_logo>;
 secure-memory-region = <&secure_memory>;
 
 route {
  route_dsi: route-dsi {
   status = "disabled";
   logo,uboot = "logo.bmp";
   logo,kernel = "logo_kernel.bmp";
   logo,mode = "center";
   charge_logo,mode = "center";
   connect = <&vopb_out_dsi>;
  };
    ......
 };
};
代码语言:javascript
复制
 vopl: vop@ff8f0000 {
  compatible = "rockchip,rk3399-vop-lit";
  reg = <0x0 0xff8f0000 0x0 0x600>,
   <0x0 0xff8f1c00 0x0 0x200>,
   <0x0 0xff8f2000 0x0 0x400>;
  reg-names = "regs", "cabc_lut", "gamma_lut";
  interrupts = <GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH 0>;
  clocks = <&cru ACLK_VOP1>, <&cru DCLK_VOP1>, <&cru HCLK_VOP1>, <&cru DCLK_VOP1_DIV>;
  clock-names = "aclk_vop", "dclk_vop", "hclk_vop", "dclk_source";
  iommus = <&vopl_mmu>;
  power-domains = <&power RK3399_PD_VOPL>;
  resets = <&cru SRST_A_VOP1>, <&cru SRST_H_VOP1>, <&cru SRST_D_VOP1>;
  reset-names = "axi", "ahb", "dclk";
  status = "disabled";

  vopl_out: port {
   #address-cells = <1>;
   #size-cells = <0>;

   vopl_out_dsi: endpoint@0 {
    reg = <0>;
    remote-endpoint = <&dsi_in_vopl>;
   };

   vopl_out_edp: endpoint@1 {
    reg = <1>;
    remote-endpoint = <&edp_in_vopl>;
   };

   vopl_out_hdmi: endpoint@2 {
    reg = <2>;
    remote-endpoint = <&hdmi_in_vopl>;
   };

   vopl_out_dp: endpoint@3 {
    reg = <3>;
    remote-endpoint = <&dp_in_vopl>;
   };

   vopl_out_dsi1: endpoint@4 {
    reg = <4>;
    remote-endpoint = <&dsi1_in_vopl>;
   };
  };
 };
    
 vopb: vop@ff900000 {
  compatible = "rockchip,rk3399-vop-big";
  reg = <0x0 0xff900000 0x0 0x600>,
   <0x0 0xff901c00 0x0 0x200>,
   <0x0 0xff902000 0x0 0x1000>;
  reg-names = "regs", "cabc_lut", "gamma_lut";
  interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH 0>;
  clocks = <&cru ACLK_VOP0>, <&cru DCLK_VOP0>, <&cru HCLK_VOP0>, <&cru DCLK_VOP0_DIV>;
  clock-names = "aclk_vop", "dclk_vop", "hclk_vop", "dclk_source";
  resets = <&cru SRST_A_VOP0>, <&cru SRST_H_VOP0>, <&cru SRST_D_VOP0>;
  reset-names = "axi", "ahb", "dclk";
  power-domains = <&power RK3399_PD_VOPB>;
  iommus = <&vopb_mmu>;
  status = "disabled";

  vopb_out: port {
   #address-cells = <1>;
   #size-cells = <0>;

   vopb_out_edp: endpoint@0 {
    reg = <0>;
    remote-endpoint = <&edp_in_vopb>;
   };

   vopb_out_dsi: endpoint@1 {
    reg = <1>;
    remote-endpoint = <&dsi_in_vopb>;
   };

   vopb_out_hdmi: endpoint@2 {
    reg = <2>;
    remote-endpoint = <&hdmi_in_vopb>;
   };

   vopb_out_dp: endpoint@3 {
    reg = <3>;
    remote-endpoint = <&dp_in_vopb>;
   };

   vopb_out_dsi1: endpoint@4 {
    reg = <4>;
    remote-endpoint = <&dsi1_in_vopb>;
   };
  };
 };

平台驱动 rockchip-drm 匹配到设备树,会到设备树 dts 查找 ports 节点和 iommus 节点,使用 component_master_add_with_match 函数注册自己到 component 框架中,设置了 rockchip_drm_ops,其 component 可以通过 component_add 函数增加,master 匹配上所有 component 后,会调用 master 的 bind 回调函数,最后通过 drm_dev_register() 函数注册到 DRM core

平台驱动源码如下 drivers/gpu/drm/rockchip/rockchip_drm_drv.c

代码语言:javascript
复制
static const struct component_master_ops rockchip_drm_ops = {
 .bind = rockchip_drm_bind,
 .unbind = rockchip_drm_unbind,
};

static int rockchip_drm_platform_probe(struct platform_device *pdev)
{
 struct device *dev = &pdev->dev;
 struct component_match *match = NULL;
 struct device_node *np = dev->of_node;
 struct device_node *port;
 int i;

 DRM_INFO("Rockchip DRM driver version: %s\n", DRIVER_VERSION);
 if (!np)
  return -ENODEV;

 for (i = 0;; i++) {
  struct device_node *iommu;
  port = of_parse_phandle(np, "ports", i);
  if (!port)
   break;

  if (!of_device_is_available(port->parent)) {
   of_node_put(port);
   continue;
  }

  iommu = of_parse_phandle(port->parent, "iommus", 0);
  if (!iommu || !of_device_is_available(iommu->parent)) {
   dev_dbg(dev, "no iommu attached for %s, using non-iommu buffers\n",
    port->parent->full_name);
    is_support_iommu = false;
  }
  component_match_add(dev, &match, compare_of, port->parent);
  of_node_put(port);
 }
 ......
 for (i = 0;; i++) {
  port = of_parse_phandle(np, "ports", i);
  if (!port)
   break;

  if (!of_device_is_available(port->parent)) {
   of_node_put(port);
   continue;
  }

  rockchip_add_endpoints(dev, &match, port);
  of_node_put(port);
 }

 port = of_parse_phandle(np, "backlight", 0);
 if (port && of_device_is_available(port)) {
  component_match_add(dev, &match, compare_of, port);
  of_node_put(port);
 }

 return component_master_add_with_match(dev, &rockchip_drm_ops, match);
}
代码语言:javascript
复制
static const struct of_device_id rockchip_drm_dt_ids[] = {
 { .compatible = "rockchip,display-subsystem", },
 { /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, rockchip_drm_dt_ids);

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,
 },
};

3、RK3399 实例

博主手里的是 MIPI DSI 屏幕,设备树配置如下:

代码语言:javascript
复制
 dsi@ff960000 {
  compatible = "rockchip,rk3399-mipi-dsi";
  reg = <0x0 0xff960000 0x0 0x8000>;
  interrupts = <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH 0>;
  clocks = <0x8 0xa2 0x8 0x170 0x8 0xa3>;
  clock-names = "ref", "pclk", "phy_cfg";
  power-domains = <&power RK3399_PD_VIO>;
  resets = <&cru SRST_P_MIPI_DSI0>;
  reset-names = "apb";
  rockchip,grf = <&grf>;
  status = "okay";
  #address-cells = <0x1>;
  #size-cells = <0x0>;
  phandle = <0x137>;

  ports {
   port {
    #address-cells = <1>;
    #size-cells = <0>;

    endpoint@0 {
     reg = <0x0>;
     remote-endpoint = <&vopb_out_dsi>;
     status = "okay";
     phandle = <0xab>;
    };

    endpoint@1 {
     reg = <0x1>;
     remote-endpoint = <&vopl_out_dsi>;
     status = "disabled";
     phandle = <0xa3>;
    };
   };
  };

  panel@0 {
   status = "okay";
   compatible = "simple-panel-dsi";
   reg = <0x0>;
   backlight = <&backlight>;
   dsi,flags = <0xa03>;
   dsi,format = <MIPI_DSI_FMT_RGB888>;
   dsi,lanes = <4>;
   dsi,channel = <0>;
   enable-delay-ms = <35>;
   prepare-delay-ms = <6>;
   unprepare-delay-ms = <0>;
   disable-delay-ms = <20>;
   size,width = <120>;
   size,height = <170>;
   panel-init-sequence = [29 ...... 29];
   panel-exit-sequence = <0x5050128 0x5780110>;
   phandle = <0x138>;

   power_ctr {
    rockchip,debug = <0>;
    power_enable = <1>;
    phandle = <0x139>;

    lcd-rst {
     gpios = <&gpio4 RK_PD6 GPIO_ACTIVE_HIGH>;
     pinctrl-names = "default";
     pinctrl-0 = <&lcd_panel_reset>;
     rockchip,delay = <6>;
     phandle = <0x13a>;
    };
   };

   display-timings {
    native-mode = <&timing0>;
    phandle = <0x13b>;

    timing0 {
     clock-frequency = <0x3938700>;
     hactive = <0x320>;
     vactive = <0x500>;
     hsync-len = <0x14>;
     hback-porch = <0x14>;
     hfront-porch = <0x14>;
     vsync-len = <0x4>;
     vback-porch = <0x4>;
     vfront-porch = <0xa>;
     hsync-active = <0x0>;
     vsync-active = <0x0>;
     de-active = <0x0>;
     pixelclk-active = <0x0>;
     phandle = <0xba>;
    };
   };
  };
 };

对应的 DSI 驱动是 drivers/gpu/drm/rockchip/dw-mipi-dsi.c

对应的 panel 驱动是 drivers/gpu/drm/panel/panel-simple.c

代码语言:javascript
复制
static const struct of_device_id dw_mipi_dsi_dt_ids[] = {
 { .compatible = "rockchip,rk3399-mipi-dsi", .data = &rk3399_socdata, },
 { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dw_mipi_dsi_dt_ids);

static struct platform_driver dw_mipi_dsi_driver = {
 .probe  = dw_mipi_dsi_probe,
 .remove  = dw_mipi_dsi_remove,
 .driver  = {
  .of_match_table = dw_mipi_dsi_dt_ids,
  .pm = &dw_mipi_dsi_pm_ops,
  .name = DRIVER_NAME,
 },
};
module_platform_driver(dw_mipi_dsi_driver);
代码语言:javascript
复制
static int dw_mipi_dsi_probe(struct platform_device *pdev)
{
 struct device *dev = &pdev->dev;
 struct dw_mipi_dsi *dsi;
 struct device_node *np = dev->of_node;
 struct resource *res;
 void __iomem *regs;
 int ret;
 int dsi_id;

 dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
 if (!dsi)
  return -ENOMEM;

 dsi_id = of_alias_get_id(np, "dsi");
 if (dsi_id < 0)
  dsi_id = 0;

 dsi->id = dsi_id;
 dsi->dev = dev;
 dsi->pdata = of_device_get_match_data(dev);
 platform_set_drvdata(pdev, dsi);

 ret = dw_mipi_dsi_parse_dt(dsi);

 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 regs = devm_ioremap_resource(dev, res);
 dsi->irq = platform_get_irq(pdev, 0);
 dsi->pclk = devm_clk_get(dev, "pclk");
 dsi->regmap = devm_regmap_init_mmio(dev, regs,&dw_mipi_dsi_regmap_config);

 if (dsi->pdata->soc_type == RK3126) {
  dsi->h2p_clk = devm_clk_get(dev, "h2p");
  }
 }

 dsi->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
 dsi->rst = devm_reset_control_get(dev, "apb");

 ret = mipi_dphy_attach(dsi);

 ret = devm_request_irq(dev, dsi->irq, dw_mipi_dsi_irq_handler,IRQF_SHARED, dev_name(dev), dsi);

 dsi->dsi_host.ops = &dw_mipi_dsi_host_ops;
 dsi->dsi_host.dev = dev;

 ret = mipi_dsi_host_register(&dsi->dsi_host);

 ret = component_add(dev, &dw_mipi_dsi_ops);
 if (ret)
  mipi_dsi_host_unregister(&dsi->dsi_host);

 return ret;
}

这里会注册 mipi dsi 中断,中断处理函数是 dw_mipi_dsi_irq_handler

4、debug

1、连接 ADB,安装测试工具 apt install libdrm-tests,执行 modetest,会打印 Encoders、Connectors、CRTCs、Planes 的详细信息。

2、DRM driver 会在 /dev/dri 下创建 3 个设备节点:card0、controlD64、renderD128,libdrm 可以打开 card0 在用户空间操作。

3、DRM 的应用编程有两种接口:legacy 接口和 atomic 接口,目前一般用 atomic 接口。

5、后记

虽然 DRM 功能符合现代显示设备的需求,但是仍有众多的老设备及软件需要 Framebuffer 的支持。所以在 DRM 框架下,有部分代码用于实现在 DRM 框架下,去模拟 FB 设备。

在 rockchip 提供的显示驱动代码中,也有模拟 FB 设备的相关代码,参见 drivers/gpu/drm/rockchip/rockchip_drm_fb.c 文件,最终效果就是设备目录下,出现熟悉的身影 /dev/fb0

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-04-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 嵌入式Linux系统开发 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、DRM 框架
    • Libdrm
      • KMS(Kernel Mode Setting)
        • 1、DRM FrameBuffer
        • 2、Planes
        • 3、CRTC
        • 4、Encoder
        • 5、Connector
      • GEM(generic DRM memory-management)
        • 2、RK 平台 DRM 实现
          • 3、RK3399 实例
            • 5、后记
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档