CFEでHello world!

BCM/mipsのBCM4712などの古いターゲットではFreeBSDで動くようにならなさそうで、捨てるのも忍びないのでBare Metalで使えないか試してみる事にしました。

CFEとはBroadcomがmips SOC用に提供しているブートローダーで、Common Firmware Environmentが正式名称です。Broadcomのmips SOCはSiByteを2000年に買収して始まりCFEはSiByte時代からのもののようです。SiByteはハイエンドのmips SOCを作っていたようなのですが、Broadcomになってからのmips SOCは小規模なものに変わっています。またCFEはもともとx86やppcなどのサポートなどもある大規模なブートローダーでした。

以下のページを参考にしました。

https://wiki.osdev.org/CFE_Bare_Bones

いつものように環境はFreeBSDですが、gccはpkgでgcc-mipsを入れました。いつものようにLinuxのtoolchainを使わなかったのはBroadcomが配っていたtoolchainはgcc-3だったからです。

```
% mips-unknown-freebsd11.2-gcc -v
Using built-in specs.
COLLECT_GCC=mips-unknown-freebsd11.2-gcc
COLLECT_LTO_WRAPPER=/usr/local/libexec/gcc/mips-unknown-freebsd11.2/6.4.0/lto-wr
apper
Target: mips-unknown-freebsd11.2
Configured with: /wrkdirs/usr/ports/devel/mips-gcc/work/gcc-6.4.0/configure --ta
rget=mips-unknown-freebsd11.2 --disable-nls --enable-languages=c,c++ --enable-gn
u-indirect-function --without-headers --with-gmp=/usr/local --with-pkgversion='F
reeBSD Ports Collection for mips' --with-system-zlib --with-gxx-include-dir=/usr
/include/c++/v1/ --with-sysroot=/ --with-as=/usr/local/bin/mips-unknown-freebsd1
1.2-as --with-ld=/usr/local/bin/mips-unknown-freebsd11.2-ld --enable-initfini-ar
ray --prefix=/usr/local --localstatedir=/var --mandir=/usr/local/man --infodir=/
usr/local/info/ --build=x86_64-unknown-freebsd11.2
Thread model: posix
gcc version 6.4.0 (FreeBSD Ports Collection for mips) 
```

Entry.Sはそのままです。

Main.cは以下のように修正しました。

```c
void putchar(char c)
{
        volatile char* lsr = (volatile char*)0xb8000305; // Line status register.
        volatile char* thr = (volatile char*)0xb8000300; // Transmitter holding register.
 
        while(((*lsr) & 0x20) == 0) ; // Wait until THR is empty.
 
        *thr = c;
}
 
void printk(const char* s)
{
        while(*s)
        {
                putchar(*s);
                s++;
        }
}
 
int main(void)
{
        printk("Hello world!¥n");
        return 0;
}
```

シリアルのアドレスはLinuxのブートログなどで確認できます。LSRのアドレスは5か倍数(0x0aか0x14)になりますが、このチップは5でした。

ldscriptはFreeBSDのsys/conf/ldscript.mips.cfeを使います。修正点は_startをstartにしてKERNLOADADDRを0x80001000にしました。

Makefileは以下のようにしました。

```make
CROSS=mips-unknown-freebsd11.2

Kernel.elf : Entry.o Main.o
        $(CROSS)-ld -EL -T ldscript.mips.cfe -o Kernel.elf Entry.o Main.o

Entry.o : Entry.S
        $(CROSS)-as -EL -msoft-float -march=mips32 -o Entry.o Entry.S

Main.o : Main.c
        $(CROSS)-gcc -EL -o Main.o -c Main.c -Wall -Wextra  -Werror ¥
                -march=mips32 -mno-abicalls -nostdlib -fno-builtin ¥
                -nostartfiles  -nodefaultlibs -O2 

clean:
        rm Kernel.elf Entry.o Main.o
```

ちょっとはまったのはpkgのgccがなぜかデフォルトがBigEndianでビルドされていたことと、元のldscriptではうまく動かなかった事です。

```
% file Kernel.elf 
Kernel.elf: ELF 32-bit LSB executable, MIPS, MIPS32 version 1 (FreeBSD), statica
lly linked, interpreter *empty*, not stripped
% readelf -h Kernel.elf 
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 09 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            FreeBSD
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           MIPS R3000 Big-Endian only
  Version:                           0x1
  Entry point address:               0x80001000
  Start of program headers:          52 (bytes into file)
  Start of section headers:          5028 (bytes into file)
  Flags:                             0x50001001, mips32, o32, noreorder
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         7
  Size of section headers:           40 (bytes)
  Number of section headers:         12
  Section header string table index: 11
```

ターゲットにシリアル接続して、電源投入後に^Cでブートローダーを止めて、ネットワーク接続して実行してみます。サーバ側でtftpdを使えるようにして、指定したディレクトリにKernel.elfをコピーしておきます。

```
CFE> ifconfig eth0 -addr=10.10.10.200
CFE> boot -elf 10.10.10.3:Kernel.elf
Loader:elf Filesys:tftp Dev:eth0 File:10.10.10.3:Kernel.elf Options:(null)
Loading: 0x80001000/256 0x80003100/4096 Entry at 0x80001000
Closing network.
et0: link down (interface down)
Starting program at 0x80001000
Hello world!
```

できました。

CFEはAPIがあり、もしAPIを使いたい場合には、メモリに張り付いているCFEの領域を壊してしまわないようにします。APIを使ってシリアル出力をすることも可能です。

```
Total memory used by CFE:  0x80400000 - 0x804A39B0 (670128)
Initialized Data:          0x80439670 - 0x8043C210 (11168)
BSS Area:                  0x8043C210 - 0x8043D9B0 (6048)
Local Heap:                0x8043D9B0 - 0x804A19B0 (409600)
Stack Area:                0x804A19B0 - 0x804A39B0 (8192)
Text (code) segment:       0x80400000 - 0x80439670 (235120)
Boot area (physical):      0x004A4000 - 0x004E4000
Relocation Factor:         I:00000000 - D:00000000

CFE> show memory
Range Start  Range End    Range Size     Description
------------ ------------ -------------- --------------------
000000000000-0000003FFFFF (000000400000) DRAM (available)
0000004A4000-000000FFFFFF (000000B5C000) DRAM (available)
*** command status = 0
```

MMUを使う場合は、この領域を別扱いにすれば良いのかもしれませんが、BareMetalで連続したメモリー空間で使う場合には、この領域を保護するのはちょっと面倒です。アプリケーション起動直後の壊す可能性の低いうちにAPIを叩いてその後は壊しても良いようにAPIを叩かないって方法がいいのかもしれません。