KOZOSの改良を進めたいのだが

テーマ:

今週末はOSC、来週末には関西オープンソースだ。発表もあるし、KOZOSの改良とか準備を進めたい。。。進めたいのだけど、現在Interface誌の連載記事の作業にかかっていて、なかなか時間がとれない。OSCは今のところ毎回「来てよかった」と思えているし、今回も非常に楽しみではあるのだが、このへんが悩みどころだ。


まあInterface誌のほうはなんとか目途は立ってきたのだけど、それとは別の校正作業(詳細はまだ秘密)も入ってしまったし、別件の執筆作業(こっちもまだ秘密)もあるし、OSCや関西オープンソースの準備もあるし。。。ということで、最近は週末はいろんな作業に時間をとられてどこにも行っとらん。平日は会社だし、帰宅後も作業だし、マジで1週間ぜんぶ仕事です。


実は先週くらいに新型インフルにかかってしまって、会社命令で1週間出社禁止になってしまい、外出もできず自動的にカンヅメ状態ということで、ずっと家にこもって作業していた(他にやることも無いしね)。ということで(不謹慎かもしれないが)まあある意味ラッキーというかかなり作業を進められて、ついでに言うならその前のシルバーウイークもずっと作業していたというのに、それでもまだまだ追いつかん。


とか言ってたら、もうキャンプのシーズンを過ぎてしまった。。。

う~ん、遊びに行きてえ。。。

AD

えー、10月末のOSC東京と11月初旬の関西オープンソースで展示&発表をします。

OSCではライトニングトーク、関西オープンソースではステージ発表です。

とはいっても関西オープンソースのほうは初参加でよく勝手がわからないので、どんなかんじになるのか本人もわかっていません。


もともとは展示だけのつもりだったのだけど、まあたまには社外で不特定多数の知らない人を相手にしてしゃべるのも必要かなーとおもってエントリーしてみました。


内容は、組込みOSを自作しようってことでふだん思っているようなことをしゃべってきます。興味のある人は参加してみてください。


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

今まで内蔵RAM上で動いていたのだけど,LANコントローラ対応でいよいよ内蔵RAMではサイズ的にきつくなってきて,これ以上の機能追加は厳しいので,OSをDRAM上で動かすように修正しよう.

で,DRAMへの以降なのだけど,OSで行っているDRAMコントローラの初期化をブートローダーで行うようにして,あとOSのリンカスクリプト書き換えてDRAM上に持っていくだけで超簡単にできると思っていたのだけど,バグがあってちょっとひと苦労.

まずDRAMの初期化なのだけど,OSのデバッグ中に気がついたことなのだけど,従来のままだとしばらく値を保持させるとビット化けしてしまうアドレスが存在することが判明.というのは,ブートローダー対応してからOSをDRAMに移したのだけど,DRAM上で動かすようにすると固まってしまう(内蔵RAM上だとなぜか正常に動作する)という問題が出て,まあ実際の原因は後述するint型の問題だったのだけど,どうもDRAMがちゃんと動いていないように思えたのでチェックプログラムを作って試してみたらやはりビット化けするアドレスが存在する.

従来のメモリチェックは値を書き込んだ直後に読む,ということをやっていたので値をしばらくのあいだ保持できているかということはチェックできていなかった.なので最初に全領域に値を書き込んで,その後で全領域を読んでチェック,さらに特定のビットが0に化けても1に化けても検出できるように,ビットを立てたパターンとビットを落としたパターンの両方でチェックする,というようなチェックをしたらビット化けが検出された.

ということでdram_init()での設定パラメータがあやしく思い,ウエイトとリフレッシュレートをいろいろ変えてみたのだけど,リフレッシュレートを上げたらビット化けしないようになった.どうもリフレッシュレートが低すぎたらしい.まあほんとはデータシート見たりして適切な値を調べるべきなのだけど,とりあえず全メモリ全パターンチェックでエラーにならなくなったのでよしとする.(なんかDRAM搭載のボートで買うと,DRAMコントローラの設定例が載ったドキュメントがついてくるみたいね.今回利用しているボードだとDRAMはおまけ扱いなのでドキュメントがついてないってことかもしれん)

ただそれだけ直しただけではまだダメ(っていうか,ビット化けするのは使っていないアドレスだったので,これが固まる原因ではなかった)で,OSのテキストとデータ領域はDRAMに移しても正常動作したのだけど,BSS領域を移すとやはりうまく動かず固まってしまう.

いろいろ見直したのだけど原因がよくわからなくて,しかたがないのでいろんなところに puts() を入れてシリアル出力させてどこまで処理が進んだか見るという原始的ないわゆる printf デバッグで追いかけたら,kz_free()でのメモリ解放でおかしな領域を解放しようとして kz_sysdown() が呼ばれて while(1) の無限ループに入っていることが判明.この時点ではうわーメモリ破壊かなーめんどくさそーと思ったのだけど,もっと追いかけたらkz_send()でのメッセージ送信でなんか送信先のスレッドIDがおかしくなっていて,そのせいでおかしなポインタの先を参照して kz_free() におかしなアドレスを渡していることが判明.

ということで原因はおかしなスレッドIDでkz_send()が呼ばれていることなのだけど,printfデバッグで調べたら,送信元は command スレッドになっている.ということは送り先は extintr なのだけど,extintr_id は uint32 としてきちんと定義されている.

