緣由
什么是 KMS ?
要了解 KMS,首先要了解 DRM。
DRM 是 Direct Rendering Manager 的縮寫,最初只是用來支持 GPU 的,最初它負責:
- Initialize GPU card, load its firmware, etc.
- Share the GPU command queue between multiple Applications
- Manage the memory (allocation, access)
那時候,Mode-Setting (包括更新畫面、配置 display pipeline、screen resolution、color depth、refresh rate等) 是在 Userspace 中實現的,這樣做的缺點是:
- Rendering 和 Mode-Setting 會發生競爭;
- 不統一,缺少抽象,不同的硬件平臺各自為營;
后來就引入了 Kernel Mode-Setting (KMS),其實就是將 Mode-Setting 的活移回到內核,DRM driver 即負責訪問 GPU 也負責訪問 Display Engine,且將 KMS 作為 DRM API 的一部分,如下圖:
目前我們關注最左邊的路徑就好,不用太關注 GEM、PRIME 等概念。
圖形應用是如何進行顯示的?
通常,一個普通的圖形應用并不會直接通過 KMS 和內核進行交互,而是先和 display server (例如給予 X11 的 Xorg, 或者基于 Wayland 的 Weston,也稱 display compositor) 進行交互:將顯示的圖像提交給 display server, 再由 display server 負責將多個 client 圖形應用的圖像合成成一張圖像,并將這張圖像通過 KMS 的接口提交給內核。
簡而言之,就是 2 個步驟:
step1: 合成
step2: 提交給內核
何時要用 KMS ?
對于普通的圖形應用,一般是不會直接去使用 KMS 的。
只有一些需要 low level control 的應用需要使用 KMS,例如:
- Display servers;
- Media players,例如 Kodi,它們有自己的 KMS backend,不需要額外的 display server;
- 游戲相關的應用,例如 RetroArch,為了支持更多的平臺,它們會做到不依賴 display server;
- VR、XR,它們對性能要求很嚴格,所以會親自訪問 KMS 以達到最小的延遲;
既然很少直接使用 KMS,為什么還要學習它 ?
- DRM Driver 的驅動開發人員有必要了解 KMS api,這樣才能理解 DRM Driver 的設計目的,從而編寫出正確的驅動程序;
- 理解原理,可以協助我們定位圖形應用不穩定或者性能相關的問題,尤其是嵌入式 linux 領域,顯示相關的功能復雜且容易出現異常;
- 如果你想要為開源軟件 Wayland 或者 Kodi 做貢獻的話,則需要了解 KMS api;
- 如果你的應用對性能和延遲性要求很高的話,也需要了解 KMS api。
要編寫 KMS 程序,首先要了解 KMS 的模型。
KMS 將硬件模塊抽象成下面幾個對象類型:
- Planes:圖層,例如在 rockchip 平臺里對應 SOC 內部 VOP 模塊的 win 圖層;
- CRTC:顯示控制器,例如在 rockchip 平臺里對應 SOC 內部的 VOP 模塊;
- Encoder:輸出轉換器,指 RGB、LVDS、DSI、eDP、HDMI、CVBS、VGA 等顯示接口;
- Connector:連接器,指 encoder 和 panel 之間交互的接口部分;
- Bridge:橋接設備,一般用于注冊 encoder 后面另外再接的轉換芯片,如 DSI2HDMI 轉換芯片;
- Panel:泛指屏,各種 LCD、HDMI 等顯示設備的抽象;
應用通過 KMS api 將這些對象連接成一條 display pipeline,最終將圖像顯示在屏幕上:
KMS 有兩套 api: legacy api (已過時) 和 atomic api:
legacy api 雖說已經過時了,但是它其實是很適合 KMS api 初學者的,因為它仍然是基于 plane、crct、encoder、connector 這些核心概念的。atomic api 只是在 legacy api 基礎上進行一些改進,待會會細說。
KMS legacy api 最簡單示例:
int main(int argc, char **argv) { int fd; drmModeConnector *conn; drmModeRes *res; uint32_t conn_id; uint32_t crtc_id; /* open the drm device */ fd = open("/dev/dri/card0"); /* get crtc/encoder/connector id */ res = drmModeGetResources(fd); crtc_id = res->crtcs[0]; conn_id = res->connectors[0]; /* get connector for display mode */ conn = drmModeGetConnector(fd, conn_id); /* create a dumb-buffer */ drmIoctl(DRM_IOCTL_MODE_CREATE_DUMB); /* bind the dumb-buffer to an FB object */ drmModeAddFB(...); /* map the dumb buffer for userspace drawing */ drmIoctl(DRM_IOCTL_MODE_MAP_DUMB); mmap(...); /* start display */ drmModeSetCrtc(crtc_id, fb_id, connector_id, mode); }
大致的思路是:
- 通過 drmModeGetResources() 獲取到 crtc、connector 等對象的 id,然后通過 id 獲取到具體的 object;
- 通過 ioctl(DRM_IOCTL_MODE_CREATE_DUMB) 和 drmModeAddFB() 創建 DRM framebuffer object,并獲得 fb id;
- 通過 ioctl(DRM_IOCTL_MODE_MAP_DUMB) 和 mmap() 將 framebuffer 映射到用戶空間,應用將自己要顯示的內容寫到 framebuffer 中;
- 將 crtc、connector、fb 的id 通過 drmModeSetCrtc() 告訴 DRM driver,讓內核幫我們配置好 display pipeline,從而將 framebuffer 里的內容顯示出來;
嵌入式物聯網需要學的東西真的非常多,千萬不要學錯了路線和內容,導致工資要不上去!
無償分享大家一個資料包,差不多150多G。里面學習內容、面經、項目都比較新也比較全!某魚上買估計至少要好幾十。
點擊這里找小助理0元領取:加微信領取資料
關于 KMS atomic api:
atomic 的核心思想是將各種設置都保存在一個個的 property 里,最后將所有 property 一次性提交給內核,對于本次 commit 操作,要么成功,要么保持原來的狀態完全不變。atomic 的好處在于可以避免操作到一半時中途失敗后難以回滾的問題,同時也能避免設置期間屏幕閃爍的問題。
non atomic
atomic
下面的代碼同樣也是將 crtc、connector 等對象連成一條 display pipeline,只不過這次用的是 atomic api。
req = drmModeAtomicAlloc(); drmModeAtomicAddProperty(req, crtc_id, property_active, 1); drmModeAtomicAddProperty(req, crtc_id, property_mode_id, blob_id); drmModeAtomicAddProperty(req, conn_id, property_crtc_id, crtc_id); drmModeAtomicCommit(fd, req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL); drmModeAtomicFree(req);
代碼雖然增多了,但是能得到更好的用戶體驗。
KMS 功能比較多,api 也比較多,需要一系列的文章才能描述清楚,網上已經有一個比較好的教程,我就不再詳細描述了。
請參考何小龍的 blog:
https://blog.csdn.NET/hexiaolong2009/article/details/83720940
相關文章列表:
最簡單的DRM應用程序 (single-buffer) 最簡單的DRM應用程序 (double-buffer) 最簡單的DRM應用程序 (page-flip) 最簡單的DRM應用程序 (plane-test) DRM應用程序進階 (Property) DRM應用程序進階 (atomic-crtc) DRM應用程序進階 (atomic-plane)
其他適合學習 KMS 的開源軟件1. drminfo
https://github.com/ascent12/drm_info
drminfo 是一個命令行工具,它可以將系統里 DRM 設備的所有信息都 dump 出來,很適合用于調試。
編譯:
$ apt-get install meson ninja-build $ git clone https://github.com/ascent12/drm_info drm_info $ cd drm_info $ meson build/ $ ninja -C build install
用法:
# ./drm_info Node: /dev/dri/card0 ├───Driver: rockchip (RockChip Soc DRM) version 2.0.0 (20140818) │ ├───DRM_CLIENT_CAP_STEREO_3D supported │ ├───DRM_CLIENT_CAP_UNIVERSAL_PLANES supported │ ├───DRM_CLIENT_CAP_ATOMIC supported │ ├───DRM_CLIENT_CAP_ASPECT_RATIO supported │ ├───DRM_CLIENT_CAP_WRITEBACK_CONNECTORS supported │ ├───DRM_CAP_DUMB_BUFFER = 1 │ ├───DRM_CAP_VBLANK_HIGH_CRTC = 1 │ ├───DRM_CAP_DUMB_PREFERRED_DEPTH = 0 │ ├───DRM_CAP_DUMB_PREFER_SHADOW = 0 │ ├───DRM_CAP_PRIME = 3 │ ├───DRM_CAP_TIMESTAMP_MONOTONIC = 1 │ ├───DRM_CAP_ASYNC_PAGE_FLIP = 1 │ ├───DRM_CAP_Cursor_WIDTH = 64 │ ├───DRM_CAP_CURSOR_HEIGHT = 64 │ ├───DRM_CAP_ADDFB2_MODIFIERS = 1 │ ├───DRM_CAP_PAGE_FLIP_TARGET = 0 │ ├───DRM_CAP_CRTC_IN_VBLANK_EVENT = 1 │ ├───DRM_CAP_SYNCOBJ = 0 │ └───DRM_CAP_SYNCOBJ_TIMELINE not supported ├───Device: platform rockchip,display-subsystem │ └───Available nodes: primary, render ├───Framebuffer size │ ├───Width: [0, 8192] │ └───Height: [0, 8192] ├───Connectors │ ├───Connector 0 │ │ ├───Object ID: 77 │ │ ├───Type: eDP │ │ ├───Status: connected │ │ ├───Physical size: 256x144 mm │ │ ├───Subpixel: unknown │ │ ├───Encoders: {0} │ │ ├───Modes │ │ │ └───1920x1080@60.00 nhsync nvsync │ │ └───Properties │ │ ├───"EDID" (immutable): blob = 0 │ │ ├───"DPMS": enum {On, Standby, Suspend, Off} = On │ │ ├───"link-status": enum {Good, Bad} = Good │ │ ├───"non-desktop" (immutable): range [0, 1] = 0 │ │ ├───"CRTC_ID" (atomic): object CRTC = 54 │ │ ├───"brightness": range [0, 100] = 50 │ │ ├───"contrast": range [0, 100] = 50 │ │ ├───"saturation": range [0, 100] = 50 │ │ └───"hue": range [0, 100] = 50 [...] ├───Encoders │ ├───Encoder 0 │ │ ├───Object ID: 76 │ │ ├───Type: TMDS │ │ ├───CRTCS: {0} │ │ └───Clones: {} │ ├───Encoder 1 │ │ ├───Object ID: 78 │ │ ├───Type: TMDS │ │ ├───CRTCS: {0, 1} │ │ └───Clones: {} │ └───Encoder 2 │ ├───Object ID: 80 │ ├───Type: TMDS │ ├───CRTCS: {1} │ └───Clones: {} ├───CRTCs │ ├───CRTC 0 │ │ ├───Object ID: 54 │ │ ├───Mode: 1920x1080@60.00 nhsync nvsync │ │ ├───Gamma size: 256 │ │ └───Properties │ │ ├───"ACTIVE" (atomic): range [0, 1] = 1 │ │ ├───"MODE_ID" (atomic): blob = 91 │ │ │ └───1920x1080@60.00 nhsync nvsync │ │ ├───"OUT_FENCE_PTR" (atomic): range [0, UINT64_MAX] = 0 │ │ ├───"left margin": range [0, 100] = 100 │ │ ├───"right margin": range [0, 100] = 100 │ │ ├───"top margin": range [0, 100] = 100 │ │ ├───"bottom margin": range [0, 100] = 100 │ │ ├───"ALPHA_SCALE" (atomic): range [0, 1] = 1 │ │ └───"FEATURE" (immutable): bitmask {afbdc} = () [...] └───Planes ├───Plane 0 │ ├───Object ID: 53 │ ├───CRTCs: {0} │ ├───FB ID: 128 │ │ ├───Object ID: 128 │ │ ├───Size: 1920x1080 │ │ ├───Pitch: 7680 bytes │ │ ├───Bits per pixel: 32 │ │ └───Depth: 24 │ ├───Formats: │ │ ├───XRGB8888 (0x34325258) │ │ ├───ARGB8888 (0x34325241) │ │ ├───XBGR8888 (0x34324258) │ │ ├───ABGR8888 (0x34324241) │ │ ├───RGB888 (0x34324752) │ │ ├───BGR888 (0x34324742) │ │ ├───RGB565 (0x36314752) │ │ └───BGR565 (0x36314742) │ └───Properties │ ├───"type" (immutable): enum {Overlay, Primary, Cursor} = Primary │ ├───"FB_ID" (atomic): object framebuffer = 128 │ │ ├───Object ID: 128 │ │ ├───Size: 1920x1080 │ │ ├───Pitch: 7680 bytes │ │ ├───Bits per pixel: 32 │ │ └───Depth: 24 │ ├───"IN_FENCE_FD" (atomic): srange [-1, INT32_MAX] = -1 │ ├───"CRTC_ID" (atomic): object CRTC = 54 │ ├───"CRTC_X" (atomic): srange [INT32_MIN, INT32_MAX] = 0 │ ├───"CRTC_Y" (atomic): srange [INT32_MIN, INT32_MAX] = 0 │ ├───"CRTC_W" (atomic): range [0, INT32_MAX] = 1920 │ ├───"CRTC_H" (atomic): range [0, INT32_MAX] = 1080 │ ├───"SRC_X" (atomic): range [0, UINT32_MAX] = 0 │ ├───"SRC_Y" (atomic): range [0, UINT32_MAX] = 0 │ ├───"SRC_W" (atomic): range [0, UINT32_MAX] = 1920 │ ├───"SRC_H" (atomic): range [0, UINT32_MAX] = 1080 │ ├───"ZPOS" (atomic): range [0, 3] = 0 │ ├───"FEATURE" (immutable): bitmask {scale, alpha, hdr2sdr, sdr2hdr, afbdc} = (alpha | afbdc) │ ├───"EOTF" (atomic): range [0, 5] = 0 │ ├───"COLOR_SPACE" (atomic): range [0, 12] = 0 │ ├───"GLOBAL_ALPHA" (atomic): range [0, UINT8_MAX] = 255 │ ├───"BLEND_MODE" (atomic): range [0, 1] = 0 │ ├───"ASYNC_COMMIT" (atomic): range [0, 1] = 0 │ └───"SHARE_ID" (atomic): range [0, UINT32_MAX] = 53 [...]
2、libdrm 自帶的測試程序:modetest
https://gitlab.freedesktop.org/mesa/drm
modetest 是由 libdrm 提供的測試程序,可以查詢顯示設備的支持狀況,進行基本的顯示測試,以及設置顯示的模式。
編譯:
$ apt-get install meson ninja-build $ git clone https://gitlab.freedesktop.org/mesa/drm libdrm $ cd libdrm $ meson build/ $ ninja -C build install
會生成庫文件和測試程序:
libkms tests/ # 包含 modetest libdrm.so.2.4.0 libdrm.so.2 libdrm.so
用法:
// 在 edp 屏上顯示測試畫面 $ modetest -M rockchip -s 77@54:1920x1080 setting mode 1920x1080-60.00Hz on connectors 77, crtc 54 // 在 hdmi 屏上顯示測試畫面 $ modetest -M rockchip -s 81@65:1920x1080 setting mode 1920x1080-60.00Hz on connectors 81, crtc 65
參數說明:
- -M:用于指定訪問哪個 DRM 設備;
- -s [,][@]:[#][-][@]:用于在指定的 pipeline 上以某個 mode 顯示某個 pattern 的畫面。
https://gitlab.freedesktop.org/mesa/kmscube/
kmscube 是一個演示程序,用于說明如何在沒有 X11、wayland 等 compositor 的情況下編寫 bare metal 圖形應用。它使用了 DRM/KMS(kernel mode setting)、GBM(graphics buffer manager)和 EGL 來使用 OpenGL 或 OpenGL ES 渲染內容。
編譯:
$ apt-get install meson ninja-build $ git clone https://gitlab.freedesktop.org/mesa/kmscube/ kmscube $ cd kmscube $ meson build/ $ ninja -C build install
用法:
$ ./kmscube
kmscube 運行效果
還有很多優秀的開源軟件,例如 Wayland 的參考實現 Weston,媒體播放器 Kodi、復古游戲模擬器前端 RetroArch 等,都是我們學習 KMS api 的優秀學習資料,感興趣的小伙伴可以自行研究一波。
到此,KMS api 的基礎知識就介紹完畢了,感謝閱讀!
文章鏈接:
https://mp.weixin.qq.com/s/2Wermbnh4GKEF8RvbdDj4A
轉載自:老吳嵌入式 ,作者吳偉東Jack
文章鏈接:對于 Display 框架,我需要了解 KMS api 嗎? | Linux 驅動