LoginSignup
10
10

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-05-13

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

参考にしたページ

10
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
10