KWin、libdrm、DRM从上到下全过程 —— drmModeAddFBxxx(1)

本文从KWin出发,深入libdrm和DRM,探讨drmModeAddFB家族函数的工作原理,包括drmModeAddFB、drmModeAddFB2和drmModeAddFB2WithModifiers。通过分析KWin源码和libdrm接口,揭示了这些函数如何在Linux图形栈中处理framebuffer,并展示了它们在内核层的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

序言

最近在研究libdrm、DRM以及KWin,发现要真正理解Linux图形栈从上到下的机制,最好的、最易于理解的方法是将KWin、libdrm和DRM由上到下的调用过程暨代码统一进行研究,这样才能更好地理清其中的关系,把握总体全貌,因此笔者决定开始做这件事。这无疑是一个庞大的工程(目前来看前无古人),笔者不敢奢望能全部完成,但希望能够尽量走得远一些。闲言少叙,开始漫长的旅程……

一切从framebuffer开始

这段旅程从哪里开始?笔者选择从drmModeAddFBxxx函数族开始(包括drmModeAddFB、drmModeAddFB2和drmModeAddFB2WithModifiers函数)。一方面,笔者前段时间刚刚对于这部分做过研究(“DRM全解析 —— ADD_FB”和“DRM全解析 —— ADD_FB2”系列文章);另一方面,framebuffer也是DRM的“最左侧”内容,如下图所示:

上层 —— KWin代码

上层KWin的相关代码在KWin源码更目录下的src/backends/drm/drm_buffer.cpp中,代码如下:

std::shared_ptr<DrmFramebuffer> DrmFramebuffer::createFramebuffer(const std::shared_ptr<DrmGpuBuffer> &buffer)
{
    const auto size = buffer->size();
    const auto handles = buffer->handles();
    const auto strides = buffer->strides();
    const auto offsets = buffer->offsets();

    uint32_t framebufferId = 0;
    int ret;
    if (buffer->gpu()->addFB2ModifiersSupported() && buffer->modifier() != DRM_FORMAT_MOD_INVALID) {
        uint64_t modifier[4];
        for (uint32_t i = 0; i < 4; i++) {
            modifier[i] = i < buffer->planeCount() ? buffer->modifier() : 0;
        }
        ret = drmModeAddFB2WithModifiers(buffer->gpu()->fd(), size.width(), size.height(), buffer->format(), handles.data(), strides.data(), offsets.data(), modifier, &framebufferId, DRM_MODE_FB_MODIFIERS);
    } else {
        ret = drmModeAddFB2(buffer->gpu()->fd(), size.width(), size.height(), buffer->format(), handles.data(), strides.data(), offsets.data(), &framebufferId, 0);
        if (ret == EOPNOTSUPP && handles.size() == 1) {
            ret = drmModeAddFB(buffer->gpu()->fd(), size.width(), size.height(), 24, 32, strides[0], handles[0], &framebufferId);
        }
    }
    if (ret == 0) {
        return std::make_shared<DrmFramebuffer>(buffer, framebufferId);
    } else {
        return nullptr;
    }
}

在这里,我们的关注重点不放在KWin代码本身的机制,而是聚焦于与libdrm相关的接口函数,即上边提到的3个函数:drmModeAddFB、drmModeAddFB2和drmModeAddFB2WithModifiers。重点关注一下三个函数的接口区别:

drmModeAddFB(buffer->gpu()->fd(), size.width(), size.height(), 24, 32, strides[0], handles[0], &framebufferId);

drmModeAddFB2(buffer->gpu()->fd(), size.width(), size.height(), buffer->format(), handles.data(), strides.data(), offsets.data(), &framebufferId, 0);

drmModeAddFB2WithModifiers(buffer->gpu()->fd(), size.width(), size.height(), buffer->format(), handles.data(), strides.data(), offsets.data(), modifier, &framebufferId, DRM_MODE_FB_MODIFIERS);

