DRM GEM 驅動程序開發(dumb)

前言

在上一篇《關於 DRM 中 DUMB 和 PRIME 名字的由來》 文章中,我們知道了 dumb buffer 名字的由來。本篇,我們將一起來寫一個最簡單的 GEM 驅動程序。

驅動程序

#include <drm/drmP.h>
#include <drm/drm_gem_cma_helper.h>

static struct drm_device drm;

static const struct file_operations mygem_fops = {
        .owner = THIS_MODULE,
        .open = drm_open,
        .release = drm_release,
        .unlocked_ioctl = drm_ioctl,
        .poll = drm_poll,
        .read = drm_read,
        .mmap = drm_gem_cma_mmap,
};

static struct drm_driver mygem_driver = {
        .driver_features        = DRIVER_GEM,
        .fops                   = &mygem_fops,

        .dumb_create            = drm_gem_cma_dumb_create,
        .gem_vm_ops             = &drm_gem_cma_vm_ops,
        .gem_free_object_unlocked = drm_gem_cma_free_object,

        .name                   = "my-gem",
        .desc                   = "My GEM Driver",
        .date                   = "20200601",
        .major                  = 1,
        .minor                  = 0,
};

static int __init mygem_init(void)
{
        drm_dev_init(&drm, &mygem_driver, NULL);
        drm_dev_register(&drm, 0);

        return 0;
}

module_init(mygem_init);

測試程序

#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>

int main(int argc, char **argv)
{
        int fd;
        char *vaddr;
        struct drm_mode_create_dumb create_req = {};
        struct drm_mode_destroy_dumb destroy_req = {};
        struct drm_mode_map_dumb map_req = {};

        fd = open("/dev/dri/card0", O_RDWR);

        create_req.bpp = 32;
        create_req.width = 240;
        create_req.height = 320;
        drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req);
        printf("create dumb: handle = %u, pitch = %u, size = %llu\n",
                create_req.handle, create_req.pitch, create_req.size);

        map_req.handle = create_req.handle;
        drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req);
        printf("get mmap offset 0x%llx\n", map_req.offset);

        vaddr = mmap(0, create_req.size, PROT_WRITE, MAP_SHARED, fd, map_req.offset);
        strcpy(vaddr, "This is a dumb buffer!");
        munmap(vaddr, create_req.size);

        vaddr = mmap(0, create_req.size, PROT_READ, MAP_SHARED, fd, map_req.offset);
        printf("read from mmap: %s\n", vaddr);
        munmap(vaddr, create_req.size);

        getchar();

        destroy_req.handle = create_req.handle;
        drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req);
        close(fd);

        return 0;
}   

描述:先創建一個 dumb buffer,然後將其 mmap 到 user-space,往裏寫入一串字符串,然後重新映射,讀取並打印 buffer 中的內容。

運行結果

root@ubuntu:~# ./dumb 
create dumb: handle = 1, pitch = 960, size = 307200
get mmap offset 0x10000000
read from mmap: This is a dumb buffer!

驅動講解

  1. DRIVER_GEM:該 feature 告訴 DRM 框架本驅動支持 GEM 操作,如 buffer 的分配和釋放,以及 GEM OPEN/FLINK/CLOSE 等操作。
  2. dumb_create:分配 dumb buffer 的回調接口,主要完成三件事:
    (1)創建 gem object
    (2)創建 gem handle
    (3)分配物理 buffer (也可以等到後面再分配)
    本例中直接使用 CMA helper 函數實現,該函數內部會分配最終的物理 buffer。
  3. mmap:創建 dumb buffer 的目的就是要拿去給 CPU 畫圖,因此沒有 mmap 的 dumb buffer 是沒有靈魂的,所以必須實現。通常使用 drm_gem_mmap() 來實現。
  4. gem_vm_ops:主要爲 mmap 服務,必須實現。下一篇文章會對它做詳細介紹。

通常在看 DRM 文檔時,還會提到 dumb_map_offsetdumb_destroy 這兩個接口,分別對應各自的 ioctl 函數。如果驅動沒有實現這兩個回調接口, 那麼 DRM 框架會使用默認的 drm_gem_dumb_map_offset()drm_gem_dumb_destroy() 代替。

爲什麼要執行 DRM_IOCTL_MODE_MAP_DUMB ?

許多人剛開始寫 dumb buffer 應用程序時都會困惑,明明有 mmap 函數,爲什麼中間要再插一腳 DRM_IOCTL_MODE_MAP_DUMB ?光看名字很容易讓人誤以爲該 ioctl 是在執行 mmap 的動作,那爲什麼要添加這個 ioctl 呢?

要回答這個問題其實很簡單,設想一下,假如你當前創建了2個 dumb buffer,你要對其中一個做 mmap 操作,請問 mmap 函數應該如何知道你當前要操作的是哪個 buffer ?因爲你當前使用的是 card0 的 fd,而不是 dumb buffer 的 fd,所以你沒法通過 fd 來對其進行區分。而 size 和 flag 這些參數都不能隨意修改,因此只能通過 offset 參數來 workaround,從而告訴 mmap 當前具體要操作的是哪個 dumb buffer。

所以,對 drm device 進行 mmap 操作時,傳進去的 offset 參數並不是真正的內存偏移量,而是一個 gem object 的索引值。通過該索引值,drm 驅動就可以準確定位到當前具體要操作的是哪個 gem object,進而獲取到與該 object 相對應的物理 buffer,並對其做真正的 mmap 操作。

那如何知道某個 gem object 的索引值呢?所以纔有了 DRM_IOCTL_MODE_MAP_DUMB !你給它一個 gem handle,它返回給你一個 offset,就這麼簡單!而該 ioctl 對應的底層實現就是前面提到的 dumb_map_offset 回調接口。(個人認爲該 ioctl 的名字改成 DRM_IOCTL_MODE_CREATE_MAP_OFFSET 可能會更好一點 :-)

這也是爲什麼上面的示例運行結果中,獲取到的 offset 值爲 0x10000000,而不是我們通常認爲的偏移 0,原因就在於此(DRM 文檔中將其稱爲 fake offset,即僞偏移)。

總結

  1. 哪裏有 dumb_create,哪裏就有 mmap
  2. 哪裏有 mmap,哪裏就有 gem_vm_ops
  3. mmap 中的 offset 參數是個假的。
  4. 沒有 DRIVER_MODESETDRIVER_GEM 一樣可以做成一個獨立的 DRM 驅動。

在這裏插入圖片描述

源碼下載

Github: drivertest

參考資料

  1. 楊楓_mind CSDN 博客
  2. DRM Memory Management

文章彙總: DRM (Direct Rendering Manager) 學習簡介

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章