在 C 中工作时,在 Rust 中使用 Linux' Direct Rendering Manager 在 dumbuffer 上调用 mmap 失败

Calling mmap on dumbbuffer with Linux’ Direct Rendering Manager in Rust fails while working in C

一段时间以来,我一直在玩 Linux' Direct Rendering Manager, which allows one to do some very very low level graphics management. This is usually done in C, with help of libdrm, or directly using the DRM headers

我正在尝试在 Rust 中创建一个与 libdrm 等效的东西,它不仅是对 C 库的绑定,而且会直接使用系统调用。这不是一件容易的事,因为那里几乎没有 DRM 文档,但我正在关注 this example in C 以获取有关从哪里开始的提示。

我现在已经到了我应该创建一个哑缓冲区并将其映射到内存中的地步,这样我就可以逐个像素地修改屏幕上显示的内容。为此,我必须使用 mmap,但我得到了一个非常奇怪的错误。

这是 C:

中的最小工作代码
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <drm/drm.h>
#include <drm/drm_mode.h>
#include <sys/ioctl.h>
#include <sys/mman.h>

int main()  {

    // STEP 1: GET ACCESS TO DRM

    int fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
    if (fd < 0) {
        printf("Error in function open(): %s\n", strerror(errno));
        return 1;
    }

    // STEP 2: CREATE DUMBBUFFER

    struct drm_mode_create_dumb dreq;
    dreq.height = 1080,
    dreq.width  = 1920,
    dreq.bpp    = 32,
    dreq.flags  = 0,
    dreq.handle = 0,
    dreq.pitch  = 0,
    dreq.size   = 0;
    int ret = ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &dreq);
    if (ret == -1)  {
        printf("Call to DRM_IOCTL_MODE_CREATE_DUMB failed: %s\n",
            strerror(errno));
        return 1;
    }

    // STEP 3: ADD FRAMEBUFFER

    struct drm_mode_fb_cmd creq;
    creq.fb_id  = 0;
    creq.width  = dreq.width;
    creq.height = dreq.height;
    creq.pitch  = dreq.pitch;
    creq.bpp    = dreq.bpp;
    creq.depth  = 24;
    creq.handle = dreq.handle;
    ret = ioctl(fd, DRM_IOCTL_MODE_ADDFB, &creq);
    if (ret == -1)  {
        printf("Call to DRM_IOCTL_MODE_ADDFB failed: %s\n", strerror(errno));
        return 1;
    }

    // STEP 4: PREPARE FOR MAPPING

    struct drm_mode_map_dumb mreq;
    mreq.handle = dreq.handle;
    mreq.pad    = 0;
    mreq.offset = 0;
    ret = ioctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
    if (ret == -1)  {
        printf("Call to DRM_IOCTL_MODE_MAP_DUMB failed: %s\n", strerror(errno));
        return 1;
    }

    // STEP 5: MAPPING PROPER

    void *map = mmap(0, dreq.size, PROT_READ | PROT_WRITE, MAP_SHARED, 
        fd, mreq.offset);
    if (map == MAP_FAILED)  {
        printf("Error in function mmap(): %s\n", strerror(errno));
        return 1;
    } else {
        printf("Address of mapped data: 0x%x\n", map);
    }

    return 0;
}

这与 Rust 中的代码完全相同。当然,我的真实代码里面有更多的东西,但是这个最小的代码足以得到错误:

#![feature(libc)]

extern crate libc;
use self::libc::{c_char, c_int, c_ulong, c_void, off_t, size_t};

extern {
    pub fn ioctl(fd : c_int, request : c_ulong, arg : *mut c_void) -> c_int;
}

fn errno() -> c_int {
    unsafe { *libc::__errno_location() }
}

fn get_c_error() -> String  {
    unsafe {
        let strerr = libc::strerror(errno()) as *mut u8;
        let length = libc::strlen(strerr as *const c_char) as usize;

        let mut string = String::with_capacity(length);

        for i in 0..length   {
            let car = *strerr.offset(i as isize) as char;
            if car == (0 as char)   { break; }
            string.push(car);
        }

        string
    }
}

#[repr(C)]
struct CCreateDumb  {
    height : u32,
    width  : u32,
    bpp    : u32,
    _flags : u32,
    handle : u32,
    pitch  : u32,
    size   : u64,
}

