(シミュレータ編第2回)シリアル対応をした

2010/09/10

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

本日はOSC東京の1日目でした.今日も面白かった.いろんなひとといっぱい話せて よかったなあ.

懇親会ではなぜか組込みOSの歌を歌ってきました.OSC京都のギークハウス企画で 酒の入った状態でテキトーに作った歌を,まさか歌うことになるとは… しかもクルマで行ったのでお酒が飲めないので,しらふの状態で. 一杯ひっかけて歌いたかったですなあ.

で,帰ってきたのでシミュレータ対応をまとめよう.

さてKOZOSのシミュレータ対応だけど,とりあえずシリアル対応をきちんと行いたい.

まず前回についてだけど,前回はgdb付属の H8シミュレータのマジックトラップの機能を利用してシリアル入出力を動作させた. まあこれは簡単に実現できるのだけど,いくつかの問題点がある.

たとえば現状のマジックトラップを利用した作りだと,標準入力からのread()待ちで ブロックしてしまっている.このため普段はシミュレータの動作は止まってしまって いるわけだ.なので,定期的に何かするような処理が行えない.それにこれだと 割り込みをベースにした動作を行うには不都合があるだろう.

これを防ぐには標準入力をノンブロッキングI/Oに変更するとか(ioctl()でできる), select()を利用して入力があるかどうか調べるとかいったことが必要になってくる. しかし,現状のマジックトラップは以下のようになっている. (gdb/sim/h8300/compile.cより引用)

		  /* And jsr's to these locations are turned into 
		     magic traps.  */

		  if (OP_KIND (dst->opcode) == O_JSR)
		    {
		      switch (dst->src.literal)
			{
			case 0xc5:
			  dst->opcode = O (O_SYS_OPEN, SB);
			  break;
			case 0xc6:
			  dst->opcode = O (O_SYS_READ, SB);
			  break;
			case 0xc7:
			  dst->opcode = O (O_SYS_WRITE, SB);
			  break;
			case 0xc8:
			  dst->opcode = O (O_SYS_LSEEK, SB);
			  break;
			case 0xc9:
			  dst->opcode = O (O_SYS_CLOSE, SB);
			  break;
			case 0xca:
			  dst->opcode = O (O_SYS_STAT, SB);
			  break;
			case 0xcb:
			  dst->opcode = O (O_SYS_FSTAT, SB);
			  break;
			case 0xcc:
			  dst->opcode = O (O_SYS_CMDLINE, SB);
			  break;
			}
		      /* End of Processing for system calls.  */
		    }
これを見ると,open() は持っているが ioctl() や select() の機能は無い. またマジックトラップで実際に実行される read() や write() はgdbのコールバック 関数を呼んでいるが,以下あたりをみると ioctl() や select() はコールバックとして 定義されていない. なので,ioctl() や select() の機能を追加するのは面倒そうだ. ということで,select()などを使った細かい制御は難しそうだ.

さらにたとえ select() とかをマジックトラップで使えるようにしたとしても, それをマジックトラップを通してファームウエア側から利用するのは面倒だろう. 下手したらシミュレータ対応のためだけで,KOZOSのソースが大幅に膨れ上がって しまうことにもなりそうだ.

外部と入出力するもうひとつの方法としては,シミュレータ側に直接手をいれて 改造してしまうというものがある.たとえば独自のマジックトラップ(のようなもの) を追加してしまったり,H8が持つシリアルコントローラ(SCI)互換の動作を シミュレータ側で作り込んでしまうというものだ.たとえばSCIのレジスタが 配置されているメモリの状態を監視して,SCIと互換の動作をするような実装を シミュレータに作り込めば,OS側のソースコードに手を入れること無くそのまま 動作させることが可能になる.

まあ結局のところ,OS,つまりプログラム側をシミュレータに合わせて動かすか, 逆にシミュレータをプログラムに合わせて動かすか,ということになる. 前者だとGDBの付属シミュレータにあまり手を入れなくていいが,動作に限界がある. 後者だとプログラムはそのままでいいがGDBの付属シミュレータに手を入れることに なるし,ターゲットボードに合わせてシミュレータに細かく手を入れなければ ならなくなる場合があるだろう.

で,ひとまずの結論なんだけど,後者の方針でいくことにした.

