Drawing in the frame buffer with QNX on the raspberry pi 4

Olof Astrand
8 min readJan 12, 2025

--

I have done previous articles where I explore how you can access the framebuffer on QNX 7.0, but it should work on newer versions of QNX as well. Using this techniques I was able to develop a very simple QNX application that can draw in the framebuffer.

https://olof-astrand.medium.com/exploring-the-mailbox-on-the-raspberry-pi4-with-qemu-and-qnx-using-the-debugger-over-an-emulated-a05665b5a6f2

AI generated image of my bedroom

This should probably be put in a github repo but for now it is only available here. It also implements a simple sin and cos function float my_cos(float x) in order to not have to link with the math library.

The code is “inspired” by this https://www.rpi4os.com/

If you start qemu in the debugger

gdb --args qemu-stm32/build/qemu-system-aarch64 \
-M raspi4b \
-kernel ifs-rpi4.bin \
-append "startup-bcm2711-rpi4 -vvv -D miniuart" \
-drive id=sdcard,if=none,format=raw,file=sd.img \
-d unimp \
-trace "bcm*" \
-trace file=trace_output.log \
-s \
-serial tcp::12345,server,nowait \
-serial stdio \
-serial tcp::12346,server,nowait

You can now insert breakpoints in qemu, in the most interesting parts and see if they seem to be correct.

b bcm2835_mbox_read
b bcm2835_mbox_write
b bcm2835_property_read
b bcm2835_property_write

The trace (trace_output.log) after running the program

bcm2835_mbox_read mbox read sz:4 addr:0x98 data:0x40000000
bcm2835_mbox_irq mbox irq:ARM level:0
bcm2835_mbox_write mbox write sz:4 addr:0xa0 data:0x27008
bcm2835_mbox_property mbox property tag:0x00048003 in_sz:8 out_sz:8
bcm2835_mbox_property mbox property tag:0x00048004 in_sz:8 out_sz:8
bcm2835_mbox_property mbox property tag:0x00048009 in_sz:8 out_sz:8
bcm2835_mbox_property mbox property tag:0x00048005 in_sz:4 out_sz:4
bcm2835_mbox_property mbox property tag:0x00048006 in_sz:4 out_sz:4
bcm2835_mbox_property mbox property tag:0x00040008 in_sz:4 out_sz:4
bcm2835_mbox_property mbox property tag:0x00000000 in_sz:0 out_sz:0
bcm2835_mbox_irq mbox irq:ARM level:0
bcm2835_mbox_irq mbox irq:ARM level:0
bcm2835_mbox_read mbox read sz:4 addr:0x98 data:0x0
bcm2835_mbox_irq mbox irq:ARM level:0
bcm2835_mbox_read mbox read sz:4 addr:0x80 data:0x27008
bcm2835_mbox_irq mbox irq:ARM level:0
bcm2835_mbox_read mbox read sz:4 addr:0x98 data:0x40000000
bcm2835_mbox_irq mbox irq:ARM level:0
bcm2835_mbox_write mbox write sz:4 addr:0xa0 data:0x27008
bcm2835_mbox_property mbox property tag:0x00040001 in_sz:8 out_sz:8
bcm2835_mbox_irq mbox irq:ARM level:0
bcm2835_mbox_irq mbox irq:ARM level:0
bcm2835_mbox_read mbox read sz:4 addr:0x98 data:0x0
bcm2835_mbox_irq mbox irq:ARM level:0
bcm2835_mbox_read mbox read sz:4 addr:0x80 data:0x27008
bcm2835_mbox_irq mbox irq:ARM level:0

The actual code,

#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/neutrino.h>

// Mailbox constants
#define MBOX_REQUEST 0x00000000
#define MBOX_RESPONSE 0x80000000

#define MBOX_STATUS_FULL 0x80000000
#define MBOX_STATUS_EMPTY 0x40000000

#define MBOX_CH_PROP 8

#define MBOX_REG_BASE 0xfe00b880
#define MBOX_BUFFER_ADDR 0x100
#define MBOX_BUFFER_SIZE 128
#define MBOX_SEND_CHANNEL 8

#define MBOX_BASE 0xFE00B880
#define MBOX_READ 0
#define MBOX_STATUS 6
#define MBOX_WRITE 8

