自作組込みOS "CHAOS" について

本記事は [Sechack365 2019 Advent Calendar 2019 - Qiita](https://qiita.com/advent-calendar/2019/sechack365_2019) の 21 日目の投稿です．

# 1. CHAOSとは？
拡張性に優れて自由に改造できる(ハッカブルな)組込みOSです．フルスクラッチでARM64bitで動作するOSというのが特徴で，現状はRaspberry Pi 3 model bで動作します．またQEMUを使ったエミュレーションも可能です．

CHAOSを使って組込みOSを理解しましょう！

サポートしている機能
- UART (PCと通信)
- GPIO (LEDやモータ，センサ等のInput/Output)
- RNG  (乱数生成)

# 2. 実際に手を動かす
### 開発環境構築
1. 下のリンクからCHAOSをクローンまたはダウンロードしてください．  
[CHAOSのレポジトリ](https://github.com/75u2u/CHAOS)
2. READMEを参照して開発環境を構築してください．
3. makeしてビルドに成功すればOKです．

### 動かしてみよう
1. QEMUで動かしてみましょう．

```
$ make run
```
するとQEMUが立ち上がって以下のようなコマンド入力画面が表示されます．

```
tsuru@rz4:~/CHAOS$ make run
qemu-system-aarch64 -M raspi3 -kernel kernel8.img -serial null -serial stdio

CHAOS started
> 
```

色々入力してみましょう．
対応コマンドはmain.cに書いてあります．

```
> echo hello
hello
> on
GPIO ON!
> off
GPIO OFF!
> reboot
rebooting...
```
入力した文字列はchar型のbuf配列に格納されてstrcmpで対応するコマンドを比較して処理を決めています．
ctrl+c でQEMUを終了できます．

2. ラズパイに書き込もう  
FAT32でフォーマットされた micro SD をPCに差し込み，ディレクトリ内のファイル群をコピーします．

```
kernel8.img
bootcode.bin
config.txt
start.elf
```

SDカードスロットに差し込み，ラズパイのGPIO14ピン,15ピン(RX,TX)とGNDをUSBシリアル変換器を使い，USB経由でPCを接続します．その後以下のコマンドでminicomを立ち上げます．

```
make com
```
minicomが立ち上がった後にラズパイの電源を入れるとQEMUでエミュレーションした時と同じようにコマンド入力画面が表示されます．

ここでGPIO16ピンとGNDにLEDを接続して以下のコマンドを入力してみましょう．

```
on
```

するとどうでしょうLEDが点灯します！めっちゃ簡単ですね！
消灯する際は

```
off
```

で出来ます．
あとは，乱数を作ったり，再起動したり，シャットダウンできたりします．

```
CHAOS started
> rand
4EBE8815
> rand
523D3704
> reboot
rebooting...
CHAOS started
> shutdown
shutdown
```
という感じで自作OS CHAOS の機能を紹介しました．
次はソースコードについて説明します．

3. CHAOSのコード読解

```bash:lib.c
#include "lib.h"
#include "uart.h"
#include "gpio.h"

void init(void) {
	uart_init();
}

int putc(unsigned char c) {
	if (c == '\n')
		uart_send('\r');
	if (c != "")
		uart_send(c);
	return 0;
}

unsigned char getc(void) {
	unsigned char c = uart_getc();
	c = (c == '\r') ? '\n' : c;
	putc(c);
	return c;
}

int puts(unsigned char *str) {
	while (*str)
		putc(*(str++));
	return 0;
}

int gets(unsigned char *buf) {
	int i = 0;
	unsigned char c;
	do {
		c = getc();
		if (c == '\n')
			c = '\0';
		buf[i++] = c;
	} while (c);
	return i - 1;
}

int strcmp(const char *s1, const char *s2) {
	while (*s1 || *s2) {
		if (*s1 != *s2)
			return (*s1 > *s2) ? 1 : -1;
		s1++;
		s2++;
	}
	return 0;
}

int strncmp(const char *s1, const char *s2, int len) {
	while ((*s1 || *s2) && (len > 0)) {
		if (*s1 != *s2)
			return (*s1 > *s2) ? 1 : -1;
		s1++;
		s2++;
		len--;
	}
	return 0;
}

void rand_init(void) {
	*RNG_STATUS = 0x40000;
	*RNG_INT_MASK |= 1;
	*RNG_CTRL |= 1;
	while(!((*RNG_STATUS) >> 24)) asm volatile("nop");
}

unsigned int rand(unsigned int min, unsigned int max) {
	return *RNG_DATA % (max - min) + min;
}

void on(unsigned int n) {
    register unsigned int r;
// ON
	*GPFSEL1 = 0x01 << 18;
	*GPSET0 = 0x01 << 16;

// UARTできるように切り替える
    r=*GPFSEL1;
    r&=~((7<<12)|(7<<15)); // gpio14, gpio15
    r|=(2<<12)|(2<<15);    // alt5
    *GPFSEL1 = r;
}

void off(unsigned int n) {
    register unsigned int r;
// OFF
	*GPFSEL1 = 0x01 << 18;
	*GPCLR0 = 0x01 << 16;

// UARTできるように切り替える
    r=*GPFSEL1;
    r&=~((7<<12)|(7<<15)); // gpio14, gpio15
    r|=(2<<12)|(2<<15);    // alt5
    *GPFSEL1 = r;
}
```

main.cで呼び出している関数がlib.cで定義してあります．注目するべきは*GPFSEL1と*GPSET0と*GPCLR0です．これらはgpio.hで定義されたレジスタです．

```bash:gpio.h
// RaspberryPi3 Memory Mapped I/O Base Address
#define MMIO_BASE       0x3F000000

// GPIO
#define GPFSEL0         ((volatile unsigned int*)(MMIO_BASE+0x00200000))
#define GPFSEL1         ((volatile unsigned int*)(MMIO_BASE+0x00200004))
#define GPFSEL2         ((volatile unsigned int*)(MMIO_BASE+0x00200008))
#define GPFSEL3         ((volatile unsigned int*)(MMIO_BASE+0x0020000C))
#define GPFSEL4         ((volatile unsigned int*)(MMIO_BASE+0x00200010))
#define GPFSEL5         ((volatile unsigned int*)(MMIO_BASE+0x00200014))
#define GPSET0          ((volatile unsigned int*)(MMIO_BASE+0x0020001C))
#define GPSET1          ((volatile unsigned int*)(MMIO_BASE+0x00200020))
#define GPCLR0          ((volatile unsigned int*)(MMIO_BASE+0x00200028))
#define GPLEV0          ((volatile unsigned int*)(MMIO_BASE+0x00200034))
#define GPLEV1          ((volatile unsigned int*)(MMIO_BASE+0x00200038))
#define GPEDS0          ((volatile unsigned int*)(MMIO_BASE+0x00200040))
#define GPEDS1          ((volatile unsigned int*)(MMIO_BASE+0x00200044))
#define GPHEN0          ((volatile unsigned int*)(MMIO_BASE+0x00200064))
#define GPHEN1          ((volatile unsigned int*)(MMIO_BASE+0x00200068))
#define GPPUD           ((volatile unsigned int*)(MMIO_BASE+0x00200094))
#define GPPUDCLK0       ((volatile unsigned int*)(MMIO_BASE+0x00200098))
#define GPPUDCLK1       ((volatile unsigned int*)(MMIO_BASE+0x0020009C))

// RNG
#define RNG_CTRL        ((volatile unsigned int*)(MMIO_BASE+0x00104000))
#define RNG_STATUS      ((volatile unsigned int*)(MMIO_BASE+0x00104004))
#define RNG_DATA        ((volatile unsigned int*)(MMIO_BASE+0x00104008))
#define RNG_INT_MASK    ((volatile unsigned int*)(MMIO_BASE+0x00104010))
```

レジスタのアドレスをgpio.hで定義されています．
詳細はラズパイ3に搭載されている[BMC2837のデータシート](https://cs140e.sergio.bz/docs/BCM2837-ARM-Peripherals.pdf)を確認してください．

※実はデータシートのベースアドレスは0x7F000000なんですが現状のラズパイ3では0x3F000000であることに注意してください．

GPFSEL...GPIOの入出力設定するレジスタ
GPSET ...対応するGPIOピンをHIGHにする
GPCLR ...対応するGPIOピンをLOWにする

ここでlib.cのon()を見てみましょう．

```bash:lib.c
void on(unsigned int n) {
    register unsigned int r;
// ON
	*GPFSEL1 = 0x01 << 18;
	*GPSET0 = 0x01 << 16;

// UARTできるように切り替える
    r=*GPFSEL1;
    r&=~((7<<12)|(7<<15)); // gpio14, gpio15
    r|=(2<<12)|(2<<15);    // alt5
    *GPFSEL1 = r;
}
```
1. *GPFSEL1 = 0x01 << 18;　はデータシートP92を参照すると"GPIO16ピンを出力に設定"
2. *GPSET0 = 0x01 << 16; はOUTしたいピン番号分(16)左シフトすることで"GPIO16をOUT"  
この2つのレジスタの演算だけでラズパイのGPIO操作ができます．

ただ，UARTする際にGPFSEL1を操作するため，UARTできるように書き換えるための演算を次の行から行っています．


# 3. 終わりに
駆け足になりましたがザックリとCHAOSの概要とGPIOの設定，操作を見ていきました．
レジスタのアドレスを変えたりリンカ・スクリプトを改造することで他のSoCでも動くと思います．ガンガン移植していって楽しむのも通かと思います．
CHAOSはKL-01というライセンスなので自由に改変，再配布可能です．またこんな機能追加したよ！というのがあればどんどんプルリク送ってほしいです！まだまだCHAOSには機能が充分に揃ってないですが，そのぶん改造の幅やいろんな可能性を秘めていると言えます．一緒にCHAOSを盛り上げていきましょう！！