Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c

linux

rust

mmap

drm

For some time, I have been playing with 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.

I’m trying to create an equivalent to libdrm in Rust, that wouldn’t be just a binding to the C library but would directly use the syscalls. This is not an easy task, given that there is almost no documentation for DRM out there, but I'm following this example in C to get hints on where to start.

I've now arrived at the point where I’m supposed to create a dumb buffer and map it in memory, so I can modify pixel per pixel what appears on screen. For that, I have to use mmap, but I get a really weird error.

Here is a minimal working code in 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;
}

This is the exact same code in Rust. Of course, my real code has much more things in it, but this minimal one is enough to get the error:

#![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);
    }
}

It compiles just fine, but when I execute it, I get this error.

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

Using an extern block to link directly against the original C mmap function rather than the Rust one of crate libc doesn’t change anything.

I took a look at how this project called mmap, and tried doing the same, to be sure that size and offset are page-aligned, but it didn’t change anything, since they were already page-aligned.

This SO question uses a facility of the stdlib called std::os::MemoryMap, but it doesn’t exist anymore.

like image 529
Karamazov Avatar asked Apr 30 '16 22:04

Karamazov


1 Answers

So I used strace as recommended by Shepmaster, and found the problem: the offset field of the CMapDumb structure should have been a u64 instead of an u32 (I have been too quick copying from the C original). The actual offset returned by the DRM_IOCTL_MODE_MAP_DUMB IOCTL is 33 bits long, and I was losing the most significant one. It now works fine.

like image 72
Karamazov Avatar answered Nov 16 '22 05:11

Karamazov