玖叶教程网

前端编程开发入门

Linux显卡驱动,DRM显示框架简单介绍

随着技术的发展,从古老的光栅命令行式的人机接口到现在的大型虚拟现实场景,计算机的显示技术始终在不停的更新换代。Linux是如何显示图形界面的呢?在这里简单的介绍一下。

一、DRM框架结构

我们看到的Linux系统界面多姿多彩,主要由两部分来实现。第一部分,窗口管理系统,比如X Windows,QWS等,负责把窗口部件渲染成一帧图形保存在显存里。第二部分,DRM(Direct Rendering Manager)框架,负责驱动显卡,把显存的内容以适当的格式传递给显示器加以显示。现在的显卡早已不仅包含了图形存储和传递的功能,还包含利用GPU渲染2D/3D图形的功能,因此,DRM框架还要负责把与GPU相关的功能通过某种方式暴露给用户空间,协助窗口系统产生美轮美奂的显示效果。 这里涉及到两个概念,窗口系统渲染和GPU渲染。

窗口系统渲染

即我们常说的GUI渲染。平时我们操作的按钮,输入框,列表框等由窗口管理系统渲染成一帧一帧的图形保存在内存中,然后通过内核把图片发送到显示器上,每一次窗口的改变都产生一帧图像,像电影一样,一帧一帧的刷新到显示器上,从而我们就看到了动态的内容。

GPU渲染

我们都知道,像大型3D游戏等应用需要大量的渲染工作,如果把这些工作放在CPU上执行将是繁重的任务,而且现在CPU是为了更好的流程控制和逻辑计算而设计的,在并行计算方便并不具有优势,所以,就有了GPU的出现。把渲染工作交给GPU,就相当于找到了一个得力的助手,而且GPU是专门为并行计算而设计的,大大减轻了CPU的负担。在实现上,相当于窗口管理系统只负责创建和管理窗口,并在窗口上挖一个矩形,但里面的内容却是交由GPU来渲染了。

二、DRM主要对象

DRM 是Linux 下的图形渲染框架,具体的说是显卡驱动框架。也就是说,一个显卡如果想在Linux中玩的溜溜溜,只有遵循DRM框架的逻辑才能把驱动程序和内核融合起来,上层的应用才可以充分的利用显卡特性,把显示功能发挥到极致。由于DRM的代码十分庞大,显卡的逻辑又特别复杂,所以很难一下子全部了解通透,只有通过对关键对象的了解,把简单的例子串起来,才能慢慢的揭开它的面纱。

DRM框架

DRM框架代码就是实现DRM具体功能的代码,实现了对显卡的文件抽象,创建显卡驱动文件(如:/dev/dri/card0)。用户程序通过open/close/ioctl 等标准接口,来驱动设备。在系统初始化的时候,内核按其他设备一样的方式加载驱动,发现设备,及其他初始化工作。 框架代码位于: drivers/gpu/drm GPU调度的代码放在 drivers/gpu/drm/scheduler TTM相关代码放在 drivers/gpu/drm/ttm 其他的子目录是各种厂家的驱动程序目录,如amd,vmwgfx等。

Framebuffer

帧缓冲区对象(struct drm_framebuffer)是帧内存对象的抽象,它提供了像素源给到CRTC。帧缓冲区依赖于底层内存管理器分配内存。应用程序通过调用ioctl,指定DRM_IOCTL_MODE_ADDFB参数显式地创建帧缓冲区,创建的对象以句柄的形式返回用户空间,随后可以给到后续CRTC,Plane及页面更新等函数。对于使用GEM缓冲区管理的驱动程序来说,这将是一个GEM句柄;使用TTM的是TTM句柄,例如vmwgfx驱动就直接向用户空间暴露TTM句柄。经过drm_framebuffer_init()函数初始化后,userspace可以使用和访问fb对象。通常通过mmap系统调用映射一段内存区,然后像读写内存一样方便的更新帧数据,之后再提交到CRTC上。帧缓冲区的生命周期由引用计数控制,驱动程序可以使用drm_framebuffer_get()获取引用,然后使用drm_framebuffer_put()释放引用。

