接前一篇文章:DRM全解析 —— CREATE_DUMB(2)
本文参考以下博文:
DRM驱动(三)之CREATE_DUMB
特此致谢!
上一回讲解了drm_mode_create_dumb函数的前半部分,本回讲解余下的部分。
为了便于理解,再次贴出drm_mode_create_dumb函数代码,在drivers/gpu/drm/drm_dumb_buffers.c中,如下:
/**
* DOC: overview
*
* The KMS API doesn't standardize backing storage object creation and leaves it
* to driver-specific ioctls. Furthermore actually creating a buffer object even
* for GEM-based drivers is done through a driver-specific ioctl - GEM only has
* a common userspace interface for sharing and destroying objects. While not an
* issue for full-fledged graphics stacks that include device-specific userspace
* components (in libdrm for instance), this limit makes DRM-based early boot
* graphics unnecessarily complex.
*
* Dumb objects partly alleviate the problem by providing a standard API to
* create dumb buffers suitable for scanout, which can then be used to create
* KMS frame buffers.
*
* To support dumb objects drivers must implement the &drm_driver.dumb_create
* and &drm_driver.dumb_map_offset operations (the latter defaults to
* drm_gem_dumb_map_offset() if not set). Drivers that don't use GEM handles
* additionally need to implement the &drm_driver.dumb_destroy operation. See
* the callbacks for further details.
*
* Note that dumb objects may not be used for gpu acceleration, as has been
* attempted on some ARM embedded platforms. Such drivers really must have
* a hardware-specific ioctl to allocate suitable buffer objects.
*/
int drm_mode_create_dumb(struct drm_device *dev,
struct drm_mode_create_dumb *args,
struct drm_file *file_priv)
{
u32 cpp, stride, size;
if (!dev->driver->dumb_create)
return -ENOSYS;
if (!args->width || !args->height || !args->bpp)
return -EINVAL;
/* overflow checks for 32bit size calculations */
if (args->bpp > U32_MAX - 8)
return -EINVAL;
cpp = DIV_ROUND_UP(args->bpp, 8);
if (cpp > U32_MAX / args->width)
return -EINVAL;
stride = cpp * args->width;
if (args->height > U32_MAX / stride)
return -EINVAL;
/* test for wrap-around */
size = args->height * stride;
if (PAGE_ALIGN(size) == 0)
return -EINVAL;
/*
* handle, pitch and size are output parameters. Zero them out to
* prevent drivers from accidentally using uninitialized data. Since
* not all existing userspace is clearing these fields properly we
* cannot reject IOCTL with garbage in them.
*/
args->handle = 0;
args->pitch = 0;
args->size = 0;
return dev->driver->dumb_create(file_priv, dev, args);
}
在做了参数检查后,先将参数args中的handle、pitch、size清零,确保健壮性,因为用户空间可能对这几个值并没有做清零操作就传了下来。之后调用dev->drivrer->dumb_create函数(实际上是dumb_create函数指针所指向的函数),并返回它的返回值。
在分析最后这个函数之前,先来看一下围绕DRM_IOCTL_MODE_CREATE_DUMB宏的用户态和内核态上下调用流程,如下图所示:
接下来对于dev->driver->dumb_create()进行解析。前文已提到,dev->driver->dumb_create的意思就是调用DRM设备的驱动中的dumb_create这一函数指针所指向的函数。那么它到底指向了哪个函数呢?
实际上具体所指向的函数是视dev->driver不同而不同的,对于不同的显卡驱动,dumb_create指向不同的函数。这里以笔者实际接触过的两款显卡Intel和AMD为例进行说明。
- Intel i915
Intel i915显卡驱动对应的struct drm_driver初始化代码在drivers/gpu/drm/i915/i915_driver.c中,如下:
static const struct drm_driver i915_drm_driver = {
/* Don't use MTRRs here; the Xserver or userspace app should
* deal with them for Intel hardware.
*/
.driver_features =
DRIVER_GEM |
DRIVER_RENDER | DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_SYNCOBJ |
DRIVER_SYNCOBJ_TIMELINE,
.release = i915_driver_release,
.open = i915_driver_open,
.lastclose = i915_driver_lastclose,
.postclose = i915_driver_postclose,
.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
.gem_prime_import = i915_gem_prime_import,
.dumb_create = i915_gem_dumb_create,
.dumb_map_offset = i915_gem_dumb_mmap_offset,
.ioctls = i915_ioctls,
.num_ioctls = ARRAY_SIZE(i915_ioctls),
.fops = &i915_driver_fops,
.name = DRIVER_NAME,
.desc = DRIVER_DESC,
.date = DRIVER_DATE,
.major = DRIVER_MAJOR,
.minor = DRIVER_MINOR,
.patchlevel = DRIVER_PATCHLEVEL,
};
由代码可见,dumb_create函数指针指向了i915_gem_dumb_create函数。该函数在drivers/gpu/drm/i915/gem/i915_gem_create.c中,代码如下:
int
i915_gem_dumb_create(struct drm_file *file,
struct drm_device *dev,
struct drm_mode_create_dumb *args)
{
struct drm_i915_gem_object *obj;
struct intel_memory_region *mr;
enum intel_memory_type mem_type;
int cpp = DIV_ROUND_UP(args->bpp, 8);
u32 format;
switch (cpp) {
case 1:
format = DRM_FORMAT_C8;
break;
case 2:
format = DRM_FORMAT_RGB565;
break;
case 4:
format = DRM_FORMAT_XRGB8888;
break;
default:
return -EINVAL;
}
/* have to work out size/pitch and return them */
args->pitch = ALIGN(args->width * cpp, 64);
/* align stride to page size so that we can remap */
if (args->pitch > intel_plane_fb_max_stride(to_i915(dev), format,
DRM_FORMAT_MOD_LINEAR))
args->pitch = ALIGN(args->pitch, 4096);
if (args->pitch < args->width)
return -EINVAL;
args->size = mul_u32_u32(args->pitch, args->height);
mem_type = INTEL_MEMORY_SYSTEM;
if (HAS_LMEM(to_i915(dev)))
mem_type = INTEL_MEMORY_LOCAL;
mr = intel_memory_region_by_type(to_i915(dev), mem_type);
obj = __i915_gem_object_create_user(to_i915(dev), args->size, &mr, 1);
if (IS_ERR(obj))
return PTR_ERR(obj);
return i915_gem_publish(obj, file, &args->size, &args->handle);
}
暂时不深入分析该函数的细节,只关注最后调用的i915_gem_publish函数。i915_gem_publish函数在同文件(drivers/gpu/drm/i915/gem/i915_gem_create.c)中,代码如下:
static int i915_gem_publish(struct drm_i915_gem_object *obj,
struct drm_file *file,
u64 *size_p,
u32 *handle_p)
{
u64 size = obj->base.size;
int ret;
ret = drm_gem_handle_create(file, &obj->base, handle_p);
/* drop reference from allocate - handle holds it now */
i915_gem_object_put(obj);
if (ret)
return ret;
*size_p = size;
return 0;
}
其中的核心函数为drm_gem_handle_create。
- AMD Raedon
AMD显卡有两款驱动:Raedon和AMDGPU。
先说Raedon驱动。Raedon显卡驱动对应的struct drm_driver初始化代码在drivers/gpu/drm/radeon/radeon_drv.c中,如下:
static const struct drm_driver kms_driver = {
.driver_features =
DRIVER_GEM | DRIVER_RENDER | DRIVER_MODESET,
.load = radeon_driver_load_kms,
.open = radeon_driver_open_kms,
.postclose = radeon_driver_postclose_kms,
.lastclose = radeon_driver_lastclose_kms,
.unload = radeon_driver_unload_kms,
.ioctls = radeon_ioctls_kms,
.num_ioctls = ARRAY_SIZE(radeon_ioctls_kms),
.dumb_create = radeon_mode_dumb_create,
.dumb_map_offset = radeon_mode_dumb_mmap,
.fops = &radeon_driver_kms_fops,
.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
.gem_prime_import_sg_table = radeon_gem_prime_import_sg_table,
.gem_prime_mmap = drm_gem_prime_mmap,
.name = DRIVER_NAME,
.desc = DRIVER_DESC,
.date = DRIVER_DATE,
.major = KMS_DRIVER_MAJOR,
.minor = KMS_DRIVER_MINOR,
.patchlevel = KMS_DRIVER_PATCHLEVEL,
};
由代码可见,dumb_create函数指针指向了radeon_mode_dumb_create函数。该函数在drivers/gpu/drm/radeon/radeon_gem.c中,代码如下:
int radeon_mode_dumb_create(struct drm_file *file_priv,
struct drm_device *dev,
struct drm_mode_create_dumb *args)
{
struct radeon_device *rdev = dev->dev_private;
struct drm_gem_object *gobj;
uint32_t handle;
int r;
args->pitch = radeon_align_pitch(rdev, args->width,
DIV_ROUND_UP(args->bpp, 8), 0);
args->size = (u64)args->pitch * args->height;
args->size = ALIGN(args->size, PAGE_SIZE);
r = radeon_gem_object_create(rdev, args->size, 0,
RADEON_GEM_DOMAIN_VRAM, 0,
false, &gobj);
if (r)
return -ENOMEM;
r = drm_gem_handle_create(file_priv, gobj, &handle);
/* drop reference from allocate - handle holds it now */
drm_gem_object_put(gobj);
if (r) {
return r;
}
args->handle = handle;
return 0;
}
其中的核心函数也是drm_gem_handle_create。
- AMD AMDGPU
再来看AMDGPU驱动。AMDGPU显卡驱动对应的struct drm_driver初始化代码在drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c中,如下:
static const struct drm_driver amdgpu_kms_driver = {
.driver_features =
DRIVER_ATOMIC |
DRIVER_GEM |
DRIVER_RENDER | DRIVER_MODESET | DRIVER_SYNCOBJ |
DRIVER_SYNCOBJ_TIMELINE,
.open = amdgpu_driver_open_kms,
.postclose = amdgpu_driver_postclose_kms,
.lastclose = amdgpu_driver_lastclose_kms,
.ioctls = amdgpu_ioctls_kms,
.num_ioctls = ARRAY_SIZE(amdgpu_ioctls_kms),
.dumb_create = amdgpu_mode_dumb_create,
.dumb_map_offset = amdgpu_mode_dumb_mmap,
.fops = &amdgpu_driver_kms_fops,
.release = &amdgpu_driver_release_kms,
.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
.gem_prime_import = amdgpu_gem_prime_import,
.gem_prime_mmap = drm_gem_prime_mmap,
.name = DRIVER_NAME,
.desc = DRIVER_DESC,
.date = DRIVER_DATE,
.major = KMS_DRIVER_MAJOR,
.minor = KMS_DRIVER_MINOR,
.patchlevel = KMS_DRIVER_PATCHLEVEL,
};
由代码可见,dumb_create函数指针指向了amdgpu_mode_dumb_create函数。该函数在drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c中,代码如下:
int amdgpu_mode_dumb_create(struct drm_file *file_priv,
struct drm_device *dev,
struct drm_mode_create_dumb *args)
{
struct amdgpu_device *adev = drm_to_adev(dev);
struct drm_gem_object *gobj;
uint32_t handle;
u64 flags = AMDGPU_GEM_CREATE_CPU_ACCESS_REQUIRED |
AMDGPU_GEM_CREATE_CPU_GTT_USWC |
AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS;
u32 domain;
int r;
/*
* The buffer returned from this function should be cleared, but
* it can only be done if the ring is enabled or we'll fail to
* create the buffer.
*/
if (adev->mman.buffer_funcs_enabled)
flags |= AMDGPU_GEM_CREATE_VRAM_CLEARED;
args->pitch = amdgpu_gem_align_pitch(adev, args->width,
DIV_ROUND_UP(args->bpp, 8), 0);
args->size = (u64)args->pitch * args->height;
args->size = ALIGN(args->size, PAGE_SIZE);
domain = amdgpu_bo_get_preferred_domain(adev,
amdgpu_display_supported_domains(adev, flags));
r = amdgpu_gem_object_create(adev, args->size, 0, domain, flags,
ttm_bo_type_device, NULL, &gobj);
if (r)
return -ENOMEM;
r = drm_gem_handle_create(file_priv, gobj, &handle);
/* drop reference from allocate - handle holds it now */
drm_gem_object_put(gobj);
if (r) {
return r;
}
args->handle = handle;
return 0;
}
其中的核心函数也是drm_gem_handle_create。
可见,不论是哪种显卡驱动,最终都调用了drm_gem_handle_create函数。对于这个函数的解析,请看下回。