中间层 —— libdrm代码

drmModeAddFB、drmModeAddFB2和drmModeAddFB2WithModifiers三个函数都在libdrm源码根目录的xf86drmMode.c文件中,代码分别如下:

  • drmModeAddFB
drm_public drmModeFBPtr drmModeGetFB(int fd, uint32_t buf)
{
	struct drm_mode_fb_cmd info;
	drmModeFBPtr r;

	memclear(info);
	info.fb_id = buf;

	if (drmIoctl(fd, DRM_IOCTL_MODE_GETFB, &info))
		return NULL;

	if (!(r = drmMalloc(sizeof(*r))))
		return NULL;

	r->fb_id = info.fb_id;
	r->width = info.width;
	r->height = info.height;
	r->pitch = info.pitch;
	r->bpp = info.bpp;
	r->handle = info.handle;
	r->depth = info.depth;

	return r;
}
  • drmModeAddFB2
drm_public int drmModeAddFB2(int fd, uint32_t width, uint32_t height,
		uint32_t pixel_format, const uint32_t bo_handles[4],
		const uint32_t pitches[4], const uint32_t offsets[4],
		uint32_t *buf_id, uint32_t flags)
{
	return drmModeAddFB2WithModifiers(fd, width, height,
					  pixel_format, bo_handles,
					  pitches, offsets, NULL,
					  buf_id, flags);
}
  • drmModeAddFB2WithModifiers
drm_public int drmModeAddFB2WithModifiers(int fd, uint32_t width,
		uint32_t height, uint32_t pixel_format, const uint32_t bo_handles[4],
		const uint32_t pitches[4], const uint32_t offsets[4],
		const uint64_t modifier[4], uint32_t *buf_id, uint32_t flags)
{
	struct drm_mode_fb_cmd2 f;
	int ret;

	memclear(f);
	f.width  = width;
	f.height = height;
	f.pixel_format = pixel_format;
	f.flags = flags;
	memcpy(f.handles, bo_handles, 4 * sizeof(bo_handles[0]));
	memcpy(f.pitches, pitches, 4 * sizeof(pitches[0]));
	memcpy(f.offsets, offsets, 4 * sizeof(offsets[0]));
	if (modifier)
		memcpy(f.modifier, modifier, 4 * sizeof(modifier[0]));

	if ((ret = DRM_IOCTL(fd, DRM_IOCTL_MODE_ADDFB2, &f)))
		return ret;

	*buf_id = f.fb_id;
	return 0;
}

从代码上就能看出来,drmModeAddFB2()和drmModeAddFB2WithModifiers()走的是一路,走的是DRM_IOCTL_MODE_ADDFB2;而drmModeAddFB是单一路,走的是DRM_IOCTL_MODE_ADDFB(其实这也就是笔者之前有两个系列文章“DRM全解析 —— ADD_FB”和“DRM全解析 —— ADD_FB2”的原因)。

底层 —— DRM代码

DRM_IOCTL_MODE_ADDFB和DRM_IOCTL_MODE_ADDFB2分别对应到底层内核DRM的代码。在Linux内核源码根目录下的drivers/gpu/drm/drm_ioctl.c中,代码如下:

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_mode_addfb_ioctl和drm_mode_addfb2_ioctl函数都在Linux内核源码根目录下的drivers/gpu/drm/drm_framebuffer.c中,代码分别如下:

  • drm_mode_addfb_ioctl
int drm_mode_addfb_ioctl(struct drm_device *dev,
			 void *data, struct drm_file *file_priv)
{
	return drm_mode_addfb(dev, data, file_priv);
}
/**
 * drm_mode_addfb - add an FB to the graphics configuration
 * @dev: drm device for the ioctl
 * @or: pointer to request structure
 * @file_priv: drm file
 *
 * Add a new FB to the specified CRTC, given a user request. This is the
 * original addfb ioctl which only supported RGB formats.
 *
 * Called by the user via ioctl, or by an in-kernel client.
 *
 * Returns:
 * Zero on success, negative errno on failure.
 */
