Ramfb: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
(Describe how to initialize a QEMU ramfb device)
 
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(4 intermediate revisions by 4 users not shown)
Line 1: Line 1:
== Introduction ==
== Introduction ==
ramfb is a simple way to get graphics on QEMU via a framebuffer in memory on embedded platforms like ARM or riscv.
ramfb is a simple way to get graphics on QEMU via a framebuffer in memory on embedded platforms like ARM or riscv.
It works by adding -device ramfb to your QEMU command line and then configuring ramfb via fw_cfg.
It works by adding <code>-device ramfb</code> to your QEMU command line and then configuring ramfb via fw_cfg.
ramfb requires QEMU DMA support which should be available on platforms that have fw_cfg as MMIO (as opposed to x86 IO ports).
ramfb requires QEMU DMA support which should be available on platforms that have fw_cfg as MMIO (as opposed to x86 IO ports).


== Explanation ==
== Explanation ==
To initialize the ramfb device we need to write the following structure to the fw_cfg entry with the name <code>etc/ramfb</code> using [[QEMU_fw_cfg#DMA]]:
To initialize the ramfb device we need to write the following structure to the fw_cfg entry with the name <code>etc/ramfb</code> using [[QEMU_fw_cfg#DMA]]:
<source lang="c">
<syntaxhighlight lang="c">
struct RAMFBCfg {
struct RAMFBCfg {
uint64_t addr;
uint64_t addr;
Line 15: Line 15:
uint32_t stride;
uint32_t stride;
};
};
</syntaxhighlight>
</source>
The field fourcc is a 4 letter code that identifies the pixelformat expected by the framebuffer. The available fourcc codes can be found in the [https://github.com/qemu/qemu/blob/master/include/standard-headers/drm/drm_fourcc.h qemu source code]
The field fourcc is a 4 letter code that identifies the pixelformat expected by the framebuffer. The available fourcc codes can be found in the [https://github.com/qemu/qemu/blob/master/include/standard-headers/drm/drm_fourcc.h qemu source code]


Line 22: Line 22:
== Example ==
== Example ==
A minimal rust example would be:
A minimal rust example would be:
<source lang="c">
<syntaxhighlight lang="c">
use core::ffi::CStr;
use core::ffi::CStr;
use core::ptr::addr_of;
use core::ptr::addr_of;
Line 127: Line 127:
}
}
}
}
</syntaxhighlight>
</source>


Now you should be able to write to the address you passed as a framebuffer and see the result on screen:
Now you should be able to write to the address you passed as a framebuffer and see the result on screen:
<source lang="c">
<syntaxhighlight lang="c">
for x in 0..(stride*height) {
for x in 0..(stride*height) {
fb_addr.add(x as usize).write_volatile(0xFF);
fb_addr.add(x as usize).write_volatile(0xFF);
}
}
</syntaxhighlight>
</source>


This should produce a white screen.
This should produce a white screen.

Latest revision as of 05:42, 9 June 2024

Introduction

ramfb is a simple way to get graphics on QEMU via a framebuffer in memory on embedded platforms like ARM or riscv. It works by adding -device ramfb to your QEMU command line and then configuring ramfb via fw_cfg. ramfb requires QEMU DMA support which should be available on platforms that have fw_cfg as MMIO (as opposed to x86 IO ports).

Explanation

To initialize the ramfb device we need to write the following structure to the fw_cfg entry with the name etc/ramfb using QEMU_fw_cfg#DMA:

struct RAMFBCfg {
    uint64_t addr;
    uint32_t fourcc;
    uint32_t flags;
    uint32_t width;
    uint32_t height;
    uint32_t stride;
};

The field fourcc is a 4 letter code that identifies the pixelformat expected by the framebuffer. The available fourcc codes can be found in the qemu source code

After that the framebuffer will be mapped at the address specified in the addr field. Writing pixel data to it will immediately show the pixels on screen.

Example

A minimal rust example would be:

use core::ffi::CStr;
use core::ptr::addr_of;
use core::mem;

#[repr(C)]
struct FWCfgFile {
    size: u32,
    select: u16,
    reserved: u16,
    name: [u8; 56]
}

#[repr(C, packed)]
struct FWCfgDmaAccess {
    control: u32,
    len: u32,
    addr: u64
}

#[repr(C, packed)]
struct RamFBCfg {
    addr: u64,
    fmt: u32,
    flags: u32,
    w: u32,
    h: u32,
    st: u32
}

const QEMU_CFG_DMA_CTL_READ:   u32 = 0x02;
const QEMU_CFG_DMA_CTL_SELECT: u32 = 0x08;
const QEMU_CFG_DMA_CTL_WRITE:  u32 = 0x10;

unsafe fn qemu_dma_transfer (control: u32, len: u32, addr: u64) {
    // Address of the DMA register on the aarch64 virt board
    let fw_cfg_dma: *mut u64 = 0x9020010 as *mut u64; 
    let dma = FWCfgDmaAccess {
        control: control.to_be(),
        len: len.to_be(),
        addr: addr.to_be()
    };
    unsafe {
        fw_cfg_dma.write_volatile((addr_of!(dma) as u64).to_be());
    }
    // Wait until DMA completed or error bit set
}

pub fn setup_ramfb(fb_addr: *mut u8, width: u32, height: u32) {
    let mut num_entries: u32 = 0;
    let fw_cfg_file_directory = 0x19;
    unsafe {
        qemu_dma_transfer((fw_cfg_file_directory << 16
                        | QEMU_CFG_DMA_CTL_SELECT 
                        | QEMU_CFG_DMA_CTL_READ) as u32,
                        mem::size_of::<u32>(),
                        addr_of!(num_entries) as u64);
    }

    // QEMU DMA is BE so need to byte swap arguments and results on LE
    num_entries = num_entries.to_be();

    let ramfb = FWCfgFile {
        size: 0,
        select: 0,
        reserved: 0,
        name: [0; 56]
    };

    for _ in 0..num_entries {
        unsafe {
            qemu_dma_transfer(QEMU_CFG_DMA_CTL_READ,
                            mem::size_of::<FWCfgFile>() as u32,
                            addr_of!(ramfb) as u64);
        }
        let entry = CStr::from_bytes_until_nul(&ramfb.name).unwrap();
        let entry = entry.to_str().unwrap();
        if entry == "etc/ramfb" {
            break;
        }
    }

    // See fourcc:
    // https://github.com/qemu/qemu/blob/54294b23e16dfaeb72e0ffa8b9f13ca8129edfce/include/standard-headers/drm/drm_fourcc.h#L188
    let pixel_format = ('R' as u32) | (('G' as u32) << 8) | 
    (('2' as u32) << 16) | (('4' as u32) << 24);

    // Stride 0 means QEMU calculates from bpp_of_format*width:
    // https://github.com/qemu/qemu/blob/54294b23e16dfaeb72e0ffa8b9f13ca8129edfce/hw/display/ramfb.c#L60
    let ramfb_cfg = RamFBCfg {
        addr: (fb_addr as u64).to_be(),
        fmt: (pixel_format).to_be(),
        flags: (0 as u32).to_be(),
        w: (width as u32).to_be(),
        h: (height as u32).to_be(),
        st: (0 as u32).to_be()
    };

    unsafe {
        qemu_dma_transfer((ramfb.select.to_be() as u32) << 16 
      | QEMU_CFG_DMA_CTL_SELECT 
      | QEMU_CFG_DMA_CTL_WRITE, mem::size_of::<RamFBCfg>() as u32,
        addr_of!(ramfb_cfg) as u64);
    }
}

Now you should be able to write to the address you passed as a framebuffer and see the result on screen:

for x in 0..(stride*height) {
    fb_addr.add(x as usize).write_volatile(0xFF);
}

This should produce a white screen.

See Also

QEMU_fw_cfg#DMA

External Links

QEMU fw_cfg Documentation

QEMU fourcc source

Example code in C

Example code in rust