相关参考


modetest的使用

在modetest使用dumb buffer的时候,操作方式如下:

  1. int main(int argc, char **argv)
  2. {
  3. open("/dev/dri/card0");
  4. drmModeGetResources(...);
  5. drmModeGetConnector(...);
  6. // 关于dumb buffer的使用, 参考modetest源码: bo_fb_create
  7. drmIoctl(DRM_IOCTL_MODE_CREATE_DUMB);
  8. drmModeAddFB(...);
  9. drmIoctl(DRM_IOCTL_MODE_MAP_DUMB);
  10. mmap(...);
  11. // ...... 填充framebuffer
  12. drmModeSetCrtc(crtc_id, fb_id, connector_id, mode);
  13. ......
  14. }

不考虑其他模块,就dumb而言,需要经过以下步骤
image.jpeg

  • 打开当前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

  1. drm_dev_init
  2. drm_gem_init(dev); // drm_driver->feature has DRIVER_GEM
  3. struct drm_vma_offset_manager {
  4. rwlock_t vm_lock;
  5. struct drm_mm vm_addr_space_mm;
  6. };
  7. // Initialize new offset-manager
  8. drm_vma_offset_manager_init(vma_offset_manager,
  9. DRM_FILE_PAGE_OFFSET_START,
  10. DRM_FILE_PAGE_OFFSET_SIZE);
  11. rwlock_init(&mgr->vm_lock);
  12. drm_mm_init(&mgr->vm_addr_space_mm, page_offset, size);

image.jpeg

DRM中DUMB使用

mali-dp drm属性

  1. #define DRM_GEM_CMA_DRIVER_OPS_WITH_DUMB_CREATE(dumb_create_func) \
  2. .gem_create_object = drm_gem_cma_create_object_default_funcs, \
  3. .dumb_create = (dumb_create_func), \
  4. .prime_handle_to_fd = drm_gem_prime_handle_to_fd, \
  5. .prime_fd_to_handle = drm_gem_prime_fd_to_handle, \
  6. .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, \
  7. .gem_prime_mmap = drm_gem_cma_prime_mmap
  8. static struct drm_driver malidp_driver = {
  9. .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
  10. DRM_GEM_CMA_DRIVER_OPS_WITH_DUMB_CREATE(malidp_dumb_create),
  11. #ifdef CONFIG_DEBUG_FS
  12. .debugfs_init = malidp_debugfs_init,
  13. #endif
  14. .fops = &fops, // DEFINE_DRM_GEM_CMA_FOPS
  15. ......
  16. };

DUMB创建
计算图片的空间大小
我们要计算一张图片所需要的空间大小,需要获取到以下参数:

  1. struct drm_mode_create_dumb {
  2. __u32 height; // 图像Height,我们这里将 YUV420相关格式的位深都写为8,但实际图像表示是超过8的,怎么办
  3. // 这里对这种情况, virtual_height = height * 3 / 2; 将图像的height 增加上UV相关占用字节数,即可。
  4. __u32 width;
  5. __u32 bpp; // 图像位深: bo_create 中计算了各种格式下的图像位深度。 YUV420将这里写为8,但height的增加解决了该问题
  6. __u32 flags;
  7. /* handle, pitch, size will be returned 内核返回以下参数 */
  8. __u32 handle; // dumb buffer的句柄
  9. __u32 pitch; // pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), alignment); 各硬件自定义对齐位数
  10. __u64 size; // 对齐后,图像占用空间的实际大小 size = pitch * height;
  11. };
  12. bo_create
  13. bo_create_dumb(fd, width, virtual_height, bpp);

用户空间 调用 drmIoctl(DRMIOCTLMODE_CREATE_DUMB) 会分配一片显存,并用drm_gem_objct管理起来,同时初始化 mmap

  1. drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create); // 用户层
  2. DRM_IOCTL_MODE_CREATE_DUMB==>drm_mode_create_dumb_ioctl // 内核层
  3. drm_mode_create_dumb_ioctl
  4. => drm_mode_create_dumb
  5. => dev->driver->dumb_create(file_priv, dev, args); // mali-dp550使用: 5.9版本使用 malidp_dumb_create
  6. struct drm_driver {
  7. int (*dumb_create)(struct drm_file *file_priv, struct drm_device *dev, struct drm_mode_create_dumb *args);
  8. };

