DRM显示的一帧图像的方法有很多,如setcrtc、setplane,及pageflip等,但这些接口都是比较老旧的,现在推荐使用Atomic接口,这篇文章简单介绍一下Atomic接口机制。
在我们的直观印象中,桌面系统纷繁复杂,各种窗口各种图案各种对象,但对于DRM来说其实比较简单,一个显示屏幕中显示的内容都可以看作是一副图像,一副图像又对应一个Framebuffer,所以Atomic接口的主要任务就是把Framebuffer中的数据提交到显卡上。
1、例子程序从例子开始学习是一种好方法,例子代码如下:https://github.com/dvdhrm/docs/tree/master/drm-howto里面有好几个例子,每个文件一个例子,Atomic实现方式的放在文件modeset-atomic.c中。
例子程序中的Atomic刷新主要通过libdrm提供的三个接口实现。
首先调用drmModeAtomicAlloc()分配一个drmModeAtomicReq对象,接着根据需要反复调用drmModeAtomicAddProperty()设置相关的属性,最后调用drmModeAtomicCommit()提交Atomic。
例子不能在Linux桌面中运行,需要通过命令切换到多用户模式下运行init 3
例子比较简单,就不细讲了,下面说一下Atomic接口的内核实现。
2、Atomic接口机制为什么Linux内核的设计者要把图像显示的接口设计成Atomic接口的形式?我想可能是由于现在的显示系统多种多样,每种显卡的功能和所需的数据又各不相同,用一种设置属性然后统一提交的方式能最大程度的兼容整体控制和程序逻辑吧。
用户空间的程序,通过drmModeAtomicAlloc、drmModeAtomicAddProperty、drmModeAtomicCommit三个接口实现一副图像的刷新(当然了,FB中的数据得提前准备好)。
libdrm库把三个接口打包成一个系统调用(ioctl)来执行,在系统调用返回的时候,通过返回值知道一整个打包调用要么成功要么失败,这可能就是接口之所以命名为Atomic的原因吧。
由于显卡的多样性,内核为了兼容各种显卡正常的工作,把它们关心的数据及对象都通过属性的方式实现,这样既可以十分方便的兼容各种厂家的特性,也可以为以后的发展提供灵活的扩展能力。
当系统调用ioctl进入内核的时候,VFS框架会把DRM的执行流程转到drm_ioctl函数中处理,几乎所有DRM功能都从这一入口进入然后再分流到各个功能函数中去。
内核定义了一个drm_ioctls表用于分发各功能函数。
来看一下drm_ioctls的定义// FILE: drivers/gpu/drm/drm_ioctl.c
#define DRM_IOCTL_DEF(ioctl, _func, _flags)
[DRM_IOCTL_NR(ioctl)] = {
.cmd = ioctl,
.func = _func,
.flags = _flags,
.name = #ioctl
}
#if IS_ENABLED(CONFIG_DRM_LEGACY)
#define DRM_LEGACY_IOCTL_DEF(ioctl, _func, _flags) DRM_IOCTL_DEF(ioctl, _func, _flags)
#else
#define DRM_LEGACY_IOCTL_DEF(ioctl, _func, _flags) DRM_IOCTL_DEF(ioctl, drm_invalid_op, _flags)
#endif
/* Ioctl table */
static const struct drm_ioctl_desc drm_ioctls[] = {
DRM_IOCTL_DEF(DRM_IOCTL_VERSION, drm_version, DRM_RENDER_ALLOW),
……
// 获取资源
DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETRESOURCES, drm_mode_getresources, 0),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCRTC, drm_mode_getcrtc, 0),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETCRTC, drm_mode_setcrtc, DRM_MASTER),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPLANE, drm_mode_getplane, 0),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETPLANE, drm_mode_setplane, DRM_MASTER),
……
// 获取连接对象
DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCONNECTOR, drm_mode_getconnector, 0),
……..
DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb_ioctl, 0),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB2, drm_mode_addfb2_ioctl, 0),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb_ioctl, 0),
// 页面flip
DRM_IOCTL_DEF(DRM_IOCTL_MODE_PAGE_FLIP, drm_mode_page_flip_ioctl, DRM_MASTER),
……..
// 创建DUMB对象
DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_DUMB, drm_mode_create_dumb_ioctl, 0),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_MAP_DUMB, drm_mode_mmap_dumb_ioctl, 0),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_DESTROY_DUMB, drm_mode_destroy_dumb_ioctl, 0),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_OBJ_GETPROPERTIES, drm_mode_obj_get_properties_ioctl, 0),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_CURSOR2, drm_mode_cursor2_ioctl, DRM_MASTER),
// Atomic接口实现函数
DRM_IOCTL_DEF(DRM_IOCTL_MODE_ATOMIC, drm_mode_atomic_ioctl, DRM_MASTER),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATEPROPBLOB, drm_mode_createblob_ioctl, 0),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_DESTROYPROPBLOB, drm_mode_destroyblob_ioctl, 0),
……..
};
#define DRM_CORE_IOCTL_COUNT ARRAY_SIZE( drm_ioctls )
drm_ioctls中的功能定义在DRM_COMMAND_BASE与DRM_COMMAND_END之间,驱动程序可以扩展功能,以实现自己的特性。
drm_ioctl函数可以说是DRM功能的总入口,来看一下代码// FILE: drivers/gpu/drm/drm_ioctl.c
/**
* drm_ioctl – ioctl callback implementation for DRM drivers
* @filp: file this ioctl is called on
* @cmd: ioctl cmd number
* @arg: user argument
*
* Looks up the ioctl function in the DRM core and the driver dispatch table,
* stored in &drm_driver.ioctls. It checks for necessary permission by calling
* drm_ioctl_permit(), and dispatches to the respective function.
*
* Returns:
* Zero on success, negative error code on failure.
*/
long drm_ioctl(struct file *filp,
unsigned int cmd, unsigned long arg)
{
struct drm_file *file_priv = filp->private_data;
struct drm_device *dev;
const struct drm_ioctl_desc *ioctl = NULL;
drm_ioctl_t *func;
unsigned int nr = DRM_IOCTL_NR(cmd);
int retcode = -EINVAL;
char stack_kdata[128];
char *kdata = NULL;
unsigned int in_size, out_size, drv_size, ksize;
bool is_driver_ioctl;
dev = file_priv->minor->dev;
if (drm_dev_is_unplugged(dev))
return -ENODEV;
// 根据功能号判断是核心功能,还是驱动扩展功能
is_driver_ioctl = nr >= DRM_COMMAND_BASE && nr < DRM_COMMAND_END;
if (is_driver_ioctl) {
/* driver ioctl */
unsigned int index = nr – DRM_COMMAND_BASE;
if (index >= dev->driver->num_ioctls)
goto err_i1;
index = array_index_nospec(index, dev->driver->num_ioctls);
ioctl = &dev->driver->ioctls[index];
} else {
/* core ioctl */
if (nr >= DRM_CORE_IOCTL_COUNT)
goto err_i1;
nr = array_index_nospec(nr, DRM_CORE_IOCTL_COUNT);
// 从drm_ioctls结构定义中获取功能的项
ioctl = &drm_ioctls[nr];
}
……
// 取得功能实现函数指针
/* Do not trust userspace, use our own definition */
func = ioctl->func;
……
if (ksize <= sizeof(stack_kdata)) {
kdata = stack_kdata;
} else {
kdata = kmalloc(ksize, GFP_KERNEL);
if (!kdata) {
retcode = -ENOMEM;
goto err_i1;
}
}
// 获取用户空间传入参数
if (copy_from_user(kdata, (void __user *)arg, in_size) != 0) {
retcode = -EFAULT;
goto err_i1;
}
if (ksize > in_size)
memset(kdata + in_size, 0, ksize – in_size);
// 执行功能函数
retcode = drm_ioctl_kernel(filp, func, kdata, ioctl->flags);
// 结果通过内存拷贝回用户空间
if (copy_to_user((void __user *)arg, kdata, out_size) != 0)
retcode = -EFAULT;
……
return retcode;
}
EXPORT_SYMBOL(drm_ioctl);
Atomic通过drm_ioctl函数后由drm_mode_atomic_ioctl函数实现。
3、Atomic接口实现我们刷新一副图像的时候,用户程序与内核通过一个Atomic调用,要么成功要么失败。
而DRM框架又是如何实现一副图像的刷新的呢?答案是:state。
Atomic接口的调用,DRM框架把它看成是一个drm_atomic_state对象切换的过程。
Atomic中的属性包含了新图像的MODE_ID,CRTC_ID,FB_ID等内容,DRM在每一次调用之初分配一个新的drm_atomic_state对象,然后把Atomic中的属性设置到drm_atomic_state中去,最后检查无误后,把它commit到显卡上,从而新的state就替换了旧的state了。
drm_atomic_state对象除了包含drm_crtc_state、drm_plane_state等子对象之外,还包含一个drm_crtc_commit对象,这个对象控制整个commit过程的过程,可以这么说,整个commit其实就是围绕CRTC的commit来处理的。
来看一下drm_crtc_commit对象的定义// FILE: include/drm/drm_atomic.h
struct drm_crtc_commit {
struct drm_crtc *crtc;
struct kref ref;
// 硬件翻转到新的缓冲区时,将发出flip_done信号。
当commit事件被发
// 送到用户空间,或者当一个out-fence 有信号时,也会同时发出此信号。
// 注意,对于大多数硬件,在大多数情况下,此信号发生在hw_done信号之后。
// 通过调用drm_crtc_send_vblank_event触发此信号。
struct completion flip_done;
// 此次Commit的所有硬件寄存器都改变时发出信号。
尤其是在禁用管道时,
// 这可能会比flip_done要晚得多,因为当屏幕变黑时,这可能已经发出信
// 号了,而要完全关闭管道,则需要更多的寄存器I/O。
// 请注意,这不需要包括单独的引用计数资源,如备份存储缓冲区固定或
// 电源pm管理的时候。
// 驱动调用drm_atomic_helper_commit_hw_done触发此信号
struct completion hw_done;
// 驱动在调用drm_atomic_helper_cleanup_planes后触发此信号。
因为这
// 只能在vblank等待完成之后才发生,所以可能会晚一点。
此信号非常有用,
// 可以限制更新频率和避免硬件更新的时候缓冲区清理工作还没有完成。
struct completion cleanup_done;
struct list_head commit_entry;
struct drm_pending_vblank_event *event;
bool abort_completion;
};
最后,我们来看一下Atomic的实现函数drm_mode_atomic_ioctl的代码是如何commit一个state的
int drm_mode_atomic_ioctl(struct drm_device *dev,
void *data, struct drm_file *file_priv)
{
struct drm_mode_atomic *arg = data;
……
struct drm_atomic_state *state;
……
// 分配一个新的state,这就是整个Atomic的中心数据结构
state = drm_atomic_state_alloc(dev);
if (!state)
return -ENOMEM;
……
retry:
……
for (i = 0; i < arg->count_objs; i++) {
……
for (j = 0; j < count_props; j++) {
……
if (copy_from_user(&prop_value,
prop_values_ptr + copied_props,
sizeof(prop_value))) {
drm_mode_object_put(obj);
ret = -EFAULT;
goto out;
}
// 把Atomic中的属性设置到state中去
ret = drm_atomic_set_property(state, file_priv,
obj, prop, prop_value);
if (ret) {
drm_mode_object_put(obj);
goto out;
}
……
}
……
}
……
if (arg->flags & DRM_MODE_ATOMIC_TEST_ONLY) {
ret = drm_atomic_check_only(state);
} else if (arg->flags & DRM_MODE_ATOMIC_NONBLOCK) {
// 根据flage指定,异步的方式Commit 一个State
ret = drm_atomic_nonblocking_commit(state);
} else {
if (drm_debug_enabled(DRM_UT_STATE))
drm_atomic_print_state(state);
// 根据flage指定,同步的方式Commit 一个State
ret = drm_atomic_commit(state);
}
out:
……
return ret;
}
state更新之后,显示器的图像也就刷新了。