2021年1月23日 星期六

Raspberry Pi Zero W Project Part 1 - bare metal printf implementation

Step 0 - update config.txt and overlay

config.txt:

enable_uart=1
dtoverlay=miniuart-bt # or disable-bt as you want
kernel=bare_metal.bin

Please remember to download miniuart-bt.dtbo or disable-bt.dtbo and place the files to a folder named "overlays" in your SD card. 

Now, Let's start the lab.

The first step to control a CPU is to dump any message you want!

$ git clone https://github.com/champyen/rpiz_bare_metal.git

$ git checkout 4e769069

There are 4 major files in the example:

  • bare_metal.c - the main example flow
  • head.S - the glue code for entering the flow
  • bare_metal.c - the main example flow
  • printf.c - printf for RPi Zero's PL011 uart
  • bare_metal.ld - linker script of the example

From the "Boot options in config.txt" of Raspberry Pi Document, we know that the default start address is 0x8000.

In bare_metal.ld, you can see the linker script:

OUTPUT_ARCH(arm)
SECTIONS {
    . = 0x8000;
    .text . : {
        *(.text)
    }
    . = ALIGN(4);
    .data . : {
        *(.data)
    }
    . = ALIGN(4);
    .bss . : {
        *(.bss)
        *(COMMON)
    }
    . = ALIGN(4);
    .rodata . : {
        *(.rodata)
        *(.rodata.*)
    }
}
In Makefile, you can see the linking order is - head.o bare_metal.o printf.o.


Therefore, after bootcode.bin, it will jump to the first function in head.S.

In head.S:

.text
_start:
    ldr    sp, =stack_top
    bl    bare_metal_start

stack_top:      .word   0x100000
Before calling the demo function - bare_metal_start, head.S does only one thing - setup the address of 0x100000 as stack pointer.

In bare_metal.c:

void printf(const char *fmt, ...);
void bare_metal_start(void)
{
    printf("\n\n%s: Hello World! %s %s %d\n\n", __func__, __DATE__, __TIME__, __LINE__);
    printf("enter busy loop\n");
    while(1);
}

the bare_metal_start function calls the 'printf' implemented in printf.c to dump two messages.

In printf, the fundamental function is _putc.

#define PL011_BASE  0x20201000
#define PL011_DR    (PL011_BASE + 0x00)
#define PL011_FR    (PL011_BASE + 0x18)
#define _REG(x)        *((unsigned int t *)(x))

void _putc(unsigned char c)
{
    while( (_REG(PL011_FR) & 0x80) == 0);
    _REG(PL011_DR) = c;
}
Please refer to Chapter 13 of the "Peripheral Specificaion" of BCM2835, the _putc just check the Transmit buffer status, and send out a char when Transmit buffer/register is empty.





沒有留言:

在 ARM 平台上使用 Function Multi-Versioning (FMV) - 以使用 Android NDK 為例

Function Multi-Versioning (FMV) 過往的 CPU 發展歷程中, x86 平台由於因應各種應用需求的提出, 而陸陸續續加入了不同的指令集, 此外也可能因為針對市場做等級區隔, 支援的數量與種類也不等. 在 Linux 平台上這些 CPU 資訊可以透過...