序言
最近在研究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函数。
欲知后事如何,且看下回分解。