// Mailbox tags
#define MBOX_TAG_LAST 0
#define MBOX_TAG_SETPHYWH 0x00048003
#define MBOX_TAG_SETVIRTWH 0x00048004
#define MBOX_TAG_SETVIRTOFF 0x00048009
#define MBOX_TAG_SETDEPTH 0x00048005
#define MBOX_TAG_SETPXLORDR 0x00048006
#define MBOX_TAG_GETFB 0x00040001
#define MBOX_TAG_GETPITCH 0x00040008
#define MBOX_TAG_ALLOCATE_BUFFER 0x00040001

// Global variables
unsigned int width, height, pitch, isrgb;
unsigned char *fb;

// VGA palette (16 colors)
unsigned int vgapal[16] = {
0xFF000000, // Black
0xFFAA0000, // Blue
0xFF00AA00, // Green
0xFFAAAA00, // Cyan
0xFF0000AA, // Red
0xFFAA00AA, // Magenta
0xFF00AAAA, // Brown
0xFFAAAAAA, // Light Gray
0xFF555555, // Dark Gray
0xFFFF5555, // Light Blue
0xFF55FF55, // Light Green
0xFFFFFF55, // Light Cyan
0xFF5555FF, // Light Red
0xFFFF55FF, // Light Magenta
0xFF55FFFF, // Yellow
0xFFFFFFFF // White
};

// Mailbox register mapping
volatile uint32_t *mbox_regs = NULL;

// Mailbox call function
int mbox_call(uint32_t *buffer, uint32_t buf_phys, uint8_t channel) {
// Ensure buffer is 16-byte aligned
if (buf_phys & 0xF) {
fprintf(stderr, "Buffer address not 16-byte aligned\n");
return -1;
}

// Wait until we can write to the mailbox
while (mbox_regs[MBOX_STATUS] & MBOX_STATUS_FULL);

// Write the address of our message to the mailbox with the channel
mbox_regs[MBOX_WRITE] = (buf_phys & ~0xF) | (channel & 0xF);

// Now wait for the response
while (1) {
// Is there a reply?
while (mbox_regs[MBOX_STATUS] & MBOX_STATUS_EMPTY);

uint32_t resp = mbox_regs[MBOX_READ];

// Is it a response to our message?
if ((resp & 0xF) == channel) {
if ((resp & ~0xF) == (buf_phys & ~0xF)) {
// Message received, check the response
if (buffer[1] == MBOX_RESPONSE) {
return 0; // Success
} else {
fprintf(stderr, "Mailbox response error\n");
return -1; // Error
}
}
}
}
return -1; // Should not reach here
}

// Framebuffer initialization
int my_fb_init() {
// Map mailbox registers if not already mapped
uint32_t buf_phys;
static void *buf;

int fd;
off_t offset;
fd = posix_typed_mem_open("/sysram&below1G", O_RDWR, POSIX_TYPED_MEM_ALLOCATE_CONTIG);
if (fd < 0) {
printf("Failed to open typed memory(%s) \n", strerror( errno ));
exit(EXIT_FAILURE);
}
buf = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_NOCACHE, MAP_SHARED, fd, 0);
if (buf == MAP_FAILED) {
printf("Failed to allocate buffer!\n");
close(fd);
exit(EXIT_FAILURE);
}

if (mem_offset(buf, NOFD, 1, &offset, 0) != EOK) {
munmap(buf, 0x1000);
close(fd);
printf("Failed to obtain physical address\n");
exit(EXIT_FAILURE);
}
buf_phys = (uint32_t)offset;
mbox_regs = mmap_device_memory(0, 0x80, PROT_NOCACHE|PROT_READ|PROT_WRITE, 0, MBOX_BASE);


// Build the message
uint32_t *mbox = (uint32_t *)buf;
int i = 0;

mbox[i++] = 0; // Placeholder for total size
mbox[i++] = MBOX_REQUEST;

mbox[i++] = MBOX_TAG_SETPHYWH;
mbox[i++] = 8;
mbox[i++] = 0;
mbox[i++] = 640; // Width 1920
mbox[i++] = 480; // Height 1080

int setvirtwh = i;
mbox[i++] = MBOX_TAG_SETVIRTWH;
mbox[i++] = 8;
mbox[i++] = 0;
mbox[i++] = 640; // Width
mbox[i++] = 480; // Height

