在C语言中使用Linux的Direct Rendering Manager在Rust中调用dumbbuffer上的mmap失败

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

一段时间以来,我一直在使用Linux的Direct Rendering Manager,它可以执行非常低级的图形管理。这通常是在C中借助libdrm或直接使用DRM标头完成的。

我正在尝试在Rust中创建一个与libdrm等效的文件,这不仅是对C库的绑定,而是直接使用syscalls。鉴于目前几乎没有有关DRM的文档,这并不是一件容易的事,但是我正在C中跟踪此示例以获取有关从何处开始的提示。

我现在已经到达创建一个哑缓冲并将其映射到内存中的位置,这样我就可以修改每个像素在屏幕上显示的像素了。为此,我必须使用mmap,但是会收到一个非常奇怪的错误。

这是C语言中最少的工作代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#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 TODRM

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

    // STEP 4: PREPARE FORMAPPING

    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\
"
, strerror(errno));
        return 1;
    }

    // STEP 5: MAPPINGPROPER

    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\
"
, strerror(errno));
        return 1;
    } else {
        printf("Address of mapped data: 0x%x\
"
, map);
    }

    return 0;
}

这与Rust中的代码完全相同。当然,我的实际代码中包含很多东西,但是这一最小的代码足以得到错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#![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 TODRM

    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 FORMAPPING

    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: MAPPINGPROPER

    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块直接链接到原始的C mmap函数,而不是Rust而不是Crate libc之一,不会改变任何内容。

我看了看这个项目如何命名为mmap,并尝试做同样的事情,以确保sizeoffset是页面对齐的,但是它没有任何改变,因为它们已经页面对齐。

这个SO问题使用名为std::os::MemoryMap的stdlib工具,但现在不存在了。


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