で,extintr_id はどこで得られているかというと,kozos.c のスレッド起動部分でkz_run()の戻り値を与えている.このへんでようやく気がついたのだけど,実は kz_run() の戻り値が int 型として定義されていたので,16ビット整数になってしまい,スレッドIDの上位ビットが削られた状態で extintr_id に格納されていたのだ.このため kz_send()でのメッセージ送信時に,おかしなスレッドID宛に送信していたというのが結局の原因.

この原因を考えると,うーんいままでよく動いていたなーと思うのだけど,実は従来は内蔵RAM上にBSSがあったのだけど,内蔵RAMのアドレスが0xffbf20 ~ 0xffff00 なので,上位ビットが削られて16ビット値となっても,その後に extintr_id に格納されるときに32ビットに拡張される際に,上位ビットが符号拡張されて,0xffffXXXX のようなアドレスになって格納されると思うのだな.で,実際にメモリアクセスされる際には上位8ビットが無視されて0x00ffXXXX のアドレスがアクセスされることで,正常動作していたようだ.なのでBSSをDRAMに移したことでアドレスが 0x400000 ~ 0x5fffff になってしまい,正常動作しなくなったということのようだ.

いやー,実は今までもBSSを動かすとなんか固まるときがあるなーとは思っていたのだけど,これが原因だったわけだ.ちょっと調べるのたいへんだったけど,まあ原因判明してよかったよかった.なおしたらあっさりと動いた.

で,今回の修正なのだけど,いい機会なのでリンカスクリプトを修正して,メモリ上のアドレスは基本的にリンカスクリプトで指定して,そこを見るように変更した.というのは,たとえばスレッドのスタックのアドレスを指定するのに今まではconfigure.h で

#define THREAD_STACK_START 0xffe820

のようなことをやっていたわけなのだけど,こーいうアドレス情報は基本的にはリンカスクリプトで指定して,その値を見るようにすべきだと思うのだな.というのは,これだとまずいろんなアドレス設定がいろんなヘッダファイルに点在してしまい,メモリ調整が面倒になったり調整し忘れでバグの原因になったりする可能性が高い.さらに,たとえばある領域の直後の領域を使いたい場合などに,#define でのベタ書きではなく,リンカスクリプトのリンク情報に応じて自動調整されるようにしたほうが効率的,といった理由がある.

ということで,今回の大きな修正は以下.
  • ブートローダーにDRAMコントローラを持っていって,ブートローダーでDRAMの初期化を行う.
  • DRAM初期化時の設定値で,リフレッシュレートを高くする.
  • DRAMチェック用関数を追加.(データ保持をチェックできるように,全領域ライトの後で全領域リードしてチェックをいくつかのメモリパターンで行う)
  • kz_run()の戻り値を32ビットに変更.
  • リンカスクリプトを修正して,メモリアドレスは基本としてリンカスクリプトですべて指定(or調整)されるようにする.
  • intが16ビットで問題ありそうな箇所を部分的に修正.
以下,修正したソース.まずブートローダーの修正点について.

main.cなのだけど,DRAMコントローラの初期化用に dram_init() の呼び出しを追加した.実際の DRAM 初期化処理は dram.c をOS側から持ってきて,リフレッシュレートの対応とメモリチェックの追加を行った.具体的には dram.c に以下のような対応が入っている.

--- os/ether/dram.c Sat Oct 3 13:22:25 2009
+++ bootload/dram/dram.c Sun Oct 4 23:35:28 2009
@@ -27,10 +27,29 @@

int dram_init()
{
+ /*
+ * dram_check2()でチェックしたら,値をうまく保持できないビットが存在した
+ * ので,リフレッシュレートを上げたら解消された.(ウエイトを多めにしても
+ * 効果は無かった)
+ * とりあえず,リフレッシュレート高めの設定にしておく.
+ */
+
*H8_3069F_ABWCR = 0xff;
+#if 0
*H8_3069F_RTCOR = 0x07;
+#else
+ *H8_3069F_RTCOR = 0x03; /* リフレッシュ周期を短めに設定 */
+#endif
+#if 1
*H8_3069F_RTMCSR = 0x37;
+#else
+ *H8_3069F_RTMCSR = 0x2f; /* リフレッシュ周波数UP */
+#endif
+#if 1
*H8_3069F_DRCRB = 0x98;
+#else
+ *H8_3069F_DRCRB = 0x9f; /* ウエイト挿入 */
+#endif
*H8_3069F_DRCRA = 0x30;

*H8_3069F_P1DDR = 0xff;
@@ -39,9 +58,17 @@
/* *H8_3069F_PBDDR = ...; */

/* H8_3069F_WCRH = ...; */
+#if 1
*H8_3069F_WCRL = 0xcf;
-
- *H8_3069F_ASTCR = 0xfb;
+#else
+ *H8_3069F_WCRL = 0xff; /* ウエイト挿入 */
+#endif
+
+#if 1
+ *H8_3069F_ASTCR = 0xfb; /* 2ステートアクセス */
+#else
+ *H8_3069F_ASTCR = 0xff; /* 3ステートアクセス */
+#endif

return 0;
}

dram.c には他にもメモリチェックとメモリクリアの関数が追加されている.これは以下.