ただ,エミュレーションの話になると後者のようにデバイスも含めてエミュレート するようなものがいいように思えるが,ぼくとしてはほんとうは前者の方針のほうが スマートでいいかなあ,と実は思う.というのはシミュレータをプログラム(この場合は 動作させるOS,つまりKOZOS)に合わせる方法だと,結局はそのターゲットボードの デバイスをエミュレーションしなければならなくなる.ということはターゲットボード ごとにシミュレータの改造や調整が必要になってしまう.一方,KOZOSをシミュレータに 合わせてマジックトラップを使うような実装にしておけば,シミュレータはCPUの コアだけをエミュレーションするだけで済むので汎用性も高い (実際,GDB付属のH8シミュレータはH8のコア部分のみのエミュレーションしか しない作りになっているようだ).これは,H8の他機種(H8/3052とか3048とか)用に 開発されたプログラムを動作させる場合に都合がいい. この場合,デバイスドライバはシミュレータ用の仮想ドライバとして作成することに なるが,それはそれで実際のデバイスとは独立して開発できるので簡便に作れるし, 独立性も見通しもいいだろう.

ただデバッグという見地から見ると,やはり割り込み周りのエミュレーションは してほしい.ところが現状のGDB付属のH8シミュレータは割り込みのエミュレーションは してくれないようだ.またマジックトラップの種類も少ない(select()が無いのが かなり痛い)ので,後述するptyの動作のためにはシミュレータには何らかの手を 加えないと,ちょっと難しそうだ(不可能ではないかもしれないが,そのために KOZOS側にシミュレータ用の対応をいっぱい入れなきゃならなくなりそう).

なのでいずれにしてもシミュレータ側に修正を入れることは避けることは難しそうで, だったらOS側にもシミュレータ側にも修正を入れるというのではなく,いっそのこと シミュレータで極力対応してOS側にはなるべく手を入れないというポリシーに固めて しまったほうがいいかなあ,という判断だ.

ということで今回はKOZOS側にはあまり手を入れず,GDBの付属H8シミュレータを KOZOSに合わせて(というか秋月のH8/3069Fマイコンボードに合わせて)改造する, という方向性で行くことにする.

もう1点,標準入出力を利用するというのにも問題がある.というのは標準入出力を 使ってしまうとコンソールに出てきてしまうので,cuとかminicomとかがそのまま 使えない.つまり,ブートローダーでのファームダウンロードが実施できない. これはttyに中継するようなツールを作ることはできるのだけど, あまり自作ツールの利用が増えても混乱するのでちょっと嫌.

たとえばFreeBSDは仮想ターミナル(pseudo-terminal, ptyとも言う)というものを 持っており,/dev/ptyXXを open() して read()/write() すると,/dev/ttyXX に 中継してくれる.で,/dev/ttyXX に対して cu などで接続することができる. これを利用すれば,シミュレータ上の入出力を /dev/ttyXX に中継して, シミュレータに対して cu で接続することができる. ptyはたぶん,Linuxも持っている.Windowsは…よくしらない.

他にもマジックトラップでファイルを扱うことはできるのだから, シリアルへの入出力データをマジックトラップによってファイルに書き出して, PC-UNIX側でファイルのサイズを監視してサイズが増加したら増加ぶんのデータを ttyに中継するようなツールを作る,とか,アイディアはいろいろある. これはこれで面白いように思う(入出力データがリアルタイムに目視できる)のだが, しかしあまり専用ツールが増えるというのも混乱してよくないので却下する.

もっともシミュレータ利用の場合には,実際にはブートローダーの起動は必須では ない.ブートローダーを利用せずにいきなりOSを読み込んで起動することもできる からだ(これについては後日説明します).ただ,せっかくなのだからブートローダーを 起動してOSをダウンロードというのも実施できるようにしたい.これができれば XMODEM通信のデバッグにも使えるしね.(将来的にYMODEM/ZMODEMに対応するとか, モトローラSレコードフォーマットなどの他フォーマットを利用したい場合に役に 立つだろう)

ということで,ptyを利用した実装にすることにする.まあここまでくるとやはり シミュレータ側に一切手を入れないというのは難しくなってくるので, 「プログラムをシミュレータに合わせるべきか,シミュレータをプログラムに 合わせるべきか」という問題には,やはり後者の路線で行く必要性が高くなってくる.

というわけで方針は以下だ.