mbox[i++] = MBOX_TAG_SETVIRTOFF;
mbox[i++] = 8;
mbox[i++] = 0;
mbox[i++] = 0; // x offset
mbox[i++] = 0; // y offset

mbox[i++] = MBOX_TAG_SETDEPTH;
mbox[i++] = 4;
mbox[i++] = 0;
mbox[i++] = 32; // Bits per pixel

mbox[i++] = MBOX_TAG_SETPXLORDR;
mbox[i++] = 4;
mbox[i++] = 0;
mbox[i++] = 1; // RGB


int getpitch = i;
mbox[i++] = MBOX_TAG_GETPITCH;
mbox[i++] = 4;
mbox[i++] = 0;
mbox[i++] = 0; // Pitch (response)

mbox[i++] = MBOX_TAG_LAST;
// Padding to make it 16-byte aligned
while (i & 3) {
mbox[i++] = 0;
}

// Total size
mbox[0] = i * sizeof(uint32_t);

// Send the message
if (mbox_call(mbox, buf_phys, MBOX_CH_PROP) != 0) {
fprintf(stderr, "mbox_call failed\n");
munmap(buf, 4096);
return -1;
}

// Check the response
if (mbox[1] != MBOX_RESPONSE) {
fprintf(stderr, "Invalid response\n");
munmap(buf, 4096);
return -1;
}

//
i=0;
fprintf(stderr, "Allocate and request buffer\n");


mbox[i++] = 0; // Placeholder for total size
mbox[i++] = MBOX_REQUEST;


int allocbuf = i;
mbox[i++] = MBOX_TAG_ALLOCATE_BUFFER;
mbox[i++] = 8;
mbox[i++] = 0;
mbox[i++] = 16; // Alignment
mbox[i++] = 0; // Framebuffer address (response)

mbox[i++] = MBOX_TAG_LAST;
// Padding to make it 16-byte aligned
while (i & 3) {
mbox[i++] = 0;
}

// Total size
mbox[0] = i * sizeof(uint32_t);

// Send the message
if (mbox_call(mbox, buf_phys, MBOX_CH_PROP) != 0) {
fprintf(stderr, "mbox_call failed\n");
munmap(buf, 4096);
return -1;
}

// Check the response
if (mbox[1] != MBOX_RESPONSE) {
fprintf(stderr, "Invalid response\n");
munmap(buf, 4096);
return -1;
}

// Get framebuffer address and pitch
uint32_t fb_addr = mbox[allocbuf + 3];
uint32_t fb_size = mbox[allocbuf + 4];
pitch = mbox[getpitch + 3];

// Convert GPU address to ARM address
fb_addr &= 0x3FFFFFFF;

// Map framebuffer into process address space
fb = mmap_device_memory(NULL, fb_size, PROT_READ | PROT_WRITE, 0, fb_addr);
if (fb == MAP_FAILED) {
perror("mmap_device_memory (framebuffer)");
munmap(buf, 4096);
return -1;
}

// Set global variables
width = mbox[setvirtwh + 3];
height = mbox[setvirtwh + 4];

// Clean up
munmap(buf, 4096);
close(fd);

return 0;
}

// Draw a pixel at (x, y) with the specified attribute (color)
void drawPixel(int x, int y, unsigned char attr) {
if (x < 0 || x >= (int)width || y < 0 || y >= (int)height) return;

int offs = y * pitch + x * 4; // 4 bytes per pixel (RGBA8888)
unsigned int color = vgapal[attr & 0x0f];

*((unsigned int *)(fb + offs)) = color;
}

// Draw a rectangle from (x1, y1) to (x2, y2)
void drawRect(int x1, int y1, int x2, int y2, unsigned char attr, int fill) {
int y = y1;
while (y <= y2) {
int x = x1;
while (x <= x2) {
if ((x == x1 || x == x2) || (y == y1 || y == y2))
drawPixel(x, y, attr);
else if (fill)
drawPixel(x, y, (attr & 0xf0) >> 4);
x++;
}
y++;
}
}