@@ -107,4 +134,82 @@
putxval((unsigned long)*p, 8);
puts("\n");
return -1;
+}
+
+static uint32 dram_check2_val0(uint32 *addr) { return 0x55555555UL; }
+static uint32 dram_check2_val1(uint32 *addr) { return 0xaaaaaaaaUL; }
+static uint32 dram_check2_val2(uint32 *addr) { return 0x00000000UL; }
+static uint32 dram_check2_val3(uint32 *addr) { return 0xffffffffUL; }
+static uint32 dram_check2_val4(uint32 *addr) { return (uint32)addr; }
+static uint32 dram_check2_val5(uint32 *addr) { return ~(uint32)addr; }
+
+int dram_check2()
+{
+ uint32 *p;
+ int ret = 0, i;
+ uint32 (*getval[])(uint32 *) = {
+ dram_check2_val0,
+ dram_check2_val1,
+ dram_check2_val2,
+ dram_check2_val3,
+ dram_check2_val4,
+ dram_check2_val5,
+ NULL
+ };
+
+ for (i = 0; getval[i]; i++) {
+
+ puts("DRAM check pattern: ");
+ putxval(i, 0);
+
+ puts("\nDRAM setting...\n");
+
+ for (p = (uint32 *)DRAM_START; p < (uint32 *)DRAM_END; p++) {
+ *p = getval[i](p);
+ if (!((uint32)p & 0xfff)) {
+ putxval((unsigned long)p, 8);
+ puts("\x08\x08\x08\x08\x08\x08\x08\x08");
+ }
+ }
+
+ puts("\nDRAM checking...\n");
+
+ for (p = (uint32 *)DRAM_START; p < (uint32 *)DRAM_END; p++) {
+ if (*p != getval[i](p)) {
+ puts("\nERROR! :");
+ putxval((unsigned long)p, 8);
+ puts(" ");
+ putxval((unsigned long)*p, 8);
+ puts("\n");
+ ret = -1;
+ }
+ if (!((uint32)p & 0xfff)) {
+ putxval((unsigned long)p, 8);
+ puts("\x08\x08\x08\x08\x08\x08\x08\x08");
+ }
+ }
+
+ puts("\n");
+
+ }
+
+ if (ret == 0) {
+ puts("\nall check OK.\n");
+ }
+
+ return ret;
+}
+
+int dram_clear()
+{
+ uint32 *p;
+
+ puts("DRAM clearing...\n");
+
+ for (p = (uint32 *)DRAM_START; p < (uint32 *)DRAM_END; p++)
+ *p = 0;
+
+ puts("DRAM cleared.\n");
+
+ return 0;
}

あとmemset()とかのライブラリ関数を集めた lib.c と,あとシリアルドライバのserial.c が,OS側のやつとちょっとなんか食い違っていたので,OSのものに実装を合わせた.

さらに main.c の init() に dram_init() の呼び出しを加えて,コマンド解釈部分には メモリチェック用に ramchk, ramchk2 と,あとメモリクリア用に ramclr を追加した.以下のような感じ.

@@ -159,6 +76,12 @@
f = (void (*)())entry_point;
f();
}
+ } else if (!strcmp(buf, "ramchk")) {
+ dram_check();
+ } else if (!strcmp(buf, "ramchk2")) {
+ dram_check2();
+ } else if (!strcmp(buf, "ramclr")) {
+ dram_clear();
} else {
puts("unknown command.\n");
}

あと serial.c をOS側に合わせて修正したことでSCIの番号指定が必要になったので,シリアルを使っている部分全般(main.cとlib.cとxmodem.c)にインデックス指定の対応が各所に入っている.

次に,OS側の修正.

まず,kz_run()の戻り値がint型になっていて,スレッドIDの上位ビットを渡せていないという大問題の修正.

diff -u os/ether/syscall.h os/ondram/syscall.h
--- os/ether/syscall.h Sat Oct 3 11:01:06 2009
+++ os/ondram/syscall.h Mon Oct 5 01:02:01 2009
@@ -31,7 +31,7 @@
int stacksize;
int argc;
char **argv;
- int ret;
+ uint32 ret;
} run;
struct {
int dummy;

修正はこれだけ.kz_run()の処理の本体である thread.c:thread_run() では戻り値はきちんとuint32になっているので,ホントに修正忘れ.これでずいぶんハマッてしまった.

次に,リンカスクリプトの修正.

diff -u os/ether/ld.scr os/ondram/ld.scr
--- os/ether/ld.scr Sat Oct 3 21:11:47 2009
+++ os/ondram/ld.scr Sun Oct 4 23:38:45 2009
@@ -5,11 +5,19 @@
MEMORY
{
/*
- * reserve 256bytes space for software interrupt vector
- * and 256bytes space for ELF header.
+ * DRAM (2MB)
+ * reserve 256bytes space for ELF header.
*/
+ dram(rwx) : o = 0x400000 + 0x100, l = 0x200000 - 0x100
+
+ /*
+ * internal RAM (16KB)
+ * reserve 256bytes space for software interrupt vector.
+ */
+ uservec(rw) : o = 0xffbf20, l = 0x000100 /* start of RAM */
ram(rwx) : o = 0xffbf20 + 0x200, l = 0x004000 - 0x200 /* 16kb */
- bootstack(rw) : o = 0xffff00, l = 0x000010 /* end of RAM */
+ userstack(rw) : o = 0xffc120, l = 0x000010
+ bootstack(rw) : o = 0xffff00, l = 0x000010
intrstack(rw) : o = 0xffff00, l = 0x000010 /* end of RAM */
}

@@ -19,29 +27,43 @@
_text_start = . ;
*(.text)
_etext = . ;
- } > ram
+ } > dram

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

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