で,実装したのが以下になる. 「シミュレータをOSに合わせて改造する」と説明したが,実際にはOS側にまったく 手を入れずに済むということはなく,ブートローダーとOSの双方に多少の修正を 入れてある(なるべくシミュレータ側で対応してOS側には修正不要な方向性ではあるが, まあ多少の必要な修正は入れてある). ブートローダーとOSは12ステップ本のソースコードの 最終ステップのコード(12ステップ目のもの)をベースにして修正を加えた.

また今回はGDBへの(ていうか,GDB付属のH8シミュレータへの)改造が入るので, そのパッチも添付してある.具体的な手順は以下になる.

  1. 上記「ソースコードはこちら」の「gdb」というフォルダから以下のパッチを ダウンロードする.
  2. GDBがすでにビルド済みならば,GDBを展開したフォルダの「sim/h8300」という フォルダに入る.
    GDBがまだビルドされていないならば展開し,やはり「sim/h8300」という フォルダに入る.
  3. GDBがすでにビルド済みならば,以下を実行しパッチを当てる.
    % patch < Makefile.in.patch
    % patch < Makefile.patch
    % patch < compile.c.patch
    

    GDBがまだビルドされていないならばMakefileは未作成なので,上記の Makefile.patch はパッチ当てしない.
  4. device.c を sim/h8300 にコピーする.
  5. GDBがすでにビルド済みならば,GDBのトップディレクトリに戻りmake (FreeBSDの場合はgmake)を実行する.
    GDBがまだビルドされていないならば,前回の 手順でビルドする.

GDB付属のH8シミュレータに対する修正の内容を説明しよう.

まずMakefileに対する修正だが,device.cのコンパイルを追加しただけ.

 SIM_RUN_OBJS = nrun.o
 
 SIM_OBJS = compile.o \
+	   device.o \
 	   $(SIM_NEW_COMMON_OBJS) \
 	   sim-cpu.o \
 	   sim-engine.o \

次にcompile.cに対する修正.

