The aim of this page is to provide some documentation on BCM2835's frame buffer, as this is not publicly documented by Broadcom. 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. A working (more or less) example code for the frame buffer can be found here.
Basic procedure to get stuff on screen
The basic procedure to get a frame buffer is:
- Set up a structure with the frame buffer specification (resolution, etc)
- Tell the GPU about this structure by writing to the mailbox
- Wait by reading from the mailbox for the GPU to modify this structure
- Write to the frame buffer at the pointer we got in stage 3
- 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 data read from the mailbox (with the 4 least significant bits set to zero) is 0 (or 1 including the channel)
- 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.
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).
To send data via the mailbox:
- Wait for space in the mailbox
- Write ((data << 4) | channel) to the write register
To receive data via the mailbox:
- Wait for the mailbox to be non-empty
- Execute a memory barrier
- Read from the read register
- Check the lowest 4 bits of the read value for the correct channel
- If the channel is not the one we wish to read from (i.e: 1), go to step 1
- Return the read value >> 4
- This 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.
- The 32 bit value we actually send over the mailbox (including the channel) is (ADDRESS | 1) where ADDRESS is the address of the structure. This is equivalent to sending as the data (ADDRESS >> 4) (remember we do data << 4)
- The mailbox channel for the GPU is channel 1.
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:
- The read register for mailbox 0 at offset 0x00
- The status register for mailbox 0 at offset 0x1C
- The write register for mailbox 0 at offset 0x20 (this is actually the read register for mailbox 1).
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|
|12-15||virtual_height||Virtual Height -- easiest thing to do is to set this to height|
|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.|
|28-31||y_offset||Offset in the y direction. The easiest thing to do is to set this to zero.|
|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.