既に世間には数多くのRaspberry Pi + Lチカの記事がたくさんありますが、GPU上でやるものが日本語記事にあまり見当たらなかったので書いてみました。
何をやるのか
GPU上で動くbootloaderのbootcode.bin
を作って、LEDを点滅させます。
以下を参考にしています。
開発環境
GPUであるVideoCoreIVは一般的なアーキテクチャではなく、独自の命令セットをもっています。その命令に対応するツールを準備する必要があります。今回は以下を使います。
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行目を変更
--- 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
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にすることでつけたりけしたりできます。
/* 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についての情報は以下が大変参考になります。
HDMIの初期化などはブラックボックスですが、Linux kernelを追って実験すれば実装できるかもしれません。