本記事は Sechack365 2019 Advent Calendar 2019 - Qiita の 21 日目の投稿です.
1. CHAOSとは?
拡張性に優れて自由に改造できる(ハッカブルな)組込みOSです.フルスクラッチでARM64bitで動作するOSというのが特徴で,現状はRaspberry Pi 3 model bで動作します.またQEMUを使ったエミュレーションも可能です.
CHAOSを使って組込みOSを理解しましょう!
サポートしている機能
- UART (PCと通信)
- GPIO (LEDやモータ,センサ等のInput/Output)
- RNG (乱数生成)
2. 実際に手を動かす
開発環境構築
- 下のリンクからCHAOSをクローンまたはダウンロードしてください.
CHAOSのレポジトリ - READMEを参照して開発環境を構築してください.
- makeしてビルドに成功すればOKです.
動かしてみよう
- 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を終了できます.
- ラズパイに書き込もう
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 の機能を紹介しました.
次はソースコードについて説明します.
- CHAOSのコード読解
#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で定義されたレジスタです.
// 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()を見てみましょう.
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;
}
- *GPFSEL1 = 0x01 << 18; はデータシートP92を参照すると"GPIO16ピンを出力に設定"
- *GPSET0 = 0x01 << 16; はOUTしたいピン番号分(16)左シフトすることで"GPIO16をOUT"
この2つのレジスタの演算だけでラズパイのGPIO操作ができます.
ただ,UARTする際にGPFSEL1を操作するため,UARTできるように書き換えるための演算を次の行から行っています.
3. 終わりに
駆け足になりましたがザックリとCHAOSの概要とGPIOの設定,操作を見ていきました.
レジスタのアドレスを変えたりリンカ・スクリプトを改造することで他のSoCでも動くと思います.ガンガン移植していって楽しむのも通かと思います.
CHAOSはKL-01というライセンスなので自由に改変,再配布可能です.またこんな機能追加したよ!というのがあればどんどんプルリク送ってほしいです!まだまだCHAOSには機能が充分に揃ってないですが,そのぶん改造の幅やいろんな可能性を秘めていると言えます.一緒にCHAOSを盛り上げていきましょう!!