linux DRM GEM 筆記

原文連接:https://www.cnblogs.com/yaongtime/p/14418357.htmlhtml

在GPU上的各種操做中涉及到多種、多個buffer的使用。
一般咱們GPU是經過圖像API來調用的,例如OPENGL、vulkan等,因此GPU上buffer的使用,實際上就是在這些圖像API中被使用。
例如在opengl es中,vertex/fragment shader、vertex index、vertex buffer object、uniform buffer object、texture、framebuffer等都須要一塊memory buffer來存儲對應的內容。
而在vulkan中有提供明確的memory管理規則,它把memory分紅兩半來管理分別是:resource和backing memory。
resource有兩種類型buffer和image。
Backing memory也被叫作device memory。
不一樣類型的resource對Backing memory的需求不同,vulkan根據resource的屬性來分配對應的backing memory。
因此memory的管理在整個GPU的操做中起着重要做用。
 
Memory buffer究其本質就是ram上的段內存空間可被表示爲:address + size。
若是支持MMU,虛擬地址連續,但物理地址不連續的一段內存。
 
由於linux系統的特色,應用層不能直接訪問物理地址等緣由,因此須要linux kernel中提供一種方法來讓用戶層圖像API訪問device buffer。
GEM(Graphics Execution Manager)便是linux DRM中用於完成memory管理的內核基礎設施(不止這一種)。
 
GEM做爲一種內存管理方式,並未覆蓋各類在userspace和kernel使用狀況(use cases)。
GEM提供了一組標準的內存相關的操做給userspace,以及一組輔助函數給kernel drivers,kernel drivers還須要實現一些硬件相關的私有操做函數。
GEM所管理的memory具體類型、屬性是不可知的,咱們並不知道它所管理的buffer對象包含了什麼。若是要獲知GEM所管理的buffer對象的具體內容和使用目的,須要kernel drivers本身實現一組私有的ioctl來獲取對應的信息。
 
一個實際的GEM對象所管理的memory類型與硬件平臺密切相關,這裏咱們主要討論嵌入式平臺上的GPU的MEMORY管理。
嵌入式平臺上GPU和CPU每每共享主存DDR,因此在本文中討論GEM的backing memory每每就是DDR上的某段物理內存頁(連續或非連續都可)。
這段物理內存會被CPU(份內核虛擬地址和用戶層虛擬地址)、GPU(虛擬地址和物理地址)訪問。
在CPU端訪問時,當在用戶層訪問時,須要經過GEM的mmap()規則映射成用戶層虛擬地址,在kernel中使用時須要映射成內核虛擬地址。
在GPU端訪問時,若是GPU支持MMU,GPU也使用MMU映射後虛擬地址,若是不支持MMU,GPU直接訪問物理地址。
 
userspace:
在userspace當須要建立一個新的GEM對象時,會經過調用driver私有的ioctl接口來獲取。
雖然不一樣driver設計的icotl接口不同,可是最終都經過返回一個handle給用戶層,來做爲kernel中一個GEM對象的引用,而這個handle就是一個u32的整數。
因此一個kernel中的GEM對象被抽象爲一個不透明的u32整數值, 因此userspace對一個GEM對象的操做均透過這個handle來進行。
 
如前所述,GEM本質上是作內存管理,而內存上最常規的操做就是讀寫。
而在對內存讀寫上咱們須要增長各類限制條件,這些條件能夠是,好比誰能夠在何時寫入什麼地方,誰能夠在何時從哪一個地址讀取等。
 
當在userspace須要訪問GEM buffer內存時,一般經過mmap()系統調用來映射GEM對象所包含的物理地址。
由於在userspace一個handle就表明一個GEM對象,在映射前還須要經過driver私有的ioctl返回一個pg_offset,做爲一個mmap()的「off_t offset」參數。
詳細的討論將在mmap節展開。
 
Kernel space:
本文主要討論內容是kernel driver中對GEM的使用。
 
GEM對象的分配和它的backing memory的分配是分開的。
一個GEM對象經過struct drm_gem_object來表示,驅動程序每每須要把struct drm_gem_object嵌入到本身的私有數據結構中,主要用於內存對象的管理。
struct drm_gem_object對象中不包含內存分配的管理,Backing memory分配將在memory分配段討論。
 