Plane

一个平面对象(struct drm_plane)表示一个图像源,从drm_framebuffer对象获取输入数据,在输出过程中与CRTC的顶部混合或覆盖。平面本身不单指定该图像的裁剪和缩放,而且指定了它放置在CRTC中的位置,当然它还有其他属性,例如像素的位置和混合方式,旋转或Z位置等,所有属性都存储在drm_plane_state中。要创建平面,驱动程序分配初始化drm_plane对象实例,然后通过drm_universal_plane_init()注册它。光标和覆盖平面是Plane的一个实例,所有驱动程序都应该至少为每个CRTC提供一个主平面。

CRTC

一个CRTC(struct drm_crtc)表示一整个显示管线。它从drm_plane平面接收像素数据并将它们混合在一起。drm_display_mode也绑定到CRTC,指定显示时序等功能。在输出端,数据被送到一个或多个drm_encoder对象中,每个drm_encoder对象又连接到一个drm_connector对象上。 要创建CRTC,KMS驱动程序分配一个drm_crtc的一个实例,然后调用drm_crtc_init_with_planes()将其注册。CRTC仍然包含有旧模式集操作的入口点,例如drm_crtc_funcs.page_flip和drm_crtc_funcs.cursor_set2,以及drm_crtc_funcs.gamma_set等。 对于更新的atomic驱动,所有功能通过drm_property和drm_mode_config_funcs.atomic_check(),及drm_mode_config_funcs.atomic_check()进行控制。

Encoder

Encoder对象(struct drm_encoder)是CRTC和连接器(drm_connector)之间的连接元素。编码器从CRTC获取像素数据,并将其转换成适合任何连接的连接器的格式。编码器用drm_encoder_init初始化,并用drm_encoder_cleanup清理。

Connector

在DRM中,连接器(struct drm_connector)是显示接收器的抽象,包括als固定面板或任何其他可以以某种形式显示像素的东西。与表示硬件的所有其他KMS对象(如CRTC、编码器或平面抽象)不同,连接器可以在运行时热插拔,因此,使用drm_connector_get和drm_connector_put()控制引用计数。KMS驱动程序必须为每个接收器创建、初始化、注册并连接到结构drm_connector。通过drm_connector_init初始化,该调用带有指向drm_connector_funcs的指针和连接器类型,然后通过对drm_connector_register的调用公开给用户空间。连接器必须连接到编码器才能使用。

三、Drm简单例子

从例子开始学习是一个比较快捷的方法,先试一个最简单的modeset,显示一副纯色的图形。注意:例子不能在Linux桌面环境下执行,因为设备已经被窗口系统占用了,需要通过命令

[root@localhost ~]#init 3

把系统切换到多用户模式。 通过代码可以看到例子的主要步骤如下:

1. 打开设备文件

Drm框架在加载成功后会创建一个/dev/dri/card0设备文件,给用户程序一个统一的入口,操作显卡的各种特性,使用的时候通过文件系统调用打开即可。

 fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
 if (fd < 0) {
  ret = -errno;
  fprintf(stderr, "cannot open '%s': %m\n", node);
  return ret;
 }

2. 获取资源句柄

获取所有与显卡有关的资源,资源都是通过句柄来操作

/**
 1. Retrives all of the resources associated with a card.
 */
extern drmModeResPtr drmModeGetResources(int fd);

3. 获取连接对象

获取到资源对象drmModeRes之后,就可以通过它获取连接对象

/**
 2. Retrieve all information about the connector connectorId. This will do a
 3. forced probe on the connector to retrieve remote information such as EDIDs
 4. from the display device.
 */
extern drmModeConnectorPtr drmModeGetConnector(int fd,
                           uint32_t connectorId);

4. 创建FB

创建FrameBuffer,然后映射出一块内存,之后在内存中填入像素数据