@@ -1896,40 +1899,41 @@
 void
 sim_resume (SIM_DESC sd, int step, int siggnal)
 {
   static int init1;
   int cycles = 0;
   int insts = 0;
   int tick_start = get_now ();
   void (*prev) ();
   int poll_count = 0;
   int res;
   int tmp;
   int rd;
   int ea;
   int bit;
   int pc;
   int c, nz, v, n, u, h, ui, intMaskBit;
   int trace, intMask;
   int oldmask;
   enum sim_stop reason;
   int sigrc;
+  int vec;
 
   init_pointers (sd);
 
   control_c_sim_desc = sd;
   prev = signal (SIGINT, control_c);
 
   if (step)
     {
       sim_engine_set_run_state (sd, sim_stopped, SIGTRAP);
     }
   else
     {
       sim_engine_set_run_state (sd, sim_running, 0);
     }
 
   pc = h8_get_pc (sd);
 
   /* The PC should never be odd.  */
   if (pc & 0x1)
     {
@@ -1954,40 +1958,85 @@
       unsigned short cidx;
       decoded_inst *code;
 
     top:
       cidx = h8_get_cache_idx (sd, pc);
       if (cidx == (unsigned short) -1 ||
 	  cidx >= sd->sim_cache_size)
 	goto illegal;
 	  
       code = sd->sim_cache + cidx;
 
 #if ADEBUG
       if (debug)
 	{
 	  printf ("%x %d %s\n", pc, code->opcode,
 		  code->op ? code->op->name : "**");
 	}
       h8_increment_stats (sd, code->opcode);
 #endif
 
+      vec = sim_device_run (sim_callback, h8_get_memory_buf(sd));
+      if (vec && !intMaskBit)
+	{
+	  tmp = h8_get_reg (sd, SP_REGNUM);
+	  if(h8300_normal_mode)
+	    {
+	      tmp -= 2;
+	      SET_MEMORY_W (tmp, code->next_pc);
+	      tmp -= 2;
+	      SET_MEMORY_W (tmp, h8_get_ccr (sd));
+	    }
+	  else
+	    {
+#if 0
+	      tmp -= 4;
+	      SET_MEMORY_L (tmp, code->next_pc);
+	      tmp -= 4;
+	      SET_MEMORY_L (tmp, h8_get_ccr (sd));
+#else
+	      tmp -= 4;
+#if 0
+	      SET_MEMORY_L (tmp, (h8_get_ccr (sd) << 24) | (code->next_pc & 0xffffff));
+#else
+	      SET_MEMORY_L (tmp, (h8_get_ccr (sd) << 24) | (pc & 0xffffff));
+#endif
+#endif
+	    }
+	  intMaskBit = 1;
+	  BUILDSR (sd);
+
+	  if (h8300smode)
+	    {
+	      tmp -= 4;
+	      SET_MEMORY_L (tmp, h8_get_exr (sd));
+	    }
+
+	  h8_set_reg (sd, SP_REGNUM, tmp);
+
+	  if(h8300_normal_mode)
+	    pc = GET_MEMORY_L (vec * 2);
+	  else
+	    pc = GET_MEMORY_L (vec * 4);
+	  goto end;
+	}
+
       if (code->opcode)
 	{
 	  cycles += code->cycles;
 	  insts++;
 	}
 
       switch (code->opcode)
 	{
 	case 0:
 	  /*
 	   * This opcode is a fake for when we get to an
 	   * instruction which hasnt been compiled
 	   */
 	  compile (sd, pc);
 	  goto top;
 	  break;
 
 	case O (O_MOVAB, SL):
 	case O (O_MOVAW, SL):
 	case O (O_MOVAL, SL):
毎ステップごとにデバイスの状態をチェックし,割り込み要因があれば割り込み処理 (CCRとPCをスタックに退避し,割り込みベクタの指す先を実行する)を行う処理を 追加している.
@@ -3569,44 +3618,51 @@
 	case O (O_BSR, SW):
 	case O (O_BSR, SL):
 	case O (O_BSR, SB):		/* bsr, branch to subroutine */
 	  if (fetch (sd, &code->src, &res))
 	    goto end;
 	  pc = code->next_pc + res;
 	  goto call;
 
 	case O (O_RTE, SN):		/* rte, return from exception */
 	rte:
 	  /* Pops exr and ccr before pc -- otherwise identical to rts.  */
 	  tmp = h8_get_reg (sd, SP_REGNUM);
 
 	  if (h8300smode)			/* pop exr */
 	    {
 	      h8_set_exr (sd, GET_MEMORY_L (tmp));
 	      tmp += 4;
 	    }
 	  if (h8300hmode && !h8300_normal_mode)
 	    {
+#if 0
 	      h8_set_ccr (sd, GET_MEMORY_L (tmp));
 	      tmp += 4;
 	      pc = GET_MEMORY_L (tmp);
 	      tmp += 4;
+#else
+	      pc = GET_MEMORY_L (tmp);
+	      h8_set_ccr (sd, (pc >> 24) & 0xff);
+	      pc &= 0xffffff;
+	      tmp += 4;
+#endif
 	    }
 	  else
 	    {
 	      h8_set_ccr (sd, GET_MEMORY_W (tmp));
 	      tmp += 2;
 	      pc = GET_MEMORY_W (tmp);
 	      tmp += 2;
 	    }
 
 	  GETSR (sd);
 	  h8_set_reg (sd, SP_REGNUM, tmp);
 	  goto end;
 
 	case O (O_RTS, SN):		/* rts, return from subroutine */
 	rts:
 	  tmp = h8_get_reg (sd, SP_REGNUM);
 
 	  if (h8300hmode && !h8300_normal_mode)
 	    {
 	      pc = GET_MEMORY_L (tmp);
@@ -3650,44 +3706,49 @@
 	    {
 	      /* Treat it as a sigtrap.  */
 	      sim_engine_set_run_state (sd, sim_stopped, SIGTRAP);
 	    }
 	  goto end;
 
 	case O (O_TRAPA, SB):		/* trapa */
 	  if (fetch (sd, &code->src, &res))
    	    goto end;			/* res is vector number.  */
   
    	  tmp = h8_get_reg (sd, SP_REGNUM);
    	  if(h8300_normal_mode)
    	    {
    	      tmp -= 2;
    	      SET_MEMORY_W (tmp, code->next_pc);
    	      tmp -= 2;
    	      SET_MEMORY_W (tmp, h8_get_ccr (sd));
    	    }
    	  else
    	    {
+#if 0
    	      tmp -= 4;
    	      SET_MEMORY_L (tmp, code->next_pc);
    	      tmp -= 4;
    	      SET_MEMORY_L (tmp, h8_get_ccr (sd));
+#else
+	      tmp -= 4;
+	      SET_MEMORY_L (tmp, (h8_get_ccr (sd) << 24) | (code->next_pc & 0xffffff));
+#endif
    	    }
    	  intMaskBit = 1;
    	  BUILDSR (sd);
  
 	  if (h8300smode)
 	    {
 	      tmp -= 4;
 	      SET_MEMORY_L (tmp, h8_get_exr (sd));
 	    }
 
 	  h8_set_reg (sd, SP_REGNUM, tmp);
 
 	  if(h8300_normal_mode)
 	    pc = GET_MEMORY_L (0x10 + res * 2); /* Vector addresses are 0x10,0x12,0x14 and 0x16 */
 	  else
 	    pc = GET_MEMORY_L (0x20 + res * 4);
 	  goto end;
 
 	case O (O_BPT, SN):
 	  sim_engine_set_run_state (sd, sim_stopped, SIGTRAP);
これはrteとtrap命令の実行時のコードなのだが,16MBアドレスでの動作時の 割り込み処理のスタックへのPCとCCRの退避にバグがあるように思える. CCRとPCが4バイトずつ領域確保してスタックに退避されているが,h8/3069Fの マニュアルにもあるように(そして12ステップ本の7stepでも説明しているように) 実際にはCCRとPCの値が4バイトに合成してスタックに格納される. なのでトラップ発行によって自身で割り込みを上げてrteで戻るようなプログラムは これでも動作するが,KOZOSのスレッド作成(thread_run())では初回のディスパッチの ためにスタック情報を自前で作成しているため,うまく動かない.なので修正する. これは近いうちバグ報告しようかと思う.

次に初期化の追加.

@@ -5038,40 +5099,42 @@
   if (h8_get_cache_idx_buf (sd))
     free (h8_get_cache_idx_buf (sd));
   if (h8_get_eightbit_buf (sd))
     free (h8_get_eightbit_buf (sd));
 
   h8_set_memory_buf (sd, (unsigned char *) 
 		     calloc (sizeof (char), memory_size));
   h8_set_cache_idx_buf (sd, (unsigned short *) 
 			calloc (sizeof (short), memory_size));
   sd->memory_size = memory_size;
   h8_set_eightbit_buf (sd, (unsigned char *) calloc (sizeof (char), 256));
 
   /* `msize' must be a power of two.  */
   if ((memory_size & (memory_size - 1)) != 0)
     {
       (*sim_callback->printf_filtered) (sim_callback, 
 					"sim_load: bad memory size.\n");
       return SIM_RC_FAIL;
     }
   h8_set_mask (sd, memory_size - 1);
+
+  sim_device_init (sim_callback, h8_get_memory_buf (sd));
 
   if (sim_load_file (sd, myname, sim_callback, prog, prog_bfd,
 		     sim_kind == SIM_OPEN_DEBUG,
 		     0, sim_write)
       == NULL)
     {
       /* Close the bfd if we opened it.  */
       if (abfd == NULL && prog_bfd != NULL)
 	bfd_close (prog_bfd);
       return SIM_RC_FAIL;
     }
 
   /* Close the bfd if we opened it.  */
   if (abfd == NULL && prog_bfd != NULL)
     bfd_close (prog_bfd);
   return SIM_RC_OK;
 }
 
 SIM_RC
 sim_create_inferior (SIM_DESC sd, struct bfd *abfd, char **argv, char **env)
これはGDBでloadコマンドを実行したときに呼ばれる sim_load() という関数の中身 なのだが,デバイスまわりの初期化関数の呼び出しを追加する.

次に device.c の説明.ファイルが長いので,説明が必要そうなところのみ 抜粋して説明.詳しくは device.c を参照してちょうだい.

static int search_freepty(host_callback *sim_callback)
{
  char ttydev[] = "/dev/ptyXX";
  int fd, i0 = 0, i1 = 0;
  char c0, c1;

  while ((c0 = "pqrsPQRS"[i0++]))
    {
      while ((c1 = "0123456789abcdefghijklmnopqrstuv"[i1++]))
	{
	  ttydev[8] = c0;
	  ttydev[9] = c1;
#ifdef USE_CALLBACK
	  fd = sim_callback->open (sim_callback, ttydev, O_RDWR);
#else
	  fd = open (ttydev, O_RDWR);
#endif
	  if (fd < 0)
	    continue;
	  ttydev[5] = 't';
	  (*sim_callback->printf_filtered) 
	    (sim_callback, "connect to %s\n", ttydev);
	  return fd;
	}
    }
  (*sim_callback->printf_filtered) (sim_callback,
				    "Free pty not found.\n");
  return -1;
}
これは空いているptyを検索する関数.openpty()のソースコードを参考にして 作成した.っていうか似たようなことを行う openpty() って関数があるのだけど, これを使うと libutil.a をリンクしなければならなくなりなんだかなあなので 自前で作成してしまう.ちなみにFreeBSD用なので,Linuxの場合はデバイスの 名前の規則をLinuxに合わせて変更する必要があるだろう. これはLinuxディストリビューションに付属している openpty() のソース(glibc?)を 参考にすればよい.Windowsだとどうなるかはしらない.
static void serial_init(host_callback *sim_callback, unsigned char *memory)
{
  volatile struct h8_3069f_sci *sci = DEFAULT_SCI;

  sci->smr  = 0x00;
  sci->brr  = 0xff;
  sci->scr  = 0x00;
  sci->tdr  = 0xff;
  sci->ssr  = 0x84;
  sci->rdr  = 0x00;
  sci->scmr = 0xf2;

  serial_fd = search_freepty(sim_callback);
}
デバイスの初期化時には,SCIのレジスタの初期値を代入しておく.
static int is_send_enable(host_callback *sim_callback, int fd)
{
  fd_set fds;
  struct timeval tm;
  int ret;

  FD_ZERO(&fds);
  FD_SET(fd, &fds);
  tm.tv_sec  = 0;
  tm.tv_usec = 0;

  ret = select(fd + 1, NULL, &fds, NULL, &tm);
  if (ret > 0) {
    if (FD_ISSET(fd, &fds)) {
      return 1;
    }
  }

  return 0;
}

static int is_recv_enable(host_callback *sim_callback, int fd)
{
  (is_send_enable()とだいたい同じなので中略)
}
SCIの送受信可能かどうかは select() を呼び出して判断する.
static void send_byte(host_callback *sim_callback, int fd, unsigned char c)
{
#ifdef USE_CALLBACK
  sim_callback->write (sim_callback, fd, &c, 1);
#else
  write (fd, &c, 1);
#endif
  fsync(fd);
}

static unsigned char recv_byte(host_callback *sim_callback, int fd)
{
  (send_byte()とだいたい似てるので中略)
}
送受処理は USE_CALLBACK の定義によって切り替わるが,USE_CALLBACK は落として あるので,sim_callback->write() ではなく write() が使われることになる. これはシミュレータ的には sim_callback->write() を使うのが流儀のようなのだが, それを使うと is_send_enable(), is_recv_enable() 内部での select() がなぜか うまく効いてくれない(そして sim_callback は select() を持っていないので, sim_callback->select() のような呼び出しは行えない).ためしに write() を 生で使ってみたらうまくいった.まあ調べるのも面倒なのでとりあえず write() を 使うことにする.受信用関数(recv_byte())についても同様.

次に毎ステップごとに呼ばれるデバイスの動作.

static int serial_run(host_callback *sim_callback, unsigned char *memory)
{
  volatile struct h8_3069f_sci *sci = DEFAULT_SCI;
  unsigned char c;
  static int send_intr = 0, recv_intr = 0;
  int vector = 0;

  if (sci->scr & H8_3069F_SCI_SCR_TE) {
    if (!(sci->ssr & H8_3069F_SCI_SSR_TDRE)) {
      if (is_send_enable(sim_callback, serial_fd)) {
	send_byte(sim_callback, serial_fd, sci->tdr);
	sci->ssr |= H8_3069F_SCI_SSR_TDRE;
	if (sci->scr & H8_3069F_SCI_SCR_TIE)
	  send_intr++;
      } else {
	send_intr = 0;
      }
    }
  }
  send_intr =
    (sci->ssr & H8_3069F_SCI_SSR_TDRE) && (sci->scr & H8_3069F_SCI_SCR_TIE);

  if (sci->scr & H8_3069F_SCI_SCR_RE) {
    if (!(sci->ssr & H8_3069F_SCI_SSR_RDRF)) {
      if (is_recv_enable(sim_callback, serial_fd)) {
	c = recv_byte(sim_callback, serial_fd);
	sci->rdr = c;
	sci->ssr |= H8_3069F_SCI_SSR_RDRF;
	if (sci->scr & H8_3069F_SCI_SCR_RIE)
	  recv_intr++;
      } else {
	recv_intr = 0;
      }
    }
  }
  recv_intr =
    (sci->ssr & H8_3069F_SCI_SSR_RDRF) && (sci->scr & H8_3069F_SCI_SCR_RIE);

  if (send_intr) vector = 54;
  if (recv_intr) vector = 53;

  return vector;
}
ptyからの送受信イベントがあるかを調べ,イベントがあるならばSCIの動作を模擬して 適切なレジスタを設定し,割り込み処理を行う.割り込みフラグをどのような位置で 立てるべきか(実際のSCIがどのようなタイミングで割り込みフラグを上げ下げするか?) がH8のマニュアル見てもちょっとよくわからんのだが, まあいろいろ試してみたらこのようなコードでうまく動作したのでまあこんな感じ なのだろう.送信割り込みの場合はベクタ番号54,受信割り込みの場合にはベクタ番号 53を返すことで,割り込みが入ったことを通知する.

ここまでがGDB付属のH8シミュレータに対する修正. 次にブートローダーとOS側の修正.12ステップ本の最終コードをベースにして, 以下の修正を加えて利用する.

まずはブートローダーに対する修正.Makefileに以下の修正を加える.

 CFLAGS = -Wall -mh -nostdinc -nostdlib -fno-builtin
 #CFLAGS += -mint32 # intを32ビットにすると掛算/割算ができなくなる
 CFLAGS += -I.
-#CFLAGS += -g
-CFLAGS += -Os
+CFLAGS += -g
+#CFLAGS += -Os
+CFLAGS += -O0
 CFLAGS += -DKZLOAD
+CFLAGS += -DSIMULATOR
 
 LFLAGS = -static -T ld.scr -L.
せっかくなのでデバッガを利用したいので,コンパイルオプションに -g を加える. またデバッガを利用する場合,最適化があまり効いているとステップ実行とかが 期待通りにできない場合があるので,-O0 で最適化を抑止する.さらにソースコード 中でシミュレータ対応のために必要な修正を,実機動作の場合と切り替えられるように 「SIMULATOR」という#define定義を追加する.これの有効/無効の切り替えで, シミュレータ用のファームと実機動作用のファームを切り替えて作成できることに なる.

次に main.c の修正.

 static int init(void)
 {
+#ifndef SIMULATOR /* シミュレータはVAにロードするので以下は不要 */
   /* 以下はリンカ・スクリプトで定義してあるシンボル */
   extern int erodata, data_start, edata, bss_start, ebss;
 
   /*
    * データ領域とBSS領域を初期化する.この処理以降でないと,
    * グローバル変数が初期化されていないので注意.
    */
   memcpy(&data_start, &erodata, (long)&edata - (long)&data_start);
   memset(&bss_start, 0, (long)&ebss - (long)&bss_start);
+#endif
 
   /* ソフトウエア・割り込みベクタを初期化する */
   softvec_init();
 
   /* シリアルの初期化 */
   serial_init(SERIAL_DEFAULT_DEVICE);
 
   return 0;
 }
どうもGDBのシミュレータ動作ではプログラムはVAをベースにロードされるようだ. ところが12ステップ本の3ステップ目でも説明しているように,KOZOSのブートローダー は起動時にフラッシュROMからRAMへのデータ領域のコピーを行う.なのでこのままだと せっかくVAにロードしたデータ領域が,上書きされて消されてしまう. なのでコピーするコードを無効にする.そもそもロード時にVAにロードされるため, このコピー操作はシミュレータでは不要ということになる.

次にmain.cでの,XMODEMでの転送終了時のウエイト処理.

 static void wait()
 {
   volatile long i;
+#ifndef SIMULATOR
   for (i = 0; i < 300000; i++)
+#else
+  for (i = 0; i < 30000; i++)
+#endif
     ;
 }
このウエイトが無いとXMODEMでの転送終了時にすぐに文字列が出力されてしまい なんか表示が壊れてしまうのだが,実機とシミュレータでは動作速度がだいぶ異なる ため,どれくらい待てばいいのかも異なってくる.ぼくのPCで試したら上の修正 くらいの値にするのがちょうどよかったのだが,これは各自のPCによって値が 異なってくるので,各自でてきとうに調整してみてほしい.あまり短いと XMODEMでの通信終了時の表示が崩れる.あまり長いとXMODEM通信終了時になかなか コマンドプロンプトが返ってこなくてうっとうしい.

次にxmodem.cでの,XMODEMの通信開始時の修正.

 /* 受信開始されるまで送信要求を出す */
 static int xmodem_wait(void)
 {
   long cnt = 0;
 
   while (!serial_is_recv_enable(SERIAL_DEFAULT_DEVICE)) {
+#ifndef SIMULATOR
     if (++cnt >= 2000000) {
+#else
+    if (++cnt >= 200000) {
+#endif
       cnt = 0;
       serial_send_byte(SERIAL_DEFAULT_DEVICE, XMODEM_NAK);
     }
   }
 
   return 0;
 }
これも上記のウエイト処理と同じで,シミュレータと実機では動作速度が異なるので 自分のPCに合わせててきとうに調整する必要がある. タイミング調整したければ,serial_send_byte() の出力をNAKでなく'A'とかに してやれば,ブートローダーのloadコマンド実行で「A」が定期的に出力されるので, どれくらいの間隔でNAKが出力されているのかを知ることができる. 5〜10秒くらいにするのがいいだろう.あまり短くすると,これは12ステップ本の 4ステップ目に説明があるが,通信開始時にスピーディにファイル選択する必要が 出てくる(でないとNAKがたまってしまい,通信に失敗する).あまり長いと 通信開始にやたら時間がかかってうっとうしい.

次にOS側の修正.まずはブートローダーと同様に,Makefileの修正.

 CFLAGS = -Wall -mh -nostdinc -nostdlib -fno-builtin
 #CFLAGS += -mint32 # intを32ビットにすると掛算/割算ができなくなる
 CFLAGS += -I.
-#CFLAGS += -g
+CFLAGS += -g
 CFLAGS += -Os
 CFLAGS += -DKOZOS
+CFLAGS += -DSIMULATOR
 
 LFLAGS = -static -T ld.scr -L.
修正の内容は似ているのだけど,最適化は行うこととした.というのは -O0 で 最適化無しにしてしまうと,ファームサイズが大きくなってXMODEMでのダウンロードで バッファサイズ超過しておかしくなってしまったから. なのでOSはデバッガでステップ実行とかすると,なんか動作がおかしく見えるときが あるかもしれない.(最適化により命令の順番入れ換えが行われるので, ステップ実行でソースコード的に上から順に実行されず,上にいったり下にいったり するように見えてしまったりとかする.また同一処理がまとめられてしまったりする ので,ソースコード上の本来実行されていない位置があたかも実行されているかの ように見えてしまったりするかもしれない)

次にmain.cの修正.

 /* システム・タスクとユーザ・タスクの起動 */
 static int start_threads(int argc, char *argv[])
 {
   kz_run(consdrv_main, "consdrv",  1, 0x200, 0, NULL);
   kz_run(command_main, "command",  8, 0x200, 0, NULL);
 
   kz_chpri(15); /* 優先順位を下げて,アイドルスレッドに移行する */
   INTR_ENABLE; /* 割込み有効にする */
   while (1) {
+#ifndef SIMULATOR /* シミュレータがsleep対応されていないので無効化する */
     asm volatile ("sleep"); /* 省電力モードに移行 */
+#endif
   }
 
   return 0;
 }
実は今回のGDB付属H8シミュレータの修正では,sleepモードに入ったときの考慮が されていない.なのでsleepで省電力モードに入ってしまうと,その後のシリアル受信で 割り込み発生するのかちょっとよくわからない.試してもいないが,まあ何らかの 対応を入れないと無理なように思う.なのでとりあえずシミュレータの場合には sleepを実行せずに無限ループするように修正する.これはidleスレッドの処理であり, やることがない(レディー状態のスレッドが他に存在しない)場合に実行される処理 なので,無限ループにしてしまって実害は無い.

さて,ここまでで修正はおしまい.いよいよ動作させて見よう!と行きたいところ なのだが,ちょっと長くなってしまったので,GDBの使いかたも含めて次回に説明 しよう.


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