在kernel中struct drm_gem_object的被定義爲:
struct drm_gem_object {
 
 
  struct kref refcount;
  unsigned handle_count;
  struct drm_device *dev;
 
 
  struct file *filp;
  struct drm_vma_offset_node vma_node;
 
 
  size_t size;
 
 
  int name;
 
 
  struct dma_buf *dma_buf;
  struct dma_buf_attachment *import_attach;
  struct dma_resv *resv;
  struct dma_resv _resv;
 
 
  const struct drm_gem_object_funcs *funcs;
};
 
提供以下幾點管理:
一、對象自己的建立銷燬管理,引用計數等。
二、vma管理,主要是配合用戶層的調用mmap()時會用到。
三、shmem文件描述符獲取
四、在PRIME中用於import/export操做
五、同步操做
六、回調函數提供平臺差別實現
 
struct drm_gem_object中主要是包含了通用的部分,存在平臺差別化的地方經過兩個方法來解決。
一是經過一組回調函數接口,讓drivers提供各自的實現版本。
二是經過嵌入struct drm_gem_object到各自私有的數據結構中,來擴展GEM對象的管理內容。
 
在一個GEM對象上涉及到的操做或者是提供的功能以下:
1.create
2.backing memory
3.mmap
4.import/export
5.sync
 
1.首須要建立一個GEM對象
2.GEM是管理一段內存,那麼必然涉及到實際物理內存分配
3.GEM分配的內存要在用戶層能訪問,須要經過對mmap()的支持
4.GEM對象的內存能夠來至driver本身的分配,一樣能夠從外部模塊引入,也支持將GEM對象所管理的內存導出給其餘模塊使用
5.當GEM對象被多個模塊使用時,就涉及到buffer數據的同步
 
GEM對象建立
一個GEM對象一般須要嵌入到driver私有數據結構中(相似於基類)。
目前的kernel中提供了helper函數,這些函數就是在嵌入了GEM對象的基礎上實現的。
kernel中提供了幾種經常使用到的GEM對象的擴展,咱們會討論到CMA、shmem這兩種擴展,圍繞這二者有相應的helper函數。
前文提到GEM把實際的內存配實際上留給了drivers本身實現,從CMA、shmem的名字便可知,這種擴展分別對應從CMA或shmem分配實際的物理內存。
 
物理內存分配
CMA(Contiguous Memory Allocator)是linux系統早期啓動時,預留的一段內存池。
CMA用於分配大塊的、連續的物理內存。
當GPU或display模塊不支持MMU時,使用CMA來分配內存是不錯的選擇。
 
CMA做爲GEM對象的內存分配:
struct drm_gem_cma_object {
  struct drm_gem_object base;
  dma_addr_t paddr;
  struct sg_table *sgt;
  void *vaddr;
};
 
base:GEM對象
paddr:分配的內存物理地址
sgt:經過PRIME導入的scatter/gather table,這個table上的物理地址必須保證是連續的。
vaddr:分配的內存虛擬地址
 
函數drm_gem_cma_create()經過調用__drm_gem_cma_create()完成一個struct drm_gem_cma_object對象分配和初始化,而後經過dma_alloc_wc()分配指定大小的內存。
 
struct drm_gem_cma_object *drm_gem_cma_create(struct drm_device *drm,
                          size_t size)
{
    struct drm_gem_cma_object *cma_obj;
    int ret;
 
 
    size = round_up(size, PAGE_SIZE);
 
 
    cma_obj = __drm_gem_cma_create(drm, size);
    if (IS_ERR(cma_obj))
        return cma_obj;
 
 
    cma_obj->vaddr = dma_alloc_wc(drm->dev, size, &cma_obj->paddr,
                      GFP_KERNEL | __GFP_NOWARN);
    if (!cma_obj->vaddr) {
        drm_dbg(drm, "failed to allocate buffer with size %zu\n",
             size);
        ret = -ENOMEM;
        goto error;
    }
 
 
    return cma_obj;
 
 
error:
    drm_gem_object_put(&cma_obj->base);
    return ERR_PTR(ret);
}
 
MMAP:
 
GEM對提供了mmap()的支持,經過映射後usersapce能夠訪問GEM對象的backing memory。
一種是徹底由driver本身提供私有的ioctl實現。
 
GEM建議的方式是走mmap系統調用:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
 