#[repr(C)]
struct CFrameBuffer {
    _fb_id  : u32,
    _width  : u32,
    _height : u32,
    _pitch  : u32,
    _bpp    : u32,
    _depth  : u32,
    _handle : u32,
}

#[repr(C)]
struct CMapDumb {
    _handle : u32,
    _pad    : u32,
    offset  : u32,
}

fn main()   {

    // STEP 1: GET ACCESS TO DRM

    let pathname = "/dev/dri/card0".to_string();
    let fd : c_int = unsafe {
        libc::open(pathname.as_ptr() as *const c_char,
                    libc::O_RDWR | libc::O_CLOEXEC)
    };
    if fd < 0 {
       panic!("Error in call of C function open(): {}", get_c_error());
    }

    // STEP 2: CREATE DUMBBUFFER

    let mut dreq = CCreateDumb {
        height : 1080,
        width  : 1920,
        bpp    : 32,
        _flags : 0,
        handle : 0,
        pitch  : 0,
        size   : 0,
    };
    // NB : 0xc02064b2 = DRM_IOCTL_MODE_CREATE_DUMB
    let mut ret = unsafe { 
        ioctl(fd, 0xc02064b2 as c_ulong, &mut dreq as *mut _ as *mut c_void)
    };
    if ret == -1    {
        panic!("Call to DRM_IOCTL_MODE_CREATE_DUMB failed: {}", get_c_error());
    }

    // STEP 3: ADD FRAMEBUFFER

    let mut creq = CFrameBuffer {
        _fb_id  : 0,
        _width  : dreq.width,
        _height : dreq.height,
        _pitch  : dreq.pitch,
        _bpp    : dreq.bpp,
        _depth  : 24,
        _handle : dreq.handle,
    };
    // NB : 0xc01c64ae = DRM_IOCTL_MODE_ADDFB
    ret = unsafe { 
        ioctl(fd, 0xc01c64ae as c_ulong, &mut creq as *mut _ as *mut c_void)
    };
    if ret == -1    {
        panic!("Call to DRM_IOCTL_MODE_ADDFB failed: {}", get_c_error());
    }

    // STEP 4: PREPARE FOR MAPPING

    let mut mreq = CMapDumb {
        _handle : dreq.handle,
        _pad    : 0,
        offset  : 0,
    };
    // NB : 0xc01064b3 = DRM_IOCTL_MODE_MAP_DUMB
    ret = unsafe { 
        ioctl(fd, 0xc01064b3 as c_ulong, &mut mreq as *mut _ as *mut c_void)
    };
    if ret == -1    {
        panic!("Call to DRM_IOCTL_MODE_MAP_DUMB failed: {}", get_c_error());
    }

    // STEP 5: MAPPING PROPER

    let map = unsafe { 
        libc::mmap(
            0 as *mut c_void,
            dreq.size as size_t,
            libc::PROT_READ | libc::PROT_WRITE,
            libc::MAP_SHARED,
            fd,
            mreq.offset as off_t
        )
    };
    if map == libc::MAP_FAILED  {
        panic!("Error in call of C function mmap(): {}", get_c_error());
    } else {
        println!("Address of mapped data: 0x{:p}", map);
    }
}

它编译得很好,但是当我执行它时,我得到这个错误。

thread '' panicked at 'Error in call of C function mmap(): Invalid argument', memmapping.rs:139 note: Run with RUST_BACKTRACE=1 for a backtrace.

使用 extern 块到 link 直接针对原始 C mmap 函数而不是 crate libc 中的 Rust 函数不会改变任何东西。

我查看了 this project 如何调用 mmap,并尝试做同样的事情,以确保 sizeoffset 是页面对齐的,但是它没有改变任何东西,因为它们已经页面对齐。

使用了名为 std::os::MemoryMap 的 stdlib 工具,但它不再存在了。

所以我按照 Shepmaster 的建议使用了 strace,发现了问题:CMapDumb 结构的 offset 字段应该是 u64 而不是u32(我从C原版抄的太快了)。 DRM_IOCTL_MODE_MAP_DUMB IOCTL 返回的实际偏移量是 33 位长,我丢失了最重要的一位。现在可以正常使用了。