summaryrefslogtreecommitdiff
path: root/md/writeup/raspberry5_baremetal_framebuffer.md
blob: 681c486a05ed8d038275d0b27cb670872be7f0ab (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
title: Raspberry 5 baremetal framebuffer example
keywords:raspi5,asm,c,kernel,arm64

# Raspberry 5 baremetal framebuffer example


## Intro

The Raspberry Pi 5 has an easy way to draw graphics on the display by using a 
communication protocol through mailboxes. Here we will get framebuffer
address and draw some simple primitives onto screen. There is not much clear 
documentation on how things should work all based on current examples and experimenting.

## Programming mailbox

### Packets

To communicate with the mailbox, you need to create an array with commands, input and 
output elements. Array elements are uint32_t.

```c
array[0] //first element
...
array[n] //last element
```

First element is size of array in bytes and request tag, last value is
the last tag value

Here is the list of mailbox commands https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface 
commands consist of tag, amount of input bytes and output bytes.

Example of allocate buffer command:
```txt
Tag: 0x00040001
Request:
	Length: 4
	Value:
		u32: alignment in bytes
Response:
	Length: 8
	Value:
		u32: frame buffer base address in bytes
		u32: frame buffer size in bytes
```

this translates to array values
```c
cmd[n+0] = 0x00040001
cmd[n+1] = 8
cmd[n+2] = 4
cmd[n+3] = 0
cmd[n+4] = 0
```

### Addresses

## Setting up the framebuffer

Raspberry Pi5 doesn't provide much that you can do with framebuffers
comparing to version 4. And there isn't much documentation on full
capabilities. Simple example how to use framebuffer is request for
framebuffer address and directly write values to the memmory range.

```c
fb_mbox[0] = 8*4;
fb_mbox[1] = MBOX_REQUEST;
fb_mbox[2] = MBOX_TAG_GETFB;
fb_mbox[3] = 8; //buffer size
fb_mbox[4] = 4; //alignment
fb_mbox[5] = 0; //frame buffer address
fb_mbox[6] = 0; //size in bytes

fb_mbox[7] = MBOX_TAG_LAST;
```

The result will be in fb_mbox[5], which needs to be masked and can then be accessed directly:

```c
fb_address = fb_mbox[5] & 0x3fffffff;
```

buffer must be aligned by 16-bytes.

To submit the buffer to mailbox for processing write to MBOX1_WRITE register.
Check for response by reading MBOX1_STATUS register. Check MBOX0_READ that
its matches the request address. And check the response status status it will
be located at second index of request buffer. 



## Drawing pixel

Drawing a pixel is as simple as calculating pixel position and write 32bit value

```c
void fb_draw_pixel(unsigned int x, unsigned int y) {
    mmio_write(fb_address + (((SCREEN_X*y)+x)*4),0xFFFFFFFF);
}
```

extended example is with using colors in format ARGB32

```c
void fb_draw_pixel_rgb(unsigned int x, unsigned int y, unsigned char r, unsigned char g, unsigned char b) {
    mmio_write(fb_address + (((SCREEN_X*y)+x)*4),0xFF000000|((unsigned int)r<<16)|((unsigned int)g<<8)|((unsigned int)b));
}
```

## Source code
__config.txt__
```txt
# Core settings
arm_64bit=1
kernel=kernel8.img
[all]
# UART configuration for debugging
enable_uart=1
uart_2ndstage=1
uart_debug=1
uart_clkrate=48000000
uart_baudrate=115200

# RP1 chip configuration (Pi5 I/O chip)
# Disable PCIe reset to access RP1 from bare metal
pciex4_reset=0

# Memory settings
gpu_mem=64
total_mem=1024

# Boot settings
disable_splash=1
boot_delay=0

# Debug settings
uart_2ndstage=1

# Pi5 specific GPIO/UART
dtparam=uart0=on

# Disable unused services to reduce boot time
enable_jtag_gpio=1
enable_rp1_uart=1
pciex4_reset=0
core_freq_min=500
hdmi_group=1
hdmi_mode=16
```

__mailbox.h__
```c
#ifndef __MAILBOX_H
#define __MAILBOX_H

//#define MMIO_BASE 0x1c00000000UL
#define MMIO_BASE_1 0x107C000000
//#define MMIO_BASE 0xc00000000UL
//#define MBOX_REG_OFFSET (MMIO_BASE+0x40008000)
//#define MBOX_REG_OFFSET (MMIO_BASE+0x8000)
#define MBOX_REG_OFFSET (MMIO_BASE_1+0x13880)
//#define MBOX_REG_OFFSET (MMIO_BASE+0xB880)


#define MBOX_COMMAND_BUFFER_SIZE 36
#define MBOX1_OFFSSET 0x20

#define MBOX0_READ       (MBOX_REG_OFFSET + 0x0)
#define MBOX0_POLL       (MBOX_REG_OFFSET + 0x10)
#define MBOX0_SENDER     (MBOX_REG_OFFSET + 0x14)
#define MBOX0_STATUS     (MBOX_REG_OFFSET + 0x18)
#define MBOX0_CONFIG     (MBOX_REG_OFFSET + 0x1C)
#define MBOX1_WRITE      (MBOX_REG_OFFSET+MBOX1_OFFSSET + 0x00)
#define MBOX1_STATUS     (MBOX_REG_OFFSET+MBOX1_OFFSSET + 0x18)
#define MBOX_RESPONSE   0x80000000
#define MBOX_RESP_ERR   0x80000001
#define MBOX_FULL       0x80000000
#define MBOX_EMPTY      0x40000000
#define MBOX_REQUEST    0x00000000

#define MBOX_CH_VIDEOCORE 0x8

#define MBOX_TAG_FW_VERSION 0x00001
#define MBOX_TAG_SETPOWER   0x28001
#define MBOX_TAG_SETCLKRATE 0x38002
#define MBOX_TAG_SETPHYWH   0x48003
#define MBOX_TAG_SETVIRTWH  0x48004
#define MBOX_TAG_SETVIRTOFF 0x48009
#define MBOX_TAG_SETDEPTH   0x48005
#define MBOX_TAG_SETPXLORDR 0x48006
#define MBOX_TAG_GETFB      0x40001
#define MBOX_TAG_GETPITCH   0x40008
#define MBOX_TAG_LAST       0x00000

//unsigned int mbox_call(volatile unsigned int *request, unsigned char channel);
unsigned int mbox_call(unsigned char channel);
#endif
```
__mailbox.c__
```c
#include <mailbox.h>
#include <pl011.h>
#include <mmio.h>

extern volatile unsigned int __attribute__((aligned(16))) fb_mbox[MBOX_COMMAND_BUFFER_SIZE];
extern unsigned int fb_address;

//unsigned int mbox_call(volatile unsigned int *request, unsigned char channel) {
unsigned int mbox_call(unsigned char channel) {
    unsigned int addr = ((unsigned int)((unsigned long) &fb_mbox) & ~0xF) | (channel & 0xF);
    while ((mmio_read(MBOX1_STATUS) & MBOX_FULL) != 0);

    unsigned int p_addr = (uint32_t)fb_mbox;
    mmio_write(MBOX1_WRITE,MBOX_CH_VIDEOCORE|p_addr);

    while (1) {
        while (mmio_read(MBOX1_STATUS) & MBOX_EMPTY);
        if (addr == mmio_read(MBOX0_READ)) {
          return fb_mbox[1] == MBOX_RESPONSE;
        }
    }
}
```

__framebuffer.h__
```c
#ifndef __FRAMEBUFFER_H
#define __FRAMEBUFFER_H

void fb_init();
void fb_fw_version();
void fb_draw_pixel(unsigned int x, unsigned int y);

#endif
```

__framebuffer.c__
```c
#include <framebuffer.h>
#include <mailbox.h>
#include <pl011.h>
#include <mmio.h>

#define FB_SCREEN_WIDTH 1920
#define FB_SCREEN_HEIGHT 1080

volatile unsigned int __attribute__((aligned(16))) fb_mbox[MBOX_COMMAND_BUFFER_SIZE];
unsigned int fb_address = 0x0;


unsigned int width, height, pitch, isrgb;
unsigned char *fb;

void fb_init() {
    fb_mbox[0] = 8*4;
    fb_mbox[1] = MBOX_REQUEST;
    fb_mbox[2] = MBOX_TAG_GETFB;
    fb_mbox[3] = 8;
    fb_mbox[4] = 4; 
    fb_mbox[5] = 0; //frame buffer address
    fb_mbox[6] = 0;
    fb_mbox[7] = MBOX_TAG_LAST;

    mbox_call(MBOX_CH_VIDEOCORE);
    pl011_puts("mbox_call read\r\n");
    pl011_puts("fw version ");
    pl011_putu32(fb_mbox[5]);
    fb_address = fb_mbox[5] & 0x3fffffff;
    pl011_puts("\r\n");
}

void fb_fw_version() {
    fb_mbox[0] = 5*4;
    fb_mbox[1] = MBOX_REQUEST;

    fb_mbox[2] = MBOX_TAG_FW_VERSION;
    fb_mbox[3] = 4;
    fb_mbox[4] = 0; //fw version

    fb_mbox[5] = MBOX_TAG_LAST;   

    mbox_call(MBOX_CH_VIDEOCORE);
    //pl011_puts("mbox_call read\r\n");
    pl011_puts("fw version ");
    pl011_putu32(fb_mbox[4]);
    pl011_puts("\r\n");
}

#define SCREEN_X 1920
#define SCREEN_Y 1080
#define BITS_PER_PIXEL 32

void fb_draw_pixel(unsigned int x, unsigned int y) {
    mmio_write(fb_address + (((SCREEN_X*y)+x)*4),0xFFFFFFFF);
}
```

## Links
[/writeup/raspberry5_baremetal_helloworld.md](/writeup/raspberry5_baremetal_helloworld.md)  
https://www.rpi4os.com/part5-framebuffer/  
https://github.com/babbleberry/rpi4-osdev/blob/master/part5-framebuffer/mb.h  
https://github.com/valvers/arm-tutorial-rpi/blob/master/part-5/armc-017/rpi-mailbox-interface.h  
https://www.valvers.com/open-software/raspberry-pi/bare-metal-programming-in-c-part-5/#mailboxes  
https://github.com/raspberrypi/linux/blob/rpi-6.12.y/arch/arm64/boot/dts/broadcom/bcm2712.dtsi#L229C3-L234C5  
https://blog.benjdoherty.com/2019/05/21/Exploring-Hardware-Compositing-With-the-Raspberry-Pi/#Double-Buffering-the-Display-List  
https://github.com/bejado/raspberry-pi-os/blob/master/src/hvs/src/kernel.c  
https://github.com/librerpi/lk-overlay/blob/master/platform/bcm28xx/mailbox/mailbox.c
https://github.com/raspberrypi/linux/pull/6312/files  
https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface  
https://github.com/raspberrypi/linux/blob/rpi-6.12.y/drivers/mailbox/bcm2835-mailbox.c
https://github.com/rsta2/circle/blob/master/include/circle/bcm2835.h  
https://github.com/raspberrypi/linux/blob/rpi-6.12.y/include/soc/bcm2835/raspberrypi-firmware.h  
https://bitbanged.com/posts/understanding-rpi/the-mailbox/  
https://github.com/BrianSidebotham/arm-tutorial-rpi/blob/master/part-5/readme.md
https://fossies.org/linux/qemu/hw/misc/bcm2835_mbox.c
https://satyria.de/arm/index.php?title=Graphics_in_C_(PI5)  
https://forums.raspberrypi.com/viewtopic.php?t=380434  
https://grasslab.github.io/osdi/en/hardware/mailbox.html