(注意)このブログは
本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.
今まで内蔵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 に変更されているので,接続時には注意してちょうだい.