.bss : {
_bss_start = . ;
*(.bss)
*(COMMON)
_ebss = . ;
- } > ram
+ } > dram /* DRAMにするとなぜか固まる.要調査 */

+ . = ALIGN(4);
_end = . ;
+
+ .freearea : {
+ _freearea = .;
+ } > dram
+
+ .uservec : {
+ _uservec = .;
+ } > uservec
+
+ .userstack : {
+ _userstack = .;
+ } > userstack

.bootstack : {
_bootstack = .;

dram空間を定義して,テキスト,データ,BSSをdram空間に持っていく.dram空間を+256バイトして定義しているのは,以前にELF形式をロードできるようにしたときと同じ理由で,ELFヘッダがうまく入る領域を空けているため(ELFヘッダがはみ出してしまうので).

さらにソフトウエア割り込みベクタの登録域として uservec 領域,スレッドのスタック領域として userstack 領域というのを新設した.これらは従来は configure.h で THREAD_STACK_START とか,interrupt.h で VECTOR_ADDR とかで直の値で定義してあったのだけど,リンカスクリプトで集中定義することでメモリ調整時の修正ミスや修正洩れを無くすため.

さらにさらに,従来は kz_alloc() で獲得できる動的メモリ領域の2048バイトの領域をDRAMの先頭からとしてこれも直接指定していたのだけど,DRAM先頭付近はOSの本体がロードされるので,それに合わせて後ろの空いた部分を使えるように,freeareaという領域をBSSの直後に作成している.(実際にはその先頭アドレスがわかればいいだけなので,空領域として定義している)

で,リンカスクリプトの修正に合わせて,configure.h と interrupt.h の修正.

diff -u os/ether/configure.h os/ondram/configure.h
--- os/ether/configure.h Sat Oct 3 16:11:48 2009
+++ os/ondram/configure.h Mon Oct 5 01:11:41 2009
@@ -4,13 +4,10 @@
#include "types.h"
#include "lib.h"

-extern uint32 intrstack;
+extern char intrstack;
+extern char userstack;
#define INTR_STACK_START (&intrstack)
-#if 0
-#define THREAD_STACK_START 0xffe820
-#else
-#define THREAD_STACK_START 0x5f0000
-#endif
+#define THREAD_STACK_START (&userstack)

#define SIGHUP 1
#define SIGILL 4

アドレス直指定でなく,リンカスクリプトで定義した userstack シンボルの位置を見るようにしている.userstack はリンカスクリプト内で作成したシンボルなので変数としての実体は無いが,C言語から参照するために extern 宣言して参照している.

次に,interrupt.h の修正.

diff -u os/ether/interrupt.h os/ondram/interrupt.h
--- os/ether/interrupt.h Sat Oct 3 11:01:06 2009
+++ os/ondram/interrupt.h Sun Oct 4 17:32:21 2009
@@ -2,7 +2,10 @@
#define _INTERRUPT_H_INCLUDED_

#define VECTOR_NUM 64
-#define VECTOR_ADDR ((void *)0xffbf20)
+
+extern char uservec;
+#define VECTOR_ADDR (&uservec)
+
typedef void (*interrupt_handler_t)(int vec);

#define VECTORS ((interrupt_handler_t *)VECTOR_ADDR)

これも configure.h の修正と同じで,アドレス直指定でなくリンカスクリプトで定義した uservec シンボルの位置を見ることで,ソフトウエア割り込みベクタの位置を求めるようにしている.どう?スマートになったでしょ?

次に,main.c の修正.

diff -u os/ether/main.c os/ondram/main.c
--- os/ether/main.c Sat Oct 3 19:45:04 2009
+++ os/ondram/main.c Mon Oct 5 01:08:59 2009
@@ -14,11 +14,25 @@
extern int ebss;

/* clear BSS */
+#if 0
memset(&bss_start, 0, (uint32)&ebss - (uint32)&bss_start);
+#else
+ /*
+ * memset()はサイズをintで渡すので,BSSが32KBを越えたときに
+ * 正常にクリアできないので,memset()を利用せずに自前でクリアする.
+ */
+ {
+ char *p;
+ for (p = (char *)&bss_start; p < (char *)&ebss; p++)
+ *p = 0;
+ }
+#endif

serial_initialize(0, 0);

+#if 0 /* ブートローダーで初期化しているので,初期化不要 */
dram_init();
+#endif

return 0;
}

まあコメントの通りなのだけど,BSSのクリアに従来は memset() を呼んでいたが,memset() のサイズは16ビットint型として渡しているので,BSSが大きくなったときにクリアしきれないことが考えられる.これについてはmemset()側を直すべきかとも思ったのだけど,BSSの初期化みたいな基本的な部分がライブラリ関数に左右されるのもよくないかと思ったので,自前で(そしてサイズに依存しないように)クリアするように修正した.

dram_init()によるDRAMの初期化はブートローダー側で行うので呼ばないように修正.ていうかこの段階ではすでにOSがDRAM上で動いているので,下手に設定値をいじったらいきなりDRAMの内容が消えたりしてOSが動かなくなる可能性があるので,dram_init()を呼んではいけない.

次に memory.c での動的メモリ管理の修正.

diff -u os/ether/memory.c os/ondram/memory.c
--- os/ether/memory.c Sat Oct 3 21:10:10 2009
+++ os/ondram/memory.c Mon Oct 5 01:15:24 2009
@@ -4,11 +4,11 @@

#include "lib.h"

