とりあえず移植して割り込み処理まわりとか必要な部分を書いて、ブートだけはできるようになった。

(ブートローダーにも何箇所か手を入れた)


で、起動はできたのだけど、起動直後に落ちる。まああらかた書いたので、あとはデバッグかな。


(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.

連休だ!OS作成日和だ!

今週はいろいろと忙しくてなんだかんだいって移植作業が進んでいなかったので,今日いっきに作業してしまおう.(そして連休は遊ぶのだ)

で,今回なのだけど,とりあえずKOZOS移植の前に準備としてシリアル割り込み処理を実装してみよう.

H8だとシリアル割り込み処理も簡単で,割り込みフラグを立てるだけ.実装したのは以下のような感じ.今回はブートローダーには手は入っていないので,アプリ側の修正のみ.

修正内容は以下のような感じ.まず serial.c の修正.

diff -ruN h8_06/os/serial.c h8_07/os/serial.c
--- h8_06/os/serial.c Tue Sep 15 00:27:07 2009
+++ h8_07/os/serial.c Sat Sep 19 17:24:41 2009
@@ -1,4 +1,5 @@
#include "types.h"
+#include "interrupt.h"
#include "serial.h"

#define H8_3069F_SCI0 ((volatile struct h8_3069f_sci *)0xffffb0)
@@ -42,15 +43,35 @@
#define H8_3069F_SCI_SSR_RDRF (1<<6)
#define H8_3069F_SCI_SSR_TDRE (1<<7)

+void serial_interrupt()
+{
+ volatile struct h8_3069f_sci *sci = H8_3069F_SCI1;
+ int status, c;
+
+ status = sci->ssr;
+
+ c = serial_getc();
+ serial_putc(c);
+
+ sci->ssr = status & ~H8_3069F_SCI_SSR_RDRF;
+
+ return;
+}
+
int serial_init()
{
volatile struct h8_3069f_sci *sci = H8_3069F_SCI1;

+ /* 割り込みハンドラ登録 */
+ interrupt_sethandler(VECTYPE_RXI1, serial_interrupt);
+
sci->scr = 0;
sci->smr = 0;
sci->brr = 64; /* 20MHz 9600bps */
sci->scr = H8_3069F_SCI_SCR_RE | H8_3069F_SCI_SCR_TE;
sci->ssr = 0;
+
+ sci->scr |= H8_3069F_SCI_SCR_RIE; /* 割り込み許可 */

return 0;
}

割り込みハンドラを用意してinterrupt_sethandler()で登録して,あとSCRの割り込みフラグを立てて割り込み有効にしているだけ.ハンドラ内では1文字受信して,そのままエコーバックしている.注意として,1文字読み込みしてから RDRF フラグを落とすこと.(でないと受信できない)

次に main.c の修正.

diff -ruN h8_06/os/main.c h8_07/os/main.c
--- h8_06/os/main.c Tue Sep 15 00:27:07 2009
+++ h8_07/os/main.c Sat Sep 19 17:24:41 2009
@@ -66,6 +66,7 @@
}
#endif

+#if 0
static int gets(char *buf)
{
int i = 0, c = -1;
@@ -78,6 +79,7 @@
}
return i - 1;
}
+#endif

#if 0
static int dump(char *buf, int size)
@@ -105,7 +107,9 @@

