title: Raspberry 5 baremetal UART example keywords:raspi5,asm,c,kernel,arm64,uart,tty,serial # Raspberry 5 bare metal UART example ## Intro As base can check the [/writeup/raspberry5_baremetal_helloworld.md](/writeup/raspberry5_baremetal_helloworld.md) Getting basic communication for Raspberry 5 UART bare metal. ## Enabling UART To find out MMIO base for UART, first step to set flag so PCIe bus doesn't reset UART, ``` enable_rp1_uart=1 ``` if flag is set then UART will output string ``` RP1_UART 0000001c00030000 ``` where MMIO base is ```c #define MMIO_BASE 0x1c00000000UL ``` and UART base ```c #define PL011_REG_OFFSET MMIO_BASE+0x30000 ``` ## Configure UART Disable UART before configure it and then set desired settings. This case just to set to 8 bit mode. After configuration done enable RX/TX and UART. ```c //disable UART pl011_write32(PL011_CR,0x0); //config uart pl011_write32(PL011_LCRH, PL011_LCRH_WLEN_8BIT); //enable UART pl011_write32( PL011_CR, PL011_CR_UARTEN|PL011_CR_TXE|PL011_CR_RXE ); ``` ## Sending first characters Sending characters is done by writing 8 bits to register DR ```c #define PL011_DR 0x00 /* Data read or written from the interface. */ ``` if just write then raspi5 will do it faster then UART can process and characters will be lost in basic case only first 2 characters where out. So before writing to DR check that TX FIFO is not full and wait if its full ```c void pl011_putc(const uint8_t c) { while (pl011_read32(PL011_FR)&PL011_FR_TXFF); pl011_write32(PL011_DR,c); } ``` ## Receiving characters to receive characters read register DR and to check if RX is empty check registers FR RX FIFO Empty flag. ```c uint8_t pl011_getc() { return pl011_read8(PL011_DR); } ``` ## Basic command line Here is basic echo terminal loop that echo's back characters sent over UART. Loop checks FR register to see if RX FIFO isn't empty, and read character. Extra processing set for Enter to output "\r\n" and move to new line ```c while (1) { uint32_t rx_status = pl011_read32(PL011_FR); //empty? if not printout if (!(rx_status&PL011_FR_RXFE)) { uint8_t c = pl011_read8(PL011_DR); if (c == 0xD) { pl011_puts("\r\n"); } else { pl011_write8(PL011_DR,c); } } } ``` ## Source code For base setup check hello world example [/writeup/raspberry5_baremetal_helloworld.md](/writeup/raspberry5_baremetal_helloworld.md) ### pl011.h ```c #ifndef __PL011_H #define __PL011_H //#include typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; //static for now for raspi5 #define MMIO_BASE 0x1c00000000UL #define PL011_REG_OFFSET MMIO_BASE+0x30000 #define PL011_DR 0x00 /* Data read or written from the interface. */ #define PL011_FR 0x18 /* Flag register (Read only). */ #define PL011_IBRD 0x24 /* Integer baud rate divisor register. */ #define PL011_FBRD 0x28 /* Fractional baud rate divisor register. */ #define PL011_LCRH 0x2c /* Line control register. */ #define PL011_CR 0x30 /* Control register. */ #define PL011_IFLS 0x34 /* Interrupt fifo level select. */ #define PL011_IMSC 0x38 /* Interrupt mask. */ #define PL011_RIS 0x3c /* Raw interrupt status. */ #define PL011_MIS 0x40 /* Masked interrupt status. */ #define PL011_ICR 0x44 /* Interrupt clear register. */ #define PL011_DMACR 0x48 /* DMA control register. */ #define PL011_FR_RXFE (1 << 4)/* RX FIFO Empty */ #define PL011_FR_TXFF (1 << 5)/* TX FIFO full */ #define PL011_LCRH_WLEN_8BIT (3 << 5) /* 8bit */ #define PL011_CR_CTSEN (1 << 15) /* CTS hardware flow control enable */ #define PL011_CR_RTSEN (1 << 14) /* RTS hardware flow control enable */ #define PL011_CR_RTS (1 << 11) /* Request to send */ #define PL011_CR_DTR (1 << 10) /* Data transmit ready. */ #define PL011_CR_RXE (1 << 9) /* Receive enable */ #define PL011_CR_TXE (1 << 8) /* Transmit enable */ #define PL011_CR_LBE (1 << 7) /* Loopback enable */ #define PL011_CR_UARTEN (1 << 0) /* UART Enable */ #define pl011_read8(offset) (*(volatile uint8_t *)(PL011_REG_OFFSET+offset)) #define pl011_write8(offset,val) (*(volatile uint8_t *)(PL011_REG_OFFSET+offset)) = val #define pl011_read32(offset) (*(volatile uint32_t *)(PL011_REG_OFFSET+offset)) #define pl011_write32(offset,val) (*(volatile uint32_t *)(PL011_REG_OFFSET+offset)) = val void pl011_init(); void pl011_putc(const uint8_t c); void pl011_puts(const char *str); uint8_t pl011_getc(); ``` ### pl011.c ```c #include "pl011.h" void pl011_init() { //disable UART pl011_write32(PL011_CR,0x0); //config uart pl011_write32(PL011_LCRH, PL011_LCRH_WLEN_8BIT); //enable UART pl011_write32( PL011_CR, PL011_CR_UARTEN|PL011_CR_TXE|PL011_CR_RXE ); } void pl011_putc(const uint8_t c) { while (pl011_read32(PL011_FR)&PL011_FR_TXFF); pl011_write32(PL011_DR,c); } void pl011_puts(const char *str) { uint16_t cnt = 0; while (cnt<256) { if (str[cnt] != 0) { pl011_putc(str[cnt]); } else { break; } cnt++; } } uint8_t pl011_getc() { return pl011_read8(PL011_DR); } ``` ### kernel.c ```c #include void main() { pl011_init(); pl011_puts("Raspberry Pi5 UART demo?!\r\n"); while (1) { uint32_t rx_status = pl011_read32(PL011_FR); //empty? if not printout if (!(rx_status&PL011_FR_RXFE)) { uint8_t c = pl011_read8(PL011_DR); if (c == 0xD) { pl011_puts("\r\n"); } else { pl011_write8(PL011_DR,c); } } } } ``` Creating Makefile and linker script is left as exercise ## Links https://github.com/BarrelfishOS/barrelfish/blob/master/kernel/arch/arm/pl011.c https://github.com/DSERIOUSGUY/HobOS/blob/main/uart.c https://github.com/rsta2/circle/blob/master/lib/serial.cpp https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/tty/serial/amba-pl011.c?h=v6.16-rc4 https://developer.arm.com/documentation/ddi0183/latest/ [/writeup/raspberry5_baremetal_helloworld.md](/writeup/raspberry5_baremetal_helloworld.md) https://wiki.osdev.org/Detecting_Raspberry_Pi_Board https://github.com/dwelch67/raspberrypi/tree/master/uart01 https://github.com/bztsrc/raspi3-tutorial/blob/master/03_uart1/uart.c https://www.rpi4os.com/part3-helloworld/ https://www.valvers.com/open-software/raspberry-pi/bare-metal-programming-in-c-part-5/#aux-peripheral https://github.com/valvers/arm-tutorial-rpi/tree/master/part-5 https://jsandler18.github.io/tutorial/boot.html https://github.com/ARM-software/arm-trusted-firmware/blob/master/plat/rpi/rpi5/include/rpi_hw.h https://github.com/ARM-software/arm-trusted-firmware/blob/master/drivers/arm/pl011/aarch64/pl011_console.S https://elixir.bootlin.com/linux/v6.15.5/source/include/linux/amba/serial.h#L39 https://github.com/ARM-software/arm-trusted-firmware/blob/master/include/drivers/arm/pl011.h [/writeup/raspberry5_baremetal_helloworld.md](/writeup/raspberry5_baremetal_helloworld.md) https://krinkinmu.github.io/2020/11/29/PL011.html https://forums.raspberrypi.com/viewtopic.php?t=34112 https://datasheets.raspberrypi.com/bcm2711/bcm2711-peripherals.pdf https://datasheets.raspberrypi.com/rp1/rp1-peripherals.pdf https://github.com/raspberrypi/linux/blob/rpi-6.12.y/arch/arm64/boot/dts/broadcom/bcm2712.dtsi https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf