上节聊到应用传下来的参数均被存到对应的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

先来看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;
}
  1. 对指针进行判断,如果传下来的指针不存在,后面的一切都没有意义
  2. check plane是否支持当前要显示的format

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;
    ... ...
}
  1. check要显示的区域是否越界
  2. check显示区域是否超过内存大小

 state->fb->width和state->fb->height可以认为是在内存中的图像大小,如果应用想要显示的部分超过显存肯定是不对的,龙哥在最简单的DRM应用程序 (plane-test)_何小龙的博客-CSDN博客的一张图片画的非常清楚,我引用一下

drm_atomic_crtc_check

        主要检查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就需要由各个厂商自己来实现,接着看第三个函数指针的调用

config->funcs->atomic_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的主要工作

  1. 检查缩放参数配置是不是符合要求
  2. 将drm_format转换成rockchip自己的format看自己是否支持
  3. yuv格式是否2pixel对齐(硬件上的限制?)
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是异步刷新相关的,我们先不关心

驱动的实现和硬件能力强相关,我们只需要了解大体框架即可。

至此所有的参数都被检查了,没有问题下一步就是要更新到硬件上了。

扯了那么多总结成一张简单的图

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