在mali-dp550中,申请framebuffer的方法

  1. malidp_dumb_create
  2. => alignment = malidp_hw_get_pitch_align(malidp->dev, 1); // !!! 重点:获取 malidp_device 中 .bus_align_bytes = 8, 硬件对齐位数,这里至少8对齐。
  3. => pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), alignment); // width*bpp 一个图像的总宽度占用字节数,然后根据硬件要求对齐
  4. => drm_gem_cma_dumb_create_internal(file_priv, drm, args); // 因为 MALI-DP550原生是为了SOC设计, 显存和内存公用,使用CMA分配
  5. => args->size = args->pitch * args->height; // 根据对齐后的数据,算出实际要占用的总空间数
  6. => drm_gem_cma_create_with_handle
  7. => drm_gem_cma_create(drm, size); // !!!重点
  8. => drm_gem_handle_create(file_priv, gem_obj, handle) // !!! 重点,这里给用户空间返回handle,是用户操作buffer的句柄
  9. => drm_gem_object_put(gem_obj);

image.jpeg
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

  1. drmIoctl(bo->fd, DRM_IOCTL_MODE_MAP_DUMB, &arg); .
  2. drm_mmap(0, bo->size, PROT_READ | PROT_WRITE, MAP_SHARED, bo->fd, arg.offset); // drm_mmap 就是内核的 mmap

关于 **Linux的mmap,这个不需要强调,将设备物理地址(物理内存)映射到用户空间的虚拟地址(虚拟内存)上**。

  1. #include <sys/mman.h>
  2. void *mmap(void *addr, size_t length, int prot, int flags,
  3. int fd, off_t offset);
  4. 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

  1. DEFINE_DRM_GEM_CMA_FOPS(fops);
  2. static const struct file_operations fops = {\
  3. .owner = THIS_MODULE,\
  4. .open = drm_open,\
  5. .release = drm_release,\
  6. .unlocked_ioctl = drm_ioctl,\
  7. .compat_ioctl = drm_compat_ioctl,\
  8. .poll = drm_poll,\
  9. .read = drm_read,\
  10. .llseek = noop_llseek,\
  11. .mmap = drm_gem_cma_mmap,\
  12. DRM_GEM_CMA_UNMAPPED_AREA_FOPS \
  13. }

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()

image.jpeg
功能:给dumb建立fake mmap, 然后提供给用户一个offset,使得用户在mmap 当前fd时可选中对应buffer。
drmModeAddFB2
问题:我们显示图片的时候, 按照前边的步骤, 好像已经可以吧 所有buffer 映射到用户空间了, 为什么还需要 add framebuffer?这个都做了什么?AddFB和AddFB2有啥区别?

  1. int drmModeAddFB2(int fd, uint32_t width, uint32_t height,
  2. uint32_t pixel_format, const uint32_t bo_handles[4],
  3. const uint32_t pitches[4], const uint32_t offsets[4],
  4. uint32_t *buf_id, uint32_t flags);
  5. 参数:
  6. buf_id是一个传出参数,看来这个函数的目的是获取buf_id,应该就是framebufferobj-id
  7. fd, widthheightpixel_format 图像的基础信息
  8. bo_handles[4]:
  9. pitches[4]:
  10. offsets[4]: bo_create中,初始化这几个字段。根据format不同,将framebuffer 划分为1-3部分
  11. flags:
  12. #define DRM_MODE_FB_INTERLACED (1<<0) /* 隔行帧 */
  13. #define DRM_MODE_FB_MODIFIERS (1<<1) /* enables ->modifer[],如果使能 dev->mode_config.allow_fb_modifiers 必须为1 */

image.jpeg
可以看到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