DRM dumb buffer

DRM dumb buffer

簡單紀錄 DRM dumb buffer

DRM dumb buffer?

refer DRM GEM 驅動程式開發(dumb)

驅動講解

  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。
  • mmap:建立 dumb buffer 的目的就是要拿去給 CPU 畫圖,因此沒有 mmap 的 dumb buffer 是沒有靈魂的,所以必須實現。通常使用 drm_gem_mmap() 來實現。
  • gem_vm_ops:主要為 mmap 服務,必須實現。下一篇文章會對它做詳細介紹。

通常在看 DRM 文件時,還會提到 dumb_map_offset 和 dumb_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,即偽偏移)。

總結 哪裡有 dumb_create,哪裡就有 mmap。 哪裡有 mmap,哪裡就有 gem_vm_ops。 mmap 中的 offset 參數是個假的。 沒有 DRIVER_MODESET,DRIVER_GEM 一樣可以做成一個獨立的 DRM 驅動。

Mapping Dumb buffer 兩種方式

透過 handle(這個方式一定要知道 操作的 DRM e.g. card0)

a. 透過 handle 取得 offset(索引值) b. 透過 DRM + offset(索引值), mmap 對應的 dumb buffer

example code

struct drm_mode_map_dumb map_arg;
memset(&map_arg, 0, sizeof(map_arg));
map_arg.handle = drm_context->handle;
if (drmIoctl(drm_context->drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_arg) != 0) {
    perror("drmIoctl DRM_IOCTL_MODE_MAP_DUMB");
    return errno;
}

drm_context->buf =
    (char *)mmap(0, drm_context->size, PROT_READ | PROT_WRITE, MAP_SHARED, drm_context->drm_fd, map_arg.offset);
if (drm_context->buf == MAP_FAILED) {
    perror("coult not mmap drm dumb buffer");
    return errno;
}

DEBUG_PRINT("import drm_fd=%d, map->fd=%d map->handle=%d buf:%p\n", drm_context->drm_fd, drm_context->dma_fd,
            drm_context->handle, drm_context->buf);

透過 DMA fd

透過 IPC 對方知道 size 直接對 DMA fd map 就行, 不需要像 handle

export DMA, 注意 export 權限 DRM_CLOEXEC | DRM_RDWR, 默認 0 是 read only, 沒注意 read only 會讓你 map後嘗試 write 造成 segfault

if (drmPrimeHandleToFD(drm_fd, drm_context->handle, DRM_CLOEXEC | DRM_RDWR, &drm_context->dma_fd) != 0) {
    perror("could not export as dma");
    ret = -1;
    break;
}

map DMA

int permission = PROT_READ;
if (fcntl(drm_context->dma_fd, F_GETFL) & O_RDWR) {
    permission |= PROT_WRITE;
    DEBUG_PRINT("map as read/write\n");
} else {
    DEBUG_PRINT("map as read-only\n");
}

drm_context->buf = (char *)mmap(0, drm_context->size, permission, MAP_SHARED, drm_context->dma_fd, 0);
if (drm_context->buf == MAP_FAILED) {
    perror("mmap");
    return -1;
}

DEBUG_PRINT("import drm_fd=%d, map->fd=%d map->handle=%d buf:%p\n", drm_context->drm_fd, drm_context->dma_fd,
            drm_context->handle, drm_context->buf);

sample

// gcc -Wall -Wextra -g -o drm_dumb_buffer drm_dumb_buffer.c $(pkg-config --cflags --libs libdrm)

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <xf86drm.h>

#define DEBUG_PRINT(fmt, ...)                                                                \
    do {                                                                                     \
        fprintf(stderr, "[%s:%d][%s]" fmt, __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__); \
    } while (0);

struct drm_buffer_s {
    enum DMA_TYPE {
        DMA_TYPE_UNKNOWN,
        DMA_TYPE_INTERNAL_ALLOCATE,
        DMA_TYPE_EXTERNAL,
    } dma_type;
    int drm_fd;       // DRM fd, e.g. card0
    uint32_t handle;  // GEM handle
    uint32_t pitch;   // width * bytes per pixel
    uint64_t size;    //
    char *buf;        // mapping memory
    int dma_fd;       // DMA fd
};

static int drm_buffer_map(struct drm_buffer_s *drm_context) {
    DEBUG_PRINT("drm_context->size: %ld dma_fd:%d\n", drm_context->size, drm_context->dma_fd);

    int permission = PROT_READ;
    if (fcntl(drm_context->dma_fd, F_GETFL) & O_RDWR) {
        permission |= PROT_WRITE;
        DEBUG_PRINT("map as read/write\n");
    } else {
        DEBUG_PRINT("map as read-only\n");
    }

    drm_context->buf = (char *)mmap(0, drm_context->size, permission, MAP_SHARED, drm_context->dma_fd, 0);
    if (drm_context->buf == MAP_FAILED) {
        perror("mmap");
        return -1;
    }

    DEBUG_PRINT("import drm_fd=%d, map->fd=%d map->handle=%d buf:%p\n", drm_context->drm_fd, drm_context->dma_fd,
                drm_context->handle, drm_context->buf);
    return 0;
}

