1. CHAOSとは?

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

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

サポートしている機能

  • UART (PCと通信)
  • GPIO (LEDやモータ,センサ等のInput/Output)
  • RNG (乱数生成)

2. 実際に手を動かす

開発環境構築

  1. 下のリンクからCHAOSをクローンまたはダウンロードしてください.
    CHAOSのレポジトリ
  2. READMEを参照して開発環境を構築してください.
  3. makeしてビルドに成功すればOKです.

動かしてみよう

  1. QEMUで動かしてみましょう.
1
$ make run

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

1
2
3
4
5
tsuru@rz4:~/CHAOS$ make run
qemu-system-aarch64 -M raspi3 -kernel kernel8.img -serial null -serial stdio

CHAOS started
>

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

1
2
3
4
5
6
7
8
> echo hello
hello
> on
GPIO ON!
> off
GPIO OFF!
> reboot
rebooting...

入力した文字列はchar型のbuf配列に格納されてstrcmpで対応するコマンドを比較して処理を決めています.
ctrl+c でQEMUを終了できます.

  1. ラズパイに書き込もう
    FAT32でフォーマットされた micro SD をPCに差し込み,ディレクトリ内のファイル群をコピーします.
1
2
3
4
kernel8.img
bootcode.bin
config.txt
start.elf

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

1
make com

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

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

1
on

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

1
off

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

1
2
3
4
5
6
7
8
9
10
CHAOS started
> rand
4EBE8815
> rand
523D3704
> reboot
rebooting...
CHAOS started
> shutdown
shutdown

という感じで自作OS CHAOS の機能を紹介しました.
次はソースコードについて説明します.

  1. CHAOSのコード読解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#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で定義されたレジスタです.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 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のデータシートを確認してください.

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
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を盛り上げていきましょう!!