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

前回までで,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 = &ethreg[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], &ethreg[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で動かすようにしないといかん.
(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.

SH2で動作したので,再びH8をいじってみよう.というのはH8/3069Fボードはおまけで2MBのDRAMがついているのと,LANコントローラが載っていてLANコネクタも実装済みなので,いじりがいがあるからだ.(この点,SH2のボードだと内蔵RAMも少ないし,外部デバイスもたいしたものは載ってない(LEDとスイッチくらい)し,いったん動いてしまうとあんましいじりがいが無い)

で,LANコントローラを動かすのも面白そうと思ったのだけど,とりあえずDRAMを動かしてみよう.というのは,ethernetのフレーム送受信とかしたときに,やはりフレーム保存のためには内蔵RAMだと容量がちょっと足りないと思うので,その前にDRAMを動くようにしておきたいからだ.

このDRAMなのだけど,H8/3069Fボードのマニュアルを見ると「もしも買ったボードに付いてたらラッキーくらいのおまけ」というようなことが書いてある.僕はこのボードはすでに2枚買ったのだけど(1枚は予備),とりあえず両方ともDRAMは載っているみたい.秋月電子のホームページを見る限りでは,DRAM付属とはっきり書いてあって,標準でついているのかおまけなのかちょっとよくわからんが,まあだいたいついているものと考えてもいいようだ.

ちなみにDRAMは,基板の裏側に実装されています.確かめたい人は基板裏の電源アダプタの下付近ちょっと中央よりに1cm×2cmくらいのICチップがあるか見てみてください(たぶんあるとおもうけど).

回路図を見ると,DRAMはCS2に接続されている.ということで0x400000~0x5fffff の2MBの空間が使えるようだ.ちなみに注意だけど,このDRAM,16Mと書いてあるのだけどこれって16Mbyteでなくて16Mbitなので,byteだと2MBということになる.CS2の空間は2MBなので実はちょうどなのだけど,最初,これを誤解しててちょっと混乱した(こんなの勘違いするのって私だけか?).

ちなみにボード付属のマニュアルを読むと「CS1に接続されている」って書いてあるのだけど,回路図見るとCS2に繋がっている.回路図はたぶんパターン引きしたときの電子データをそのまま持ってきているのだろうし,回路図のほうがあっているのだろうと思ってはいたのだが,やってみたところ,やっぱしそうだったようだ.なのでマニュアル読んでいるひとは気をつけてください.

で,なーんだCS2に繋がっているのならなにもしなくても0x400000以降を読み書きすればもう使えるんじゃーないの? とか気楽に思っていたのだけど(動くようになったあとで冷静に考えてみると,そんなわけないよね.でもハードに無知なソフトウエアプログラマの感覚ってこんなもんなのよ),全然動かなかった.以下,動くようになるまでをちょっと説明.

まずCPUのモード設定なのだけど,今までディップスイッチの設定はボード付属のマニュアルに載っているままに使っているのだけど,これはCPUの「モード5」というやつで使っていることになる.

このモード5ってやつがくせもので,3052Fとかだとモード5って1MBアクセスのモードなのよ.ところが3069Fだと16MBアクセスモードなのだな.最初これを知らなくて,H8の書籍(3048や3052を使っている場合が多い)を読むとモード5だと1MBまでって書いてあるのでなんでこれで動いてるんだろうとか別モードにする必要があるのかもとか思っていたのだけど,付属CD-ROMの3069Fのマニュアル見たらモード5は16MBになっていた.同じシリーズのCPUなのでモードの意味は同じだと思っていたのだけど,うーんこういうことってあるんだなあ.要注意だ.

で,3069FのマニュアルのCPUのモードの説明のところを読むとわかるのだけど,内蔵ROM有効でアドレスは16MB空間で,さらに外部メモリを使うにはモード5でないとならない.ということでCPUモードは従来通り,モード5のままでいい.

あとチップセレクトであるCS2がI/Oポートとピンが共通になっているので,たぶんピン指定をしないといけないだろーなと思って調べたら,P8DDRというので指定するようだ.

で,このへんを指定して0x400000以降に適当な値を書き込んで読んで比較するようなメモリチェックを実装してためしてみたんだけど,うまく読み書きできてない.

いろいろマニュアル調べているうちに,設定するのはP8DDRだけでは不十分で,DRCRAとかDRCRBとかRTMCSRといったレジスタをいろいろ設定しないとならないようだ.DRAMのウエイトとかリフレッシュ関連とか設定しないとならんらしい(まあそりゃそうだよね).このへんはマニュアルの「I/Oポート」の章と「バスコントローラ」の章(とくに,「DRAMインタフェース」の節)とあと回路図をよく見て,設定値を考えなければならない.

でもこのへんってちょっとよくわからなくて,とりあえず上記の章で目につくレジスタをひととおり適当に設定してみたのだけどダメ.ほんとならDRAMのデータシート見てちゃんと調べるべきで,付属CD-ROMにデータシートがあったのでちょろっと見てみたのだけど,よくわからんしこれを真剣に読みだしたらかなり先の長い話になってしまうのでとりあえずこれは保留.

で,ネットで検索してみた.もっともこのへんはボードや搭載するDRAMや回路接続の方法によっても設定値が変わってくるだろうから,まさに同じボードでDRAM使っているという例でないとダメなのだけど,運良く以下のサイトがサクッと見つかった.

「soukz理研 AKI-H8 > 3069F LANボードのDRAM増設あまり」

こちらはDRAMをさらに増設して使っているようなのだけど,とりあえず同じ秋月のボードでDRAMを使えるようにしていて,上記レジスタの設定内容とその理由がひととおり書いてある.とりあえず上記サイトの設定例を参考に,CS2(エリア2)のみを使うようにしてレジスタの設定をしてみた.まあ設定値とその理由については上記サイトを参考にしてほしい.いやー,ありがたいありがたい.

で,動かしてみたらちゃんと読み書きできた.ほんとは設定値をもっとちゃんと調べて細かくチューニングすべきかもしれないけど,まあ動いたしべつに速度を追求する必要もないので,気にしない.

ということで以下,修正したソース.

OSにDRAM関連の処理として dram.c を追加.さらにOSのinit()で,dram_init()を呼ぶことでDRAM関連の初期設定を行っている.これ以降,0x400000~0x5fffffにDRAMが見えるようになる.

さらに command.c にメモリチェックコマンドを追加した.OS起動して ramchk を実行することで,メモリチェックを行う.メモリチェックはそのアドレスの値と0x00000000 と 0xffffffff を,1バイト単位,2バイト単位,4バイト単位で書き込んで読み出して,値の比較を行うというもの.16ビットCPUなので4バイト単位は実際には意味無いような気がするが,とりあえず最初に全領域全パターンチェックをしておきたいので,なんとなくいちおう4バイトでのチェックも入れといた.まあ修正点に関しては,前回との差分をみてちょうだい.今回はブートローダーの修正は無し.(将来的にはDRAMの初期化はブートローダーで行って,OSはDRAM上にロードするようにしてもいいかもしれない)

実際には上記チェック処理を2MBの全アドレスに渡って行うので,それなりに時間がかかる.僕のボードでやってみたところ,1時間くらいだったかなあ.

以下,チェックの実行結果.

kzboot> run
starting from entry point: ffc120
kozos boot succeed!
command> echo

OK
command> echo aaa
aaa
OK
command> ramchk
DRAM checking...
005ffffc
all check OK.
OK
command>

ramchk実行でチェック開始し,正常終了している.問題無いようだ.

今回わかったことだが,たとえCS2に繋がっているからといってなにも考えずにそのアドレスにアクセスすれば読み書きできるというものではない.これはCS1に接続されているLANコントローラでも同じことで,いきなりそのコントローラのレジスタを読み書きできるわけではなく,まずは最低限,CS1のピン設定は(たぶん他のピンと共用になっているので)絶対に必要だと思われる.

ほかにもアドレスピンとかもI/Oとピンが共通化されているので,このへんもアドレス信号を出すようにピン設定しなければならない(これは後になって考えると当り前なのだけど,上記サイトでそのような設定を行っているのを見たときは非常に意外でびっくりした.アドレス線はなにもしないでほっといてもアドレス信号が出ているもんだと思っていた).で,それらの設定をして,ようやくLANコントローラのレジスタが見えるようになる.使うためにはさらにその後,LANコントローラのレジスタ設定を行わなければならないわけだ.

今回は運良く上記のサイトが見つかって,参考にして設定したらあっさり動いたけど,これってなにも参考にできずにマニュアルとデータシートだけ見て自分一人でやってたら,ハマリにハマッただろうな~としみじみ思う.だってアドレス線までピン設定しなきゃならんとは思わなかったもん.いやー,ありがたや,ありがたや.

それにしても,DRAM動くようにするだけでもこんなにいろんなレジスタ設定しなきゃならんのね.H8ってすげー簡単と思っていたけど,それは今まで内蔵デバイスだけを使っていたからで,外部デバイス使うとなるとそれなりに設定は必要だっつーことだ.

ソフトウエア・プログラマの視点だとDRAMなんて使えて当り前で最初っから必ず使えるものっつーイメージがあるけど,そんなことはなくて,使うためにはメモリコントローラの設定がいろいろ必要だということだ(それも,接続されているハードや回路接続のしかたに合わせた設定が必要なので,本に載っているような値をそのまま設定すればいいというわけではなく,ボードに合わせて考えなければならない).何もしなくても動くのでは?なーんて考えていた自分が恥ずかしい.

今回の僕の場合はたまたま同じボードでのサンプルがあったのでよかったけど,新規ハードでまったくサンプルの無い状態だと,回路図とかデバイスのデータシートを見ながら自分で設定値を選ぶわけだから,そりゃードライバ作る人が苦労するわけだよねえ.
(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.

H8への移植がひと段落したけれど,実はH8のボードを買ったときについでにSH2のボードも買っていて,なんとなくSH2のマニュアルを見ていたのだけど,なんか割り込み周りとかSCIのレジスタとか,H8とほとんど同じなので,実はKOZOS/H8をベースにしてSH2にもサクッと移植できてしまうのでは...と思ってSH2への移植をやってみた.

ターゲットボードは秋月電子の「AKI-7125マイコンボード」というもの.やはり完成品で,シリアルコネクタも実装済みなので半田づけは一切不要で買ってきたその日からすぐ遊べる.値段も4800円なので個人レベルで買える金額だ.シリアル経由でフラッシュROMを書き換えできるのもH8と同じ.ついでに,電源にH8/3069Fで使った電源アダプタがそのまま使えてさらに嬉しい.(SH/7144のボードは電源電圧が3.3Vなので,H8/3069Fボードと電源アダプタを共用できない)

画像はこちら

画像はこちら

このボードなのだけど,SH2の7125というCPUが載っている.SH2の中でも「SH/Tiny」とか呼ばれているやつで,SH2の中では比較的新しい,廉価版のSH2のようだ.

で,SH2への移植なのだけど,まず開発環境から.クロスコンパイラはH8のときとほとんど同じで,以下で作成できた.環境はいつもどおりFreeBSDね.

(binutils)

% ./configure --target=sh-elf --prefix=/usr/local --disable-nls
% gmake
# gmake install

(gcc)

% setenv SHELL /usr/local/bin/bash
% ./configure --target=sh-elf --prefix=/usr/local --disable-nls --disable-thread
s --disable-shared --enable-languages=c
% gmake
# setenv SHELL /usr/local/bin/bash
# gmake install

次にフラッシュROMの書き込みソフトなのだけど,ちょっといろいろ探してみたのだけどどうもFreeBSDから使えるもので,SH/7125に対応してるものが無い.実はSH/7144のボードも買ってあって,こっちだとfw(だったかな?)というシェアウエアがFreeBSD上で使えるみたいなのだけど,まあ最初だしよくわからんところではまってもしょうがないので,とりあえず添付CD-ROMに付属のWindows用の書き込みソフトを使うことにした.割と有名な「FDT」ってやつね.

移植はH8と同様に,ブートローダーとKOZOS本体の両方を移植する.理由はH8のときと同じ.ブートローダーはH8用に作ったものをそのまま移植.

ただしOSに関しては,SH/7125はRAMが8KBしかなくて,これはかなりきつい(H8/3069Fは16KB).OSが入りきるか心配だったのだけど,実際にH8用のソースコードを持ってきてSH2用にビルドして見ると,RAMのサイズが全然足りなくて入りきらない.ということでKOZOS/H8をベースにするのはあきらめて,Interface誌にPowerPC用に移植したときのかなりシェイプアップしたソースコードをベースにして移植を行った.

で,移植したソースコードは以下.

移植時に注意したこととかはまったことは以下.
  • RAMのサイズが足りないので,ELF形式でのダウンロードはやめてモトローラSフォーマットでの直接展開を利用する.
  • SHはアセンブラがちょっと特殊というか,癖があって慣れるまでちょっとひと苦労.意外なことに,アセンブラを書くための資料とか書籍が少なかった.(割り込み周りとか説明している書籍はあるが,メーカー純正コンパイラ(HEW)の#pragma 機能とか使ってアセンブラ書かずに実装している場合が多く,まったく参考にならないのが多い.がっくし)
  • SH2はジャンプ命令が遅延スロットを持つので,ジャンプ命令の後にNOPを入れておくとか考慮する必要有り.これは忘れがちなので要注意.
  • 50MHzと書いてあるのだけど起動時は実は25MHzで,50MHzで動作させるには分周比を設定しなおさないとならないみたい.SCIに供給するクロックはCPUとは別の分周比設定だが,これも25MHzになっているので,SCIのBCCの値を25MHzで計算する必要有り.
  • SCIが利用するCPUのピンが標準I/O入出力ポートとして割り当てられているので,SCIで使うという設定をする必要有り.これはブートローダーの serial_init() で行っている.(ブートローダーで設定しているので,OS側では現状でとくに設定はしていない)
  • SCIがデフォルトで省電力状態になっているので,起動後にシリアルの初期設定をしただけではシリアルが使えない(クロックが止まっている).STBCR3というレジスタを操作してクロック供給するようにする必要有り.(ブートローダーの serial_init() を参照.これはわりとハマッた.このへんのために,シリアル出力するだけでちょっとひと苦労)
  • RAMがとにかく少ないので,スレッドのスタックとかをさらに節約してギリギリまでメモリチューニングした.(スタック不足とかでたまに動かなくなったりしてハマることが数回あった)
  • シリアルの割り込みを有効にする際に,IPRLというレジスタを操作して,シリアル割り込みの優先度を設定しないといけない.これが起動時のデフォルトでは優先度最低になってしまっているため,割り込み有効化しても割り込みが入らない.これはかなりハマッたところだった...
  • ブートローダーの割り込みハンドラが

    void interrupt(int vec);

    のようになっていて,割り込み発生時にはintr.Sで割り込み番号を引数にしてinterrupt()を呼び出すのだけど,割り込み番号をアセンブラで設定する際に即値としてレジスタに設定するため,int型の値でなく1バイトの数値として符号拡張されてレジスタに値が入る.このためシリアル受信割り込み(221)がcharの上限を越えてしまい符号拡張されて,負の値になって interrupt() が呼ばれてしまうため,シリアル受信時に interrupt() がおかしなソフトウエア割り込みベクタを実行してしまい,誤動作していた.(割り込みが入ってもハンドラが呼ばれず,これはかなりハマッた)
  • 割り込みベクタ数がH8に比べて多く(H8は64個だが,SH2は256個),またRAMも少ないので,ソフトウエア・割り込みベクタを割り込みごとに用意するのはやめにしてひとつのハンドラですべてまかなうように変更(ハンドラ呼出し時に割り込みベクタ番号を引数にして関数コールするので,ハンドラでは番号見て処理を行う)
まあこんなところかな.最初のシリアル送信できるようになるまでと,OS起動後のSCI割り込み受け付けでハマッた.まあ赤と白のLEDがあったので,LEDデバッグでなんとかなりはしたのだけど,思いのほかハマッて苦戦した.やっぱし32ビットCPUは複雑さが全然違うね.H8よりずっと高機能で,動かすのにいろいろ設定も必要な感じ.

以下,実際に動かしてみたところ.動作方法はH8/3069Fとほとんど同じ.1点だけ,フラッシュROMの書き込みに h8write でなく FDT を使うので,このときだけはWindowsで作業する必要がある.

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

Transfer complete
eceive succeeded.
kzboot> run
starting from entry point: ffffa010
kozos boot succeed!

start command thread.
> echo test
test
OK
> threads
extintr
idle
command
OK
>

ブートローダーを起動してOSをダウンロードし,起動してコマンド受け付けできている.いやー,なんとか動いたね.よかったよかった.

雑感として,tinyといえどやはりSH2は32ビットRISCプロセッサだなあ,と思った.アセンブラとか,割り込み処理とか,レジスタ周りとか,I/Oポートまわりの設定の必要性とか,H8よりもはるかに複雑で,パッと見は似てるけど,路線というかポリシーはまったく違うCPUなのだなあ,と思った.対してH8は,やっぱしマイクロコントローラだなあ,という感じ.

そう思うとH8は移植が簡単だったなあ.シリアルとかタイマとか,たいして設定せずにあっさり動いたし.H8移植を先にやっといてよかったかも.

あとやっぱし8KBというのはOS動かすには少なくて,ROM上で動かすか,ライブラリ周りをROMに持っていってBIOSみたいにするか,なんか考えないとこれ以上の機能追加は現実,厳しい.H8/3069Fのボードは16KBある上におまけで2MBのDRAMがついている(マニュアルでは「あったらラッキーなおまけ」と書いてあるのだけど,web上のカタログでは必ずついているように書かれている.よくわからんけどまあだいたいついているもんだと思ってしまっていいみたいな気がする)ので,機能拡張していく楽しみがある.

H8はCISCなので最初はちょっとうーんと思っていたのだけど,逆にSH2はRISCなので32ビット即値をレジスタに直接代入することができず,値を近くのてきとうな場所に置いとく必要がある(このへんは,startup.s でのスタックポインタ設定とかmain()へのジャンプとかを参照してほしい).これがけっこう面倒で,32ビット即値を一撃で代入できるH8は楽で初心者にはいいなあ,と思う.初心者はH8ボードからはじめた方がいいかも.

H8の移植がだいたいいいかんじで動いたので次なのだけど、まあほんとはH8版のソースコードをきれいにしていくべきなのだけど、こないだSH2のマニュアルをちょっとみたらペリフェラル周りとか割り込みまわりがH8にかなり似ていて、もしかしたらH8をベースに移植すれば、SH2でもあっさり動くのでは。。。と思って現在SH2に移植中。


ターゲットはまた秋月のボード。今度はSH7125Fってやつ。


SH2なのだけど、アセンブラまわりとかI/Oまわりも、さすがにH8よりは格段に32ビットCPUっぽいかんじで、H8ほど単純ではなく、あっさり簡単にはいかないようだ。ちょっと甘く見ていたか。。。


とりあえずブートローダーは動くようになった。で、OSが起動するとこまでは動いたのだけど、そのあとコマンド応答が無い。うーん、ブートメッセージは出ていてスレッドに切り替わるあたりで固まっているように思えるので、スレッドのディスパッチ周りか割り込み周りに問題がありそうだ。


SHもおもしろいね。さすがに32ビットCPUってかんじで、いろいろできそうだ。

ただ、内蔵RAMが少なすぎ!せめて16KBはほしかった。。。8KBしかないので、H8用のKOZOSだとあふれてしまうので、Interface誌に掲載したときの極限までサイズを削ったやつをベースにして移植を進めている。。。

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

昨日からアホみたいに更新しているが,久々に時間がとれて一気に書き進めるいい機会なので,いっきにやってしまおう.

ということでちょっと見直しして,細かいところというかイマイチな部分を直してみた.

以下,修正したソース.

■ ブートローダーの修正

まず大きな修正として,割り込み発生時にスタックにレジスタを退避する際に,レジスタの退避順番を逆にするように仕様変更した.これは,従来の順番だとアドレスの低位から ER6, ER5, ER4, ... のように格納されてしまっていて,なんか順番が逆になっていたので,逆にするようにした.これによりOSのthread.c のレジスタ値保存部分で,memcpy() で一撃でレジスタ値を退避できるようになるメリットがある.

実際に修正したのは intr.S で,以下のように手を入れている.

diff -ruN h8_10/kzload/intr.S h8_11/kzload/intr.S
--- h8_10/kzload/intr.S Sun Sep 20 13:39:14 2009
+++ h8_11/kzload/intr.S Sun Sep 20 18:42:36 2009
@@ -8,20 +8,20 @@
mov.l er7,er0
mov.l #_stack,sp
mov.l er0,@-er7
- mov.l er1,@-er7
- mov.l er2,@-er7
- mov.l er3,@-er7
- mov.l er4,@-er7
- mov.l er5,@-er7
mov.l er6,@-er7
+ mov.l er5,@-er7
+ mov.l er4,@-er7
+ mov.l er3,@-er7
+ mov.l er2,@-er7
+ mov.l er1,@-er7
mov.w #1,r0
jsr @_interrupt
- mov.l @er7+,er6
- mov.l @er7+,er5
- mov.l @er7+,er4
- mov.l @er7+,er3
- mov.l @er7+,er2
mov.l @er7+,er1
+ mov.l @er7+,er2
+ mov.l @er7+,er3
+ mov.l @er7+,er4
+ mov.l @er7+,er5
+ mov.l @er7+,er6
mov.l @er7+,er0
mov.l er0,er7
mov.l @er7+,er0
...



次に,シリアルの速度を上げる修正.モトローラSフォーマットにしたためにOSのダウンロードにやたら時間がかかるようになってしまったので,とりあえずシリアルの転送レートを上げる.いろいろ試した結果,19200bpsよりも上げると通信がうまくいかないので,19200bpsとする.

diff -ruN h8_10/kzload/serial.c h8_11/kzload/serial.c
--- h8_10/kzload/serial.c Sun Sep 20 13:39:14 2009
+++ h8_11/kzload/serial.c Sun Sep 20 18:42:36 2009
@@ -15,8 +15,10 @@
volatile uint8 scmr;
};

-#define H8_3069F_SCI_SMR_CKS0 (1<<0)
-#define H8_3069F_SCI_SMR_CKS1 (1<<1)
+#define H8_3069F_SCI_SMR_CKS_PER1 (0<<0)
+#define H8_3069F_SCI_SMR_CKS_PER4 (1<<0)
+#define H8_3069F_SCI_SMR_CKS_PER16 (2<<0)
+#define H8_3069F_SCI_SMR_CKS_PER64 (3<<0)
#define H8_3069F_SCI_SMR_MP (1<<2)
#define H8_3069F_SCI_SMR_STOP (1<<3)
#define H8_3069F_SCI_SMR_OE (1<<4)
@@ -48,7 +50,17 @@

sci->scr = 0;
sci->smr = 0;
+#if 0
sci->brr = 64; /* 20MHz 9600bps */
+#elif 1
+ sci->brr = 32; /* 20MHz 19200bps */
+#elif 0
+ sci->brr = 15; /* 20MHz 38400bps */
+#elif 0
+ sci->brr = 9; /* 20MHz 57600bps */
+#else
+ sci->brr = 4; /* 20MHz 115200bps */
+#endif
sci->scr = H8_3069F_SCI_SCR_RE | H8_3069F_SCI_SCR_TE;
sci->ssr = 0;

同様の修正はOS側にも入れる.このため,今後はシリアル接続の際には9600bpsでなく19200bpsを選択する必要あり.これは cu で接続する場合には

# cu -s 19200 -l /dev/cuad0

のようにして,-sオプションで転送レートを指定できる.

次に,XMODEMでのモトローラSフォーマット転送時の処理をちょろっと修正.エラー発生時にエラー検出できていなかったので,戻り値を int としてエラーを返せるように修正した.

diff -ruN h8_10/kzload/xmodem.c h8_11/kzload/xmodem.c
--- h8_10/kzload/xmodem.c Sun Sep 20 13:39:14 2009
+++ h8_11/kzload/xmodem.c Sun Sep 20 18:42:36 2009
@@ -33,7 +33,7 @@
return 0;
}

-static char *xmodem_read_block(char *buf)
+static int xmodem_read_block(char **buf)
{
unsigned char c, block_num, check_sum;
int i;
@@ -42,19 +42,20 @@
block_num ^= serial_getb();

if (block_num != 0xff)
- return NULL;
+ return -1;

check_sum = 0;
for (i = 0; i < XMODEM_BLOCK_SIZE; i++) {
c = serial_getb();
- if (!buf) {
+ if (*buf == NULL) {
if (srec_decode(c) < 0)
- return NULL;
+ return -1;
} else {
#ifdef USE_UUENCODE
- buf = uu_decode(buf, c);
+ *buf = uu_decode(*buf, c);
#else
- *(buf++) = c;
+ **buf = c;
+ (*buf)++;
#endif
}
check_sum += c;
@@ -62,16 +63,15 @@

check_sum ^= serial_getb();
if (check_sum)
- return NULL;
+ return -1;

- return buf;
+ return 0;
}

int xmodem_recv(char *buf)
{
int receiving = 0, size = 0;
unsigned char c;
- unsigned char *p;

srec_init();

@@ -86,12 +86,9 @@
break;
} else if (c == XMODEM_SOH) {
receiving++;
- p = xmodem_read_block(buf);
- if (buf && !p) {
+ if (xmodem_read_block(&buf) < 0) {
serial_putc(XMODEM_NAK);
} else {
- if (p)
- buf = p;
size += XMODEM_BLOCK_SIZE;
serial_putc(XMODEM_ACK);
}

ブートローダーの修正はこんだけ.次はOSの修正点.

■ OSの修正

まず,シリアルの転送レートを19200bpsにする修正.これはブートローダーの修正と同じもの.

diff -ruN h8_10/os/serial.c h8_11/os/serial.c
--- h8_10/os/serial.c Sun Sep 20 13:39:30 2009
+++ h8_11/os/serial.c Sun Sep 20 18:43:09 2009
@@ -18,8 +18,10 @@
volatile uint8 scmr;
};

-#define H8_3069F_SCI_SMR_CKS0 (1<<0)
-#define H8_3069F_SCI_SMR_CKS1 (1<<1)
+#define H8_3069F_SCI_SMR_CKS_PER1 (0<<0)
+#define H8_3069F_SCI_SMR_CKS_PER4 (1<<0)
+#define H8_3069F_SCI_SMR_CKS_PER16 (2<<0)
+#define H8_3069F_SCI_SMR_CKS_PER64 (3<<0)
#define H8_3069F_SCI_SMR_MP (1<<2)
#define H8_3069F_SCI_SMR_STOP (1<<3)
#define H8_3069F_SCI_SMR_OE (1<<4)
@@ -64,7 +66,17 @@

sci->scr = 0;
sci->smr = 0;
+#if 0
sci->brr = 64; /* 20MHz 9600bps */
+#elif 1
+ sci->brr = 32; /* 20MHz 19200bps */
+#elif 0
+ sci->brr = 15; /* 20MHz 38400bps */
+#elif 0
+ sci->brr = 9; /* 20MHz 57600bps */
+#else
+ sci->brr = 4; /* 20MHz 115200bps */
+#endif
sci->scr = H8_3069F_SCI_SCR_RE | H8_3069F_SCI_SCR_TE;
sci->ssr = 0;

次に,消費電力低減のためにアイドル時にスリープモードに入るようにする修正.

diff -ruN h8_10/os/idle.c h8_11/os/idle.c
--- h8_10/os/idle.c Sun Sep 20 13:39:30 2009
+++ h8_11/os/idle.c Sun Sep 20 18:43:09 2009
@@ -5,6 +5,6 @@
int idle_main(int argc, char *argv[])
{
while (1) {
- /* none */
+ asm volatile ("sleep");
}
}

スリープ命令を実行すればいいだけのようなので,そのように修正した.idleスレッド中にシリアル出力するような処理を入れて実際に試してみたところ,sleep実行するとシリアル出力されなくなるので,たしかにスリープしているようだ.

次に,スタックサイズ指定の修正.これにより,idleスレッドのようなたいしてスタックを利用しないスレッドはスタックサイズを少なく指定して起動できるので,メモリの節約になる.

具体的には,kz_start()とkz_run()に stacksize というパラメータを追加して,スレッド単位でスタックサイズを指定できるように修正している.ちょっとソースコード全体に渡って修正が入っているのだけど,まあパラメータ追加とか引数追加のちょろっとした修正なので,全部ここで説明するのはやめておく.興味のある人はdiffをとって見てみて.とりあえず kozos.h と syscall.[ch] の修正内容を以下に添付しておく.

diff -ruN h8_10/os/kozos.h h8_11/os/kozos.h
--- h8_10/os/kozos.h Sun Sep 20 13:39:30 2009
+++ h8_11/os/kozos.h Sun Sep 20 18:43:09 2009
@@ -11,7 +11,7 @@
typedef int (*kz_dbgfunc)(struct _kz_thread *thp, int signo);

/* syscall */
-uint32 kz_run(kz_func func, char *name, int pri, int argc, char *argv[]);
+uint32 kz_run(kz_func func, char *name, int pri, int stacksize, int argc, char *argv[]);
void kz_exit();
int kz_wait();
int kz_sleep();
@@ -35,7 +35,7 @@
int kx_kmfree(void *p);

/* library */
-void kz_start(kz_func func, char *name, int pri, int argc, char *argv[]);
+void kz_start(kz_func func, char *name, int pri, int stacksize, int argc, char *argv[]);
void kz_sysdown();
void kz_trap();
void kz_break();


diff -ruN h8_10/os/syscall.c h8_11/os/syscall.c
--- h8_10/os/syscall.c Sun Sep 20 13:39:30 2009
+++ h8_11/os/syscall.c Sun Sep 20 18:43:09 2009
@@ -12,12 +12,13 @@

/* System Call */

-uint32 kz_run(kz_func func, char *name, int pri, int argc, char *argv[])
+uint32 kz_run(kz_func func, char *name, int pri, int stacksize, int argc, char *argv[])
{
kz_syscall_param_t param;
param.un.run.func = func;
param.un.run.name = name;
param.un.run.pri = pri;
+ param.un.run.stacksize = stacksize;
param.un.run.argc = argc;
param.un.run.argv = argv;
kz_syscall(KZ_SYSCALL_TYPE_RUN, &param);


diff -ruN h8_10/os/syscall.h h8_11/os/syscall.h
--- h8_10/os/syscall.h Sun Sep 20 13:39:30 2009
+++ h8_11/os/syscall.h Sun Sep 20 18:43:09 2009
@@ -28,6 +28,7 @@
kz_func func;
char *name;
int pri;
+ int stacksize;
int argc;
char **argv;
int ret;

次に,extintr でメッセージ使用するための修正.

diff -ruN h8_10/os/extintr.c h8_11/os/extintr.c
--- h8_10/os/extintr.c Sun Sep 20 13:39:30 2009
+++ h8_11/os/extintr.c Sun Sep 20 18:43:09 2009
@@ -5,7 +5,7 @@

#include "lib.h"

-/* #define USE_MESSAGE */
+#define USE_MESSAGE

#define BUFFER_SIZE 16

これを有効にする場合には,extintr が優先度ゼロで起動して,さらに優先度ゼロのスレッドは割り込み禁止で動作するという対処を入れる必要がある(でないと割り込みフラグが落ちないままにスレッドのディスパッチが行われ,割り込み有効になって再度割り込みが入って無限ループになってしまう).割り込み禁止にする処理はすでに thread.c の thread_run() に入っているので,extintr の起動時に優先度ゼロで起動するように kozos.c に対処を入れる.

diff -ruN h8_10/os/kozos.c h8_11/os/kozos.c
--- h8_10/os/kozos.c Sun Sep 20 13:39:30 2009
+++ h8_11/os/kozos.c Sun Sep 20 18:43:09 2009
@@ -10,18 +10,18 @@
kz_debug(stub_proc);
#endif

- extintr_id = kz_run(extintr_main, "extintr", 1, 0, NULL);
- idle_id = kz_run(idle_main, "idle", 31, 0, NULL);
+ extintr_id = kz_run(extintr_main, "extintr", 0, 0x200, 0, NULL);
+ idle_id = kz_run(idle_main, "idle", 31, 0x100, 0, NULL);
#if 0
- command0_id = kz_run(command_main, "command0", 11, 0, NULL);
+ command0_id = kz_run(command_main, "command0", 11, 0x200, 0, NULL);
#endif
- command1_id = kz_run(command_main, "command1", 11, 1, NULL);
+ command1_id = kz_run(command_main, "command1", 11, 0x200, 1, NULL);

return 0;
}

int kozos_start(int argc, char *argv[])
{
- kz_start(mainfunc, "main", 0, argc, argv);
+ kz_start(mainfunc, "main", 0, 0x100, argc, argv);
return 0;
}

上の差分だとスレッドのスタック指定の差分がいっしょになっていてちょっとわかりづらいのだけど,extintr の優先度が1→0に変更されている点に注意.

次に thread.c の修正.以下の点を修正してある.
  • thread_run()にstacksizeの引数を追加し,スタックをスレッドごとに指定されたサイズで獲得するように処理を変更.
  • schedule()の処理をデバッグ用にベタ書きで書いてそのまま忘れていたので,もとに戻す.
  • 割り込み発生時のレジスタ退避順序が変更されたので,それに合わせて退避処理を修正.ついでにmemcpy()で簡略化する.

diff -ruN h8_10/os/thread.c h8_11/os/thread.c
--- h8_10/os/thread.c Sun Sep 20 13:39:30 2009
+++ h8_11/os/thread.c Sun Sep 20 18:43:09 2009
@@ -80,7 +80,7 @@
thread_end();
}

-static uint32 thread_run(kz_func func, char *name, int pri,
+static uint32 thread_run(kz_func func, char *name, int pri, int stacksize,
int argc, char *argv[])
{
int i;
@@ -100,9 +100,9 @@
thp->func = func;
thp->pri = pri;

- memset(thread_stack, 0, SIGSTKSZ);
+ memset(thread_stack, 0, stacksize);

- thread_stack += THREAD_STACK_SIZE;
+ thread_stack += stacksize;
thp->stack = thread_stack;

thp->context.pc = (uint32)thread_init | ((uint32)(pri ? 0 : 0xc0) << 24);
@@ -306,7 +306,8 @@
/* システムコールの実行中にcurrentが書き換わるので注意 */
switch (type) {
case KZ_SYSCALL_TYPE_RUN:
- p->un.run.ret = thread_run(p->un.run.func, p->un.run.name, p->un.run.pri,
+ p->un.run.ret = thread_run(p->un.run.func, p->un.run.name,
+ p->un.run.pri, p->un.run.stacksize,
p->un.run.argc, p->un.run.argv);
break;
case KZ_SYSCALL_TYPE_EXIT:
@@ -384,7 +385,6 @@

static void schedule()
{
-#if 0
#if PRI_NUM > 32
#error ビットマップを配列化する必要あり
#endif
@@ -412,14 +412,6 @@
while (1)
;
}
-#else
- int n;
- for (n = 0; n < PRI_NUM; n++)
- if (readyque[n].head) break;
- if (n == PRI_NUM)
- while (1)
- ;
-#endif

current = readyque[n].head;
}
@@ -505,14 +497,8 @@
break;
}

- p = (uint32 *)INTR_STACK_START;
- current->context.er[7] = *(--p);
- current->context.er[1] = *(--p);
- current->context.er[2] = *(--p);
- current->context.er[3] = *(--p);
- current->context.er[4] = *(--p);
- current->context.er[5] = *(--p);
- current->context.er[6] = *(--p);
+ p = (uint32 *)INTR_STACK_START - 7;
+ memcpy(&current->context.er[1], p, sizeof(*p) * 7);
current->context.er[0] = *(uint32 *)(current->context.er[7]);

current->context.pc = *(uint32 *)(current->context.er[7] + 4);
@@ -521,7 +507,7 @@
current = thp;
}

-static void thread_start(kz_func func, char *name, int pri, int argc, char *argv[])
+static void thread_start(kz_func func, char *name, int pri, int stacksize, int argc, char *argv[])
{
memset(threads, 0, sizeof(threads));
memset(readyque, 0, sizeof(readyque));
@@ -535,16 +521,16 @@
* 直接関数を呼び出してスレッド作成する.
*/
current = NULL;
- current = (kz_thread *)thread_run(func, name, pri, argc, argv);
+ current = (kz_thread *)thread_run(func, name, pri, stacksize, argc, argv);

dispatch(&current->context);
}

-void kz_start(kz_func func, char *name, int pri, int argc, char *argv[])
+void kz_start(kz_func func, char *name, int pri, int stacksize, int argc, char *argv[])
{
kzmem_init();

- thread_start(func, name, pri, argc, argv);
+ thread_start(func, name, pri, stacksize, argc, argv);

/* ここには返ってこない */
while (1)

修正は以上.いちおう,これできちんと動作することは確認できた.

これでOSもばっちり動いて,だいたいソースもすっきりした.さて次は何をやろうか.