Linux图形子系统之setCRTC流程
引言
drm通过DRM_IOCTL_MODE_SETCRTC实现最原始的模式设置和送显操作。
1 数据结构
setCrtc的整个流程是围绕的各种state结构,如下图:
注:本图及后述内容没特别说明都是以linux5.4.162的源码版本为准。
2 关键流程
2.1 drm_mode_setcrtc
drm_mode_setcrtc是DRM_IOCTL_MODE_SETCRTC的处理函数。其源码如下:
int drm_mode_setcrtc(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
# 略略略... ...
mutex_lock(&crtc->dev->mode_config.mutex);
//构建struct drm_mode_set set
# 略略略... ...
set.crtc = crtc;
set.x = crtc_req->x;
set.y = crtc_req->y;
set.mode = mode;
set.connectors = connector_set;
set.num_connectors = crtc_req->count_connectors;
set.fb = fb;
//执行set_config操作
if (drm_drv_uses_atomic_modeset(dev))
ret = crtc->funcs->set_config(&set, &ctx);
else
ret = __drm_mode_set_config_internal(&set, &ctx);
out:
# 略略略... ...
mutex_unlock(&crtc->dev->mode_config.mutex);
# 略略略... ...
}
整个函数流程概况:
- 首先,通过参数构建一个drm_mode_set对象;
- 执行set_config操作:具体来说,当dev支持原子设置就直接调用drm_crtc_funcs的set_config;否则进入__drm_mode_set_config_internal函数,该函数在调用drm_crtc_funcs的set_config回调前,会让每个crtc的primary plane的old_fb字段备份fb,在调用结束后,再恢复其fb,同时将当前crtc的primary plane的fb指向DRM_IOCTL_MODE_SETCRTC指定的fb。
- 最后就是执行资源清理工作。
2.2 drm_atomic_helper_set_config
drm_atomic_helper_set_config函数是drm_crtc_funcs的set_config回调默认实现函数。关键源码如下:
int drm_atomic_helper_set_config(struct drm_mode_set *set,
struct drm_modeset_acquire_ctx *ctx)
{
struct drm_atomic_state *state;
struct drm_crtc *crtc = set->crtc;
int ret = 0;
state = drm_atomic_state_alloc(crtc->dev);
if (!state)
return -ENOMEM;
state->acquire_ctx = ctx;
ret = __drm_atomic_helper_set_config(set, state);
if (ret != 0)
goto fail;
ret = handle_conflicting_encoders(state, true);
if (ret)
goto fail;
ret = drm_atomic_commit(state);
fail:
drm_atomic_state_put(state);
return ret;
}
EXPORT_SYMBOL(drm_atomic_helper_set_config);
整个函数流程概况:
- 首先,通过drm_atomic_state_alloc分配drm_atomic_state。其内部实现为:若驱动的drm_mode_config_funcs函数集合实现了atomic_state_alloc回调,则通过其分配;否则分配内存后,按默认函数drm_atomic_state_init对它初始化。
- 然后, 通过__drm_atomic_helper_set_config函数往drm_atomic_state里面添加drm_crtc_state、drm_plane_state和drm_connector_state状态。其中通过通过各个组件的drm_{crtc/plane/connector}_funcs的atomic_duplicate_state创建状态机。
- 然后,通过handle_conflicting_encoders为connector选择一个encoder。其中会调用drm_connector_helper_funcs的atomic_best_encoder/best_encoder(按顺序选第一个非空的函数)回调,如果这些回调都不存在,则调用pick_single_encoder_for_connector选择connector的第一个encoder。
- 最后,通过drm_atomic_commit提交drm_atomic_state。在正式提交前,会检查是否支持原子特性,最终会调用drm_mode_config_funcs的atomic_check(前提是实现了该回调);在正式提交中,执行drm_mode_config_funcs的atomic_commit回调,其第三个参数nonblock为false(也就是采用阻塞方式调用)。
2.3 drm_atomic_helper_commit
drm_atomic_helper_commit是drm_mode_config_funcs的atomic_commit回调的默认实现函数。关键源码如下:
int drm_atomic_helper_commit(struct drm_device *dev,
struct drm_atomic_state *state,
bool nonblock) //noblock = false
{
# 略去代码块if (state->async_update)... ...
ret = drm_atomic_helper_setup_commit(state, nonblock);
if (ret)
return ret;
# 略去INIT_WORK代码... ...
ret = drm_atomic_helper_prepare_planes(dev, state);
if (ret)
return ret;
if (!nonblock) {
ret = drm_atomic_helper_wait_for_fences(dev, state, true);
if (ret)
goto err;
}
ret = drm_atomic_helper_swap_state(state, true);
if (ret)
goto err;
drm_atomic_state_get(state);
if (nonblock)
# 略去queue_work代码... ...
else
commit_tail(state);
return 0;
err:
drm_atomic_helper_cleanup_planes(dev, state);
return ret;
}
整个函数流程概况:
- 首先,调用drm_atomic_helper_setup_commit为state构建drm_crtc_commit对象;
- 然后,调用drm_atomic_helper_prepare_planes准备plane资源,在该函数中,根据条件先调用drm_connector_helper_funcs的prepare_writeback_job回调,再调用drm_plane_helper_funcs的prepare_fb回调;
- 然后, 调用drm_atomic_helper_wait_for_fences去等待new_plane_state->fence,该fence是在drm_plane_helper_funcs的prepare_fb回调中取出的fence(通常代表渲染动作是否完成);
- 然后,调用drm_atomic_helper_swap_state函数将当前的state设置到各自的组件对象的state字段;
- 最后,调用commit_tail做最后正式的提交动作,这也是真正与硬件提交相关的代码;
2.4 commit_tail
在该函数中,完成最后的提交动作。关键源码如下:
static void commit_tail(struct drm_atomic_state *old_state)
{
# 略略略... ...
drm_atomic_helper_wait_for_fences(dev, old_state, false);
drm_atomic_helper_wait_for_dependencies(old_state);
# 略略略... ...
if (funcs && funcs->atomic_commit_tail)
funcs->atomic_commit_tail(old_state);
else
drm_atomic_helper_commit_tail(old_state);
# 略略略... ...
drm_atomic_helper_commit_cleanup_done(old_state);
drm_atomic_state_put(old_state);
}
整个函数流程概况:
- 首先,调用drm_atomic_helper_wait_for_fences函数,该操作在上个函数drm_atomic_helper_commit已经调用过,所以内部直接返回。之所以要重复调用是考虑到非阻塞的调用的情况;
- 然后,调用drm_atomic_helper_wait_for_dependencies等待各个组件(也就是crtc、plane、connector)的drm_crtc_commit的hw_done和flip_done完成动作;
- 然后,调用drm_mode_config_helper_funcs的atomic_commit_tail回调,当然若驱动没有实现,则进入默认函数drm_atomic_helper_commit_tail;
- 然后,调用drm_atomic_helper_commit_cleanup_done函数做清理动作。会触发drm_crtc_commit的cleanup_done动作;
- 最后,释放state的引用计数。因为在上个函数调用前 先施加了一次引用计数。
2.4 drm_atomic_helper_commit_tail
drm_atomic_helper_commit_tail是drm_mode_config_helper_funcs的atomic_commit_tail回调的默认实现函数。关键源码如下:
void drm_atomic_helper_commit_tail(struct drm_atomic_state *old_state)
{
struct drm_device *dev = old_state->dev;
drm_atomic_helper_commit_modeset_disables(dev, old_state);
drm_atomic_helper_commit_planes(dev, old_state, 0);
drm_atomic_helper_commit_modeset_enables(dev, old_state);
drm_atomic_helper_fake_vblank(old_state);
drm_atomic_helper_commit_hw_done(old_state);
drm_atomic_helper_wait_for_vblanks(dev, old_state);
drm_atomic_helper_cleanup_planes(dev, old_state);
}
整个函数流程概况:
- 首先,调用drm_atomic_helper_commit_modeset_disables对之前老的state对应的组件做清理和复位;
- 然后,调用drm_atomic_helper_commit_planes对显示做正式提交;
- 然后,调用drm_atomic_helper_commit_modeset_enables使能硬件,使之显示提交的fb;
- 然后,调用drm_atomic_helper_fake_vblank。在该函数中,若不支持vblank事件,就会在此刻通过drm_crtc_send_vblank_event发送vblank,这里面会触发drm_crtc_commit的flip_done动作;
- 然后,调用drm_atomic_helper_commit_hw_done函数,触发drm_crtc_commit的hw_done动作;
- 然后,调用drm_atomic_helper_wait_for_vblanks去等待硬件的vblank事件;
- 最后,调用drm_atomic_helper_cleanup_planes做清理工作,调用驱动实现drm_plane_helper_funcs的cleanup_fb回调。
2.4.1 drm_atomic_helper_commit_modeset_disables
drm_atomic_helper_commit_modeset_disables函数用于对上一次的状态设置做disable和复位,同时对当前的state使能。源码如下:
void drm_atomic_helper_commit_modeset_disables(struct drm_device *dev,
struct drm_atomic_state *old_state)
{
disable_outputs(dev, old_state);
drm_atomic_helper_update_legacy_modeset_state(dev, old_state);
crtc_set_mode(dev, old_state);
}
整个函数流程概况:
- 首先,调用disable_outputs去关闭上一次的state设置。先调用drm_encoder_helper_funcs的atomic_disable/prepare/disable/dpms(按顺序选第一个非空的函数)回调;再调用drm_crtc_helper_funcs的prepare/atomic_disable/disable/dpms(按顺序选第一个非空的函数)回调;
- 然后,调用drm_atomic_helper_update_legacy_modeset_state函数,设置connector的encoder等操作;
- 最后,调用crtc_set_mode做模式设置。先调用drm_crtc_helper_funcs的mode_set_nofb回调(前提是驱动实现了该回调);再调用drm_encoder_helper_funcs的atomic_mode_set/mode_set(按顺序选第一个非空的函数)回调;
2.4.2 drm_atomic_helper_commit_planes
该函数是硬件提交的核心函数,其源码如下:
void drm_atomic_helper_commit_planes(struct drm_device *dev,
struct drm_atomic_state *old_state,
uint32_t flags)
{
# 略略略... ...
for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) {
const struct drm_crtc_helper_funcs *funcs;
funcs = crtc->helper_private;
if (!funcs || !funcs->atomic_begin)
continue;
if (active_only && !new_crtc_state->active)
continue;
funcs->atomic_begin(crtc, old_crtc_state);
}
for_each_oldnew_plane_in_state(old_state, plane, old_plane_state, new_plane_state, i) {
const struct drm_plane_helper_funcs *funcs;
bool disabling;
funcs = plane->helper_private;
if (!funcs)
continue;
disabling = drm_atomic_plane_disabling(old_plane_state,
new_plane_state);
# 略去代码块if (active_only) ... ...
if (disabling && funcs->atomic_disable) {
# 略略略... ...
funcs->atomic_disable(plane, old_plane_state);
} else if (new_plane_state->crtc || disabling) {
funcs->atomic_update(plane, old_plane_state);
}
}
for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) {
const struct drm_crtc_helper_funcs *funcs;
funcs = crtc->helper_private;
if (!funcs || !funcs->atomic_flush)
continue;
if (active_only && !new_crtc_state->active)
continue;
funcs->atomic_flush(crtc, old_crtc_state);
}
}
整个函数流程概况:
- 首先,对每个相关的drm_crtc_state,调用drm_crtc_helper_funcs的atomic_begin回调(前提是驱动实现了该回调);
- 然后,对每个相关的drm_plane_state,调用drm_plane_helper_funcs的atomic_disable,或者atomic_update;
- 最后,对每个相关的drm_crtc_state,调用drm_crtc_helper_funcs的atomic_flush回调。
2.4.3 drm_atomic_helper_commit_modeset_enables
该函数做硬件的真正使能动作。其核心源码如下:
void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
struct drm_atomic_state *old_state)
{
# 略略略... ...
for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) {
const struct drm_crtc_helper_funcs *funcs;
/* Need to filter out CRTCs where only planes change. */
if (!drm_atomic_crtc_needs_modeset(new_crtc_state))
continue;
if (!new_crtc_state->active)
continue;
funcs = crtc->helper_private;
if (new_crtc_state->enable) {
DRM_DEBUG_ATOMIC("enabling [CRTC:%d:%s]\n",
crtc->base.id, crtc->name);
if (funcs->atomic_enable)
funcs->atomic_enable(crtc, old_crtc_state);
else if (funcs->commit)
funcs->commit(crtc);
}
}
for_each_new_connector_in_state(old_state, connector, new_conn_state, i) {
const struct drm_encoder_helper_funcs *funcs;
struct drm_encoder *encoder;
if (!new_conn_state->best_encoder)
continue;
if (!new_conn_state->crtc->state->active ||
!drm_atomic_crtc_needs_modeset(new_conn_state->crtc->state))
continue;
# 略略略... ...
if (funcs) {
if (funcs->atomic_enable)
funcs->atomic_enable(encoder, old_state);
else if (funcs->enable)
funcs->enable(encoder);
else if (funcs->commit)
funcs->commit(encoder);
}
# 略略略... ...
}
# 略略略... ...
}
整个函数流程概况:
- 首先,对每个相关的drm_crtc_state,调用drm_crtc_helper_funcs的atomic_enable/commit(按顺序选第一个非空的函数)回调;
- 然后,对每个相关connector的drm_encoder,调用drm_encoder_helper_funcs的atomic_enable/enable/commit(按顺序选第一个非空的函数)回调;
3 状态变化
以drm_crtc_state为例(其他的类似),整个过程的状态变化如下:
- 首先,在drm_atomic_helper_swap_state调用后,crtc obj指向了新的crtc_state 1;__crtc_state 1的state字段指向了之前的crtc_state 0;同时crtc_state 0的state指向了state 1、而crtc_state 1的state字段不再指向state 1;
- 最后,在drm_atomic_helper_commit_hw_done调用后,crtc_state 0不再指向commit 0,而是指向了新的commit 1