-#define MEMORY_AREA1_SIZE 32
+#define MEMORY_AREA1_SIZE (uint32)32
#define MEMORY_AREA1_NUM 16
-#define MEMORY_AREA2_SIZE 64
-#define MEMORY_AREA2_NUM 8
-#define MEMORY_AREA3_SIZE 2048
+#define MEMORY_AREA2_SIZE (uint32)256
+#define MEMORY_AREA2_NUM 16
+#define MEMORY_AREA3_SIZE (uint32)2048
#define MEMORY_AREA3_NUM 64

#if 0
@@ -16,7 +16,8 @@
static char memory_area_2[MEMORY_AREA2_SIZE * MEMORY_AREA2_NUM];
static char memory_area_3[MEMORY_AREA3_SIZE * MEMORY_AREA3_NUM];
#else
-#define memory_area_1 ((char *)DRAM_START)
+extern char freearea;
+#define memory_area_1 (&freearea)
#define memory_area_2 (memory_area_1 + MEMORY_AREA1_SIZE * MEMORY_AREA1_NUM)
#define memory_area_3 (memory_area_2 + MEMORY_AREA2_SIZE * MEMORY_AREA2_NUM)
#endif

まずメモリサイズを uint32 として定義するように修正.というのは,サイズ×領域数の掛け算を行う際に,(int)×(int)になるとintの上限あふれしたときにまたおかしなことになってしまうかな~と思ったので.実際にどうなるかは未確認なのだけど.

あと,DRAM_STARTでなくリンカスクリプトで定義した freearea を見ることで,OSのBSSの直後から無駄無くメモリを利用するように修正.こーいうのができるのが,リンカスクリプトにメモリ定義をすべて移した利点ではある.(自動調整してくれる)

最後に,これはおまけだけど,extintr の修正.

diff -u os/ether/extintr.c os/ondram/extintr.c
--- os/ether/extintr.c Sat Oct 3 16:13:04 2009
+++ os/ondram/extintr.c Sun Oct 4 23:55:35 2009
@@ -8,7 +8,8 @@

#define USE_MESSAGE

-#define BUFFER_SIZE 1800
+#define CONS_BUFFER_SIZE 16
+#define ETH_BUFFER_SIZE 1800

uint32 extintr_id;

@@ -125,9 +126,9 @@
char *buffer;

#ifdef USE_MESSAGE
- buffer = kz_kmalloc(BUFFER_SIZE);
+ buffer = kz_kmalloc(CONS_BUFFER_SIZE);
#else
- buffer = kx_kmalloc(BUFFER_SIZE);
+ buffer = kx_kmalloc(CONS_BUFFER_SIZE);
#endif
size = 1;

@@ -220,9 +221,9 @@
char *buffer;

#ifdef USE_MESSAGE
- buffer = kz_kmalloc(BUFFER_SIZE);
+ buffer = kz_kmalloc(ETH_BUFFER_SIZE);
#else
- buffer = kx_kmalloc(BUFFER_SIZE);
+ buffer = kx_kmalloc(ETH_BUFFER_SIZE);
#endif

size = ether_recv(intp - interrupts, buffer);

従来はシリアル入力もLANからのパケットも1800バイトの領域を獲得することでmemory.c の2048バイト領域を利用していたのだけど,シリアル入力は1文字入力であり,さすがにこれは無駄なので,シリアル入力では少ない領域が利用されるように修正.

あと dram.c は dram_init() を呼ばなくなったのでOS側では必要は無くなったのだけど,いちおうブートローダー側の修正に合わせた修正をしておいた.

