Detecting Raspberry Pi Board
There are several Raspberry Pi boards, all shipped with an ARM SoC, but each has different memory addresses.
Detecting the board
In order to detect which board you have, you have to read the MIDR_ELx system register. The manufacturer is encoded in bits [31:24], and the model in the so called PartNum in the bits [15:4]. PartNum is implementation defined, meaning each manufacturer are free to assign values as they like.
Board | AArch64 | PartNum | MMIO base address |
---|---|---|---|
Rpi1 / Rpi Zero | No | 0xB76 | 0x20000000 |
Rpi2 | No | 0xC07 | 0x3F000000 |
Rpi3 | Yes | 0xD03 | 0x3F000000 |
Rpi4 | Yes | 0xD08 | 0xFE000000 |
You can run kernels compiled for AArch32 mode on all boards, and they are loaded into memory at 0x8000. The RaspberryPi 3 and upwards also supports AArch64, loaded into 0x80000, and uses a different Assembly to read the same system register. Regardless to mode and load address, MMIO base address is tied to the board.
#include <stdint.h>
void _start()
{
uint32_t reg;
uint32_t *mmio_base;
char *board;
/* read the system register */
#if __AARCH64__
asm volatile ("mrs %x0, midr_el1" : "=r" (reg));
#else
asm volatile ("mrc p15,0,%0,c0,c0,0" : "=r" (reg));
#endif
/* get the PartNum, detect board and MMIO base address */
switch ((reg >> 4) & 0xFFF) {
case 0xB76: board = "Rpi1"; mmio_base = 0x20000000; break;
case 0xC07: board = "Rpi2"; mmio_base = 0x3F000000; break;
case 0xD03: board = "Rpi3"; mmio_base = 0x3F000000; break;
case 0xD08: board = "Rpi4"; mmio_base = 0xFE000000; break;
default: board = "????"; mmio_base = 0x20000000; break;
}
}
Once you know the base address, you can add the peripheral's address to it to get its IO registers. For example, BCM System Timer can be accessed at base address + 0x3000, and the UART0 is at base address + 0x201000.