接前一篇文章:DRM全解析 —— ADD_FB(4)
本文参考以下博文:
特此致谢!
上一回讲解了drm_mode_addfb函数中的第二步暨第二个函数drm_driver_legacy_fb_format。本回继续对于drm_mode_addfb函数往下进行解析。为了便于理解,再次贴出其代码,在drivers/gpu/drm/drm_framebuffer.c中,如下:
/**
* 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。
先来看一下这一句:
struct drm_mode_fb_cmd2 r = {};
struct drm_mode_fb_cmd2的定义在include/uapi/drm/drm_mode.h中,代码如下:
/**
* struct drm_mode_fb_cmd2 - Frame-buffer metadata.
*
* This struct holds frame-buffer metadata. There are two ways to use it:
*
* - User-space can fill this struct and perform a &DRM_IOCTL_MODE_ADDFB2
* ioctl to register a new frame-buffer. The new frame-buffer object ID will
* be set by the kernel in @fb_id.
* - User-space can set @fb_id and perform a &DRM_IOCTL_MODE_GETFB2 ioctl to
* fetch metadata about an existing frame-buffer.
*
* In case of planar formats, this struct allows up to 4 buffer objects with
* offsets and pitches per plane. The pitch and offset order are dictated by
* the format FourCC as defined by ``drm_fourcc.h``, e.g. NV12 is described as:
*
* YUV 4:2:0 image with a plane of 8-bit Y samples followed by an
* interleaved U/V plane containing 8-bit 2x2 subsampled colour difference
* samples.
*
* So it would consist of a Y plane at ``offsets[0]`` and a UV plane at
* ``offsets[1]``.
*
* To accommodate tiled, compressed, etc formats, a modifier can be specified.
* For more information see the "Format Modifiers" section. Note that even
* though it looks like we have a modifier per-plane, we in fact do not. The
* modifier for each plane must be identical. Thus all combinations of
* different data layouts for multi-plane formats must be enumerated as
* separate modifiers.
*
* All of the entries in @handles, @pitches, @offsets and @modifier must be
* zero when unused. Warning, for @offsets and @modifier zero can't be used to
* figure out whether the entry is used or not since it's a valid value (a zero
* offset is common, and a zero modifier is &DRM_FORMAT_MOD_LINEAR).
*/
struct drm_mode_fb_cmd2 {
/** @fb_id: Object ID of the frame-buffer. */
__u32 fb_id;
/** @width: Width of the frame-buffer. */
__u32 width;
/** @height: Height of the frame-buffer. */
__u32 height;
/**
* @pixel_format: FourCC format code, see ``DRM_FORMAT_*`` constants in
* ``drm_fourcc.h``.
*/
__u32 pixel_format;
/**
* @flags: Frame-buffer flags (see &DRM_MODE_FB_INTERLACED and
* &DRM_MODE_FB_MODIFIERS).
*/
__u32 flags;
/**
* @handles: GEM buffer handle, one per plane. Set to 0 if the plane is
* unused. The same handle can be used for multiple planes.
*/
__u32 handles[4];
/** @pitches: Pitch (aka. stride) in bytes, one per plane. */
__u32 pitches[4];
/** @offsets: Offset into the buffer in bytes, one per plane. */
__u32 offsets[4];
/**
* @modifier: Format modifier, one per plane. See ``DRM_FORMAT_MOD_*``
* constants in ``drm_fourcc.h``. All planes must use the same
* modifier. Ignored unless &DRM_MODE_FB_MODIFIERS is set in @flags.
*/
__u64 modifier[4];
};
根据该结构的注释,drm_mode_fb_cmd2结构中包含framebuffer的元数据,可以理解为framebuffer的属性数据。
drm_mode_addfb2函数也是在drivers/gpu/drm/drm_framebuffer.c中,代码如下:
/**
* 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;
}
drm_mode_addfb2函数中也是一上来先调用drm_core_check_feature函数检查是否支持DRIVER_MODESET这一feature,这一部分参见前文DRM全解析 —— ADD_FB(3)。有的读者可能会问,之前不是调用过这个函数检查过了嘛,怎么又调用一次?这不是冗余多余吗?实际上在这里调用drm_core_check_feature函数进行检查是因为drm_mode_addfb2函数可能会直接被单独调用,而并不是在drm_mode_addfb函数中调用,这就有必要进行检查了。
接下来就来到了关键函数:drm_internal_framebuffer_create。该函数同样在drivers/gpu/drm/drm_framebuffer.c中(实际上就在drm_mode_addfb2函数的上边),代码如下:
struct drm_framebuffer *
drm_internal_framebuffer_create(struct drm_device *dev,
const struct drm_mode_fb_cmd2 *r,
struct drm_file *file_priv)
{
struct drm_mode_config *config = &dev->mode_config;
struct drm_framebuffer *fb;
int ret;
if (r->flags & ~(DRM_MODE_FB_INTERLACED | DRM_MODE_FB_MODIFIERS)) {
drm_dbg_kms(dev, "bad framebuffer flags 0x%08x\n", r->flags);
return ERR_PTR(-EINVAL);
}
if ((config->min_width > r->width) || (r->width > config->max_width)) {
drm_dbg_kms(dev, "bad framebuffer width %d, should be >= %d && <= %d\n",
r->width, config->min_width, config->max_width);
return ERR_PTR(-EINVAL);
}
if ((config->min_height > r->height) || (r->height > config->max_height)) {
drm_dbg_kms(dev, "bad framebuffer height %d, should be >= %d && <= %d\n",
r->height, config->min_height, config->max_height);
return ERR_PTR(-EINVAL);
}
if (r->flags & DRM_MODE_FB_MODIFIERS &&
dev->mode_config.fb_modifiers_not_supported) {
drm_dbg_kms(dev, "driver does not support fb modifiers\n");
return ERR_PTR(-EINVAL);
}
ret = framebuffer_check(dev, r);
if (ret)
return ERR_PTR(ret);
fb = dev->mode_config.funcs->fb_create(dev, file_priv, r);
if (IS_ERR(fb)) {
drm_dbg_kms(dev, "could not create framebuffer\n");
return fb;
}
return fb;
}
EXPORT_SYMBOL_FOR_TESTS_ONLY(drm_internal_framebuffer_create);
drm_internal_framebuffer_create函数首先检查各个传入参数是否合法,注意framebuffer_check函数也在其中。
然后调用dev->mode_config.funcs->fb_create(dev, file_priv, r)。fb_create又是一个函数指针,也可以说是一个回调函数,其是在drm驱动注册时初始化的。仍以Intel显卡和AMD显卡为例。
- Intel i915
Intel i915显卡对应的mode_config.funcs初始化代码也在drivers/gpu/drm/i915/i915_driver.c中,如下:
static const struct drm_mode_config_funcs intel_mode_funcs = {
.fb_create = intel_user_framebuffer_create,
.get_format_info = intel_fb_get_format_info,
.output_poll_changed = intel_fbdev_output_poll_changed,
.mode_valid = intel_mode_valid,
.atomic_check = intel_atomic_check,
.atomic_commit = intel_atomic_commit,
.atomic_state_alloc = intel_atomic_state_alloc,
.atomic_state_clear = intel_atomic_state_clear,
.atomic_state_free = intel_atomic_state_free,
};
intel_user_framebuffer_create函数在drivers/gpu/drm/i915/display/intel_fb.c中,代码如下:
struct drm_framebuffer *
intel_user_framebuffer_create(struct drm_device *dev,
struct drm_file *filp,
const struct drm_mode_fb_cmd2 *user_mode_cmd)
{
struct drm_framebuffer *fb;
struct drm_i915_gem_object *obj;
struct drm_mode_fb_cmd2 mode_cmd = *user_mode_cmd;
struct drm_i915_private *i915;
obj = i915_gem_object_lookup(filp, mode_cmd.handles[0]);
if (!obj)
return ERR_PTR(-ENOENT);
/* object is backed with LMEM for discrete */
i915 = to_i915(obj->base.dev);
if (HAS_LMEM(i915) && !i915_gem_object_can_migrate(obj, INTEL_REGION_LMEM_0)) {
/* object is "remote", not in local memory */
i915_gem_object_put(obj);
return ERR_PTR(-EREMOTE);
}
fb = intel_framebuffer_create(obj, &mode_cmd);
i915_gem_object_put(obj);
return fb;
}
- AMD Raedon
AMD Raedon显卡对应的mode_config.funcs初始化代码在drivers/gpu/drm/radeon/radeon_display.c中,如下:
static const struct drm_mode_config_funcs radeon_mode_funcs = {
.fb_create = radeon_user_framebuffer_create,
.output_poll_changed = drm_fb_helper_output_poll_changed,
};
radeon_user_framebuffer_create函数也在drivers/gpu/drm/radeon/radeon_display.c中,代码如下:
static struct drm_framebuffer *
radeon_user_framebuffer_create(struct drm_device *dev,
struct drm_file *file_priv,
const struct drm_mode_fb_cmd2 *mode_cmd)
{
struct drm_gem_object *obj;
struct drm_framebuffer *fb;
int ret;
obj = drm_gem_object_lookup(file_priv, mode_cmd->handles[0]);
if (obj == NULL) {
dev_err(dev->dev, "No GEM object associated to handle 0x%08X, "
"can't create framebuffer\n", mode_cmd->handles[0]);
return ERR_PTR(-ENOENT);
}
/* Handle is imported dma-buf, so cannot be migrated to VRAM for scanout */
if (obj->import_attach) {
DRM_DEBUG_KMS("Cannot create framebuffer from imported dma_buf\n");
drm_gem_object_put(obj);
return ERR_PTR(-EINVAL);
}
fb = kzalloc(sizeof(*fb), GFP_KERNEL);
if (fb == NULL) {
drm_gem_object_put(obj);
return ERR_PTR(-ENOMEM);
}
ret = radeon_framebuffer_init(dev, fb, mode_cmd, obj);
if (ret) {
kfree(fb);
drm_gem_object_put(obj);
return ERR_PTR(ret);
}
return fb;
}
- AMD AMDGPU
AMD AMDGPU显卡对应的mode_config.funcs初始化代码在drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c中,如下:
static const struct drm_mode_config_funcs amdgpu_dm_mode_funcs = {
.fb_create = amdgpu_display_user_framebuffer_create,
.get_format_info = amd_get_format_info,
.output_poll_changed = drm_fb_helper_output_poll_changed,
.atomic_check = amdgpu_dm_atomic_check,
.atomic_commit = drm_atomic_helper_commit,
};
amdgpu_display_user_framebuffer_create函数在drivers/gpu/drm/amd/amdgpu/amdgpu_display.c中,代码如下:
struct drm_framebuffer *
amdgpu_display_user_framebuffer_create(struct drm_device *dev,
struct drm_file *file_priv,
const struct drm_mode_fb_cmd2 *mode_cmd)
{
struct amdgpu_framebuffer *amdgpu_fb;
struct drm_gem_object *obj;
struct amdgpu_bo *bo;
uint32_t domains;
int ret;
obj = drm_gem_object_lookup(file_priv, mode_cmd->handles[0]);
if (obj == NULL) {
drm_dbg_kms(dev, "No GEM object associated to handle 0x%08X, "
"can't create framebuffer\n", mode_cmd->handles[0]);
return ERR_PTR(-ENOENT);
}
/* Handle is imported dma-buf, so cannot be migrated to VRAM for scanout */
bo = gem_to_amdgpu_bo(obj);
domains = amdgpu_display_supported_domains(drm_to_adev(dev), bo->flags);
if (obj->import_attach && !(domains & AMDGPU_GEM_DOMAIN_GTT)) {
drm_dbg_kms(dev, "Cannot create framebuffer from imported dma_buf\n");
drm_gem_object_put(obj);
return ERR_PTR(-EINVAL);
}
amdgpu_fb = kzalloc(sizeof(*amdgpu_fb), GFP_KERNEL);
if (amdgpu_fb == NULL) {
drm_gem_object_put(obj);
return ERR_PTR(-ENOMEM);
}
ret = amdgpu_display_gem_fb_verify_and_init(dev, amdgpu_fb, file_priv,
mode_cmd, obj);
if (ret) {
kfree(amdgpu_fb);
drm_gem_object_put(obj);
return ERR_PTR(ret);
}
drm_gem_object_put(obj);
return &amdgpu_fb->base;
}
无论是Intel i915中的intel_user_framebuffer_create函数、AMD Raedon中的radeon_user_framebuffer_create函数还是AMD AMDGPU中的amdgpu_display_user_framebuffer_create函数,其中都调用了drm_framebuffer_init函数。对于这个函数的解析,请看下回。