最近在研究libdrm、DRM以及KWin,发现要真正理解Linux图形栈从上到下的机制,最好的、最易于理解的方法是将KWin、libdrm和DRM由上到下的调用过程暨代码统一进行研究,这样才能更好地理清其中的关系,把握总体全貌,因此笔者决定开始做这件事。这无疑是一个庞大的工程(目前来看前无古人),笔者不敢奢望能全部完成,但希望能够尽量走得远一些。闲言少叙,开始漫长的旅程……
这段旅程从哪里开始?笔者选择从drmModeAddFBxxx函数族开始(包括drmModeAddFB、drmModeAddFB2和drmModeAddFB2WithModifiers函数)。一方面,笔者前段时间刚刚对于这部分做过研究(“DRM全解析 —— ADD_FB”和“DRM全解析 —— ADD_FB2”系列文章);另一方面,framebuffer也是DRM的“最左侧”内容,如下图所示:

上层KWin的相关代码在KWin源码更目录下的src/backends/drm/drm_buffer.cpp中,代码如下:
- std::shared_ptr
DrmFramebuffer::createFramebuffer(const std::shared_ptr &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
(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);
drmModeAddFB、drmModeAddFB2和drmModeAddFB2WithModifiers三个函数都在libdrm源码根目录的xf86drmMode.c文件中,代码分别如下:
- 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;
- }
- 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);
- }
- 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中,代码分别如下:
- 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;
- }
- 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函数。
欲知后事如何,且看下回分解。