(H8移植編第13回)LANにつないでみよう

2009/10/03

あなたは 人目のお客様です.

前回までで,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だけ通るようなのを 書いてみた.以下,修正したソース.

大きな修正は以下だ.

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

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

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に応答してくれているだけなのだけど感動〜. ちなみに no reply って出ているやつは,おそらくHUB上の他機器からのパケットを 受けてしまったときだと思うので,とくに気にしなくてよい.

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

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


メールは kozos(アットマーク)kozos.jp まで