Edited at

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

More than 1 year has passed since last update.

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

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

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

QEMUのraspi3は、PL011のUARTがデフォルトで使われれていました。 miniuartを標準入出力で使いたい場合は-serial を2回指示すると、使えました。1個目でPL011のUARTを無視し、2個目で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


参考にしたページ