修正は以上.で,これでようやくDRAM上で実際に動作するようになった.面倒なので今回はお試し結果は省略.各自で試してみてね.シリアルの速度はちょっと前に 19200bps に変更されているので,接続時には注意してちょうだい.
AD
(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.

前回までで,DRAMが使えるようになった.いよいよLANコントローラを動かして,LANにつないでみよう.

H8/3069Fボードには,RTL8019というLANコントローラがのっている.まあ有名な「カニチップ」と呼ばれるやつだ.

これはNE2000互換のチップなので動作させること自体はそれほど難しくないはずなのだが,LANコントローラをはじめて触るぼくとしては,なんのサンプルも無しにゼロからぜんぶレジスタ設定するのはちょっときつい.ということでH8/3069FボードでRTL8019動作させているようなサンプルが無いかなーと探してみたら,以下のサイトが見つかった.

「無限ループの法則」

上記サイトで同じ秋月のボードにNTPを実装していて,ntp.zipというファイルで公開されているので,それを参考にしてRTL8019の設定をしてみる.

参考にするとはいっても,ポート周りの設定の見直し(DRAMも併用するので,ポート関連のレジスタの設定を見直す)とか,あと割り込みまわりの設定をしなければならない.上のサイトの実装では,割り込み有効化していないからだ.なのでそのまま使えばいいというわけにはいかず,ボードのCD-ROMに付属していたRTL8019のデータシートを見て,レジスタ類を適当に設定する.回路図を見ると,RTL8019の割り込みはINT4というピンがH8/3069FのIRQ5に接続されているので,IRQ5の割り込み有効化する対処も入れる.あとポート周りの設定も行う.まあ詳しくは ether.c を参照してほしい.ether_init()でRTL8019のレジスタ設定,port_init()でH8/3069Fのポートの設定を行っている.

で,ARPに返事してpingに応答することで,とりあえずpingだけ通るようなのを書いてみた.以下,修正したソース.大きな修正は以下だ.
  • LANコントローラのデバイスドライバとして ether.c を追加.
  • pingに応答するスレッドとして network スレッドを追加.(network.c)
  • etherの送受信を受け付けるように extintr.c を拡張し,LAN対応する.
  • IRQ5の割り込み対応.(ソフトウエア割り込みベクタの設定と,thread.cでの受け付け処理)
  • メモリ調整.(いよいよ内蔵RAMでは足りなくなったので,スレッドのスタックと動的獲得するメモリ領域をDRAMに移動)


以下に修正点について簡単に説明しよう.

まず,メモリ調整の修正から.

diff -ruN h8_12/os/configure.h h8_13/os/configure.h
--- h8_12/os/configure.h Mon Sep 28 23:19:11 2009
+++ h8_13/os/configure.h Sat Oct 3 22:16:01 2009
@@ -6,7 +6,11 @@

extern uint32 intrstack;
#define INTR_STACK_START (&intrstack)
+#if 0
#define THREAD_STACK_START 0xffe820
+#else
+#define THREAD_STACK_START 0x5f0000
+#endif

#define SIGHUP 1
#define SIGILL 4

スレッドのスタックをDRAM領域の後ろの方にもっていった.ether.cを入れた当初はテキスト領域が膨れ上がってスレッドのスタックとぶつかってしまいまともに起動しなかったので,とりあえずこの対応を入れた.

次に,リンカスクリプトの対応.

diff -ruN h8_12/os/ld.scr h8_13/os/ld.scr
--- h8_12/os/ld.scr Mon Sep 28 23:19:11 2009
+++ h8_13/os/ld.scr Sat Oct 3 22:16:01 2009
@@ -9,7 +9,7 @@
* and 256bytes space for ELF header.
*/
ram(rwx) : o = 0xffbf20 + 0x200, l = 0x004000 - 0x200 /* 16kb */
- bootstack(rw) : o = 0xfffd00, l = 0x000010 /* end of RAM */
+ bootstack(rw) : o = 0xffff00, l = 0x000010 /* end of RAM */
intrstack(rw) : o = 0xffff00, l = 0x000010 /* end of RAM */
}

もともとはブート時のスタックと割り込み用スタックは別々にしていたが,メインスレッドが起動した後にはブート処理に戻ることは無く,独立させる必要性は無いので,共通化してしまう.

次に動的獲得するメモリをDRAMにもっていくたいおう.

diff -ruN h8_12/os/memory.c h8_13/os/memory.c
--- h8_12/os/memory.c Mon Sep 28 23:19:11 2009
+++ h8_13/os/memory.c Sat Oct 3 22:16:01 2009
@@ -1,18 +1,25 @@
#include "kozos.h"
+#include "dram.h"
#include "memory.h"

#include "lib.h"

#define MEMORY_AREA1_SIZE 32
#define MEMORY_AREA1_NUM 16
-#define MEMORY_AREA2_SIZE 48
+#define MEMORY_AREA2_SIZE 64
#define MEMORY_AREA2_NUM 8
-#define MEMORY_AREA3_SIZE 64
-#define MEMORY_AREA3_NUM 4
+#define MEMORY_AREA3_SIZE 2048
+#define MEMORY_AREA3_NUM 64

+#if 0
static char memory_area_1[MEMORY_AREA1_SIZE * MEMORY_AREA1_NUM];
static char memory_area_2[MEMORY_AREA2_SIZE * MEMORY_AREA2_NUM];
static char memory_area_3[MEMORY_AREA3_SIZE * MEMORY_AREA3_NUM];
+#else
+#define memory_area_1 ((char *)DRAM_START)
+#define memory_area_2 (memory_area_1 + MEMORY_AREA1_SIZE * MEMORY_AREA1_NUM)
+#define memory_area_3 (memory_area_2 + MEMORY_AREA2_SIZE * MEMORY_AREA2_NUM)
+#endif

typedef struct _kzmem {
struct _kzmem *next;

etherフレームを扱うので,2048バイトの領域を新たに作成する.で,領域をDRAMに持っていくことで内蔵RAMに空きを作る.

ていうかこれでももう内蔵RAMはいっぱいいっぱいに近いので,いっそのことプログラムをすべてDRAMに持っていって,DRAM上で動作するようにしてしまったほうがよかったかもしれない(リンカスクリプトの設定をちょろっと修正するのと,ブートローダーにDRAM初期化処理を入れるだけなので,実は簡単にできる).まあこれはそのうち考えよう.(なるべくなら内蔵RAM上で動かしたいなあとなんとなく思っているので)

次に,extintrの修正.

diff -ruN h8_12/os/extintr.c h8_13/os/extintr.c
--- h8_12/os/extintr.c Mon Sep 28 23:19:11 2009
+++ h8_13/os/extintr.c Sat Oct 3 22:16:01 2009
@@ -2,12 +2,13 @@
#include "thread.h"
#include "extintr.h"
#include "serial.h"
+#include "ether.h"

#include "lib.h"

#define USE_MESSAGE

-#define BUFFER_SIZE 16
+#define BUFFER_SIZE 1800

uint32 extintr_id;

まず,割り込み発生時のスレッドへの通知で,etherフレームが送られるのでバッファに2048バイト領域が使われるように,BUFFER_SIZE を拡張する.etherフレームは最大1500バイトちょいくらいなので,1800バイトもあれば十分.(これはシリアルとLANでサイズを別にしたほうがいいかもしれない.現状,シリアル受信時にも2048バイト領域が使われてしまってなんか無駄)

続けて extintr.c の修正.

@@ -17,6 +18,10 @@
struct interrupts *interrupt;
} consreg[EXTINTR_CONSOLE_NUM];