static int drm_buffer_map_by_handle(struct drm_buffer_s *drm_context) {
    if (drm_context->handle <= 0) {
        return -1;
    }

    struct drm_mode_map_dumb map_arg;
    memset(&map_arg, 0, sizeof(map_arg));
    map_arg.handle = drm_context->handle;
    if (drmIoctl(drm_context->drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_arg) != 0) {
        perror("drmIoctl DRM_IOCTL_MODE_MAP_DUMB");
        return errno;
    }

    drm_context->buf =
        (char *)mmap(0, drm_context->size, PROT_READ | PROT_WRITE, MAP_SHARED, drm_context->drm_fd, map_arg.offset);
    if (drm_context->buf == MAP_FAILED) {
        perror("coult not mmap drm dumb buffer");
        return errno;
    }

    DEBUG_PRINT("import drm_fd=%d, map->fd=%d map->handle=%d buf:%p\n", drm_context->drm_fd, drm_context->dma_fd,
                drm_context->handle, drm_context->buf);
    return 0;
}

static void drm_buffer_unmap(struct drm_buffer_s *drm_context) {
    if (drm_context->buf && drm_context->buf != MAP_FAILED) {
        if (munmap(drm_context->buf, drm_context->size) == -1) {
            perror("munmap failed");
        }
        drm_context->buf = NULL;
    }
}

static void drm_buffer_destroy(struct drm_buffer_s *drm_context) {
    drm_unmap(drm_context);

    if (drm_context->dma_type == DMA_TYPE_INTERNAL_ALLOCATE) {
        // close DMA fd
        if (drm_context->dma_fd > 0) {
            close(drm_context->dma_fd);
        }

        // destroy dumb buffer
        struct drm_mode_destroy_dumb dreq;
        memset(&dreq, 0, sizeof(dreq));
        dreq.handle = drm_context->handle;
        drmIoctl(drm_context->drm_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
    }

    drm_context->dma_fd = 0;
    drm_context->handle = 0;
}

static int drm_buffer_create(int drm_fd, int bpp, int width, int height, struct drm_buffer_s *drm_context) {
    int ret = 0;
    struct drm_mode_create_dumb arg;
    memset(&arg, 0, sizeof(arg));

    do {
        arg.bpp = bpp;
        arg.width = width;
        arg.height = height;
        // DEBUG_PRINT("bpp=%d, width=%d, height=%d\n", arg.bpp, arg.width, arg.height);
        // 0 if successful, or -1 otherwise.
        if (drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &arg) == -1) {
            perror("failed to create dumb buffer");
            ret = -1;
            break;
        }

        drm_context->drm_fd = drm_fd;
        drm_context->handle = arg.handle;
        drm_context->size = arg.size;
        drm_context->pitch = arg.pitch;
        DEBUG_PRINT("bpp=%d, width=%d, height=%d, size=%lld, pitch=%d handle:%d\n", arg.bpp, arg.width, arg.height,
                    arg.size, arg.pitch, arg.handle);

        drm_context->dma_type = DMA_TYPE_INTERNAL_ALLOCATE;

        // export as DMA fd
        if (drmPrimeHandleToFD(drm_fd, drm_context->handle, DRM_CLOEXEC | DRM_RDWR, &drm_context->dma_fd) != 0) {
            perror("could not export as dma");
            ret = -1;
            break;
        }
        DEBUG_PRINT("drm_fd=%d, dma_fd=%d\n", drm_fd, drm_context->dma_fd);
    } while (0);

    // clean up
    if (ret != 0) {
        drm_destroy(drm_context);
    }

    return ret;
}

#define CARD0 "/dev/dri/card0"

int main(int argc, char const *argv[]) {
    int width = 1920;
    int height = 1080;
    int color_depth = 3;
    int bpp = color_depth * 8;  // bits per pixel

    int drm_fd = -1;
    int ret = 0;

    struct drm_buffer_s drm_context;
    do {
        memset(&drm_context, 0, sizeof(drm_context));

        drm_fd = open(CARD0, O_RDWR | O_CLOEXEC);
        if (drm_fd < 0) {
            DEBUG_PRINT("could not open %s %s\n", CARD0, strerror(errno));
            ret = -1;
            break;
        }

        if (drm_buffer_create(drm_fd, bpp, width, height, &drm_context) != 0) {
            DEBUG_PRINT("could not create DRM dumb buffer\n");
            ret = -1;
            break;
        }

        if (drm_buffer_map(&drm_context) != 0) {
            DEBUG_PRINT("could not map DRM dumb buffer\n");
            ret = -1;
            break;
        }

        // if (drm_buffer_map_by_handle(&drm_context) != 0) {
        //     DEBUG_PRINT("could not map DRM dumb buffer\n");
        //     ret = -1;
        //     break;
        // }

        memset(drm_context.buf, 255, drm_context.size);

        drm_buffer_unmap(&drm_context);

    } while (0);

    drm_buffer_destroy(&drm_context);

    if (drm_fd > 0) {
        close(drm_fd);
        drm_fd = -1;
    }

    return ret;
}