上节聊到应用传下来的参数均被存到对应的state。为了使驱动的容错能力比较强,在更新到硬件寄存器之前还需要进行一系列的参数检查,比如要显示图像的大小是否会超过支持分辨率,如果超过了显示的硬件可能会异常;再比如应用需要硬件进行缩放图像但是硬件不支持,强制配置到硬件上面即使不出错也肯定达不到预期的效果,等等场景,一起看下drm驱动中是如何进行处理的。
驱动错误检查的入口为drm_atomic_check_only
int drm_atomic_check_only(struct drm_atomic_state *state)
{
struct drm_device *dev = state->dev;
struct drm_mode_config *config = &dev->mode_config;
struct drm_plane *plane;
struct drm_plane_state *plane_state;
struct drm_crtc *crtc;
struct drm_crtc_state *crtc_state;
for_each_plane_in_state(state, plane, plane_state, i) {
ret = drm_atomic_plane_check(plane, plane_state);
if (ret) {
return ret;
}
}
for_each_crtc_in_state(state, crtc, crtc_state, i) {
ret = drm_atomic_crtc_check(crtc, crtc_state);
if (ret) {
return ret;
}
}
if (config->funcs->atomic_check)
ret = config->funcs->atomic_check(state->dev, state);
if (!state->allow_modeset) {
for_each_crtc_in_state(state, crtc, crtc_state, i) {
if (crtc_state->mode_changed ||
crtc_state->active_changed) {
return -EINVAL;
}
}
}
return ret;
}
这里主要调用了三个函数
drm_atomic_plane_check:用来check plane state的相关参数
drm_atomic_crtc_check:用来check crtc state的相关参数
config->funcs->atomic_check:此函数是可选的,可以不实现,也可以由drm驱动实现,也可以直接使用drm_atomic_helper_check
比如rockchip
先来看drm_atomic_plane_check做了什么事情
static int drm_atomic_plane_check(struct drm_plane *plane,
struct drm_plane_state *state)
{
unsigned int fb_width, fb_height;
int ret;
if (WARN_ON(state->crtc && !state->fb)) {
return -EINVAL;
} else if (WARN_ON(state->fb && !state->crtc)) {
return -EINVAL;
}
if (!state->crtc)
return 0;
/* Check whether this plane is usable on this CRTC */
if (!(plane->possible_crtcs & drm_crtc_mask(state->crtc))) {
return -EINVAL;
}
/* Check whether this plane supports the fb pixel format. */
ret = drm_plane_check_pixel_format(plane, state->fb->format->format);
if (ret) {
struct drm_format_name_buf format_name;
return ret;
}
/* Give drivers some help against integer overflows */
if (state->crtc_w > INT_MAX ||
state->crtc_x > INT_MAX - (int32_t) state->crtc_w ||
state->crtc_h > INT_MAX ||
state->crtc_y > INT_MAX - (int32_t) state->crtc_h) {
return -ERANGE;
}
fb_width = state->fb->width << 16;
fb_height = state->fb->height << 16;
/* Make sure source coordinates are inside the fb. */
if (state->src_w > fb_width ||
state->src_x > fb_width - state->src_w ||
state->src_h > fb_height ||
state->src_y > fb_height - state->src_h) {
state->src_w >> 16, ((state->src_w & 0xffff) * 15625) >> 10,
state->src_h >> 16, ((state->src_h & 0xffff) * 15625) >> 10,
state->src_x >> 16, ((state->src_x & 0xffff) * 15625) >> 10,
state->src_y >> 16, ((state->src_y & 0xffff) * 15625) >> 10);
return -ENOSPC;
}
return 0;
}
drm_plane_check_pixel_format的实现非常简单,遍历所有plane支持的format,找到返回0,找不到返回错误。
int drm_plane_check_pixel_format(const struct drm_plane *plane, u32 format)
{
unsigned int i;
for (i = 0; i < plane->format_count; i++) {
if (format == plane->format_types[i])
return 0;
}
return -EINVAL;
}
plane->format_types是在plane创建的时候指定的,可以详细看下drm_universal_plane_init这里贴出来部分代码
drm_universal_plane_init(... ...const uint32_t *formats, unsigned int format_count,.)
{
... ...
plane->format_types = kmalloc_array(format_count, sizeof(uint32_t),GFP_KERNEL);
memcpy(plane->format_types, formats, format_count * sizeof(uint32_t));
plane->format_count = format_count;
... ...
}
state->fb->width和state->fb->height可以认为是在内存中的图像大小,如果应用想要显示的部分超过显存肯定是不对的,龙哥在最简单的DRM应用程序 (plane-test)_何小龙的博客-CSDN博客的一张图片画的非常清楚,我引用一下
主要检查crtc 状态逻辑是否正常;正如注释里提到的这里只进行通用的状态检查,厂商之间的硬件差异由crtc->atomic_check()来进行处理(稍后会介绍到在哪里调用)
For hw that does not, it should be checked in driver's crtc->atomic_check() vfunc;Add generic modeset state checks once we support those.
因此此函数做的工作比较少,逻辑也比较清楚,不做解释。
static int drm_atomic_crtc_check(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
/* NOTE: we explicitly don't enforce constraints such as primary
* layer covering entire screen, since that is something we want
* to allow (on hw that supports it). For hw that does not, it
* should be checked in driver's crtc->atomic_check() vfunc.
*
* TODO: Add generic modeset state checks once we support those.
*/
if (state->active && !state->enable) {
DRM_DEBUG_ATOMIC("[CRTC:%d:%s] active without enabled\n",
crtc->base.id, crtc->name);
return -EINVAL;
}
if (drm_core_check_feature(crtc->dev, DRIVER_ATOMIC) &&
WARN_ON(state->enable && !state->mode_blob)) {
DRM_DEBUG_ATOMIC("[CRTC:%d:%s] enabled without mode blob\n",
crtc->base.id, crtc->name);
return -EINVAL;
}
if (drm_core_check_feature(crtc->dev, DRIVER_ATOMIC) &&
WARN_ON(!state->enable && state->mode_blob)) {
DRM_DEBUG_ATOMIC("[CRTC:%d:%s] disabled with mode blob\n",
crtc->base.id, crtc->name);
return -EINVAL;
}
if (state->event && !state->active && !crtc->state->active) {
DRM_DEBUG_ATOMIC("[CRTC:%d:%s] requesting event but off\n",
crtc->base.id, crtc->name);
return -EINVAL;
}
return 0;
}
上面两个函数主要是基本的,通用的参数检查,而不同的soc厂商的显示硬件支持的功能是不同的;这些差异化功能的check就需要由各个厂商自己来实现,接着看第三个函数指针的调用
此回调是在驱动初始化的时候配置的,一般使用drm_atomic_helper_check也可以自己实现,但基本思想都一样——调用drm驱动各组件的atomic_check回调;另外还有一些标记位的置位,mode_changed,active_changed等
int drm_atomic_helper_check(struct drm_device *dev,
struct drm_atomic_state *state)
{
int ret;
ret = drm_atomic_helper_check_modeset(dev, state);
if (ret)
return ret;
ret = drm_atomic_helper_check_planes(dev, state);
if (ret)
return ret;
if (state->legacy_cursor_update)
state->async_update = !drm_atomic_helper_async_check(dev, state);
return ret;
}
心心念念由驱动实现的plane->helper_private->atomic_check和crtc->helper_private->atomic_check它终于来了
int drm_atomic_helper_check_modeset(struct drm_device *dev,
struct drm_atomic_state *state)
{
for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) {
bool has_connectors = !!new_crtc_state->connector_mask;
WARN_ON(!drm_modeset_is_locked(&crtc->mutex));
if (!drm_mode_equal(&old_crtc_state->mode, &new_crtc_state->mode)) {
new_crtc_state->mode_changed = true;
}
if (old_crtc_state->enable != new_crtc_state->enable) {
new_crtc_state->mode_changed = true;
new_crtc_state->connectors_changed = true;
}
if (old_crtc_state->active != new_crtc_state->active) {
new_crtc_state->active_changed = true;
}
if (new_crtc_state->enable != has_connectors) {
return -EINVAL;
}
}
for_each_oldnew_connector_in_state(state, connector, old_connector_state, new_connector_state, i) {
const struct drm_connector_helper_funcs *funcs = connector->helper_private;
if (funcs->atomic_check)
ret = funcs->atomic_check(connector, new_connector_state);
if (ret)
return ret;
connectors_mask += BIT(i);
}
}
可能看到这里还是会有疑惑,回调里的atomic_check到底做了什么事情。如果不了解显示硬件不是很容易理解,我们先看下别人家的驱动是怎么实现的,以开源的rockchip的vop_plane_atomic_check为例,代码在rockchip_drm_vop.c
vop_plane_atomic_check的主要工作
static int vop_plane_atomic_check(struct drm_plane *plane,
struct drm_plane_state *state)
{
struct drm_crtc *crtc = state->crtc;
struct drm_crtc_state *crtc_state;
struct drm_framebuffer *fb = state->fb;
struct vop_win *vop_win = to_vop_win(plane);
const struct vop_win_data *win = vop_win->data;
int ret;
struct drm_rect clip;
int min_scale = win->phy->scl ? FRAC_16_16(1, 8) :
DRM_PLANE_HELPER_NO_SCALING;
int max_scale = win->phy->scl ? FRAC_16_16(8, 1) :
DRM_PLANE_HELPER_NO_SCALING;
if (!crtc || !fb)
return 0;
crtc_state = drm_atomic_get_existing_crtc_state(state->state, crtc);
if (WARN_ON(!crtc_state))
return -EINVAL;
clip.x1 = 0;
clip.y1 = 0;
clip.x2 = crtc_state->adjusted_mode.hdisplay;
clip.y2 = crtc_state->adjusted_mode.vdisplay;
ret = drm_plane_helper_check_state(state, &clip,
min_scale, max_scale,
true, true);
if (ret)
return ret;
if (!state->visible)
return 0;
ret = vop_convert_format(fb->format->format);
if (ret < 0)
return ret;
/*
* Src.x1 can be odd when do clip, but yuv plane start point
* need align with 2 pixel.
*/
if (is_yuv_support(fb->format->format) && ((state->src.x1 >> 16) % 2)) {
DRM_ERROR("Invalid Source: Yuv format not support odd xpos\n");
return -EINVAL;
}
return 0;
}
drm_atomic_helper_async_check是异步刷新相关的,我们先不关心
驱动的实现和硬件能力强相关,我们只需要了解大体框架即可。
至此所有的参数都被检查了,没有问题下一步就是要更新到硬件上了。
扯了那么多总结成一张简单的图