+static struct ethreg {
+ struct interrupts *interrupt;
+} ethreg[EXTINTR_ETHER_NUM];
+
static struct interrupts {

uint32 id;
@@ -24,11 +29,13 @@
enum {
INTERRUPTS_TYPE_UNKNOWN = 0,
INTERRUPTS_TYPE_CONSOLE,
+ INTERRUPTS_TYPE_ETHER,
} type;

union {
void *reg;
struct consreg *cons;
+ struct consreg *eth;
} regs;

int (*mask)(struct interrupts *intp);
@@ -56,9 +63,23 @@
return 0;
}

+static int ethreg_init()
+{
+ int i;
+ struct ethreg *erp;
+
+ for (i = 0; i < EXTINTR_ETHER_NUM; i++) {
+ erp = ðreg[i];
+ memset(erp, 0, sizeof(*erp));
+ }
+
+ return 0;
+}
+
static int regs_init()
{
consreg_init();
+ ethreg_init();
return 0;
}

@@ -176,6 +197,93 @@
return 0;
}

+static int eth_mask(struct interrupts *intp)
+{
+ ether_intr_disable(intp - interrupts);
+ return 0;
+}
+
+static int eth_unmask(struct interrupts *intp)
+{
+ ether_intr_enable(intp - interrupts);
+ return 0;
+}
+
+static int eth_checkintr(struct interrupts *intp)
+{
+ return ether_checkintr(intp - interrupts);
+}
+
+static int eth_intr(struct interrupts *intp)
+{
+ int size;
+ char *buffer;
+
+#ifdef USE_MESSAGE
+ buffer = kz_kmalloc(BUFFER_SIZE);
+#else
+ buffer = kx_kmalloc(BUFFER_SIZE);
+#endif
+
+ size = ether_recv(intp - interrupts, buffer);
+
+ if ((size >= 0) && intp->id)
+#ifdef USE_MESSAGE
+ kz_send(intp->id, size, buffer);
+#else
+ kx_send(intp->id, size, buffer);
+#endif
+ else
+#ifdef USE_MESSAGE
+ kz_kmfree(buffer);
+#else
+ kx_kmfree(buffer);
+#endif
+
+ return 0;
+}
+
+static int eth_command(struct interrupts *intp, uint32 id, int size, char *command)
+{
+ switch (command[0]) {
+ case EXTINTR_CMD_ETHER_USE:
+ intp->id = id;
+ break;
+
+ case EXTINTR_CMD_ETHER_ENABLE:
+ ether_intr_enable(intp - interrupts);
+ break;
+
+ case EXTINTR_CMD_ETHER_DISABLE:
+ ether_intr_disable(intp - interrupts);
+ break;
+
+ case EXTINTR_CMD_ETHER_SEND:
+ ether_send(intp - interrupts, size - 1, &command[1]);
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int eth_init(struct interrupts *intp)
+{
+ intp->type = INTERRUPTS_TYPE_ETHER;
+ intp->mask = eth_mask;
+ intp->unmask = eth_unmask;
+ intp->checkintr = eth_checkintr;
+ intp->intr = eth_intr;
+ intp->command = eth_command;
+ intp->regs.eth->interrupt = intp;
+
+ ether_init(intp - interrupts);
+
+ return 0;
+}
+
static int extintr_intr_regist(struct interrupts *intp, void *regs, initfunc init)
{
intp->regs.reg = regs;
@@ -217,6 +325,9 @@
case EXTINTR_CMD_CONSOLE:
intp = consreg[p[1] - '0'].interrupt;
break;
+ case EXTINTR_CMD_ETHER:
+ intp = ethreg[p[1] - '0'].interrupt;
+ break;
default:
break;
}
@@ -238,6 +349,7 @@
interrupts_init();

extintr_intr_regist(&interrupts[1], &consreg[1], cons_init);
+ extintr_intr_regist(&interrupts[2], ðreg[0], eth_init);

extintr_mainloop();

追加したのはコンソール用の処理と同様に,LANの処理を追加しただけ.割り込み発生時の受け付け処理と,あとスレッドからのコマンドを受けて動作する処理(パケット送信とか)が入っている.まあこのへんもコンソールの処理とほとんど同じ.

次に,pingに応答するためのスレッドを追加.

diff -ruN h8_12/os/kozos.c h8_13/os/kozos.c
--- h8_12/os/kozos.c Mon Sep 28 23:19:11 2009
+++ h8_13/os/kozos.c Sat Oct 3 22:16:01 2009
@@ -16,6 +16,7 @@
command0_id = kz_run(command_main, "command0", 11, 0x200, 0, NULL);
#endif
command1_id = kz_run(command_main, "command1", 11, 0x200, 1, NULL);
+ network_id = kz_run(network_main, "network", 11, 0x200, 1, NULL);

return 0;
}


diff -ruN h8_12/os/kozos.h h8_13/os/kozos.h
--- h8_12/os/kozos.h Mon Sep 28 23:19:11 2009
+++ h8_13/os/kozos.h Sat Oct 3 22:16:01 2009
@@ -57,6 +57,8 @@
/* user thread */
extern uint32 command0_id;
extern uint32 command1_id;
+extern uint32 network_id;
int command_main(int argc, char *argv[]);
+int network_main(int argc, char *argv[]);

#endif

最後に,IRQ5の割り込みを受け付ける対応を追加.

diff -ruN h8_12/os/main.c h8_13/os/main.c
--- h8_12/os/main.c Mon Sep 28 23:19:11 2009
+++ h8_13/os/main.c Sat Oct 3 22:16:01 2009
@@ -34,6 +34,7 @@
interrupt_sethandler(VECTYPE_RXI0, defhandler);
interrupt_sethandler(VECTYPE_RXI1, defhandler);
interrupt_sethandler(VECTYPE_RXI2, defhandler);
+ interrupt_sethandler(VECTYPE_IRQ5, defhandler); /* ether */
}

