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

前回までで,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で動かすようにしないといかん.