int drm_mode_addfb(struct drm_device *dev, struct drm_mode_fb_cmd *or,
		   struct drm_file *file_priv)
{
	struct drm_mode_fb_cmd2 r = {};
	int ret;

	if (!drm_core_check_feature(dev, DRIVER_MODESET))
		return -EOPNOTSUPP;

	r.pixel_format = drm_driver_legacy_fb_format(dev, or->bpp, or->depth);
	if (r.pixel_format == DRM_FORMAT_INVALID) {
		drm_dbg_kms(dev, "bad {bpp:%d, depth:%d}\n", or->bpp, or->depth);
		return -EINVAL;
	}

	/* convert to new format and call new ioctl */
	r.fb_id = or->fb_id;
	r.width = or->width;
	r.height = or->height;
	r.pitches[0] = or->pitch;
	r.handles[0] = or->handle;

	ret = drm_mode_addfb2(dev, &r, file_priv);
	if (ret)
		return ret;

	or->fb_id = r.fb_id;

	return 0;
}
  • drm_mode_addfb2_ioctl
int drm_mode_addfb2_ioctl(struct drm_device *dev,
			  void *data, struct drm_file *file_priv)
{
#ifdef __BIG_ENDIAN
	if (!dev->mode_config.quirk_addfb_prefer_host_byte_order) {
		/*
		 * Drivers must set the
		 * quirk_addfb_prefer_host_byte_order quirk to make
		 * the drm_mode_addfb() compat code work correctly on
		 * bigendian machines.
		 *
		 * If they don't they interpret pixel_format values
		 * incorrectly for bug compatibility, which in turn
		 * implies the ADDFB2 ioctl does not work correctly
		 * then.  So block it to make userspace fallback to
		 * ADDFB.
		 */
		drm_dbg_kms(dev, "addfb2 broken on bigendian");
		return -EOPNOTSUPP;
	}
#endif
	return drm_mode_addfb2(dev, data, file_priv);
}
/**
 * drm_mode_addfb2 - add an FB to the graphics configuration
 * @dev: drm device for the ioctl
 * @data: data pointer for the ioctl
 * @file_priv: drm file for the ioctl call
 *
 * Add a new FB to the specified CRTC, given a user request with format. This is
 * the 2nd version of the addfb ioctl, which supports multi-planar framebuffers
 * and uses fourcc codes as pixel format specifiers.
 *
 * Called by the user via ioctl.
 *
 * Returns:
 * Zero on success, negative errno on failure.
 */
int drm_mode_addfb2(struct drm_device *dev,
		    void *data, struct drm_file *file_priv)
{
	struct drm_mode_fb_cmd2 *r = data;
	struct drm_framebuffer *fb;

	if (!drm_core_check_feature(dev, DRIVER_MODESET))
		return -EOPNOTSUPP;

	fb = drm_internal_framebuffer_create(dev, r, file_priv);
	if (IS_ERR(fb))
		return PTR_ERR(fb);

	drm_dbg_kms(dev, "[FB:%d]\n", fb->base.id);
	r->fb_id = fb->base.id;

	/* Transfer ownership to the filp for reaping on close */
	mutex_lock(&file_priv->fbs_lock);
	list_add(&fb->filp_head, &file_priv->fbs);
	mutex_unlock(&file_priv->fbs_lock);

	return 0;
}

到这里,可以说是“三分归一统”了。在应用层,drmModeAddFB2()和drmModeAddFB2WithModifiers()并在一起,统一使用DRM_IOCTL_MODE_ADDFB2;而在内核层,drmModeAddFB()也最终合并在了一起,统一调用了drm_mode_addfb2函数。

欲知后事如何,且看下回分解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝天居士

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值