RPi Framebuffer

From eLinux.org
Revision as of 15:31, 9 July 2012 by Techmeology (talk | contribs) (Partial Memory Map at Address 0x7E00B880)
Jump to: navigation, search

Introduction

The aim of this page is to provide some documentation on BCM2835's frame buffer, as this is not publicly documented by Broadcom, and is necessary to write a basic OS for the Raspberry Pi. Most of this was worked out by reading the Linux source code (mostly drivers/video/bcm2708_fb.c and arch/arm/mach-bcm2708/) and experimentation. Note: At times I have used a different naming convention to that used by Broadcom's kernel source, partially because I used slightly different semantics where the kernel source is either unclear or seemed confusing. A working (more or less) example code for the frame buffer can be found here (note: this page includes corrections to the large comment in fb.c).

Basic procedure to get stuff on screen

The basic procedure to get a frame buffer is:

  1. Set up a structure with the frame buffer specification (resolution, etc)
  2. Tell the GPU about this structure by writing to the mailbox
  3. Wait by reading from the mailbox for the GPU to modify this structure
  4. Write to the frame buffer at the pointer we got in stage 3

Notes

  • Only step 4 is required for subsequent writes to the frame buffer.
  • Currently, I do not know how to enable the HDMI output, so this will always operate the composite, and not the HDMI (there have been suggestions that this might be achieved using a combination of config.txt, parameters specified by the GPU, and appropriate resolution settings).
  • The two conditions for successfully acquiring a frame buffer are:
    • The 28 bits of data read from the mailbox is zero
    • The pointer in the structure is non-zero after the mailbox read
  • For some reason (that I haven't discovered yet), the code sometimes fails (not meeting one of the two criteria above) unless it is retried. Hence steps 1-3 of the basic procedure are tried in a loop until success.
  • Once we have the frame buffer, we can just write to it. The pixels (in 24 bit mode) are RGB ordered by y then x coordinate. The address of a subpixel is given by: y * pitch + x * 3 + rgb_channel, where rgb_channel is 0 for red, 1 for green, and 2 for blue.

Memory mapped registers

The bus address for the mailbox memory mapped registers is 0x7E00B880. This corresponds to an ARM physical address of 0x2000B880 (the address we use from the ARM processor, and hence here). We use three registers from the mail box:

  • MAIL0_READ
  • MAIL0_WRITE
  • MAIL0_STATUS

Partial Memory Map at Address 0x7E00B880

Offset Address Name (as used on this page) Description
0x00 MAIL0_READ The read register for mailbox 0 at offset (the Linux source mentions something of "and the next 4 words", but I've found it sufficient to read only from this address)
0x10 MAIL0_PEAK Read from the mailbox without removing data from it.
0x14 MAIL0_SENDER Sender ID (bottom 2 bits only)
0x18 MAIL0_STATUS The status register for mailbox 0
0x1C MAIL0_CONFIG The configuration register for mailbox 0
0x20 MAIL0_WRITE The write register for mailbox 0 (this is actually the read register for mailbox 1).

Format of MAIL0_READ

Bits Name (as used on this page) Description
0-3 channel The mailbox channel number from which the data originated
4-31 data The 28 bits of data sent to the CPU

Format of the MAIL0_STATUS

Bits Name (as used on this page) Description
0-29 N/A Not documented here. Unused?
30 MAIL_FULL Set if the mailbox is empty, and thus no more data is available to be read from it.
31 MAIL_EMPTY Set if the mailbox is full, and thus no more data can be written to it.

Format of the MAIL0_WRITE

Bits Name (as used on this page) Description
0-3 channel The mailbox channel number to which the data is to be sent
4-31 data The 28 bits of data to be sent to the destination

Mailbox operations

Read/write operatitons on the mailbox consist of transfering data via a 32 bit register. 28 bits of this 32 bit register are the data to be sent to the receiver, while the lower 4 bits specify the channel (channel 1 is the frame buffer, but there are others).

Procedure to Send Via the Mailbox

  1. Wait for space in the mailbox
    1. Execute a memory barrier
    2. Read MAIL0_STATUS
    3. Goto step 1 if MAIL_FULL bit is set
  2. Write channel and data to MAIL0_WRITE
  3. Execute a memory barrier

Procedure to Receive Via the Mailbox

  1. Wait for the mailbox to be non-empty
    1. Execute a memory barrier
    2. Read MAIL0_STATUS
    3. Goto step 1 if MAIL_EMPTY bit is set
  2. Execute a memory barrier
  3. Read from MAIL0_READ
  4. Check the channel (lowest 4 bits) of the read value for the correct channel
  5. If the channel is not the one we wish to read from (i.e: 1), go to step 1
  6. Return the data (i.e: the read value >> 4)

Mailbox Channels

Channel Number Description
0 Power management interface
1 Framebuffer
2 Virtual UART
3 VCHIQ interface
4 LEDs interface
5 Buttons interface
6 Touch screen interface

Notes

  • The read procedure will not work if we're interested in reading from more than one channel as it does not handle the reception of other channels' data
  • The address of the frame buffer structure must be at least a multiple of 16 (in order to be accurately transmitted in the 28 bits available in the mailbox). I used (1 << 22) as the address of this structure.

Format of GPU Framebuffer Structure

The structure expected by the GPU for initializing the frame buffer is as follows:

Bytes Name (as used on this page) Description
0-3 width Width of the requested frame buffer. My code uses a value of 640 here.
4-7 height Height of the requested frame buffer. My code uses a value of 480 here.
8-11 virtual_width Virtual Width -- easiest thing to do is to set this to width. I'm not entirely certain what this does (perhaps rescales?).
12-15 virtual_height Virtual Height -- easiest thing to do is to set this to height. I'm not entirely certain what this does (perhaps rescales?).
16-19 pitch Number of bytes between each row of the frame buffer. This is set by the GPU; in my code, I set it to zero before passing the structure to the GPU.
20-23 depth The number of bits per pixel of the requested frame buffer. I have not managed to make this work with anything other than a value of 24, however the Linux source seems to use 16 bit?!
24-27 x_offset Offset in the x direction. The easiest thing to do is to set this to zero. I'm not entirely certain exactly what this does.
28-31 y_offset Offset in the y direction. The easiest thing to do is to set this to zero. I'm not entirely certain exactly what this does.
32-35 pointer The pointer to the frame buffer into which your code should write. This is set by the GPU. I set this to zero before passing the structure to the GPU.
36-39 size The size of the frame buffer. Set by the GPU. I set this to zero before passing the structure to the GPU.

Each of the 32-bit values should be little endian (i.e: that of the included ARM processor). Hence a simple C struct with a data type of uint32_t for each of these fields will suffice.