int main()


diff -ruN h8_12/os/thread.c h8_13/os/thread.c
--- h8_12/os/thread.c Mon Sep 28 23:19:11 2009
+++ h8_13/os/thread.c Sat Oct 3 22:16:01 2009
@@ -488,10 +488,23 @@
case 0x07: signo = SIGTRAP; break;
case 0x09: signo = SIGALRM; break;
#endif
+ case VECTYPE_IRQ0:
+ case VECTYPE_IRQ1:
+ case VECTYPE_IRQ2:
+ case VECTYPE_IRQ3:
+ case VECTYPE_IRQ4:
+ case VECTYPE_IRQ5:
case VECTYPE_RXI0:
case VECTYPE_RXI1:
- case VECTYPE_RXI2: signo = SIGHUP; break;
- case VECTYPE_TRAPA0: signo = SIGSYS; break;
+ case VECTYPE_RXI2:
+ signo = SIGHUP;
+ break;
+ case VECTYPE_TRAPA0:
+ case VECTYPE_TRAPA1:
+ case VECTYPE_TRAPA2:
+ case VECTYPE_TRAPA3:
+ signo = SIGSYS;
+ break;
default:
signo = SIGBUS;
break;

実は thread.c の thread_intr() に IRQ5 を追加するのが必要で,これを忘れてて割り込み受けられなくてちょっとハマッた.でも追加したら,だいたいあっさり動いた.

ちなみにLANの設定と,あとARPとpingの応答は network スレッドというのがやっていて,本体は network.c にある.まあ応答するだけなので,command.c を雛型にしててきとうに書いてある.

では,試してみよう.今回もブートローダーは変更する必要無し.

まず,通常通り起動する.

kzboot (kozos boot loader) started.
kzboot> load
~CLocal command? lsx kozos.mot
Sending kozos.mot, 229 blocks: Give your local XMODEM receive command now.
Bytes Sent: 29440 BPS:1689

Transfer complete
cceeded.
kzboot> run
starting from entry point: ffc120
kozos boot succeed!
network ready.
command>

起動したら,H8/3069FのLANコネクタにLANケーブルを挿して,HUBに繋いでみる.リンクアップするとボード上の青いLEDが光るみたい.もしかしたら10base/Tでないとダメかもしんない.

この状態でFreeBSDからpingを投げてみる.network スレッドは,MACアドレスが 00:11:22:33:44:55 でIPアドレスが 192.168.0.16 という超適当な値に固定で設定してある(もちろんLANコントローラは通常は自分宛てのフレームしか受け取らないので,このために,RTL8019にはプロミスキャスモードの設定をしている)ので,FreeBSDのIPアドレスは192.168.0.xのてきとうなものを設定して,ping 192.168.0.16 を実行してみる.

hiroaki@teapot:~>% ping 192.168.0.16
PING 192.168.0.16 (192.168.0.16): 56 data bytes
64 bytes from 192.168.0.16: icmp_seq=0 ttl=64 time=30.889 ms
64 bytes from 192.168.0.16: icmp_seq=1 ttl=64 time=14.062 ms
64 bytes from 192.168.0.16: icmp_seq=2 ttl=64 time=14.225 ms
64 bytes from 192.168.0.16: icmp_seq=3 ttl=64 time=13.335 ms
64 bytes from 192.168.0.16: icmp_seq=4 ttl=64 time=13.783 ms
^C
--- 192.168.0.16 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max/stddev = 13.335/17.259/30.889/6.822 ms
hiroaki@teapot:~>%

おー,ちゃんと応答している.すげー.

パケット受信すると,ボードのLANコネクタの横の白色LEDが点灯するみたい.

以下,H8/3069Fのシリアル出力.

command> received: 0x3cbytes
replyed.
received: 0x62bytes
replyed.
received: 0x62bytes
replyed.
received: 0x62bytes
replyed.
received: 0x0bytes
no reply.
received: 0x62bytes
replyed.
received: 0x62bytes
replyed.

パケット受信して応答を返している.うん,ちゃんと動いている.wiresharkでパケットキャプチャして見ても,正常に動いているようだ.

コマンド入力も従来通り受け付けるので,コマンドとpingに並行で応答できて,なんかいかにもスレッドで動いていますってな感じの動作になっている.(まあ実際にスレッド動作しているので,あたりまえといえばあたりまえなんだけれど,ちょっと感動)

今回でとりあえずLANの送受信ができるようになった.あとは簡単なTCP通信を実装すれば,HTTPサーバくらいできるようになるなあ.あと内蔵メモリがもういっぱいでこれ以上は機能拡張できないので,プログラムを全面的にDRAMで動かすようにしないといかん.