不知道大家是否还记得,之前我有引用 Wiki 中对 DRM 的介绍,这里我们再回顾一下:DRM 由两个部分组成:一是 Kernel 的子系统,这个子系统对硬件 GPU 操作进行了一层框架封装。二是提供了一个 libdrm 库,里面封装了一系列 API,用来进行图像显示。整体来看和 Android 上所采用的 Direct Frame Buffer 差不多。Android Kernel 走的是 FB 的框架,并在 HAL 抽象出一个 FBDEV,来进行 FB IOCTL 统一管理。DRM 就相当于直接对图形设备集中处理,并且多出了一个 libdrm库。
其整体脉络如下:
Component framework
在讲述启动过程之前,先简单了解一下 Component framework。
因为 drm 下挂了许多的设备,启动顺序经常会引发各种问题:
这时就需要有一个统一管理的机制,将所有设备统合起来,按照一个统一的顺序加载,Display-subsystem 正式用来解决这个问题的,依赖于 Component 的驱动,通过这个驱动,可以把所有的设备以组件的形式加在一起,等所有的组件加载完毕后,统一进行 bind/unbind。
代码路径:drivers/base/component.c
以下为 rockchip drm master probe 阶段 Component 主要逻辑,为了减小篇幅,去掉了无关的代码:
static int rockchip_drm_platform_probe(struct platform_device *pdev)
{
for(int=0;; i++){
/* ports 指向了 vop 的设备节点 */
port = of_parse_phandle(np, "ports", i);
component_match_add(dev, &match, compare_of, port->parent);
}
for(i=0; ; i++) {
port = of_parse_phandle(np, "ports", i);
component_match_add(dev, &match, compre_of, port->parent);
}
for(i=0;; i++) {
port = of_parse_phandle(np, "ports", i);
/* 搜查 port 下的各个 endpoint,将它们也加入到 match 列表 */
rockchip_add_endpoints(dev, &match,port);
}
return component_master_add_with_match(dev, &rockchip_drm_ops, match);
}
static void rockchip_add_endpoints(...)
{
for_each_child_of_node(port, ep) {
remote = of_graph_get_remote_port_parent(ep);
/* 这边的 remote 即为和 vop 关联的输出设备,即为 edp,mipi,或 hdmi */
component_match_add(dev, match, compare_of, remote);
}
}
图自 makyzq gitbook:
基于 Component 矿建,在 probe 阶段:
device tree
display_subsystem: display-subsystem {
compatible = "rockchip,display-subsystem";
ports = <&vopl_out>, <&vopb_out>;
status = "disabled";
};- compatible: Should be "rockchip, display-subsystem"
- ports: Should contain a list of phandles pointing to display interface port of vop devices. vop definitions as defined in
kernel/Documentation/devicetree/bindings/display/rockchip/rockchip-vop.txt
drm driver
代码路径:
static struct drm_driver rockchip_drm_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM |
DRIVER_PRIME | DRIVER_ATOMIC |
DRIVER_RENDER,
.preclose = rockchip_drm_preclose,
.lastclose = rockchip_drm_lastclose,
.get_vblank_counter = drm_vblank_no_hw_counter,
.open = rockchip_drm_open,
.postclose = rockchip_drm_postclose,
.enable_vblank = rockchip_drm_crtc_enable_vblank,
.disable_vblank = rockchip_drm_crtc_disable_vblank,
.gem_vm_ops = &rockchip_drm_vm_ops,
.gem_free_object = rockchip_gem_free_object,
.dumb_create = rockchip_gem_dumb_create,
.dumb_map_offset = rockchip_gem_dumb_map_offset,
.dumb_destroy = drm_gem_dumb_destroy,
.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
.gem_prime_import = drm_gem_prime_import,
.gem_prime_export = drm_gem_prime_export,
.gem_prime_get_sg_table = rockchip_gem_prime_get_sg_table,
.gem_prime_import_sg_table = rockchip_gem_prime_import_sg_table,
.gem_prime_vmap = rockchip_gem_prime_vmap,
.gem_prime_vunmap = rockchip_gem_prime_vunmap,
.gem_prime_mmap = rockchip_gem_mmap_buf,
#ifdef CONFIG_DEBUG_FS
.debugfs_init = rockchip_drm_debugfs_init,
.debugfs_cleanup = rockchip_drm_debugfs_cleanup,
#endif
.ioctls = rockchip_ioctls,
.num_ioctls = ARRAY_SIZE(rockchip_ioctls),
.fops = &rockchip_drm_driver_fops,
.name = DRIVER_NAME,
.desc = DRIVER_DESC,
.date = DRIVER_DATE,
.major = DRIVER_MAJOR,
.minor = DRIVER_MINOR,
};
vop driver
代码路径:
drivers/gpu/drm/rockchip/rockchip_drm_vop.c
drivers/gpu/drm/rockchip/rockchip_vop_reg.c
结构体:
struct vop;
// vop 驱动根结构,一个 vop 对应一个 struct vop 结构struct vop_win;
// 描述图层信息,一个硬件图层对应一个 struct vop_win 结构
寄存器读写:为了兼容各种不同版本的 vop,vop 驱动里面使用了寄存器级的抽象,由一个结构体来保存抽象关系,这样主题逻辑只需要操作抽象后的功能定义,由抽象的读写接口根据抽象关系写到真实的 vop 硬件中。
示例:
static const struct vop_win_phy rk3288_win23_data = {
.enable = VOP_REG(RK3288_WIN2_CTRL0, 0x1, 4),
}static const struct vop_win_phy rk3368_win23_data = {
.enable = VOP_REG(RK3368_WIN2_CTRL0, 0x1, 4),
}
rk3368 和 rk3288 图层的地址分布不同,但在结构定义的时候,可以将不同的硬件图层 bit 映射到同一个 enable 功能上,这样 vop 驱动主体调用 VOP_WIN_SET(vop, win, enable, 1) 时就能操作到真实的 vop 寄存器了。
DRM 处于内核空间,这意味着用户空间需要通过系统调用来申请它的服务。不过 DRM 并没有定义它自己的系统调用。相反,它遵循 “Everything is file” 的原则,通过文件系统,在 /dev/dri/ 目录下暴露了 GPU 的访问方式。DRM 会检测每个 GPU,并生成对应的 DRM 设备,创建设备文件 /dev/dri/cardX 与 GPU 相接。X 为 0-15 的数值,默认是 Card0.
用户空间的程序如果希望访问 GPU 则必须打开该文件,并使用 ioctl 与 DRM 通信。不同的 ioctl 对应 DRM API 的不同功能。
我们定义一个外部的内存结构来更好的描述如何进行 userspace 的 drm 操作。首先使用到 DRM 相关操作的时候需要引用 drm.h。
#include
struct bo {
int fd;
void *ptr;
size_t size;
size_t offset;
size_t pitch;
unsigned handle;
};
2.2.1、获取设备节点
bo->fd = open("/dev/dri/card0", O_RDWR, 0);
2.2.2、分配内存空间
struct drm_mode_create_dumb arg;
int handle, size, pitch;
int ret;
memset(&arg, 0, sizeof(arg));
arg.bpp = bpp;
arg.width = width;
arg.height = height;
ret = drmIoctl(bo->fd, DRM_IOCTL_MODE_CREATE_DUMB, &arg);
if(ret) {
fprintf(stderr, "failed to create dumb buffer: %s\n", strerror(errno));
return ret;
}
bo->handle = arg.handle;
bo->size = arg.size;
bo->pitch = arg.pitch;
2.2.3、映射物理内存
struct drm_mode_map_dumb arg;
void *map;
int ret;
memset(&arg, 0, sizeof(arg));
arg.handle = bo->handle;
ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &arg);
if(ret){
return ret;
}
map = drm_mmap(0, bo->size, PROT_READ | PROT_WRITE, MAP_SHARED, FD, arg.offset);
if(map == MAP_FAILED){
return -EINVAL;
}
bo->ptr = map;
2.2.4、解除物理内存映射
drm_munmap(bo->ptr, bo->size);
bo->ptr = NULL;
2.2.5、释放内存
struct drm_mode_destroy_dumb arg;
int ret;
memset(&arg, 0, sizeof(arg));
arg.handle = bo->hand;
ret = drmIoctl(bo->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &arg);
if(ret) {
fprintf(stderr, "failed to destroy dumb buffer: %s\n", strerror(errno));
}
2.2.6、释放 gem handle
struct drm_gem_close args;
memset(&arg, 0, sizeof(args));
args.handle = bo->handle;
drmIoctl(bo->fd, DRM_IOCTL_GEM_CLOSE, &args);
2.2.7、export dmafd
int export_dmafd;
ret = drmPrimeHandleToFD(bo->fd, bo->handle, 0, &export_dmafd);
//drmPrimeHandleToFD 是会给 drm_buf 加引用计数的
//使用完 export_dmafd 后,需要使用 close(export_dmafd) 来减掉引用计数
2.2.8、import dmafd
ret = drmPrimeFDToHandle(bo->fd, import_dmafd, &bo->handle);
// drmPrimeHandleToFd 是会给 dma_buf 加引用计数的
// 使用完 bo->handle 后,需要对 handle 减引用,参看 free gem handle 部分。
libdrm 被创建以用于方便用户空间和 DRM 子系统的联系。它仅仅只提供了一些函数的包装(C),这些函数是为 DRM API 的每一个 ioctl、敞亮、结构体而写。使用 libdrm 这个库不仅仅避免了将内核接口直接暴露给用户空间,也有代码复用等常见有点。
分为两个部分:通用的 DRM Core 和适配于不同类型硬件的 DRM Driver。
DRM Core 向用户空间应用程序导出了多个接口,让相应的 libdrm 包装成函数后来使用。
DRM Driver 导出的特定设备的接口,可以通过 ioctls 和 sysfs 来供用户空间使用。
DRM API 中有几个 ioctl 由于并发问题仅限于用户空间单个进程使用。为了实现这种限制,将 DRM 设备分为 Master 和 Auth。上述的那些 ioctl 只能被 DRM-Master 的进程调用。打开了 /dev/dri/cardX 的进程的文件句柄会被标志为 master,特别是第一个调用该 SET_MASTER ioctl 的进程。如果不是 DRM-Master 的进程在使用这些限制 ioctl 的时候会返回错误。进程也可以通过 DROP_MASTER ioctl 放弃 Master 角色,来让其他进程变成 Master。
X Server 或者其他的 Display Server 通常会是他们所管理的 DRM 设备的 DRM-Master 进程。当 DRM 设备启动的时候,这些 Display Server 打开设备节点,获取 DRM-Master 权限,直到关闭设备。
对于其他的用户空间进程,还有一种办法可以获得 DRM 设备的这些受限权限,这就是 DRM-Auth。它是一种针对 DRM 设备的验证方式,用来证明该进程已经获得了 DRM-Master 对于他们去访问受限 ioctls 的许可。
步骤:
DRM 在源码中的架构(图自 Mark.Yao):
使用 DRM 访问 Video Card(图自 Wikipedia):
没有 DRM 时,用户空间进程访问 GPU 的方式,如上图。
有 DRM 后,用户空间访问 GPU 的方式,如上图。