// Draw a line from (x1, y1) to (x2, y2)
void drawLine(int x1, int y1, int x2, int y2, unsigned char attr) {
int dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
int dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
int err = dx + dy, e2;

while (1) {
drawPixel(x1, y1, attr);
if (x1 == x2 && y1 == y2) break;
e2 = 2 * err;
if (e2 >= dy) { err += dy; x1 += sx; }
if (e2 <= dx) { err += dx; y1 += sy; }
}
}

// Draw a circle centered at (x0, y0) with the specified radius
void drawCircle(int x0, int y0, int radius, unsigned char attr, int fill) {
int x = radius;
int y = 0;
int err = 0;

while (x >= y) {
if (fill) {
drawLine(x0 - y, y0 + x, x0 + y, y0 + x, (attr & 0xf0) >> 4);
drawLine(x0 - x, y0 + y, x0 + x, y0 + y, (attr & 0xf0) >> 4);
drawLine(x0 - x, y0 - y, x0 + x, y0 - y, (attr & 0xf0) >> 4);
drawLine(x0 - y, y0 - x, x0 + y, y0 - x, (attr & 0xf0) >> 4);
}
drawPixel(x0 - y, y0 + x, attr);
drawPixel(x0 + y, y0 + x, attr);
drawPixel(x0 - x, y0 + y, attr);
drawPixel(x0 + x, y0 + y, attr);
drawPixel(x0 - x, y0 - y, attr);
drawPixel(x0 + x, y0 - y, attr);
drawPixel(x0 - y, y0 - x, attr);
drawPixel(x0 + y, y0 - x, attr);

if (err <= 0) {
y += 1;
err += 2 * y + 1;
}
if (err > 0) {
x -= 1;
err -= 2 * x + 1;
}
}
}

float my_fmod(float x, float y) {
if (y == 0.0f) return x;

float div = x / y;
float q = div >= 0.0f ? (float)((int)div) : (float)((int)div - 1);
return x - y * q;
}

float normalize_angle(float x) {
// Normalize angle to [-π, π]
x = my_fmod(x, 2 * 3.14159f);
if (x > 3.14159f)
x -= 2 * 3.14159f;
else if (x < -3.14159f)
x += 2 * 3.14159f;
return x;
}

float my_cos(float x) {
x = normalize_angle(x);
float x2 = x * x;
float x4 = x2 * x2;
return 1.0f - x2/2.0f + x4/24.0f - (x2 * x4)/720.0f;
}

float my_sin(float x) {
x = normalize_angle(x);
float x2 = x * x;
float x3 = x2 * x;
float x5 = x2 * x3;
float x7 = x2 * x5;
return x - x3/6.0f + x5/120.0f - x7/5040.0f;
}

// Initialize the sine and cosine lookup tables


// Example main function to demonstrate usage
int main(int argc, char *argv[]) {
printf("Hello from framebuffer demo\n");

my_fb_init();

// Clear the screen
memset(fb, 0, height * pitch);

// Draw some graphics
drawRect(100, 100, 300, 200, 0x0E, 1); // Filled rectangle
drawLine(50, 50, 400, 400, 0x0F); // Line
drawCircle(500, 300, 100, 0x0C, 0); // Circle

// Draw lines starting in the middle and ending in a circle
for (int i = 0; i < 360; i += 15) {
int x = 200 + 100 * my_cos(i * 3.14159 / 180);
int y = 300 + 100 * my_sin(i * 3.14159 / 180);
drawLine(200, 300, x, y, 0x0D);
}

// Keep the application running to view the graphics
printf("Press Enter to exit...\n");
getchar();

// Clean up
munmap(fb, height * pitch);
munmap((void *)mbox_regs, 0x1000);

return EXIT_SUCCESS;
}

When testing on actual hardware it worked but colours was wrong. If you get “Mailbox response error” double check that the monitor supports the resolution and that the request is properly aligned.

This code will probably interfere with other graphics libraries but for simple drawings is good enough. Only some simple text rendering would be nice. You can easily add it with this, https://github.com/babbleberry/rpi4-osdev/blob/master/part5-framebuffer/terminal.h

Depending on your monitor, read this. https://www.raspberrypi.com/documentation/computers/config_txt.html#video-options and this, https://pimylifeup.com/raspberry-pi-screen-resolution/

I was successful with these settings + enable uart.

config.txt

core_freq_min=500
hdmi_group=1
hdmi_mode=16

--

--

Olof Astrand
Olof Astrand

No responses yet