static int modeset_create_fb(int fd, struct modeset_dev *dev)
{
 struct drm_mode_create_dumb creq;
 struct drm_mode_destroy_dumb dreq;
 struct drm_mode_map_dumb mreq;
 int ret;

 /* create dumb buffer */
 memset(&creq, 0, sizeof(creq));
 creq.width = dev->width;
 creq.height = dev->height;
 creq.bpp = 32;
 ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
 if (ret < 0) {
  fprintf(stderr, "cannot create dumb buffer (%d): %m\n",
   errno);
  return -errno;
 }
 dev->stride = creq.pitch;
 dev->size = creq.size;
 dev->handle = creq.handle;

 /* create framebuffer object for the dumb-buffer */
 ret = drmModeAddFB(fd, dev->width, dev->height, 24, 32, dev->stride,
      dev->handle, &dev->fb);
 if (ret) {
  fprintf(stderr, "cannot create framebuffer (%d): %m\n",
   errno);
  ret = -errno;
  goto err_destroy;
 }

 /* prepare buffer for memory mapping */
 memset(&mreq, 0, sizeof(mreq));
 mreq.handle = dev->handle;
 ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
 if (ret) {
  fprintf(stderr, "cannot map dumb buffer (%d): %m\n",
   errno);
  ret = -errno;
  goto err_fb;
 }

 /* perform actual memory mapping */
 dev->map = mmap(0, dev->size, PROT_READ | PROT_WRITE, MAP_SHARED,
          fd, mreq.offset);
 if (dev->map == MAP_FAILED) {
  fprintf(stderr, "cannot mmap dumb buffer (%d): %m\n",
   errno);
  ret = -errno;
  goto err_fb;
 }

 /* clear the framebuffer to 0 */
 memset(dev->map, 0, dev->size);

 return 0;

err_fb:
 drmModeRmFB(fd, dev->fb);
err_destroy:
 memset(&dreq, 0, sizeof(dreq));
 dreq.handle = dev->handle;
 drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
 return ret;
}

5. 设置Crtc模式

FB创建好了,并且清0了,也可以填充任何数据,然后设CRTC,FB的内容就会显示到屏幕上,drmModeSetCrtc的参数有fd,crtc句柄,FB句柄,x,y坐标等

/**
 * Set the mode on a crtc crtcId with the given mode modeId.
 */
int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId,
                   uint32_t x, uint32_t y, uint32_t *connectors, int count,
           drmModeModeInfoPtr mode);

6. 清理工作

显示完成后,可以执行清理工作。一般的GUI会一直运行下去,不会自己执行清楚工作,但为了程序的完整性,还是要包含清理资源的代码。

static void modeset_cleanup(int fd)
{
 struct modeset_dev *iter;
 struct drm_mode_destroy_dumb dreq;

 while (modeset_list) {
  /* remove from global list */
  iter = modeset_list;
  modeset_list = iter->next;

  /* restore saved CRTC configuration */
  drmModeSetCrtc(fd,
          iter->saved_crtc->crtc_id,
          iter->saved_crtc->buffer_id,
          iter->saved_crtc->x,
          iter->saved_crtc->y,
          &iter->conn,
          1,
          &iter->saved_crtc->mode);
  drmModeFreeCrtc(iter->saved_crtc);

  /* unmap buffer */
  munmap(iter->map, iter->size);

  /* delete framebuffer */
  drmModeRmFB(fd, iter->fb);

  /* delete dumb buffer */
  memset(&dreq, 0, sizeof(dreq));
  dreq.handle = iter->handle;
  drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);

  /* free allocated memory */
  free(iter);
 }
}

例子的源码是参考网上的,地址: https://github.com/dvdhrm/docs/tree/master/drm-howto 里面有几个例子,都可以在虚拟机上运行通过

参考文章

DRM(Direct Rendering Manager)学习简介

https://blog.csdn.net/hexiaolong2009/article/details/83720940

Linux DRM(二)基本概念和特性

https://blog.csdn.net/dearsq/article/details/78394388

例子代码

https://github.com/dvdhrm/docs/tree/master/drm-howto

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言