int main()
{
+#if 0
char buf[80];
+#endif

init();

@@ -114,10 +118,12 @@
ENABLE_INTR;

while (1) {
+#if 0
puts("os> ");
gets(buf);
puts(buf);
puts("\n");
+#endif
}

return 0;

単に従来のポーリング処理に蓋をしただけ.

では実行してみよう.今回はブートローダーには手は入っていないので,フラッシュROMを書き換える必要は無い.

Hello World!
> load
~CLocal command? lsx hello
Sending hello, 19 blocks: Give your local XMODEM receive command now.
Bytes Sent: 2560 BPS:870

Transfer complete
eceive succeeded.
> run
starting from entry point.boot succeed!
IabIcIABICIIIIIII

問題なくあっさり動いた.H8は楽でいいねー.

これでKOZOS移植の準備はだいたいできた.あとはいよいよOS実装だ!
(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.

ちょっと別件の作業をしていたので滞っていたけど,昨日ようやくそっちがなんとか片付いたので,H8移植をまた進めよう.

今回は,割り込み処理の実装だ.とりあえずタイマ割り込みを実装したい.

で,いろいろちょっと調べたのだけど,今回利用しているH8ボードでは,割り込みベクタがROM上にあるのでソフトウエアから起動後に書き換えるのは無理っぽい.なので,割り込みベクタの管理はブートローダーにまかせるしかない.ていうかブートローダーのROMへの書き込み時に,割り込みベクタもいっしょに書き込まれてしまい,OS起動後に設定しなおすようなことができない.

で,とりあえず割り込みの入口はブートローダーにやってもらって,ソフトウエア的に独自に割り込みベクタみたいなのをRAM上に作っておいて,ブートローダー側ではそのソフトウエア・割り込みベクタを見て適切なアドレスに飛ぶ,というよくありがちな2段構成にしてみよう.これならソフトウエア・割り込みベクタをOS側で書き換えることで,割り込みハンドラを自由に登録できるようになる.

ソフトウエア・割り込みベクタの場所なのだけど,RAMの先頭領域である0xffbf20 から256バイトを割り当てよう.現状でRAMの先頭付近からOSをロードして使うようになっているので,OSのロード先は256バイト後ろにずらして調整する.

ということで実装したのが以下のような感じ.まずブートローダー側の修正なのだけど,割り込みの入口として intr.S を新規追加してある.ちょっとベタな書き方になってしまっていて,ほんとはマクロとか使って短くしたいところなんだけど,なんかうまくアセンブルできなかったので面倒なのでベタで書いてしまった.

ちなみにファイル名が *.s だとプリプロセッサを通さないが,*.S だとプリプロセッサを通して前処理が行われるようだ(man gcc にそう書いてあった).アセンブラのソースで #define とか使いたいときは要注意.なお intr.S は内部ではひとまず #define とかは使ってないので現状ではプリプロセッサを通す必要はないのだけど,将来的に #define でまとめたいので,intr.S というファイル名にしている.

vector.c では割り込みハンドラとしてとりあえずスタートアップを全部に登録していたが,intr.Sで定義してある各割り込みに応じたハンドラを登録するように修正した.

割り込みの入口では,スタックにレジスタを退避して,割り込みベクタ番号を引数にして interrupt() という関数を呼んでいる.interrupt() の本体はinterrupt.c で定義してあって,ソフトウエア・割り込みベクタ(VECTORS[])を参照して,登録してあるハンドラを呼び出すような作りになっている.OSが起動したらここにハンドラを登録しておけば,割り込みが入ったときに適切にハンドラを呼び出してくれるわけだ.割り込み関連の定義は interrupt.h に書いておいた.

あと main.c からソフトウエア・割り込みベクタの初期化用関数であるinterrupt_init()を呼んで,最初はNULLクリアするようにしている.

ブートローダーの修正はこんなもんかな.次にOS側の修正.

まず,RAMの先頭である 0xffbf20 番地はソフトウエア・割り込みベクタを配置するので,プログラムのロード先を256バイト後ろにずらす.このためにリンカスクリプトに以下の修正を入れている.

--- hello/ld.scr Wed Sep 2 23:32:57 2009
+++ timer/ld.scr Mon Sep 14 22:13:00 2009
@@ -4,8 +4,11 @@

MEMORY
{
- /* reserve 256bytes space for ELF header */
- ram(rwx) : o = 0xffbf20 + 0x100, l = 0x004000 - 0x100 /* 16kb */
+ /*
+ * reserve 256bytes space for software interrupt vector
+ * and 256bytes space for ELF header.
+ */
+ ram(rwx) : o = 0xffbf20 + 0x200, l = 0x004000 - 0x200 /* 16kb */
stack(rw) : o = 0xffff00, l = 0x000010 /* end of RAM */
}

まあ,単にずらしただけ.

あとタイマ関連の処理のために timer.[ch] を追加.これらはH8の持つタイマの各種レジスタを操作して,タイマ起動とあと割り込み時の処理を行うもの.ボード付属のCD-ROMについてたH8のマニュアルとか,あと本とか読んでてきとうに書いてみた.とりあえず,1/50秒周期でタイマをかけてカウントして,1秒おきに「I」という文字を出力するようにしてみた.

あと割り込み処理用にブートローダーの interrupt.[ch] を流用して,ソフトウエア・割り込みベクタに割り込みハンドラを登録できるようにした.これは実際には timer.c のタイマ初期化関数である timer_init() で,

int timer_init()
{
...
interrupt_sethandler(VECTYPE_IMIA0, timer_interrupt);
...

のようにして,timer_interrupt() をベクタ番号24(IMIA0)に登録している.

main.c では timer_init() を呼んでタイマの初期化をして,あと ENABLE_INTR を呼んで割り込み有効にしている.

では,実際に実行してみよう.今回はブートローダーに割り込み処理の機能追加がされているので,まずはブートローダーをビルドしてフラッシュROMに上書きする.

teapot# make write
../../h8write/h8write -3069 -f20 kzload.mot
H8/3069F is ready! 2002/5/20 Yukio Mituiwa.
writing
WARNING:This Line dosen't start with"S".
Address Size seems wrong
WARNING:This Line dosen't start with"S".
Address Size seems wrong
.........................................................................
EEPROM Writing is successed.
teapot#

気のせいか,書き込み時の「.」の数が増えたような...intr.S がベタで書いてあるのが効いているのかしら.

次にブートローダーを起動して,OSをダウンロードして実行する.

teapot# cu -l /dev/cuad0
Connected
Hello World!
> load
~CLocal command? lsx hello
Sending hello, 20 blocks: Give your local XMODEM receive command now.
Bytes Sent: 2688 BPS:665

Transfer complete
eceive succeeded.
> run
starting from entry point.boot succeed!
os> IIItest
test
os> IIIsampleI
sample
os> III

なんと驚くことに一発で動いてしまって,作った本人がビックリ.

いやホントに一発で動いたのよ.今回は割り込み周りのコーディングだし,H8のアセンブラってろくに書いたことないし,もっとハマることを覚悟していたのだけど,コーディングしただけであっさり動いてホント,びっくり.

なんつーか,H8は使うのが簡単でとても楽.H8だとシリアルもタイマもちょろっと設定しただけであっさり動くのですげー楽.PowerPCだとこうはいかないよなあ.

とりあえず割り込みも受けられるようになって,なんか随分前進した感じ.次はシリアルの受信割り込みかなあ.
(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.

前回作成したブートローダーだけど,前回はちょろっと動作させただけでろくに解説していなかったので,ちょっと解説しよう.

まずブートローダーについてなのだけど,リンカスクリプトに以下の行が追加されている.

buffer(rwx) : o = 0xffdf20, l = 0x002000 /* 8kb */

さらにデータ領域とBSS領域は,上記 buffer 領域に割り当てられるように変更してある.

これはどーいうことかというと,まあ本当はシリアルからプログラムをダウンロードしながらロード先のメモリ上に直接展開できればいいのだけど,面倒なので,いったんRAM上のバッファにぜんぶダウンロードして,で,ELFヘッダを解析してロード先にコピーするという構成にしてある.

で,プログラムをロードする先はRAMの先頭付近にしておきたいので,RAMの終端付近にデータ領域とBSSを置いてブートローダーはそこで動作する,というメモリ配置にしてある.バッファはブートローダー内で

#define BUFSIZE 4096
static unsigned char loadbuf[BUFSIZE];

のようにして文字配列として定義してあるので,BSSに配置される.これが上記の buffer 領域に置かれることになる.loadbufのサイズが4KBなので,buffer 領域は余裕をみて8KBとして定義している.

いったんバッファ上に置いておいて,さらにロード先に展開することを考えると,loadbuf[]の最大サイズは,RAMの16KBの半分の8KBとなる.(loadbuf[]を12KBにすれば12KBまでのプログラムをダウンロードできるが,残りRAMは4KBしかないので展開先が無くて意味がない.よってRAMサイズの半分が,バッファサイズの最大となる.

このためこのブートローダーは現状,8KBまでのプログラムしかロードできない.あとロードされるプログラムは,RAMの先頭付近を使うようにリンカスクリプトでメモリ配置する必要がある.

あと serial.c には,バイナリデータの送受信用に serial_getb(), serial_putb() を新規作成した.これらは改行コードの変換を行わずに生データを送受信する.前回はここで従来の serial_getc(), serial_putc() を使っていたために改行コードが変換されてしまってチェックサムとかが壊れてしまう,というのにハマッたわけだ.

あと uuencode 形式のデコードのために,uudecode.c を追加してある.xmodem.c からは USE_UUENCODE の定義の有無で,uudecode を呼び出すか,生データとして扱うかが切り替わるようになっている.シリアルからデータをダウンロードしながらデコードを行えるように,uudecode.c はストリームデータを処理できるような書き方をしてある.(同様に書けば,ELF形式をダウンロードしながらヘッダ解析してロード先に直接展開,ということもできると思う)

あとELF形式の解析のために elf.c が追加されている.ELF形式はそれなりに複雑なフォーマットだけど,ここでやってるのはELFヘッダのチェックをしてからプログラムヘッダを見て展開することだけなので,超簡単に書けている.ELF形式について詳しくはこの記事の第2回を参照してほしい.

main.c は,putval(), putxval() に実は負の値を渡すと無限ループに陥ってしまうという問題があったので,引数をunsignedとして受け取るように修正.あと run コマンドを追加して,ダウンロードしたプログラムをロード先に展開して実行するように機能追加してある.

次に,ダウンロードされるプログラムのほうについて.まあ現状では入力された単語をエコーするだけの機能しかなくて,実はブートローダーを流用して書いてある.

シリアルまわりとかスタートアップはブートローダーから持ってきたそのまま.スタートアップ(startup.s)ではスタックポインタを設定しているので,サンプルプログラムを起動するとスタックは設定されなおすことになる(ブートローダーが使用していたスタックを引続き利用するわけではない).ライブラリ(lib.c)もそのまま.異なるのはリンカスクリプトとmain.cなのだけど,main.cは使わない関数に蓋をして,単語をエコーするだけに書き換えただけなのでたいした変更は無い.あ,あとデータ領域の展開とBSSのクリアはブートローダー側でやっているので,main.c の init() からは削除してある.

大きな変更があるのはリンカスクリプトだ.以下,ブートローダーとロードされるサンプルプログラムでの,リンカスクリプトの変更点.

--- kzload/ld.scr Fri Sep 4 00:16:17 2009
+++ os/ld.scr Fri Sep 4 00:16:35 2009
@@ -4,44 +4,38 @@

MEMORY
{
- vectors(r) : o = 0x000000, l = 0x000100 /* top of ROM */
- rom(rx) : o = 0x000100, l = 0x07ff00 /* 512kb */
- ram(rwx) : o = 0xffbf20, l = 0x004000 /* 16kb */
- buffer(rwx) : o = 0xffdf20, l = 0x002000 /* 8kb */
+ /* reserve 256bytes space for ELF header */
+ ram(rwx) : o = 0xffbf20 + 0x100, l = 0x004000 - 0x100 /* 16kb */
stack(rw) : o = 0xffff00, l = 0x000010 /* end of RAM */
}

SECTIONS
{
- .vectors : {
- vector.o(.data)
- } > vectors
-
.text : {
_text_start = . ;
*(.text)
_etext = . ;
- } > rom
+ } > ram

.rodata : {
_rodata_start = . ;
*(.strings)
*(.rodata)
_erodata = . ;
- } > rom
+ } > ram

- .data : AT(_erodata) {
+ .data : {
_data_start = . ;
*(.data)
_edata = . ;
- } > buffer
+ } > ram

.bss : {
_bss_start = . ;
*(.bss)
*(COMMON)
_ebss = . ;
- } > buffer
+ } > ram

_end = . ;



まずロード時ていうかリンク時の注意なのだけど,ELF形式はリンカによって作成される
際に,先頭にELFヘッダが付加される.で,このELFヘッダがテキスト領域の直前に
配置&ロードされるようにプログラムヘッダが作成される.




これはロードされたプログラム側で,自身のELFヘッダを参照できるようにするためだと
思われる(あくまで想像だけど,もしかしたら共有ライブラリがらみで参照が必要
なのかもしれない)のだが,RAMの先頭(0xffbf20)から利用するようにふつうに書くと,
RAM領域の先頭として指定された 0xffbf20 というアドレスよりも少し前にELFヘッダが
はみ出して配置されてしまう.いろいろやってみたのだけどこれをうまく削ったり
調整したりする方法が見つからなくて,しかたがないので対策として,RAM領域の
先頭256バイトをELFヘッダ用に空けるようにしてある.
(今思ったのだけど,ブートローダがプログラムヘッダを見て展開するときに,
RAM領域内でないデータは展開しないようにすればいいだけだね.まあでもいいや)




ということで,サンプルプログラムのほうではRAMは先頭 0xffbf20 付近から利用する.
RAMの終端のほうはブートローダーが利用しているので,ブートローダーからロードした
プログラムに実行が移るまでは,上書きすることはできない.つまり,RAM終端付近に
なにかを配置するようにリンカスクリプトを書いてはいけない.




割り込みベクタはいまのところとくに上書きしていないので,リンカスクリプトから
削除.あとテキスト,リードオンリーデータ,データ,BSSをすべてRAM上に配置する
ように変更してある.




まあ解説はこんなとこかな.ざざっと説明しただけだけど.
(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.

今回は,いよいよブートローダーでプログラムをロードして,実行してみよう.最終的にやりたいのは,ブートローダーでの起動後にOSをロードして実行することでOSに処理を渡すことなのだけど,とりあえずはブートローダーで起動して,簡単なコマンド受け付けのサンプルプログラムをロードして実行してみる.

まずロードするプログラムのオブジェクトフォーマットなのだけど,いろいろ考えたのだけどELF形式で行くことにした.というのは,まあぼくがELFの記事を書いたことがあって馴染み深くてよく知っているのと,ELFでできないことはそうそう無いので融通が効くこと,フォーマット解析がそんなに難しくないこと(ローダの実装がラクそう),標準的なフォーマットとして普通に使われていること,などが理由だ.ELFフォーマットについて詳細は,この記事の第2回をぜひ読んでほしい.かなり詳細に書いてある.(追記:この記事を書いている現段階では,モトローラSフォーマットを知った今となってはそっちにすればよかったかなと思います.展開もラクそうだし,あとロード先に直接展開ができるのでRAMを最大限まで使えるし.まあ対応は簡単そうなので,そのうち対応しよう)

で,ELFの実行形式をいったんバッファにロードしてから実行コマンドでロード(メモリ上の動作すべきアドレスに展開)し,実行を渡すようにブートローダーを書いてみたのだけど,いまいちうまく動かない.

まあこのへんでいろいろハマってしまいちょっと時間をくってしまった.最初のうちはロードしたプログラムの挙動がなんかおかしくて,スタックポインタの設定とかシリアルの設定とかXMODEMのチェックサム計算とかをいろいろ疑っていたのだけど,じつはそもそもシリアルの受信関数(serial_getc())の内部で改行コードの変換をしているので,バイナリデータが壊れてしまうという問題だった.あー間抜け.(たとえテキストデータだとしても,XMODEMのブロックナンバとかチェックサム部分が壊れてしまい,エラーになってしまう)

ということで実装したのが以下のような感じ.

上のリンクでは,ブートローダーと,ロードして実行させるサンプルプログラム(テキスト入力を受け付けて,それを表示するだけのもの)を配布している.kzload というフォルダがブートローダーで,osというフォルダがサンプルプログラムだ.(将来的にOSをロードするようにするつもりなので,osというフォルダ名にしている)

なので手順としては,以下のようになる.
  1. ブートローダーとサンプルプログラムを別々にビルドする.
  2. ブートローダーを h8write でフラッシュに焼き込む.
  3. ブートローダーを起動する.
  4. loadコマンドでXMODEMでのファイル受信に入り,サンプルプログラムのELFファイルをシリアル転送する.
  5. 転送完了したらrunコマンド(これは今回新設)でサンプルプログラムを実行する,
  6. サンプルプログラムが起動する.
なお上のプログラムでは,実はuuencode形式でのファイル転送もサポートしている.というのは,うまく転送できないのはバイナリファイルで転送しているからでは?と考えて,uuencode形式でファイル転送するようにして試したから,その名残り.まあ実は serial_getc() の改行コード変換が問題だったのでバイナリファイルを転送することに問題は無く,その後 uuencode 形式は使用しないように修正したが,もしも uuencode 形式を利用したいならば以下のようにすればよい.
  • xmodem.c の USE_UUENCODE という定義を有効にしてブートローダーをビルド.
  • サンプルプログラムのビルド時に ./make.sh image を行うことで hello.uu という uuencode 形式のファイルが生成されるので,それを転送する.
デフォルトでは USE_UUENCODE は無効になっているので,ELF形式をそのままシリアル転送すればよいのだけど,なんかうまくシリアル転送できない場合にはuuencode 形式での転送を試してみるといいかもしれない.

ちなみに今回この uuencode 形式にすることを試したときに,モトローラSフォーマットでもいいのでは? と思ってモトローラSフォーマットについて調べてみたのだけど,実は以下のような利点がある.(uuencode形式を選択したのは,単に実装がラクそうだったから)
  1. テキスト形式なので,回線が7bitだったらどうとかフロー制御のコードがあったらどうとか余計なことを考えなくていい.
  2. データを受信しながらそのままメモリ上に展開する,という処理に向いている.
とくに,今回ブートローダーを作っていて,「データをバッファ上に一度読み込んでからロードアドレスに展開するのではなく,データを読み込みながら展開先を調べて直接ロードできないか?」という疑問があった.というのは,いったんどこかのバッファに置いてからロード先にコピーするような動作だと,処理自体は楽なのだけど,まあワーク領域として倍のメモリを使うことになるので,今回のマイコンボードのようにRAMが少ない場合には,問題となるからだ.(結局,今はいったんバッファに置く実装になっているので,これは将来課題ではある)

で,上のような問題意識があったうえでフォーマットを調べていたからこそ今回気がついたことなのだが,モトローラSフォーマットというのは,データを読み込みながらロード先のメモリ上に直接展開するのに非常に向いている.というのは,ロード先のアドレスが先頭にあるので,まずアドレスを読んで,後続のデータをそのアドレスの指す先に配置していけばいいからだ.うーん,きっとこういうことを考えた上で作られたフォーマットなんだろうなあ.

ついでにいうと,これもそーいう問題意識で考えていたからこそ気がついたことなのだけど,ELF形式というのも,まあモトローラSフォーマットほどやりやすくは無いが,プログラムヘッダが先頭付近にあるため,読み込みながら直接展開,ということは原理的にはできる(プログラムヘッダがファイルの終端にあったりすると,最後まで読まないとロード先がわからないので,直接展開は原理的に不可能).

この記事からもわかるのだけど,ELF形式は先頭付近にプログラムヘッダ,末尾にセクションヘッダが配置されており,プログラムのロードはプログラムヘッダを参照して行われるが,リンク作業はセクションヘッダを参照して行われる.なぜこのような配置になっているのか今までとっても疑問だったのだけど,おそらくプログラムヘッダが先頭付近にあるのは上記のようにプログラムを読み込みしながら展開先に直接展開するためだ.あとセクションヘッダが終端にあるのは,これもおそらくだけど,プログラムのロード&実行にはセクションヘッダは必要無いので,サイズ節約のために実行形式から取り除きたい場合がある.この場合,ファイルの先頭付近にあると,そこを削除すると後続のデータのオフセットが変わってしまうため,様々なオフセット計算をやり直さなければならなくなってこれはそうとう面倒臭い.しかし終端にあれば,単にそれを取り除くだけでいいからだ,と思う.

で,難しい話はあとに回して,とりあえず実行してみよう.今までのおさらいも兼ねて,ビルドからひととおり説明する.

まず,ブートローダーとサンプルプログラムをビルドする.ブートローダーは今まで通り,以下でビルドできる.

% ./make.sh clean ; ./make.sh ; ./make.sh image

これで kzload.mot というファイルが生成される.ここまでは(ファイル名が kzload* に変わっているけど)前回通り.

次に,サンプルプログラムをビルドする.

% ./make.sh clean ; ./make.sh

上に書いたように,ファイル転送に uuencode 形式を使うなら,以下も実行しておく.

% ./make.sh image

ここまでで準備は完了.

次に,h8write を使ってブートローダーをフラッシュROMに転送する.実際には Makefile に書いてあるので,make write するだけでいい.

teapot# make write
../../h8write/h8write -3069 -f20 kzload.mot
H8/3069F is ready! 2002/5/20 Yukio Mituiwa.
writing
WARNING:This Line dosen't start with"S".
Address Size seems wrong
WARNING:This Line dosen't start with"S".
Address Size seems wrong
.......................................
EEPROM Writing is successed.
teapot#

これで完了.

cuでシリアル接続し,ディップスイッチを起動用に切替えてリセットボタンを押してブートローダーを起動する.あとでサンプルプログラムを転送するときにファイル指定しやすくするために,cuはサンプルプログラムをビルドしたディレクトリで起動するとよい.

teapot# cu -l /dev/cuad0
Connected
Hello World!
>

ブートローダーが起動して,コマンド入力待ちになる.コマンド待ちするので,ブートローダーっつうよりモニタっつったほうが適切かもしんない.

loadコマンドを実行して,XMODEMによるファイル転送待ちに入る.

teapot# cu -l /dev/cuad0
Connected
Hello World!
> load
(待ち状態)

前回説明したように,「~」「C」でコマンド指定に入り,XMODEM送信アプリとしてlsxを指定する.引数にはサンプルプログラムのELF形式である「hello」ファイルを指定する.(uuencode形式を利用する場合は,ここで「hello.uu」を指定する)

teapot# cu -l /dev/cuad0
Connected
Hello World!
> load
~CLocal command? lsx hello

Enterを押すと転送が始まる.

> load
~CLocal command? lsx hello
Sending hello, 15 blocks: Give your local XMODEM receive command now.
Bytes Sent: 2048 BPS:586

Transfer complete
eceive succeeded.
>



うまく転送できた.実はこの「うまく転送できるまで」でハマッていろいろな試行錯誤があったのだけど,まあうまくいったのでいいや.

今回,ブートローダーにrunコマンドというのを新規追加してある.runコマンドを実行すると,loadによってバッファ上に読み込んだファイルをELF形式とみなして解析し,ロード先のアドレスを調べてメモリ上に展開し,エントリポイントに処理を渡す(要するに,ロードして起動する).

runコマンドで実行してみよう.

> run
starting from entry point.boot succeed!
os>

おー,起動した.かなり感動.ここまで来るのにどれだけ苦労したことか...(いやH8への移植だけに関して言えばここ数日のことなのだけど,OS作りたいなあとむかし漠然と思っていたときから考えると,ホント,しみじみ思ってしまう)

てきとうにコマンドを打ってみる.

> run
starting from entry point.boot succeed!
os> run
run
os> test
test
os> dump
dump
os> load
load
os> aaa
aaa
os>

入力した文字列をそのまま返している.ひとまずちゃんと動いているようだ.runやloadを実行しても,ブートローダーでの動作が行われずにただ文字列をそのまま返しているので,サンプルプログラムに完全に処理が移っていることがわかる.

うーんちょっと感動.結局,ブートローダーが作れてしまった.まあものすごく簡単なものではあるが,きちんと動作して,他のプログラムをブートできている.

OS作りたいなあと漠然と思ってからいろいろ勉強したりしてきたけれど,実際作ろうとなったときにこーいうのがパパッと作れるというのは,やっぱしいままで勉強してきた甲斐があったなあというものだ.しみじみ.やっぱりなんでも基礎力が大事だね!

ちなみに今回のソースコードだけど,ブートローダーには前回に対して以下のファイルが新規追加されている.
  • ELF形式のファイルの解析と展開 (elf.c)
  • uudecode の復号 (uudecode.c)
ここで注目したいのはこれらのファイルサイズなのだけど,elf.cは92行,uudecode.cは48行だ.ちなみにXMODEMの処理である xmodem.c は96行.ELFとかXMODEMとかUUENCODE実装とかってなんか難しそうなイメージあるけど,特定の処理に限定すればこんなもんで済むもんです.これくらいならちょっと読めば誰でも十分に理解できるし,じゃあモトローラSフォーマットに対応してみようとかBinHexにも対応しようとか思ったときに,なんか簡単にできそうな気がするよね.

いま調べたら,ブートローダーぜんぶ含めても634行だった.まあエラー処理とかがかなりザルではあるが,ファイルを読んで展開するだけなら,こんなんで書けるということだ.ブートローダーってなんか難しいイメージあるけど,600行ちょいなら自分でも十分に読めるかな?作れるかな?って感じがするでしょ?OS作るとかブートローダー自作とかってなんか難しいイメージあるけど,あんま難しいこと考えずに作れば,これくらいでできちゃうもんなのよ.

あと今回思ったことだけど,ELF形式についてなのだけど,実際に自分で作ってみないと気が付かない,逆に言えば自分で作ってみれば気がつくこと,というのはあるもんだなあ,と.ELF形式のプログラムヘッダがなぜ先頭付近にあるのか?セクションヘッダがなぜ終端にあるのか?は,今回実際にいろいろ作ってみて(試行錯誤してみて)はじめて気がついたことだ.

よく,通り一辺倒の知識を得ただけで「この分野に関しては制覇したぜ!」みたいなこという人がいるよね.「C言語は完璧だぜ!」とか言っちゃう感じ.でもそーいうのって全然不十分で,そもそも本に載っている知識を得ただけで満足するだけじゃだめで,なぜそのような仕様になっているのか?なぜそーいう実装になっているのか?という製作者の考えとか気配りとか苦肉の策とかに気がつくようにならないと,その分野をマスターしたとはいえないなあ,と思う.こうこういう経緯で,歴史の流れでそうなってしまった,とかね.(まあ別に自分がELF形式をマスターしたぜ!という意味ではなく,自分に対する戒めです).

C言語なら,文法事項をマスターしただけでなく,なぜそのような言語仕様になっているのか?とかね.こーいうのは習って知るだけではなく,作られた経緯とか歴史とか実際の使われかたとかを勉強していく過程で,自分で自然と気がつけるようになりたいものだ.1を知ることで10を知る,って感じでね.

今日はとりあえず動いたので満足.サンプルプログラムの構成とか詳しい解説は次回にしよう.