QEMUでRaspberry Pi 3のUARTをベアメタルで動かす

QEMUのシステムエミュレーションを使ってRaspberry Pi 3のUARTをベアメタル動作させてみました。

Ubuntu 18.04で試してました。

# 準備
Raspberry Pi 3のシステムエミュレーションはQEMUが新しくないと対応していないので、QEMU 2.12をコンパイルして使いました。

```
% sudo apt build-dep qemu
% sudo apt install gcc-aarch64-linux-gnu

$ git clone git://git.qemu.org/qemu.git
$ cd qemu
$ git checkout -b v2.12.0-release refs/tags/v2.12.0
$ git submodule update --init

$ ./configure --prefix=`pwd`/build --target-list=arm-softmmu,aarch64-softmmu --audio-drv-list=alsa
$ make
$ make install
```

# 動作例

```
% cd src
% make
aarch64-linux-gnu-gcc -mcpu=cortex-a53 -fpic -ffreestanding -c boot.S -o boot.o
aarch64-linux-gnu-gcc -mcpu=cortex-a53 -fpic -ffreestanding -std=gnu99 -O2 -Wall -Wextra -c kernel.c -o kernel.o
aarch64-linux-gnu-gcc -T linker.ld -o kernel.elf -ffreestanding -O2 -nostdlib boot.o kernel.o

% file kernel.elf 
kernel.elf: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked,
interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=cbfa706293919f9c941e3d63c748e339643c4cc9, not stripped

% cd ..
% qemu/build/bin/qemu-system-aarch64 -m 128 -M raspi3  -nographic -kernel src/kernel.elf
Hello, kernel World!
Hello, kernel World!
Hello, kernel World!
Hello, kernel World!
QEMU: Terminated
```

ソースコードでは１回しか出力してないのに、なぜか４回出力されます。なんでだろうと思ったけど、４コアで実行されているから４回出力されているっぽいです。

QEMUを終了するときは、Ctrl-a xを入力します。

QEMUのraspi3は、PL011のUARTがデフォルトで使われれていました。　miniuartを標準入出力で使いたい場合は-serial を２回指示すると、使えました。１個目でPL011のUARTを無視し、２個目でminiuartを標準入出力に設定します。

```
qemu-system-aarch64 -m 128 -M raspi3 -nographic -kernel kernel.elf -serial null -serial mon:stdio
```


# ソースコード
## ブート

boot.S

```
// To keep this in the first portion of the binary.
.section ".text.boot"
 
// Make _start global.
.globl _start
 
// Entry point for the kernel.
// r15 -> should begin execution at 0x0080000.
// r0 -> 0x00000000
// r1 -> 0x00000C42
// r2 -> 0x00000100 - start of ATAGS
// preserve these registers as argument for kernel_main
_start:
	// Setup the stack.
	mov sp, #0x0080000
 
	// Call kernel_main
	bl kernel_main

	b .
```

## UART処理
kernel.c


```
#include <stdint.h>
 
// Memory-Mapped I/O output
static inline void mmio_write(uint32_t reg, uint32_t data)
{
	*(volatile uint32_t*)reg = data;
}
 
// Memory-Mapped I/O input
static inline uint32_t mmio_read(uint32_t reg)
{
	return *(volatile uint32_t*)reg;
}
 
enum
{
    // The base address for UART.
    UART0_BASE = 0x3F201000, // for raspi2 & 3, 0x20201000 for raspi1
 
    // The offsets for reach register for the UART.
    UART0_DR     = (UART0_BASE + 0x00),
    UART0_FR     = (UART0_BASE + 0x18),
};
 
void uart_putc(unsigned char c)
{
	// Wait for UART to become ready to transmit.
	while ( mmio_read(UART0_FR) & (1 << 5) ) { }
	mmio_write(UART0_DR, c);
}
 
unsigned char uart_getc()
{
	// Wait for UART to have received something.
	while ( mmio_read(UART0_FR) & (1 << 4) ) { }
	return mmio_read(UART0_DR);
}
 
void uart_puts(const char* str)
{
	int i;
	for (i = 0; str[i] != '\0'; i ++)
		uart_putc((unsigned char)str[i]);
}
 
#if defined(__cplusplus)
extern "C" /* Use C linkage for kernel_main. */
#endif
void kernel_main(uint32_t r0, uint32_t r1, uint32_t atags)
{
	// Declare as unused
	(void) r0;
	(void) r1;
	(void) atags;
 
	uart_puts("Hello, kernel World!\n");
 
	while (1)
		uart_putc(uart_getc());
}
```

UARTは初期化しないでも動いたので初期化処理は省略しました。

# リンカスクリプト

linker.ld

```
ENTRY(_start)
 
SECTIONS
{
    /* Starts at LOADER_ADDR. */
    . = 0x80000;
    __start = .;
    __text_start = .;
    .text :
    {
        KEEP(*(.text.boot))
        *(.text)
    }
    . = ALIGN(4096); /* align to page size */
    __text_end = .;
 
    __rodata_start = .;
    .rodata :
    {
        *(.rodata)
    }
    . = ALIGN(4096); /* align to page size */
    __rodata_end = .;
 
    __data_start = .;
    .data :
    {
        *(.data)
    }
    . = ALIGN(4096); /* align to page size */
    __data_end = .;
 
    __bss_start = .;
    .bss :
    {
        bss = .;
        *(.bss)
    }
    . = ALIGN(4096); /* align to page size */
    __bss_end = .;
    __end = .;
}
```


## Makefile
Makefile

```
CC = aarch64-linux-gnu-gcc
CFLAGS =  -mcpu=cortex-a53 -fpic -ffreestanding -std=gnu99 -O2 -Wall -Wextra
ASM_FLAGS = -mcpu=cortex-a53 -fpic -ffreestanding
OBJ = boot.o kernel.o

kernel.elf: ${OBJ}
	${CC} -T linker.ld -o $@ -ffreestanding -O2 -nostdlib ${OBJ}

boot.o: boot.S
	${CC} ${ASM_FLAGS} -c $< -o $@

kernel.o: kernel.c
	${CC} ${CFLAGS} -c $< -o $@

clean:
	rm -f *.o *.elf

.PHONY: clean
```


# 参考にしたページ

* [ラズパイ２＋RustでOSはじめの一歩](https://qiita.com/maueki/items/4aa16be2efd0bdc0405b)
* [Raspberry Pi Bare Bones](https://wiki.osdev.org/Raspberry_Pi_Bare_Bones)