相关参考
modetest的使用
在modetest使用dumb buffer的时候,操作方式如下:
int main(int argc, char **argv)
{
open("/dev/dri/card0");
drmModeGetResources(...);
drmModeGetConnector(...);
// 关于dumb buffer的使用, 参考modetest源码: bo_fb_create
drmIoctl(DRM_IOCTL_MODE_CREATE_DUMB);
drmModeAddFB(...);
drmIoctl(DRM_IOCTL_MODE_MAP_DUMB);
mmap(...);
// ...... 填充framebuffer
drmModeSetCrtc(crtc_id, fb_id, connector_id, mode);
......
}
不考虑其他模块,就dumb而言,需要经过以下步骤:
- 打开当前DRM模块,获取到fd,对应底层会生成自己的drm_file;
- 创建N块自己需要的显存,这个显存可以是GPU显存-独立显卡, 也可以是内存-集显或SOC设备;
- 我们使用显存肯定要mmap到用户空间,来减少拷贝的耗时。但此时我们只有一个fd,所以需要底层fake mmap,将不同的空间,提供一个索引给用户;
- 用户调用mmap,此时会去映射到自己 选择 的 offset 空间;
- AddFramebuffer将用户分配的buffer描述成framebuffer,挂到drm_device中。
**
下边参考 mali-dp550 使用 内核是如何初始化gem的 // malidp-drv.c kernel_version: 5.9
因为mali-dp550在ARM上是SoC,所以显存和内存公用,所以关于frmabuffer的分配都是基于CMA 来分配连续的大块内存,可供DMA使用**。 CMA,Contiguous Memory Allocator,是内存管理子系统中的一个模块,负责物理地址连续的内存分配**。一般系统会在启动过程中,从整个memory中配置一段连续内存用于CMA,然后内核其他的模块可以通过CMA的接口API进行连续内存的分配。CMA的核心并不是设计精巧的算法来管理地址连续的内存块,实际上它的底层还是依赖内核伙伴系统这样的内存管理机制,或者说CMA是处于需要连续内存块的其他内核模块(例如DMA mapping framework)和内存管理模块之间的一个中间层模块,主要功能包括: 1、解析DTS或者命令行中的参数,确定CMA内存的区域,这样的区域我们定义为CMA area。 2、提供cma_alloc和cma_release两个接口函数用于分配和释放CMA pages 3、记录和跟踪CMA area中各个pages的状态 4、调用伙伴系统接口,进行真正的内存分配。
gem的初始化
在drm初始化时,如果支持GEM属性的话,在初始化drm_device时会初始化一个 vma,这个是做什么用的??? // TBD
drm_dev_init
drm_gem_init(dev); // drm_driver->feature has DRIVER_GEM
struct drm_vma_offset_manager {
rwlock_t vm_lock;
struct drm_mm vm_addr_space_mm;
};
// Initialize new offset-manager
drm_vma_offset_manager_init(vma_offset_manager,
DRM_FILE_PAGE_OFFSET_START,
DRM_FILE_PAGE_OFFSET_SIZE);
rwlock_init(&mgr->vm_lock);
drm_mm_init(&mgr->vm_addr_space_mm, page_offset, size);
DRM中DUMB使用
mali-dp drm属性
#define DRM_GEM_CMA_DRIVER_OPS_WITH_DUMB_CREATE(dumb_create_func) \
.gem_create_object = drm_gem_cma_create_object_default_funcs, \
.dumb_create = (dumb_create_func), \
.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 = drm_gem_cma_prime_import_sg_table, \
.gem_prime_mmap = drm_gem_cma_prime_mmap
static struct drm_driver malidp_driver = {
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
DRM_GEM_CMA_DRIVER_OPS_WITH_DUMB_CREATE(malidp_dumb_create),
#ifdef CONFIG_DEBUG_FS
.debugfs_init = malidp_debugfs_init,
#endif
.fops = &fops, // DEFINE_DRM_GEM_CMA_FOPS
......
};
DUMB创建
计算图片的空间大小
我们要计算一张图片所需要的空间大小,需要获取到以下参数:
struct drm_mode_create_dumb {
__u32 height; // 图像Height,我们这里将 YUV420相关格式的位深都写为8,但实际图像表示是超过8的,怎么办
// 这里对这种情况, virtual_height = height * 3 / 2; 将图像的height 增加上UV相关占用字节数,即可。
__u32 width;
__u32 bpp; // 图像位深: bo_create 中计算了各种格式下的图像位深度。 YUV420将这里写为8,但height的增加解决了该问题
__u32 flags;
/* handle, pitch, size will be returned 内核返回以下参数 */
__u32 handle; // dumb buffer的句柄
__u32 pitch; // pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), alignment); 各硬件自定义对齐位数
__u64 size; // 对齐后,图像占用空间的实际大小 size = pitch * height;
};
bo_create
bo_create_dumb(fd, width, virtual_height, bpp);
用户空间 调用 drmIoctl(DRMIOCTLMODE_CREATE_DUMB) 会分配一片显存,并用drm_gem_objct管理起来,同时初始化 mmap
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create); // 用户层
DRM_IOCTL_MODE_CREATE_DUMB==>drm_mode_create_dumb_ioctl // 内核层
drm_mode_create_dumb_ioctl
=> drm_mode_create_dumb
=> dev->driver->dumb_create(file_priv, dev, args); // mali-dp550使用: 5.9版本使用 malidp_dumb_create
struct drm_driver {
int (*dumb_create)(struct drm_file *file_priv, struct drm_device *dev, struct drm_mode_create_dumb *args);
};
在mali-dp550中,申请framebuffer的方法
malidp_dumb_create
=> alignment = malidp_hw_get_pitch_align(malidp->dev, 1); // !!! 重点:获取 malidp_device 中 .bus_align_bytes = 8, 硬件对齐位数,这里至少8对齐。
=> pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), alignment); // width*bpp 一个图像的总宽度占用字节数,然后根据硬件要求对齐
=> drm_gem_cma_dumb_create_internal(file_priv, drm, args); // 因为 MALI-DP550原生是为了SOC设计, 显存和内存公用,使用CMA分配
=> args->size = args->pitch * args->height; // 根据对齐后的数据,算出实际要占用的总空间数
=> drm_gem_cma_create_with_handle
=> drm_gem_cma_create(drm, size); // !!!重点
=> drm_gem_handle_create(file_priv, gem_obj, handle) // !!! 重点,这里给用户空间返回handle,是用户操作buffer的句柄
=> drm_gem_object_put(gem_obj);
drm_gem_cma_create 此函数创建一个 CMA GEM 对象并分配一个连续的内存块作为后备存储。后备内存设置了 writecombine 属性。
可以看到这个函数中,会有三操作:
- struct drm_gem_object *gem_obj **gem buffer对象结构体**分配及初始化;
- drm_gem_create_mmap_offset() offset的初始化;
- dma_alloc_wc : 真实物理内存的分配-dma writecombine的内存属性; !!! 注意,gem_obj 和 gem_mmap 并不会分配真实的内存,只是建立一个管理机制。
struct drm_gem_object *obj
drm_gem_handle_create
重点:用户空间在打开当前 /dev/dri/cardx后,可能会操作若干个dumb buffer,所以需要一个idr来定位当前申请的dumb buffer。
在 struct drm_file *file_priv 中 file_priv->object_idr 挂接了当前dumb buffer的所有 struct drm_gem_object 。
dumb的mmap和linux的mmap
在modetest里边,我们使用 dumb buffer,不仅仅是需要分配 bo_create_dumb,还需要mmap : bo_map
drmIoctl(bo->fd, DRM_IOCTL_MODE_MAP_DUMB, &arg); .
drm_mmap(0, bo->size, PROT_READ | PROT_WRITE, MAP_SHARED, bo->fd, arg.offset); // drm_mmap 就是内核的 mmap
关于 **Linux的mmap,这个不需要强调,将设备物理地址(物理内存)映射到用户空间的虚拟地址(虚拟内存)上**。
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
int munmap(void *addr, size_t length);
建立映射 void mmap(void addr, size_t length, int prot, int flags, int fd, off_t offset); addr: 指定映射的起始地址,通常设为NULL, 由系统指定。 len: 映射到内存的文件长度 prot: 映射区的保护方式, 可以是: PROT_EXEC: 映射区可被执行 PROT_READ: 映射区可被读取 PROT_WRITE: 映射区可被写入 flags: 映射区的特性,可以是: MAP_SHARED: 写入映射区的数据会复制回文件,且允许其他映射该文件的进程共享 MAP_PRIVATE:对映射区的写入操作会产生一个映射区的复制(copy-on-write), 对此区域所做的修改不会写回原文件。 fd: 由open返回的文件描述符,代表要映射的文件 offset: 以文件开始处的偏移量/或其他对象,必须是分页大小的整数倍,通常为0,表示从文件开头映射
解除映射 int munmap(void *start, size_t length)
功能: 取消参数start所指向的映射内存,参数length表示欲取消的内存大小
addr: mmap的返回值
返回值: 解除成功返回0,否则返回-1,错误原因存于errno中。
mmap调用 struct drm_driver 中的 mmap, 在mali-dp550中调用 drm_gem_cma_mmap
DEFINE_DRM_GEM_CMA_FOPS(fops);
static const struct file_operations fops = {\
.owner = THIS_MODULE,\
.open = drm_open,\
.release = drm_release,\
.unlocked_ioctl = drm_ioctl,\
.compat_ioctl = drm_compat_ioctl,\
.poll = drm_poll,\
.read = drm_read,\
.llseek = noop_llseek,\
.mmap = drm_gem_cma_mmap,\
DRM_GEM_CMA_UNMAPPED_AREA_FOPS \
}
dumb的mmap
dumb_map_offset 的作用:
在 drm 设备节点的地址空间中分配一个偏移量,以便能够内存映射一个 dumb缓冲区。 默认实现是 drm_gem_create_mmap_offset()。 基于 GEM 的驱动程序不能覆盖它。 由用户通过 ioctl 调用。 返回值 成功为零,失败为负 errno。 在 static struct drm_driver malidp_driver 中未实现 dumb_map_offset,默认使用 : drm_gem_dumb_map_offset()
功能:给dumb建立fake mmap, 然后提供给用户一个offset,使得用户在mmap 当前fd时可选中对应buffer。
drmModeAddFB2
问题:我们显示图片的时候, 按照前边的步骤, 好像已经可以吧 所有buffer 映射到用户空间了, 为什么还需要 add framebuffer?这个都做了什么?AddFB和AddFB2有啥区别?
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);
参数:
buf_id是一个传出参数,看来这个函数的目的是获取buf_id,应该就是framebuffer的obj-id
fd, width,height,pixel_format 图像的基础信息
bo_handles[4]:
pitches[4]:
offsets[4]: 在bo_create中,初始化这几个字段。根据format不同,将framebuffer 划分为1-3部分
flags:
#define DRM_MODE_FB_INTERLACED (1<<0) /* 隔行帧 */
#define DRM_MODE_FB_MODIFIERS (1<<1) /* enables ->modifer[],如果使能 dev->mode_config.allow_fb_modifiers 必须为1 */
可以看到addfb实现了以下行为:
- framebuffer_check 对 buf数据格式的正确性进行校验。
- 创建drm_framebuffer *fb,并根据地址填充
- drm_framebuffer 内部包含 struct drm_mode_object,将这个挂接到mode_config的object_idr 查找表中,同时叶挂到 mode_config.fb_list 链表里
总结: 这里就是将 dumb buffer 划分成framebuffer,并添加到 drm_device->mode_config 中,使得用户modeset 可检测出当前framebuffer