前面的 DRM 應(yīng)用程序系列文章中,我們學(xué)習(xí)了如何使用 libdrm 接口編寫(xiě) DRM 應(yīng)用程序。本篇我們將進(jìn)入一個(gè)全新的世界,一起來(lái)學(xué)習(xí)如何在 kernel 空間編寫(xiě) DRM 驅(qū)動(dòng)程序。
DRM 驅(qū)動(dòng)相關(guān)的概念
Objects
在開(kāi)始編寫(xiě) DRM 驅(qū)動(dòng)程序之前,我有必要對(duì) DRM 內(nèi)部的 Objects 進(jìn)行一番介紹。因?yàn)檫@些 Objects 是 DRM 框架的核心,它們?nèi)币徊豢伞?/p>
上圖藍(lán)色部分則是對(duì)物理硬件的抽象,黃色部分則是對(duì)軟件的抽象。虛線以上的為 drm_mode_object,虛線以下為 drm_gem_object。
之前曾對(duì)這些 objects 做過(guò)簡(jiǎn)要介紹,這里有必要再?gòu)?qiáng)調(diào)一下這些 objects 的概念:
這些 objects 之間的關(guān)系:
通過(guò)上圖可以看到,plane 是連接 framebuffer 和 crtc 的紐帶,而 encoder 則是連接 crtc 和 connector 的紐帶。與物理 buffer 直接打交道的是 gem 而不是 framebuffer。
需要注意的是,上圖藍(lán)色部分即使沒(méi)有實(shí)際的硬件與之對(duì)應(yīng),在軟件驅(qū)動(dòng)中也需要實(shí)現(xiàn)這些 objects,否則 DRM 子系統(tǒng)無(wú)法正常運(yùn)行。
drm_panel
drm_panel 不屬于 objects 的范疇,它只是一堆回調(diào)函數(shù)的集合。但它的存在降低了 LCD 驅(qū)動(dòng)與 encoder 驅(qū)動(dòng)之間的耦合度。
耦合的產(chǎn)生:
- connector 的主要作用就是獲取顯示參數(shù),所以會(huì)在 LCD 驅(qū)動(dòng)中去構(gòu)造 connector object。但是 connector 初始化時(shí)需要 attach 上一個(gè) encoder object,而這個(gè) encoder object 往往是在另一個(gè)硬件驅(qū)動(dòng)中生成的,為了訪問(wèn)該 encoder object,勢(shì)必會(huì)產(chǎn)生一部分耦合的代碼。encoder 除了扮演信號(hào)轉(zhuǎn)換的角色,還擔(dān)任著通知顯示設(shè)備休眠喚醒的角色。因此,當(dāng) encoder 通知 LCD 驅(qū)動(dòng)執(zhí)行相應(yīng)的 enable/disable 操作時(shí),就一定會(huì)調(diào)用 LCD 驅(qū)動(dòng)導(dǎo)出的全局函數(shù),這也必然會(huì)產(chǎn)生一部分的耦合代碼。
為了解決該耦合的問(wèn)題,DRM 子系統(tǒng)為開(kāi)發(fā)人員提供了 drm_panel 結(jié)構(gòu)體,該結(jié)構(gòu)體封裝了 connector & encoder 對(duì) LCD 訪問(wèn)的常用接口。
于是,原來(lái)的 Encoder 驅(qū)動(dòng)和 LCD 驅(qū)動(dòng)之間的耦合,就轉(zhuǎn)變成了上圖中 Encoder 驅(qū)動(dòng)與 drm_panel、drm_panel 與 LCD 驅(qū)動(dòng)之間的“耦合”,從而實(shí)現(xiàn)了 Encoder 驅(qū)動(dòng)與 LCD 驅(qū)動(dòng)之間的解耦合。
為了方便驅(qū)動(dòng)程序設(shè)計(jì),通常都將 encoder 與 connector 放在同一個(gè)驅(qū)動(dòng)中初始化,即 encoder 在哪,connector 就在哪。
如何抽象硬件
對(duì)于初學(xué)者來(lái)說(shuō),往往讓他們迷惑的不是 DRM 中 objects 的概念,而是如何去建立這些 objects 與實(shí)際硬件的對(duì)應(yīng)關(guān)系。因?yàn)椴⒉皇撬械?Display 硬件都能很好的對(duì)應(yīng)上 plane/crtc/encoder/connector 這些 objects。下面我們就來(lái)一起學(xué)習(xí),如何去抽象顯示硬件到具體的 DRM object。
MIPI DSI 接口
下圖為一個(gè)典型的 MIPI DSI 接口屏的硬件連接框圖:
它在軟件架構(gòu)上與 DRM object 的對(duì)應(yīng)關(guān)系如下圖:
多余的細(xì)節(jié)不做介紹,這里只說(shuō)明為何如此分配 drm object:
MIPI DPI 接口
DPI 接口也就是我們常說(shuō)的 RGB 并行接口,Video 數(shù)據(jù)通過(guò) RGB 并行總線傳輸,控制命令(如初始化、休眠、喚醒等)則通過(guò) SPI/I2C 總線傳輸,比如早期的 S3C2440 SoC 平臺(tái)。下圖為一個(gè)典型的 MIPI DPI 接口屏的硬件連接框圖:
該硬件連接在軟件架構(gòu)上與 DRM object 的對(duì)應(yīng)關(guān)系如下圖:
多余的細(xì)節(jié)不做介紹,這里只說(shuō)明為何如此分配 drm object:
VKMS 學(xué)習(xí)
VKMS 是 “Virtual Kernel Mode Setting” 的縮寫(xiě),它于2018年7月5日被合入到 linux-4.19 主線版本中,并存放在 drivers/gpu/drm/vkms 目錄下。之所以稱(chēng)它為 Virtual KMS,是因?yàn)樵擈?qū)動(dòng)不需要真實(shí)的硬件,它完全是一個(gè)軟件虛擬的“顯示”設(shè)備,甚至連顯示都算不上,因?yàn)楫?dāng)它運(yùn)行時(shí),你看不到任何顯示內(nèi)容。它唯一能提供的,就是一個(gè)由高精度 timer 模擬的 VSYNC 中斷信號(hào)!該驅(qū)動(dòng)存在的目的,主要是為了 DRM 框架自測(cè)試,以及方便那些無(wú)頭顯示器設(shè)備的調(diào)試應(yīng)用。雖然我們看不到 VKMS 的顯示效果,但是在驅(qū)動(dòng)流程上,它實(shí)現(xiàn)了 modesetting 該有的基本操作。因其邏輯簡(jiǎn)單,代碼量少,拿來(lái)做學(xué)習(xí)案例講解再好不過(guò)。
隨著內(nèi)核版本的不斷升級(jí),添加到 VKMS 的功能也越來(lái)越多,截止到內(nèi)核版本 kernel 5.7-rc2,該 VKMS 驅(qū)動(dòng)已經(jīng)集成了如下功能:
- Atomic ModesetVBlankDumb BufferCursor & Primary PlaneFramebuffer CRC 校驗(yàn)Plane CompositionGEM Prime Import
下面就跟著我一起來(lái)學(xué)習(xí),如何從0到1實(shí)現(xiàn)一個(gè) VKMS 驅(qū)動(dòng)吧!
示例 1
這是一個(gè)最簡(jiǎn)單的 DRM 驅(qū)動(dòng)代碼:
#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 框架還為我們做了下面這些事情:
- 創(chuàng)建設(shè)備節(jié)點(diǎn):/dev/dri/card0創(chuàng)建 sysfs 節(jié)點(diǎn):/sys/class/drm/card0創(chuàng)建 debugfs 節(jié)點(diǎn):/sys/kernel/debug/dri/0
不過(guò)該驅(qū)動(dòng)目前什么事情也做不了,你唯一能做的就是查看該驅(qū)動(dòng)的名字:
$?cat?/sys/kernel/debug/dri/0/name
vkms?unique=vkms
你甚至都無(wú)法對(duì) /dev/dri/card0 進(jìn)行 open 操作,因?yàn)樵擈?qū)動(dòng)還沒(méi)有實(shí)現(xiàn) fops 接口。
示例 2
接下來(lái)我們給 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,我們就可以對(duì) card0 進(jìn)行 open,read,ioctl?操作了。讓我們看看現(xiàn)在可以執(zhí)行哪些 IOCTL:
但是到目前為止,凡是和 modesetting 相關(guān)的操作,還是操作不了。
示例 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);
重點(diǎn):
- 給 driver_features 添加上 DRIVER_MODESET 標(biāo)志位,告訴 DRM Core 當(dāng)前驅(qū)動(dòng)支持 modesetting 操作;drm_mode_config_init() 初始化一些全局的數(shù)據(jù)結(jié)構(gòu)。注意,那些 Standard Properties 就是在這里創(chuàng)建的。drm_xxx_init() 則分別用于創(chuàng)建 plane、crtc、encoder、connector 這4個(gè) drm_mode_object。
由于上面4個(gè) objects 在創(chuàng)建時(shí),它們的 callback funcs 沒(méi)有賦初值,所以真正的 modeset 操作目前還無(wú)法正常執(zhí)行,不過(guò)我們至少可以使用下面這些只讀的 modeset IOCTL 了:
示例 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);
重點(diǎn):
- 給 driver_features 添加上 DRIVER_GEM 標(biāo)志位,告訴 DRM Core 該驅(qū)動(dòng)支持 GEM 操作;dumb_create 回調(diào)接口用于創(chuàng)建 gem object,并分配物理 buffer。這里直接使用 CMA helper 函數(shù)來(lái)實(shí)現(xiàn);fb_create 回調(diào)接口用于創(chuàng)建 framebuffer object,并綁定 gem objects。這里直接使用 CMA helper 函數(shù)實(shí)現(xiàn)。fops 中的 mmap 接口,用于將 dumb buffer 映射到 userspace,它依賴(lài) drm driver 中的 gem_vm_ops 實(shí)現(xiàn)。這里也直接使用 CMA helper 函數(shù)來(lái)實(shí)現(xiàn)。
現(xiàn)在,我們可以使用如下 IOCTL 來(lái)進(jìn)行一些標(biāo)準(zhǔn)的 GEM 和 FB 操作了!
示例 5
實(shí)現(xiàn) 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);
重點(diǎn):
- xxx_funcs 必須有,xxx_helper_funcs 可以沒(méi)有。drm_xxx_init() 必須有,drm_xxx_helper_add() 可以沒(méi)有。只有當(dāng) xxx_funcs 采用 DRM 標(biāo)準(zhǔn)的 helper 函數(shù)實(shí)現(xiàn)時(shí),才有可能 需要定義 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() 接口必須實(shí)現(xiàn)。
提示:本示例中的 funcs 和 helper funcs 接口無(wú)法再精簡(jiǎn),否則運(yùn)行時(shí)將出現(xiàn) kernel crash!
helper 函數(shù)的作用:drm_xxx_funcs 是 drm ioctl 操作的最終入口,但是對(duì)于大多數(shù) SoC 廠商來(lái)說(shuō),它們的 drm_xxx_funcs 操作流程基本相同,只是在寄存器配置上存在差異,因此開(kāi)發(fā)者們將那些 common 的操作流程做成了 helper 函數(shù),而將那些廠商差異化的代碼放到了 drm_xxx_helper_funcs 中去,由 SoC 廠商自己實(shí)現(xiàn)。
有了各種 funcs 和 helper funcs,我們現(xiàn)在終于可以執(zhí)行真正的 modeset 操作了。當(dāng)前支持的 modeset IOCTL:
示例 6
將上面的 Legacy code 轉(zhuǎn)換為 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);
重點(diǎn):
- 給 driver_features 添加上 DRIVER_ATOMIC 標(biāo)志位,告訴 DRM Core 該驅(qū)動(dòng)支持 Atomic 操作。drm_mode_config_funcs.atomic_commit() 接口是 atomic 操作的主要入口函數(shù),必須實(shí)現(xiàn)。這里直接使用 drm_atomic_helper_commit() 函數(shù)實(shí)現(xiàn)。Atomic 操作依賴(lài) VSYNC 中斷(即 VBLANK 事件),因此需要使用 hrtimer 來(lái)提供軟件中斷信號(hào)。在驅(qū)動(dòng)初始化時(shí)調(diào)用 drm_vblank_init(),在 VSYNC 中斷處理函數(shù)中調(diào)用 drm_handle_vblank()。在 plane/crtc/encoder/connector objects 初始化完成之后,一定要調(diào)用 drm_mode_config_reset() 來(lái)動(dòng)態(tài)創(chuàng)建各個(gè) pipeline 的軟件狀態(tài)(即 drm_xxx_state)。與 Legacy 相比,Atomic 的 xxx_funcs 必須 實(shí)現(xiàn)如下接口:reset(),atomic_duplicate_state(),atomic_destroy_state(),它們主要用于維護(hù) drm_xxx_state 數(shù)據(jù)結(jié)構(gòu),不能省略!drm_plane_helper_funcs.atomic_update() 必須實(shí)現(xiàn)!
終于,我們可以使用 drmModeAtomicCommit() 了。
總結(jié)
要實(shí)現(xiàn)一個(gè) DRM KMS 驅(qū)動(dòng),通常需要實(shí)現(xiàn)如下代碼:
- fops、drm_driverdumb_create、fb_create、atomic_commitdrm_xxx_funcs、drm_xxx_helper_funcsdrm_xxx_init()、drm_xxx_helper_add()drm_dev_init()、drm_dev_register()
但這都只是表象,核心仍然是上面介紹的7個(gè) objects,一切都圍繞著這幾個(gè) objects 展開(kāi):
為了創(chuàng)建 crtc/plane/encoder/connector objects,需要調(diào)用 drm_xxx_init()。
為了創(chuàng)建 framebuffer object,需要實(shí)現(xiàn) fb_create() callback。
為了創(chuàng)建 gem object,需要實(shí)現(xiàn) dumb_create() callback。
為了創(chuàng)建 property objects,需要調(diào)用 drm_mode_config_init()。
為了讓這些 objects 動(dòng)起來(lái),需要實(shí)現(xiàn)各種 funcs 和 helper funcs。
為了支持 atomic 操作,需要實(shí)現(xiàn) atomic_commit() callback。
希望我的文章,能為那些還在 DRM 學(xué)習(xí)路上的小伙伴們提供幫助。下一篇,我將介紹 DRM GEM 相關(guān)的知識(shí),敬請(qǐng)期待!