前文提到一個GEM對象在usersapce看來就是一個u32的不透明handle值,這個handle值不能直接和mmap配合使用。
因此要如何經過mmap來映射一個GEM對象,使其能在usersapce被訪問呢?
DRM是經過mmap的offset參數來識別出一個要被映射的GEM對象的。
在一個GEM對象能被mmap映射前,這個GEM對象會調用函數drm_gem_create_mmap_offset()去分配一個fake offset。
而後driver自身須要實現一個獨有的ioctl(),將這個fake offset傳遞給usersapce。
當usersapce拿到這個fake offset後,做爲參數傳遞給mmap 的offset參數。
當mmap執行在kernel中DRM部分時,DRM經過這個offset參數返回GEM對象,獲取其上的backing memory,從而完成對這個GEM對象的映射。
 
若是GPU支持MMU,能夠用shmem分配memory。
shmem分配的物理頁可能不連續,由於GPU支持MMU,因此GPU也能訪問這種不連續的物理頁。使用shmem能夠充分利用缺頁異常分配memory的特性, 真正創建頁表是在用戶空間對映射地址進行read/write時,觸發缺頁異常時,才執行虛擬地址到物理地址的映射。
而若是GEM對象的backing memory是CMA時,在mmap系統調用,進入kernel driver部分執行時,就須要完成用戶虛擬地址到物理地址的映射。
 
static struct drm_gem_cma_object *
__drm_gem_cma_create(struct drm_device *drm, size_t size)
{
    struct drm_gem_cma_object *cma_obj;
    struct drm_gem_object *gem_obj;
    int ret;
 
 
    if (drm->driver->gem_create_object)
        gem_obj = drm->driver->gem_create_object(drm, size);
    else
        gem_obj = kzalloc(sizeof(*cma_obj), GFP_KERNEL);
    if (!gem_obj)
        return ERR_PTR(-ENOMEM);
 
 
    if (!gem_obj->funcs)
        gem_obj->funcs = &drm_gem_cma_default_funcs;
 
 
    cma_obj = container_of(gem_obj, struct drm_gem_cma_object, base);
 
 
    ret = drm_gem_object_init(drm, gem_obj, size);
    if (ret)
        goto error;
 
 
    ret = drm_gem_create_mmap_offset(gem_obj);
    if (ret) {
        drm_gem_object_release(gem_obj);
        goto error;
    }
 
 
    return cma_obj;
 
 
error:
    kfree(cma_obj);
    return ERR_PTR(ret);
}
 
 
PRIME IMPORT/EXPORT
 
GPU上完成一幀圖像的渲染後,一般要送到display模塊去顯示,或是在有多個GPU的桌面機上,GPU間的buffer切換。
這都涉及到將本地內存對象共享給其餘模塊(本質上是讓其餘模塊訪問GPU渲染後的framebuffer)。
一樣其餘模塊也可能指定一塊buffer,讓GPU把數據渲染在其之上,對GPU driver來講就須要引入某個內存buffer。
 
PRIME IMPORT/EXPORT是DRM的標準特性,GEM只是其中一個具體的實現的方式,由於本文只討論GEM,因此後續均討論不對這二者作區分。
 
這種導入/導出操做均由userspace來發起,由driver來提供具體的實現。
之因此要有userspace來發起,由於driver不能預先知道什麼時候須要導入/導出,也不能預先知道要導入/導出的去向來路。
DRM提供了兩個ioctl命令,分別對應導入和導出:
DRM_IOCTL_DEF(DRM_IOCTL_PRIME_HANDLE_TO_FD, drm_prime_handle_to_fd_ioctl, DRM_RENDER_ALLOW)
DRM_IOCTL_DEF(DRM_IOCTL_PRIME_FD_TO_HANDLE, drm_prime_fd_to_handle_ioctl, DRM_RENDER_ALLOW)
 
導出GEM:
咱們知道在userspace一個GEM對象經過一個handle來表示。
當要把這個GEM對象導出,咱們經過ioctl傳遞這個handle值給driver,而後driver會返回一個fd。
這個fd就是一個文件描述符,和經過open()系統調用返回的fd是同一個東西。
而這個fd能夠經過UNIX domain sockets在進程間傳遞。
 
從driver中返回這個fd是經過dma_buf來實現的。
dma_buf是專門設計來供多個模塊間進行DMA共享的。
 
導入GEM:
GEM對象是由driver建立,但backing memory是經過dma_buf的來獲取。
 
這篇筆記寫的潦草了一點,但願之後有時間能補全。
 
參考連接:
相關文章
相關標籤/搜索