Raspberry Pi ZeroでLチカする

既に世間には数多くのRaspberry Pi + Lチカの記事がたくさんありますが、GPU上でやるものが日本語記事にあまり見当たらなかったので書いてみました。

## 何をやるのか

GPU上で動くbootloaderの`bootcode.bin`を作って、LEDを点滅させます。
以下を参考にしています。

- [hermanhermitage/videocoreiv - GitHub](https://github.com/hermanhermitage/videocoreiv)
- [christinaa/rpi-open-firmware - GitHub](https://github.com/christinaa/rpi-open-firmware)


## 開発環境

GPUであるVideoCoreIVは一般的なアーキテクチャではなく、独自の命令セットをもっています。その命令に対応するツールを準備する必要があります。今回は以下を使います。

- [puppeh/vc4-toolchain - GitHub](https://github.com/puppeh/vc4-toolchain)

ubuntu/debianなどはREADME.mdに従えば環境構築できると思います。
Gentoo Linuxの場合は、以下の手順に従ってinstallを行います。

### guile-1.8のinstall

```
 # emerge guile:12/8
```


### build

git cloneします。

```
 $ git clone --depath 1 --recursive https://github.com/puppeh/vc4-toolchain.git
 $ cd vc4-toolchain
```
Makefileの中にguileを実行する箇所があるんですが、`guile-1.8`もしくは`guile1.8`というコマンドを探して実行します。
Gentooの場合、guileのパスは/usr/bin/guileなのでsymbolic linkをはるか、以下を編集してください。

```
 $ vim binutils-vc4/opcodes/Makefile.in
```

661行目を変更

```.diff
--- a/binutils-vc4/opcodes/Makefile.in       2018-08-05 14:51:17.748076392 +0900
+++ b/binutils-vc4/opcodes/Makefile.in       2018-08-05 14:51:04.248077012 +0900
@@ -608,7 +608,7 @@

 CGENDIR = @cgendir@
 CPUDIR = $(srcdir)/../cpu
-CGEN = "`if test -f ../guile/libguile/guile ; then echo ../guile/libguile/guile; else echo \`which 2> /dev/null guile{1.8,-1.8}\` ; fi` -l ${cgendir}/guile.scm -s"
+CGEN = "`if test -f ../guile/libguile/guile ; then echo ../guile/libguile/guile; else echo \`which 2> /dev/null guile{,1.8,-1.8}\` ; fi` -l ${cgendir}/guile.scm -s"
 CGENFLAGS = -v
 CGENDEPS = \
        $(CGENDIR)/desc.scm $(CGENDIR)/desc-cpu.scm \
```

あとはbuildするだけです。gcc,binutilsなどをbuildするので結構時間かかります。

```
 $ ./build-all.sh
```

ビルドが完了したら`vc4-toolchain/prefix/bin`を環境変数PATHに追加します。

## Lチカプログラムを作る


### Makefile

```gnumake

TARGET  = bootcode.bin
THOST   = vc4-elf-

# flags
CFLAGS  = -nostdlib -std=c11 -msingle-float
ASFLAGS =
LDFLAGS = -nostdlib -nostartfiles --build-id=none

# tools
CC      = $(THOST)gcc
AS      = $(THOST)as
LD      = $(THOST)ld
OBJDUMP = $(THOST)objdump
OBJCOPY = $(THOST)objcopy

# intermediate files
ELF      = $(TARGET:%.bin=%.elf)
DISASMED = $(TARGET:%.bin=%.S)
AOBJS    = start.o
COBJS    = main.o
LDSCRIPT = boot.lds

.PHONY: all clean

#
# build
#

all: $(TARGET) $(DISASMED)

$(TARGET): $(ELF)
	$(OBJCOPY) $< -O binary $@

$(DISASMED): $(ELF)
	$(OBJDUMP) -D $< > $(DISASMED)

$(ELF): $(COBJS) $(AOBJS) $(LDSCRIPT)
	$(LD) $(LDFLAGS) $(AOBJS) $(COBJS) -T $(LDSCRIPT) -o $@

$(COBJS): $(COBJS:%.o=%.c)
	$(CC) $(CFLAGS) -c $< -o $@

$(AOBJS): $(AOBJS:%.o=%.s)
	$(AS) $(ASFLAGS) -c $< -o $@

#
# clean up
#

clean:
	@rm -vf $(COBJS) $(AOBJS) $(DISASMED) $(ELF) $(TARGET)
```

### start.s

C言語のmainを起動するAssembly codeです。コードは`0x200`から実行されるため、`.space 0x200`を追加しています。

```
/* simply call main */
.space 0x200
.globl _start
.align 2
_start:
        /* set stack pointer */
        mov sp, #0x1c000

        /* jump to C code */
        bl main

hang:
        b hang
```

### main.c

Raspberry Pi ZeroのLEDは`GP_FSEL4`を適切に設定し、`GP_SET1/GP_CLR1`の15bit目をONにすることでつけたりけしたりできます。


```c:
/* define hardware register */
#define HW_REGISTER_RW(addr) (*(volatile unsigned int *)(addr))

#define IO_BASE  (0x7e200000)
#define GP_FSEL4 HW_REGISTER_RW(IO_BASE + 0x10)
#define GP_SET1  HW_REGISTER_RW(IO_BASE + 0x2c)
#define GP_CLR1  HW_REGISTER_RW(IO_BASE + 0x20)

/* called by _start */
void main()
{
        /* activate LED */
        unsigned int ra = GP_FSEL4;
        ra &= ~(0x7 << 21);
        ra |=  (0x1 << 21);
        GP_FSEL4 = ra;

        for(unsigned int j = 0; ; ++j) {
                /* wait */
                for(unsigned int i = 0x8000; i != 0; --i)
                {
                        __asm__ volatile("nop");
                }

                /* led on/off */
                if((j % 2) == 0)
                {
                        GP_SET1 = (0x1 << 15);
                }
                else {
                        GP_CLR1 = (0x1 << 15);
                }
        }
}
```

### boot.lds

linker scriptを作ります。rpi-open-firmwareを参考にしています。

```
SECTIONS
{
        . = 0x000;

        .text : {
                *(.text)
                *(.text.*)
                *(.gnu.warning)
        }

        .rodata : {
                *(.rodata)
                *(.rodata.*)
        }

        . = ALIGN(4096);

        .data : {
                *(.data)
                *(.data.*)
        }

        .bss : {
                *(.bss)
                *(.bss.*)
        }

        . = ALIGN(32 / 8);
}
```

### build

```
 $ make
```


## 実行

出来上がった`bootcode.bin`をmicroSDに入れて電源をつければLEDが点滅します。


## 参考資料

VideoCoreIVについての情報は以下が大変参考になります。

- [hermanhermitage/videocoreiv - GitHub](https://github.com/hermanhermitage/videocoreiv)

HDMIの初期化などはブラックボックスですが、Linux kernelを追って実験すれば実装できるかもしれません。