本篇是 DRM 的第三篇文章。 在 《Linux DRM (一) Display Server》 中我們了解了 DRM 誕生的歷史。 在 《Linux DRM (二) 基本概念和特性》 中我們了解了一些基本的概念。 現(xiàn)在,我們終于要向 DRM 源碼進軍了。 + 一、概覽不知大家是否還記得,之前我有引用 Wiki 中對 DRM 的介紹,這里我們再回顧一下: DRM 由兩個部分組成: 一是 Kernel 的子系統(tǒng),這個子系統(tǒng)對硬件 GPU 操作進行了一層框架封裝。 二是 提供了一個 libdrm 庫,里面封裝了一系列 API,用來進行圖像顯示。 整體來看和 Android 上所采用的 Direct Frame Buffer 差不多。 Android Kernel 走的是 FB 的框架,并在 HAL 抽象出一個 FBDEV,來進行 FB IOCTL 統(tǒng)一管理。 DRM 就相當(dāng)于直接對圖形設(shè)備集中處理,并且多出了一個 libdrm 庫。 + 其整體脈絡(luò)如下: + 源碼文件component framework在講述啟動過程之前,先簡單了解一下 component framework。 + 因為 drm下掛了許多的設(shè)備, 啟動順序經(jīng)常會引發(fā)各種問題: +
這時就需要有一個統(tǒng)一管理的機制, 將所有設(shè)備統(tǒng)合起來, 按照一個統(tǒng)一的順序加載, Display-subsystem正是用來解決這個問題的, 依賴于component的驅(qū)動, 通過這個驅(qū)動, 可以把所有的設(shè)備以組件的形式加在一起, 等所有的組件加載完畢后, 統(tǒng)一進行bind/unbind. + 代碼路徑 + 以下為rockchip drm master probe階段component 主要邏輯, 為了減小篇幅, 去掉了無關(guān)的代碼: + static int rockchip_drm_platform_probe(struct platform_device *pdev) { for (i = 0;; i++) { /* ports指向了vop的設(shè)備節(jié)點 */ 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); /* 搜查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關(guān)聯(lián)的輸出設(shè)備, 即為edp, mipi或hdmi */ component_match_add(dev, match, compare_of, remote); } } 啟動過程圖自 markyzq gitbook: + 基于 component 框架,在 probe 階段 +
RK DRM Device Driverdevice treedisplay_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代碼路徑 drivers/gpu/drm/rockchip/rockchip_drm_drv.c drivers/gpu/drm/rockchip/rockchip_drm_drv.h 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 結(jié)構(gòu)體: struct vop; // vop 驅(qū)動根結(jié)構(gòu), 一個vop對應(yīng)一個struct vop結(jié)構(gòu) struct vop_win; // 描述圖層信息, 一個硬件圖層對應(yīng)一個struct vop_win結(jié)構(gòu) 寄存器讀寫: 為了兼容各種不同版本的vop, vop驅(qū)動里面使用了寄存器級的抽象, 由一個結(jié)構(gòu)體來保存抽象關(guān)系, 這樣主體邏輯只需要操作抽象后的功能定義, 由抽象的讀寫接口根據(jù)抽象關(guān)系寫到真實的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圖層的地址分布不同, 但在結(jié)構(gòu)定義的時候, 可以將不同的硬件圖層bit
映射到同一個enable功能上, 這樣vop驅(qū)動主體調(diào)用 + 2.1 設(shè)備文件 cardXDRM 處于內(nèi)核空間,這意味著用戶空間需要通過系統(tǒng)調(diào)用來申請它的服務(wù)。
不過 DRM 并沒有定義它自己的系統(tǒng)調(diào)用。相反,它遵循“Everything is file”的原則,通過文件系統(tǒng),在 + 用戶空間的程序如果希望訪問 GPU 則必須打開該文件,并使用 ioctl 與 DRM 通信。不同的 ioctl 對應(yīng) DRM API 的不同功能。 + 2.2 用戶空間的內(nèi)存操作我們定義一個外部的內(nèi)存結(jié)構(gòu)來更好的描述如何進行 userspace 的 drm 操作。
首先使用到 DRM 相關(guān)操作的時候需要引用 + #include <drm.h> struct bo { int fd; void *ptr; size_t size; size_t offset; size_t pitch; unsigned handle; }; 2.2.1 獲取設(shè)備節(jié)點bo->fd = open("/dev/dri/card0"),O_RDWR,0); 2.2.2 分配內(nèi)存空間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 映射物理內(nèi)存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 解除物理內(nèi)存映射drm_munmap(bo->ptr, bo->size); bo->ptr = NULL; 2.2.5 釋放內(nèi)存struct drm_mode_destroy_dumb arg; int ret; memset(&arg, 0, sizeof(arg)); arg.handle = bo->handle; 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 handlestruct drm_gem_close args; memset(&args, 0, sizeof(args)); args.handle = bo->handle; drmIoctl(bo->fd, DRM_IOCTL_GEM_CLOSE, &args); 2.2.7 export dmafdint export_dmafd; ret = drmPrimeHandleToFD(bo->fd, bo->handle, 0, &export_dmafd); // drmPrimeHandleToFD是會給dma_buf加引用計數(shù)的 // 使用完export_dmafd后, 需要使用 close(export_dmafd)來減掉引用計數(shù) 2.2.8 import dmafdret = drmPrimeFDToHandle(bo->fd, import_dmafd, &bo->handle); // drmPrimeHandleToFD是會給dma_buf加引用計數(shù)的 // 使用完bo->handle后, 需要對handle減引用, 參看free gem handle部分. 2.2 DRM libdrmlibdrm 被創(chuàng)建以用于方便用戶空間和 DRM 子系統(tǒng)的聯(lián)系。它僅僅只提供了一些函數(shù)的包裝(C),這些函數(shù)是為 DRM API 的每一個 ioctl、常量、結(jié)構(gòu)體 而寫。 使用 libdrm 這個庫不僅僅避免了將內(nèi)核接口直接暴露給用戶空間,也有代碼復(fù)用等常見優(yōu)點。 + 2.3 DRM 代碼結(jié)構(gòu)分為兩個部分:通用的 DRM Core 和適配于不同類型硬件的 DRM Driver。 DRM Core 提供了不同 DRM 驅(qū)動程序可以注冊的基本框架, 并且為用戶空間提供了具有通用,獨立于硬件功能的最小 ioctl 集合。 DRM Driver 實現(xiàn)了 API 的硬件依賴部分。 它提供了沒有被 DRM core 覆蓋的其余 ioctl 的實現(xiàn),它也可以拓展 API,提供額外的 ioctl。比如某個特定的 DRM Driver 提供了一個增強的 API ,用戶空間的 libdrm 也需要以額外的 libdrm-driver 拓展,用來使用這些額外的 ioctl。 + 2.4 DRM APIDRM Core 向用戶空間應(yīng)用程序?qū)С隽硕鄠€接口,讓相應(yīng)的 libdrm 包裝成函數(shù)后來使用。 DRM Driver 導(dǎo)出的特定設(shè)備的接口,可以通過 ioctls 和 sysfs 來供用戶空間使用。 + 2.5 DRM-Master 和 DRM-AuthDRM API 中有幾個 ioctl 由于并發(fā)問題僅限于用戶空間的單個進程使用。
為了實現(xiàn)這種限制,將 DRM 設(shè)備分為 Master 和 Auth。
上述的那些 ioctl 只能被 DRM-Master 的進程調(diào)用。
打開了 + X Server 或者其他的 Display Server 通常會是他們所管理的 DRM 設(shè)備的 DRM-Master 進程。當(dāng) DRM 設(shè)備啟動的時候哦,這些 Display Server 打開設(shè)備節(jié)點,獲取 DRM-Master 權(quán)限,直到關(guān)閉設(shè)備。 + 對于其他的用戶空間進程,還有一種辦法可以獲得 DRM 設(shè)備的這些 受限權(quán)限,這就是 DRM-Auth。它是一種針對 DRM 設(shè)備的驗證方式,用來證明該進程已經(jīng)獲得了 DRM-Master 對于他們?nèi)ピL問受限 ioctls 的許可。 + 步驟:
#DRM 在源碼中的架構(gòu) (圖自 Mark.Yao): + 使用 DRM 訪問 Video Card (圖自 wikipedia): +
|
|