前言
距離上一篇《DRM 驅動程序開發(開篇)》已經過去快整整1年了,如果再不更新的話,這個 DRM 系列教程很可能就夭折了。之所以現在才寫本文,主要有兩個原因:1. 本人工作內容發生變動。2. 始終沒找到合適的硬件平臺來做示例講解。其中第2點是我一直拖延的主要原因,因爲作爲示例教程,我希望它是硬件無關的,這樣即使大家沒有硬件環境也可以學習 DRM 驅動開發。可惜,最終找來找去也就 QEMU 比較可行,但需要自己開發模擬硬件,還在摸索中。
與其等到 QEMU 硬件開發完成(猴年馬月),不如先找個最簡單的 DRM 驅動程序講講,也不至於讓那些關注本教程的人苦苦等待。好了,那就讓我們直接上 VKMS 吧!
VKMS 簡介
VKMS 是 “Virtual Kernel Mode Setting” 的縮寫,它於2018年7月5日被合入到 linux-4.19 主線版本中,並存放在 drivers/gpu/drm/vkms 目錄下。之所以稱它爲 Virtual KMS,是因爲該驅動不需要真實的硬件,它完全是一個軟件虛擬的“顯示”設備,甚至連顯示都算不上,因爲當它運行時,你看不到任何顯示內容。它唯一能提供的,就是一個由高精度 timer 模擬的 VSYNC 中斷信號!該驅動存在的目的,主要是爲了 DRM 框架自測試,以及方便那些無頭顯示器設備的調試應用。雖然我們看不到 VKMS 的顯示效果,但是在驅動流程上,它實現了 modesetting 該有的基本操作。因其邏輯簡單,代碼量少,拿來做學習案例講解再好不過。
隨着內核版本的不斷升級,添加到 VKMS 的功能也越來越多,截止到目前最新的內核版本 kernel 5.7-rc2,該 VKMS 驅動已經集成了如下功能:
- Atomic Modeset
- VBlank
- Dumb Buffer
- Cursor & Primary Plane
- Framebuffer CRC 校驗
- Plane Composition
- GEM Prime Import
linux-4.19-rc1 是 VKMS 的第一個版本,也最爲簡單,因此本文就以該版本爲例,並將其反向移植到 linux-4.14.143 上,同時對代碼做了進一步精簡,儘量避免細枝末節。下面就跟着我一起來學習,如何從0到1實現一個 VKMS 驅動吧!
示例 1
這是一個最簡單的 DRM 驅動代碼:
#include <drm/drmP.h>
static struct drm_device drm;
static struct drm_driver vkms_driver = {
.name = "vkms",
.desc = "Virtual Kernel Mode Setting",
.date = "20180514",
.major = 1,
.minor = 0,
};
static int __init vkms_init(void)
{
drm_dev_init(&drm, &vkms_driver, NULL);
drm_dev_register(&drm, 0);
return 0;
}
module_init(vkms_init);
你沒看錯,就這麼幾行代碼!是不是顛覆了你對 DRM Driver 的認識?
我知道你在懷疑什麼,讓我們來看看這點代碼能給我們帶來些什麼。我們將該驅動以 build-in 方式編譯進內核,然後啓動內核,如果你在 kernel log 中仔細查找,會發現有如下 drm log:
[drm] Initialized vkms 1.0.0 20180514 for virtual device on minor 0
這些信息正是從上面的 name
、date
、major
、minor
字段中獲取的。除此之外,DRM 框架還爲我們做了下面這些事情:
- 創建設備節點:
/dev/dri/card0
- 創建 sysfs 節點:
/sys/class/drm/card0
- 創建 debugfs 節點:
/sys/kernel/debug/dri/0
當然,簡單是以犧牲功能爲代價的。該驅動目前什麼事情也做不了,你唯一能做的就是查看該驅動的名字:
$ cat /sys/kernel/debug/dri/0/name
vkms unique=vkms
你甚至都無法對 /dev/dri/card0 進行 open 操作,因爲該 vkms driver 都還沒有實現 fops 接口!
即使不添加任何 DRM 驅動,僅僅只是讓 DRM Core 代碼參與編譯,你會發現 DRM 框架已經爲我們創建好了如下調試節點:
- /sys/class/drm/
- /sys/kernel/debug/dri/
- /sys/module/drm/parameters/
- /sys/module/drm_kms_helper/parameters/
示例 2
接下來我們給 vkms 添加上 fops 操作接口。
#include <drm/drmP.h>
static struct drm_device drm;
static const struct file_operations vkms_fops = {
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
.poll = drm_poll,
.read = drm_read,
};
static struct drm_driver vkms_driver = {
.fops = &vkms_fops,
.name = "vkms",
.desc = "Virtual Kernel Mode Setting",
.date = "20180514",
.major = 1,
.minor = 0,
};
static int __init vkms_init(void)
{
drm_dev_init(&drm, &vkms_driver, NULL);
drm_dev_register(&drm, 0);
return 0;
}
module_init(vkms_init);
有了 fops,我們就可以對 card0 進行 open / read 操作了。更重要的是,我們現在可以進行一些簡單的 ioctl 操作了!讓我們看看現在可以執行哪些 IOCTL:
IOCTL | Userspace API | 描述 |
---|---|---|
DRM_IOCTL_VERSION | drmGetVersion() | 獲取 Driver 版本信息,即上面的 name、desc、date、major、minor 字段 |
DRM_IOCTL_GET_UNIQUE | drmGetBusid() | 獲取 Bus ID |
DRM_IOCTL_GET_MAGIC | drmGetMagic() | 獲取 Magic Number,用於 GEM ioctl 權限檢查 |
DRM_IOCTL_AUTH_MAGIC | drmAuthMagic() | 通過當前 Magic Number 獲取 GEM ioctl 的權限認證 |
DRM_IOCTL_GET_CLIENT | drmGetClient() | 獲取當前 DRM 設備上的所有 client 進程 |
DRM_IOCTL_GET_CAP | drmGetCap() | 獲取當前 DRM 設備所支持的能力 |
DRM_IOCTL_SET_CLIENT_CAP | drmSetClientCap() | 告訴 DRM 驅動當前用戶進程所支持的能力 |
DRM_IOCTL_SET_MASTER | drmSetMaster() | 獲取 DRM-Master 訪問權限 |
DRM_IOCTL_DROP_MASTER | drmDropMaster() | 放棄 DRM-Master 訪問權限 |
到目前爲止,凡是和 modesetting 相關的操作,我們還是操作不了。
示例 3
添加 drm mode objects:
#include <drm/drmP.h>
#include <drm/drm_encoder.h>
static struct drm_device drm;
static struct drm_plane primary;
static struct drm_crtc crtc;
static struct drm_encoder encoder;
static struct drm_connector connector;
static const struct drm_plane_funcs vkms_plane_funcs;
static const struct drm_crtc_funcs vkms_crtc_funcs;
static const struct drm_encoder_funcs vkms_encoder_funcs;
static const struct drm_connector_funcs vkms_connector_funcs;
static const u32 vkms_formats[] = {
DRM_FORMAT_XRGB8888,
};
static void vkms_modeset_init(void)
{
drm_mode_config_init(&drm);
drm_universal_plane_init(&drm, &primary, 0, &vkms_plane_funcs,
vkms_formats, ARRAY_SIZE(vkms_formats),
NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
drm_crtc_init_with_planes(&drm, &crtc, &primary, NULL, &vkms_crtc_funcs, NULL);
drm_encoder_init(&drm, &encoder, &vkms_encoder_funcs, DRM_MODE_ENCODER_VIRTUAL, NULL);
drm_connector_init(&drm, &connector, &vkms_connector_funcs, DRM_MODE_CONNECTOR_VIRTUAL);
}
static const struct file_operations vkms_fops = {
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
.poll = drm_poll,
.read = drm_read,
};
static struct drm_driver vkms_driver = {
.driver_features = DRIVER_MODESET,
.fops = &vkms_fops,
.name = "vkms",
.desc = "Virtual Kernel Mode Setting",
.date = "20180514",
.major = 1,
.minor = 0,
};
static int __init vkms_init(void)
{
drm_dev_init(&drm, &vkms_driver, NULL);
vkms_modeset_init();
drm_dev_register(&drm, 0);
return 0;
}
module_init(vkms_init);
重點:
- 給 driver_features 添加上
DRIVER_MODESET
標誌位,告訴 DRM Core 當前驅動支持 modesetting 操作; drm_mode_config_init()
初始化一些全局的數據結構。注意,那些 Standard Properties 就是在這裏創建的。drm_xxx_init()
則分別用於創建 plane、crtc、encoder、connector 這4個 drm_mode_object。
由於上面4個 objects 在創建時,它們的 callback funcs 沒有賦初值,所以真正的 modeset 操作目前還無法正常執行,不過我們至少可以使用下面這些只讀的 modeset IOCTL 了:
IOCTL | Userspace API | 描述 |
---|---|---|
DRM_IOCTL_MODE_GETRESOURCES | drmModeGetResources() | 獲取 fb、crtc、connector、encoder 資源列表, |
DRM_IOCTL_MODE_GETPLANERESOURCES | drmModeGetPlaneResources() | 獲取 Plane 資源列表 |
DRM_IOCTL_MODE_GETCRTC | drmModeGetCrtc() | 獲取 CRTC 當前的狀態信息 |
DRM_IOCTL_MODE_GETPLANE | drmModeGetPlane() | 獲取 Plane 當前的狀態信息 |
DRM_IOCTL_MODE_GETENCODER | drmModeGetEncoder() | 獲取 Encoder 當前的狀態信息 |
DRM_IOCTL_MODE_GETCONNECTOR | drmModeGetConnector() | 獲取 Connector 的當前狀態信息 |
DRM_IOCTL_MODE_GETPROPBLOB | drmModeGetPropertyBlob() | 獲取1個 Property Blob 對象 |
DRM_IOCTL_MODE_CREATEPROPBLOB | drmModeCreatePropertyBlob() | 創建1個 Property Blob 對象 |
DRM_IOCTL_MODE_DESTROYPROPBLOB | drmModeDestroyPropertyBlob() | 銷燬1個 Property Blob 對象 |
示例 4
添加 FB 和 GEM 支持:
#include <drm/drmP.h>
#include <drm/drm_encoder.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
static struct drm_device drm;
static struct drm_plane primary;
static struct drm_crtc crtc;
static struct drm_encoder encoder;
static struct drm_connector connector;
static const struct drm_plane_funcs vkms_plane_funcs;
static const struct drm_crtc_funcs vkms_crtc_funcs;
static const struct drm_encoder_funcs vkms_encoder_funcs;
static const struct drm_connector_funcs vkms_connector_funcs;
/* add here */
static const struct drm_mode_config_funcs vkms_mode_funcs = {
.fb_create = drm_fb_cma_create,
};
static const u32 vkms_formats[] = {
DRM_FORMAT_XRGB8888,
};
static void vkms_modeset_init(void)
{
drm_mode_config_init(&drm);
drm.mode_config.max_width = 8192;
drm.mode_config.max_height = 8192;
/* add here */
drm.mode_config.funcs = &vkms_mode_funcs;
drm_universal_plane_init(&drm, &primary, 0, &vkms_plane_funcs,
vkms_formats, ARRAY_SIZE(vkms_formats),
NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
drm_crtc_init_with_planes(&drm, &crtc, &primary, NULL, &vkms_crtc_funcs, NULL);
drm_encoder_init(&drm, &encoder, &vkms_encoder_funcs, DRM_MODE_ENCODER_VIRTUAL, NULL);
drm_connector_init(&drm, &connector, &vkms_connector_funcs, DRM_MODE_CONNECTOR_VIRTUAL);
}
static const struct file_operations vkms_fops = {
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
.poll = drm_poll,
.read = drm_read,
/* add here */
.mmap = drm_gem_cma_mmap,
};
static struct drm_driver vkms_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM,
.fops = &vkms_fops,
/* add here */
.dumb_create = drm_gem_cma_dumb_create,
.gem_vm_ops = &drm_gem_cma_vm_ops,
.gem_free_object_unlocked = drm_gem_cma_free_object,
.name = "vkms",
.desc = "Virtual Kernel Mode Setting",
.date = "20180514",
.major = 1,
.minor = 0,
};
static int __init vkms_init(void)
{
drm_dev_init(&drm, &vkms_driver, NULL);
vkms_modeset_init();
drm_dev_register(&drm, 0);
return 0;
}
module_init(vkms_init);
重點:
- 給 driver_features 添加上
DRIVER_GEM
標誌位,告訴 DRM Core 該驅動支持 GEM 操作; dumb_create
回調接口用於創建 gem object,並分配物理 buffer。這裏直接使用 CMA helper 函數來實現;fb_create
回調接口用於創建 framebuffer object,並綁定 gem objects。這裏直接使用 CMA helper 函數實現。- fops 中的
mmap
接口,用於將 dumb buffer 映射到 userspace,它依賴 drm driver 中的gem_vm_ops
實現。這裏也直接使用 CMA helper 函數來實現。
現在,我們可以使用如下 IOCTL 來進行一些標準的 GEM 和 FB 操作了!
IOCTL | Userspace API | 描述 |
---|---|---|
DRM_IOCTL_MODE_CREATE_DUMB | dumb_bo_create() | 創建一個 dumb buffer 對象 |
DRM_IOCTL_MODE_MAP_DUMB | dumb_bo_map() | 將 dumb buffer 映射到用戶空間 |
DRM_IOCTL_MODE_DESTROY_DUMB | dumb_bo_destroy() | 銷燬一個 dumb buffer 對象 |
DRM_IOCTL_MODE_ADDFB | drmModeAddFB() | 向 DRM 驅動註冊一個 framebuffer object |
DRM_IOCTL_MODE_ADDFB2 | drmModeAddFB2() | drmModeAddFB() 的升級版 |
DRM_IOCTL_MODE_GETFB | drmModeGetFB() | 獲取指定 ID 的 framebuffer object |
DRM_IOCTL_MODE_RMFB | drmModeRmFB() | 銷燬指定的 framebuffer object |
示例 5
實現 callback funcs,添加 Legacy Modeset 支持(代碼量較大):
#include <drm/drm_crtc_helper.h>
#include <drm/drm_plane_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
static struct drm_device drm;
static struct drm_plane primary;
static struct drm_crtc crtc;
static struct drm_encoder encoder;
static struct drm_connector connector;
static void vkms_crtc_dpms(struct drm_crtc *crtc, int mode)
{
}
static int vkms_crtc_mode_set(struct drm_crtc *crtc,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode,
int x, int y, struct drm_framebuffer *old_fb)
{
return 0;
}
static void vkms_crtc_prepare(struct drm_crtc *crtc)
{
}
static void vkms_crtc_commit(struct drm_crtc *crtc)
{
}
static int vkms_crtc_page_flip(struct drm_crtc *crtc,
struct drm_framebuffer *fb,
struct drm_pending_vblank_event *event,
uint32_t page_flip_flags,
struct drm_modeset_acquire_ctx *ctx)
{
unsigned long flags;
crtc->primary->fb = fb;
if (event) {
spin_lock_irqsave(&crtc->dev->event_lock, flags);
drm_crtc_send_vblank_event(crtc, event);
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
}
return 0;
}
static const struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = {
.dpms = vkms_crtc_dpms,
.mode_set = vkms_crtc_mode_set,
.prepare = vkms_crtc_prepare,
.commit = vkms_crtc_commit,
};
static const struct drm_crtc_funcs vkms_crtc_funcs = {
.set_config = drm_crtc_helper_set_config,
.page_flip = vkms_crtc_page_flip,
.destroy = drm_crtc_cleanup,
};
static const struct drm_plane_funcs vkms_plane_funcs = {
.update_plane = drm_primary_helper_update,
.disable_plane = drm_primary_helper_disable,
.destroy = drm_plane_cleanup,
};
static int vkms_connector_get_modes(struct drm_connector *connector)
{
int count;
count = drm_add_modes_noedid(connector, 8192, 8192);
drm_set_preferred_mode(connector, 1024, 768);
return count;
}
static struct drm_encoder *vkms_connector_best_encoder(struct drm_connector *connector)
{
return &encoder;
}
static const struct drm_connector_helper_funcs vkms_conn_helper_funcs = {
.get_modes = vkms_connector_get_modes,
.best_encoder = vkms_connector_best_encoder,
};
static const struct drm_connector_funcs vkms_connector_funcs = {
.dpms = drm_helper_connector_dpms,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
};
static const struct drm_encoder_funcs vkms_encoder_funcs = {
.destroy = drm_encoder_cleanup,
};
static const struct drm_mode_config_funcs vkms_mode_funcs = {
.fb_create = drm_fb_cma_create,
};
static const u32 vkms_formats[] = {
DRM_FORMAT_XRGB8888,
};
static void vkms_modeset_init(void)
{
drm_mode_config_init(&drm);
drm.mode_config.max_width = 8192;
drm.mode_config.max_height = 8192;
drm.mode_config.funcs = &vkms_mode_funcs;
drm_universal_plane_init(&drm, &primary, 0, &vkms_plane_funcs,
vkms_formats, ARRAY_SIZE(vkms_formats),
NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
drm_crtc_init_with_planes(&drm, &crtc, &primary, NULL, &vkms_crtc_funcs, NULL);
drm_crtc_helper_add(&crtc, &vkms_crtc_helper_funcs);
drm_encoder_init(&drm, &encoder, &vkms_encoder_funcs, DRM_MODE_ENCODER_VIRTUAL, NULL);
drm_connector_init(&drm, &connector, &vkms_connector_funcs, DRM_MODE_CONNECTOR_VIRTUAL);
drm_connector_helper_add(&connector, &vkms_conn_helper_funcs);
drm_mode_connector_attach_encoder(&connector, &encoder);
}
static const struct file_operations vkms_fops = {
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
.poll = drm_poll,
.read = drm_read,
.mmap = drm_gem_cma_mmap,
};
static struct drm_driver vkms_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM,
.fops = &vkms_fops,
.dumb_create = drm_gem_cma_dumb_create,
.gem_vm_ops = &drm_gem_cma_vm_ops,
.gem_free_object_unlocked = drm_gem_cma_free_object,
.name = "vkms",
.desc = "Virtual Kernel Mode Setting",
.date = "20180514",
.major = 1,
.minor = 0,
};
static int __init vkms_init(void)
{
drm_dev_init(&drm, &vkms_driver, NULL);
vkms_modeset_init();
drm_dev_register(&drm, 0);
return 0;
}
module_init(vkms_init);
重點:
- xxx_funcs 必須有,xxx_helper_funcs 可以沒有。
- drm_xxx_init() 必須有,drm_xxx_helper_add() 可以沒有。
- 只有當 xxx_funcs 採用 DRM 標準的 helper 函數實現時,才有可能 需要定義 xxx_helper_funcs 接口。
drmModeSetCrtc()
===> crtc_funcs.set_config()
drmModePageFlip()
===> crtc_funcs.page_flip()
drmModeSetPlane()
===> plane_funcs.update_plane()
drmModeGetConnector()
===> connector_funcs.fill_modes()
- xxx_funcs.
destroy()
接口必須實現。
提示:本示例中的 funcs 和 helper funcs 接口無法再精簡,否則運行時將出現 kernel crash!
helper 函數的作用:
drm_xxx_funcs 是 drm ioctl 操作的最終入口,但是對於大多數 SoC 廠商來說,它們的 drm_xxx_funcs 操作流程基本相同(你抄我,我抄你),只是在寄存器配置上存在差異,因此開發者們將那些 common 的操作流程做成了 helper 函數,而將那些廠商差異化的代碼放到了 drm_xxx_helper_funcs 中去,由 SoC 廠商自己實現。
有了各種 funcs 和 helper funcs,我們現在終於可以執行真正的 modeset 操作了,不過目前只支持 legacy modeset,趕緊使用《最簡單的DRM應用程序 (single-buffer)》 來驗證一下吧!
當前支持的 modeset IOCTL:
IOCTL | Userspace API | 描述 |
---|---|---|
DRM_IOCTL_MODE_SETCRTC | drmModeSetCrtc() | 初始化整個硬件 pipeline 並顯示圖像 |
DRM_IOCTL_MODE_SETPLANE | drmModeSetPlane() | 設置單個 Plane 的顯示參數 |
DRM_IOCTL_MODE_PAGE_FLIP | drmModePageFlip() | 基於 VSYNC 同步機制的顯示刷新 |
DRM_IOCTL_MODE_GETPROPERTY | drmModeGetProperty() | 根據 Property ID 獲取對應 Property 的值 |
DRM_IOCTL_MODE_SETPROPERTY | drmModeConnectorSetProperty() | 根據 Property ID 設置對應 Property 的值,目前僅用於 Connector 對象 |
示例 6
將上面的 Legacy code 轉換爲 Atomic 版本:
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <linux/hrtimer.h>
static struct drm_device drm;
static struct drm_plane primary;
static struct drm_crtc crtc;
static struct drm_encoder encoder;
static struct drm_connector connector;
static struct hrtimer vblank_hrtimer;
static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
{
drm_crtc_handle_vblank(&crtc);
hrtimer_forward_now(&vblank_hrtimer, 16666667);
return HRTIMER_RESTART;
}
static void vkms_crtc_atomic_enable(struct drm_crtc *crtc,
struct drm_crtc_state *old_state)
{
hrtimer_init(&vblank_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
vblank_hrtimer.function = &vkms_vblank_simulate;
hrtimer_start(&vblank_hrtimer, 16666667, HRTIMER_MODE_REL);
}
static void vkms_crtc_atomic_disable(struct drm_crtc *crtc,
struct drm_crtc_state *old_state)
{
hrtimer_cancel(&vblank_hrtimer);
}
static void vkms_crtc_atomic_flush(struct drm_crtc *crtc,
struct drm_crtc_state *old_crtc_state)
{
unsigned long flags;
if (crtc->state->event) {
spin_lock_irqsave(&crtc->dev->event_lock, flags);
drm_crtc_send_vblank_event(crtc, crtc->state->event);
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
crtc->state->event = NULL;
}
}
static const struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = {
.atomic_enable = vkms_crtc_atomic_enable,
.atomic_disable = vkms_crtc_atomic_disable,
.atomic_flush = vkms_crtc_atomic_flush,
};
static const struct drm_crtc_funcs vkms_crtc_funcs = {
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.destroy = drm_crtc_cleanup,
.reset = drm_atomic_helper_crtc_reset,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
};
static void vkms_plane_atomic_update(struct drm_plane *plane,
struct drm_plane_state *old_state)
{
}
static const struct drm_plane_helper_funcs vkms_plane_helper_funcs = {
.atomic_update = vkms_plane_atomic_update,
};
static const struct drm_plane_funcs vkms_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = drm_plane_cleanup,
.reset = drm_atomic_helper_plane_reset,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
};
static int vkms_conn_get_modes(struct drm_connector *connector)
{
int count;
count = drm_add_modes_noedid(connector, 8192, 8192);
drm_set_preferred_mode(connector, 1024, 768);
return count;
}
static const struct drm_connector_helper_funcs vkms_conn_helper_funcs = {
.get_modes = vkms_conn_get_modes,
};
static const struct drm_connector_funcs vkms_connector_funcs = {
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static const struct drm_encoder_funcs vkms_encoder_funcs = {
.destroy = drm_encoder_cleanup,
};
static const struct drm_mode_config_funcs vkms_mode_funcs = {
.fb_create = drm_fb_cma_create,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};
static const u32 vkms_formats[] = {
DRM_FORMAT_XRGB8888,
};
static void vkms_modeset_init(void)
{
drm_mode_config_init(&drm);
drm.mode_config.max_width = 8192;
drm.mode_config.max_height = 8192;
drm.mode_config.funcs = &vkms_mode_funcs;
drm_universal_plane_init(&drm, &primary, 0, &vkms_plane_funcs,
vkms_formats, ARRAY_SIZE(vkms_formats),
NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
drm_plane_helper_add(&primary, &vkms_plane_helper_funcs);
drm_crtc_init_with_planes(&drm, &crtc, &primary, NULL, &vkms_crtc_funcs, NULL);
drm_crtc_helper_add(&crtc, &vkms_crtc_helper_funcs);
drm_encoder_init(&drm, &encoder, &vkms_encoder_funcs, DRM_MODE_ENCODER_VIRTUAL, NULL);
drm_connector_init(&drm, &connector, &vkms_connector_funcs, DRM_MODE_CONNECTOR_VIRTUAL);
drm_connector_helper_add(&connector, &vkms_conn_helper_funcs);
drm_mode_connector_attach_encoder(&connector, &encoder);
drm_mode_config_reset(&drm);
}
static const struct file_operations vkms_fops = {
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
.poll = drm_poll,
.read = drm_read,
.mmap = drm_gem_cma_mmap,
};
static struct drm_driver vkms_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
.fops = &vkms_fops,
.dumb_create = drm_gem_cma_dumb_create,
.gem_vm_ops = &drm_gem_cma_vm_ops,
.gem_free_object_unlocked = drm_gem_cma_free_object,
.name = "vkms",
.desc = "Virtual Kernel Mode Setting",
.date = "20180514",
.major = 1,
.minor = 0,
};
static int __init vkms_init(void)
{
drm_dev_init(&drm, &vkms_driver, NULL);
vkms_modeset_init();
drm_vblank_init(&drm, 1);
drm.irq_enabled = true;
drm_dev_register(&drm, 0);
return 0;
}
module_init(vkms_init);
重點:
- 給 driver_features 添加上
DRIVER_ATOMIC
標誌位,告訴 DRM Core 該驅動支持 Atomic 操作。 - drm_mode_config_funcs.
atomic_commit()
接口是 atomic 操作的主要入口函數,必須實現。這裏直接使用drm_atomic_helper_commit()
函數實現。 - Atomic 操作依賴 VSYNC 中斷(即 VBLANK 事件),因此需要使用 hrtimer 來提供軟件中斷信號。在驅動初始化時調用
drm_vblank_init()
,在 VSYNC 中斷處理函數中調用drm_handle_vblank()
。 - 在 plane/crtc/encoder/connector objects 初始化完成之後,一定要調用
drm_mode_config_reset()
來動態創建各個 pipeline 的軟件狀態(即 drm_xxx_state)。 - 與 Legacy 相比,Atomic 的 xxx_funcs 必須 實現如下接口:
reset()
atomic_duplicate_state()
atomic_destroy_state()
,
它們主要用於維護 drm_xxx_state 數據結構,不能省略! drm_plane_helper_funcs.atomic_update()
必須實現!
終於,我們可以使用 drmModeAtomicCommit()
了,趕緊使用前面的《DRM應用程序進階 (atomic-plane)》 示例程序測試一下吧!
IOCTL | Userspace API | 描述 |
---|---|---|
DRM_IOCTL_MODE_ATOMIC | drmModeAtomicCommit() | 不解釋 |
DRM_IOCTL_MODE_OBJ_GETPROPERTIES | drmModeObjectGetProperties() | 根據 drm mode object 的 ID,獲取該 object 所擁有的所有 Property 對象 |
DRM_IOCTL_MODE_OBJ_SETPROPERTY | drmModeObjectSetProperty() | 根據 drm mode object 的 ID,設置該 object 某個具體的 Property 對象的值 |
到這裏,一個 “最簡單” 的 Atomic 驅動程序也就完成了,雖然最終看起來並不那麼簡單。。。
總結
要實現一個 DRM KMS 驅動,通常需要實現如下代碼:
fops
、drm_driver
dumb_create
、fb_create
、atomic_commit
drm_xxx_funcs
、drm_xxx_helper_funcs
drm_xxx_init()
、drm_xxx_helper_add()
drm_dev_init()
、drm_dev_register()
但這都只是表象,核心仍然是上一篇《DRM 驅動程序開發(開篇)》中介紹的7個 objects,一切都圍繞着這幾個 objects 展開:
- 爲了創建 crtc/plane/encoder/connector objects,需要調用
drm_xxx_init()
。 - 爲了創建 framebuffer object,需要實現
fb_create()
callback。 - 爲了創建 gem object,需要實現
dumb_create()
callback。 - 爲了創建 property objects,需要調用
drm_mode_config_init()
。 - 爲了讓這些 objects 動起來,需要實現各種 funcs 和 helper funcs。
- 爲了支持 atomic 操作,需要實現
atomic_commit()
callback。
結語
DRM Driver 是內核中一個較爲複雜的子系統,在內核社區的更新也十分活躍,但因其涉及到的知識點較多,教程寫起來難度也比較大,我想這也是爲什麼國內熟悉 DRM 驅動的人不在少數,卻很少有人能輸出 系統性 的 DRM 學習文檔,畢竟要寫好真的很難,而且大牛們真的都很忙。
希望我的文章,能爲那些還在 DRM 學習路上的小夥伴們提供幫助,解答他們心中的疑惑。下一篇,我將介紹 DRM GEM 相關的知識,敬請期待!
最後,送上 VKMS 社區簡化版(相比本文的示例,更接近於 kernel 4.19 版本):vkms_drv.c
源碼下載
我的碼雲:vkms