导读:
- dma-buf: vmap 新增对 I/O-Memory 的支持
- drm/gem: vmap 新增对 I/O memory 的支持
- drm/gem: 全面启用 GEM object functions
- drm/ttm: 删除 TTM_PL_FLAG_NO_EVICT,新增 ttm_bo_pin()/ttm_bo_unpin()
- interfaces: bugfix for use-after-free in mapper 2.0
- SurfaceFlinger: 采用绝对时间来设置 VSP timer
- GPU Memory: 新增 libgpumem 用于 BPF 调试
- Android vulkan 升级到 1.2.158
- Taiwins 0.2 发布,支持 Lua 脚本
之前曾在周刊第1期中报道过关于 为 GEM vmap 添加 I/O memory 的支持 的提交,该提交是以 drm prime 为基础进行修改的,不过该 patch 并没有立即被合入,而是重新提交了基于更底层的 dma-buf 修改的 patch,并最终于9月25日 merge 到 drm-misc 分支,预计将被合入到 linux-5.11 rc1 版本中。以下是该 patch 的背景:
众所周知,CPU 在 kernel space 是通过虚拟地址(virtual address)来访问物理内存的。同样的,如果 CPU 想访问一块 dma buffer 的物理内存,也需要先将该 dma buffer 映射到 kernel 的虚拟地址空间中才能正常访问,而这个映射的操作则由 dma_buf_vmap()
接口来实现。无论该 dma buffer 在物理上是连续的还是离散的,经过 dma_buf_vmap() 后的虚拟地址一定是连续的,这样 CPU 就可以像访问普通内存一样来访问 dma buffer。如果你还不了解 dmabuf 的 vmap 接口,建议回顾一下本人的 《dma-buf 系列之 kmap / vmap》
CPU 访问 dma buffer 的应用场景在 DRM 驱动中非常普遍,如 fbdev console 以及 tinydrm,都会涉及到在驱动中动态修改 framebuffer 内容。由于 CPU 将这块 vmap() 后的内存当作普通内存来访问,因此它使用的操作指令则是通用的 load/store 指令。设想一下,当 vmap 的物理内存来自于 I/O 端口(如外设寄存器)时,会发生什么样的情况?如果是 ARM 平台则不会出现什么异常,因为 ARM 架构中 IO 地址空间是和内存空间统一编址的,所以访问 IO 寄存器和访问普通内存没有什么区别。但是在 SPARC 平台上就会导致 kernel panic,原因是 SPARC 架构中 I/O 地址空间是独立编址的,不能使用普通的 load/store 访存指令来访问 IO 内存,而需要使用专门的 I/O 操作指令才能正常访问 I/O 端口上的内容。
为了解决上述平台兼容性问题,Thomas Zimmermann(drm-misc maintainer, SUSE)向社区提交了一组 patch,新增了 dma_buf_map
结构体,用于专门区分 vmap() 后的地址是位于 IO Memory 还是 System Memory 上。dma_buf_map 具体定义如下:
struct dma_buf_map {union {void __iomem *vaddr_iomem;void *vaddr;};bool is_iomem;
};
该结构体作为 dma_buf_vmap() 的返回值,通过 is_iomem 来告诉调用者当前 memory 的类型,这样驱动程序就可以针对不同的 memory 类型,使用不同的访存接口(如 memcpy() 还是 memcpy_toio())来操作内存,避免 kernel panic 的发生。需要注意的是,该结构体虽然名字中包含“dma buf” 关键字,但是该结构体并不依赖于 dma-buf 驱动,它有自己独立的 dma-buf-map.h 头文件,任何想要区分 IO / System memory 的驱动都可以引用该头文件,而无需开启任何 dma-buf 相关的宏定义。关于该结构体的命名,邮件列表中也有不少反对意见,而 Thomas 解释之所以给它取名带 “dma buf” 关键字,是因为目前只有 dma-buf 驱动才会用到这个结构体,如果后面还有其他驱动也需要用到该结构体,可以到时候再修改,毕竟就现阶段而言,尽早合入该 patch 能加快其他依赖驱动的合入进程。
(其实我个人觉得这个结构体名字取的真的很不好,因为它容易和函数 dma_buf_mmap()
混淆)
详情:[v3,0/4] dma-buf: Flag vmap’ed memory as system or I/O memory
基于上面的 dma-buf-map patch,Thomas 将上次关于 GEM I/O memory mapping 的 patch rebase 到了 dma-buf-map 基础上,并对 drm 中凡是和 vmap 相关的接口进行了修改,包括 cma、shmem、vram、ttm、prime、client 以及 drm_gem_object_funcs
。除此之外,他还对 drm_fb_helper.c 做了大量修改,并新增了 dma_buf_map_memcpy_to()
和 dma_buf_map_incr()
两个辅助函数来简化 fbdev 驱动访问显存的代码编写。该 patch 将在 linux-5.11 merge window 开启时被合入。
详情:[PATCH v5 00/10] Support GEM object mappings from I/O memory
两个月前曾在周刊第1期中介绍过关于“彻底废弃 drm_driver 中的 prime callbacks,全面启用 GEM Object functions”的消息。drm_gem_object_funcs
从 kernel-4.19 开始被引入,该结构体定义如下(linux-5.10-rc1):
struct drm_gem_object_funcs {void (*free)(struct drm_gem_object *obj);int (*open)(struct drm_gem_object *obj, struct drm_file *file);void (*close)(struct drm_gem_object *obj, struct drm_file *file);void (*print_info)(struct drm_printer *p, unsigned int indent,const struct drm_gem_object *obj);struct dma_buf *(*export)(struct drm_gem_object *obj, int flags);int (*pin)(struct drm_gem_object *obj);void (*unpin)(struct drm_gem_object *obj);struct sg_table *(*get_sg_table)(struct drm_gem_object *obj);void *(*vmap)(struct drm_gem_object *obj);void (*vunmap)(struct drm_gem_object *obj, void *vaddr);int (*mmap)(struct drm_gem_object *obj, struct vm_area_struct *vma);
};
可以看到,该结构体几乎覆盖了所有 dma-buf export 的接口,因此它可以完全取代 drm_driver 结构体中的 prime export 接口。Thomas Zimmermann( drm-misc Maintainer, SUSE)总共提交了 22 笔 patch,其中第 1~ 21 笔 patch 是专门用来清理各个 drm 设备驱动的,将 drm_driver 中的 prime export 接口挪到 drm_gem_object_funcs 中去。第 22 笔 patch 则是彻底删除 drm_driver 中与 prime export 相关的 callbacks。该 patch 已于9月25日合入到 drm-misc-next 分支,接下来准备合入 linux-5.11 主线。
详情:[PATCH v2 00/21] Convert all remaining drivers to GEM object functions
TTM(Translation Table Manager)是 DRM 驱动中的一种显存管理机制,和 GEM 是同一个级别的概念,它主要适用于带独立显存的显卡驱动,因此在移动平台很少看到它的身影。因为 ttm 主要适用于带独立显卡的应用场景,因此在分配显存的时候就会涉及到具体显存分配的位置,进一步说就是在 VRAM 中分配还是在系统的 RAM 中分配。为了告诉 ttm 驱动具体应该在什么样的介质上分配 buffer,drm 框架引入了 ttm placement flag,如 TTM_PL_FLAG_SYSTEM 代表在系统 memory 中分配 buffer,TTM_PL_FLAG_VRAM 在 VRAM 中分配 buffer。而当 VRAM 中空间不足时,驱动则会将那些使用频率并不高但是又不能立即释放的 buffer 迁移(驱逐/evict)到 System Memory 中,以此来为新的 buffer 提供可用的空间(有点类似于 kernel 中的 CMA 机制)。而 TTM_PL_FLAG_NO_EVICT 则是告诉驱动当前分配的 buffer 不能进行迁移,一旦分配了就固定在当前介质中了(即pin住了)。随着时间的推移,开发人员发现 TTM_PL_FLAG_NO_EVICT 这个 flag 越来越不好用,因为该 flag 将 buffer 限定死了,有时候我们希望某块 buffer 当我们要使用它时,就将他固定(pin)在 VRAM 中,当我们中途不想用时,就把它 unpin 掉,这样后续就可以对它进行迁移操作,提高 VRAM 的利用率。所以后来各个厂商开始通过 pin_count 引用计数来对某个 buffer 实现 pin/unpin 操作,而不再使用原生的 TTM_PL_FLAG_NO_EVICT flag。Christian König 提交的这组 patch 则顺应了各大厂商的意愿,将 vendor 实现的 pin_count 挪到了 ttm 框架中,通过 ttm_bo_pin()
和 ttm_bo_unpin()
函数来操作 pin_count 引用计数,同时删除了 TTM_PL_FLAG_NO_EVICT flag 和 ttm_bo_create()
函数。该 patch 也将被合入到 Linux-5.11 中。
详情:[PATCH 01/11] drm/ttm: add ttm_bo_pin() ttm_bo_unpin() v2
DMA-BUF Heaps 是用来替代 ION 的多媒体内存分配器,而在周刊第3期中也曾报道过 kernel mainline 已经彻底删除了 ION 驱动,而 Google 也将在 AndroidS 中正式启用 DMA-BUF Heaps 来作为 ION 的备选方案。
如下提交在 rootdir/ueventd.rc 中新增了 dma_heap 节点访问权限:
/dev/dma_heap/system 0666 system system
详情:aosp/core[master]: Setup ueventd to support DMA-BUF heaps
在谷歌官方的mapper2.0 hal实现中,通过importBuffer()导入的 native_handle_t 都是保存在 mBufferHandles 集合中的,当应用程序不再需要这个 native_handle_t 时,则通过 freeBuffer() 来释放这个handle所对应的进程资源,最终擦除该 native_handle_t 在 mBufferHandles 中的位置。由于应用程序 importBuffer() 和 freeBuffer() 可能在两个不同的线程中执行,所以在对 mBufferHandles 进行 add/erase 操作时都是用锁保护起来的,以防出现竞争。但最近开发人员发现在 Mapper 2.0 中还是出现了竞争的问题,因为在 freeBuffer() 的实现中, 是先调用底层的 mHal->freeBuffer() 然后再执行 mBufferHandles.erase() 操作,由于互斥锁只保护了 erase 操作,没有保护 mHal->freeBuffer(),这就有可能导致一个线程刚刚执行完 mHal->freeBuffer(),正好另一个线程又在执行 importBuffer() 操作,于是出错。解决方法则是将 mHal->freeBuffer() 和 mBufferHandles.erase() 操作放在同一把锁里保护起来,这样就能避免多线程的竞争问题。
详情:aosp/interfaces[master]: graphics: fix use-after-free in mapper 2.0 passthrough
VSyncPredictor(VSP)是 Android11 新引的一个 Vsync 预测器,它内部有一个 timer 模块,基于 timerfd 来实现的。timerfd 是 Linux 为用户程序提供的一个定时器接口。这个接口基于文件描述符,通过文件描述符的可读事件进行超时通知,因此可以配合 select/poll/epoll 等使用。该 patch 的重点是在 timerfd_settime() 函数上,这个函数接收一个 flag 参数,当 flag 为 TFD_TIMER_ABSTIME 时代表设置的是绝对时间,当 flag 为0时代表设置的时间为相对时间。VSP timer 将 timerfd_settime() 封装在了 Timer::alarmIn() 函数中,使用的 flag 都是0(即相对时间),本意是调用 alarmIn(5) 就会在5ms后唤醒定时器,正常情况下没有什么问题的。但是一旦在 alarmIn() 中 timerfd_settime() 之前发生了线程抢占,那么这个 alarmIn() 设置的 timer 就会往后推迟,导致 timer 不准确。因此需要将 timerfd_settime() 的 flag 修改成 TFD_TIMER_ABSTIME,这样采用绝对时间就能避免因线程调度而导致的 timer 不准确的问题发生。
详情:aosp/native[master]: SurfaceFlinger: use TFD_TIMER_ABSTIME for VSP timer
AChoreographer 原本就是从 DisplayManager 那边获取刷新率(refresh rate)的,因此没必要通过 requestLatestConfig() 接口从 SurfaceFlinger 那边获取。 AChoreographer 只需要将最新的 refresh rate 封装成一个 event,然后发送给 looper 线程即可。该 Patch 能够确保 AChoreographer 的回调函数的执行是和 DisplayManager 完全同步的。
详情:aosp/native[master]: Fix refresh rate callback fan-out for choreographer
为了增加 GPU 调试手段,Google 在 frameworks/native/services/gpuservice/bpfprogs 下新增 gpu_mem.c 文件,用于生成 BPF 调试程序 gpu_mem.o。同时在 frameworks/native/services/gpuservice 下新增了 libgpumem 模块,用于和 BPF 下的 gpu_mem.o 程序通信。当 BPF 加载 gpu_mem.o 文件后,会生成如下调试节点:
- /system/etc/bpf/gpu_mem.o
- /sys/fs/bpf/map_gpu_mem_gpu_mem_total_map
- /sys/fs/bpf/prog_gpu_mem_tracepoint_gpu_mem_gpu_mem_total
通过 libgpumem.so,我们可以抓取当前时刻系统所有进程的 GPU Memory 使用情况,同时也可以直接使用 “adb shell dumpsys gpu --gpumem” 命令来动态打印 gpu memory 的信息,非常方便。
详情:aosp/native[master]: GPU Memory: implement libgpumem to interact with bpf
Android 目前的 libvulkan API 版本已升级到 1.2.158,新增如下 API,Extension 无新增。
vkCmdBeginRenderPass2
vkCmdDrawIndexedIndirectCount
vkCmdDrawIndirectCount
vkCmdEndRenderPass2
vkCmdNextSubpass2
vkCreateRenderPass2
vkGetBufferDeviceAddress
vkGetBufferOpaqueCaptureAddress
vkGetSemaphoreCounterValue
vkResetQueryPool
vkSignalSemaphore
vkWaitSemaphores
详情:aosp/native[master]: Vulkan: update generated framework according to vulkan-headers
之前曾在我的星球中介绍过 Taiwins 这个新项目(https://t.zsxq.com/R3vJyJM),Taiwins 是一个开源的、模块化的 Wayland Compositor,由 Xichen.Zhou 创建。该项目最初基于 libweston 库开发,可实现平铺式窗口和浮动窗口,编写 client 程序代码量少,易于上手。此次 Taiwins 0.2 版本发布,将底层基础库从原来的 libweston 转移到了 wlroots 库,除了支持 Lua 脚本外,还带来了如下新 feature:
官网:https://taiwins.org/index.html
github: https://github.com/taiwins/taiwins
详情:Taiwins 0.2 is out