(H8移植編第9回)移植内容を説明しよう

2009/09/20

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

前回よーやくH8への移植ができたが, 移植の作業内容について説明しよう.

作業した内容として,だいたい以下のようなものがあった.

■ ブートローダー側の対応

■ OS側の対応

まあこんなとこかな.

まずブートローダーの修正だけど,ロードするELFファイルのサイズが格段に 増えたので,そのままだとうまくロードできなかったので何箇所か手を入れた.

まずはそもそもバッファのサイズが足りなかったので拡張する修正.

diff -ruN h8_06/kzload/main.c h8_08/kzload/main.c
--- h8_06/kzload/main.c	Tue Sep 15 00:26:55 2009
+++ h8_08/kzload/main.c	Sun Sep 20 00:33:51 2009
@@ -4,7 +4,7 @@
 #include "elf.h"
 #include "interrupt.h"
 
-#define BUFSIZE 4096
+#define BUFSIZE 7400
 static unsigned char loadbuf[BUFSIZE];
 
 static int init()
さらに,BSSの展開時に(BSSのサイズがこれがまたでかいため)上記 loadbuf の 内容を上書きしてしまうことが判明した.このためloadbuf上にあるELFヘッダを 上書きしてしまい誤動作していることが判明.本当はなんかきちんと考える必要が あるのだが,とりあえずBSSを展開したらそのまま終了するように修正.
diff -ruN h8_06/kzload/elf.c h8_08/kzload/elf.c
--- h8_06/kzload/elf.c	Tue Sep 15 00:26:55 2009
+++ h8_08/kzload/elf.c	Sun Sep 20 00:33:51 2009
@@ -72,6 +72,11 @@
 	     phdr->file_size);
     } else {
       memset((char *)phdr->physical_addr, 0, phdr->memory_size); /* for BSS */
+      /*
+       * BSSがバッファ領域にまたがっている場合,BSSをクリアすることで
+       * バッファが破壊されてしまうので,BSSが見つかったらそのまま終了する.
+       */
+      break;
     }
   }
 
@@ -81,12 +86,16 @@
 char *elf_load(char *buf)
 {
   struct elf_header *header = (struct elf_header *)buf;
+  char *entry_point;
 
   if (elf_check(header) < 0)
     return NULL;
 
+  /* BSSの展開でバッファが書きつぶされる場合があるので,保存しておく */
+  entry_point = (char *)header->entry_point;
+
   if (elf_load_program(header) < 0)
     return NULL;
 
-  return (char *)header->entry_point;
+  return entry_point;
 }
上述のようにBSSの展開でELFヘッダが破壊されてしまうので,エントリポイントを 保存しておくように修正している.

次に,割り込みまわり.

まず割り込みハンドラを呼ぶ際に,引数として割り込み番号を渡すように修正. これはOS側で割り込みハンドラを共通化して内部で割り込み番号を見て処理を 分けるために利用する.

diff -ruN h8_06/kzload/interrupt.c h8_08/kzload/interrupt.c
--- h8_06/kzload/interrupt.c	Tue Sep 15 00:26:55 2009
+++ h8_08/kzload/interrupt.c	Sun Sep 20 00:33:51 2009
@@ -19,6 +19,6 @@
 {
   interrupt_handler_t handler = VECTORS[vec];
   if (handler)
-    handler();
+    handler(vec);
   return;
 }
diff -ruN h8_06/kzload/interrupt.h h8_08/kzload/interrupt.h
--- h8_06/kzload/interrupt.h	Tue Sep 15 00:26:55 2009
+++ h8_08/kzload/interrupt.h	Sun Sep 20 00:33:51 2009
@@ -3,7 +3,7 @@
 
 #define VECTOR_NUM 64
 #define VECTOR_ADDR ((void *)0xffbf20)
-typedef void (*interrupt_handler_t)(void);
+typedef void (*interrupt_handler_t)(int vec);
 
 #define VECTORS ((interrupt_handler_t *)VECTOR_ADDR)
 
さらに,従来は割り込み発生時にはそのときのスタックをそのまま利用して いたのだけど,スレッドベースでの動作になったときに,スレッドの スタックをそのまま割り込み処理に使うのはなんかイヤ.スレッドから見ると, なんか突然スタックの未使用領域が汚されることになるからね.ということで, 割り込み処理用のスタックを用意してそっちに切替えて動作するように修正.
diff -ruN h8_06/kzload/intr.S h8_08/kzload/intr.S
--- h8_06/kzload/intr.S	Tue Sep 15 00:26:55 2009
+++ h8_08/kzload/intr.S	Sun Sep 20 00:33:51 2009
@@ -5,6 +5,9 @@
 	.type	_intr01,@function
 _intr01:
 	mov.l	er0,@-er7
+	mov.l	er7,er0
+	mov.l	#_stack,sp
+	mov.l	er0,@-er7
 	mov.l	er1,@-er7
 	mov.l	er2,@-er7
 	mov.l	er3,@-er7
@@ -20,12 +23,17 @@
 	mov.l	@er7+,er2
 	mov.l	@er7+,er1
 	mov.l	@er7+,er0
+	mov.l	er0,er7
+	mov.l	@er7+,er0
 	rte
 
スタックポインタ(ER7)の保存のためにER0を使っている.なのでER0の値は 現在使用中のスタックに退避するようになっている.なので厳密にアプリのスタックを まったく使わないということではないのだけど,まあそもそも割り込み発生時に スタックにPCとかCCRとかが退避されてしまうので,アプリのスタックをまったく 汚さないというのは不可能なので,まあそのまま引続きアプリのスタック上で 動作しなければまあいいかなということで,これでいいとする.

ちなみに上記修正のために,前々回のサンプルプログラムは そのままでは動作しなくなってしまう.これは,前々回まではスレッドベースの 動作ではなくアプリは 0xffff00 をスタックとして利用していたが,割り込み処理でも 0xffff00 をスタックとして利用するようになったため,スタックポインタが前に 戻ってアプリのスタックを上書きしてしまうから.

なのでもしも前々回のアプリを動かすならば,アプリに以下の修正を入れて, アプリ用のスタックと割り込み処理用のスタックを別々にする必要あり.

diff -ruN serial/ld.scr serial2/ld.scr
--- serial/ld.scr	Sat Sep 19 16:58:54 2009
+++ serial2/ld.scr	Sat Sep 19 19:56:47 2009
@@ -9,7 +9,8 @@
 	 * and 256bytes space for ELF header.
 	 */
 	ram(rwx)	: o = 0xffbf20 + 0x200, l = 0x004000 - 0x200 /* 16kb */
-	stack(rw)	: o = 0xffff00, l = 0x000010 /* end of RAM */
+	bootstack(rw)	: o = 0xfffd00, l = 0x000010 /* end of RAM */
+	intrstack(rw)	: o = 0xffff00, l = 0x000010 /* end of RAM */
 }
 
 SECTIONS
@@ -42,7 +43,11 @@
 
 	_end = . ;
 
-	.stack : {
-		_stack = .;
-	} > stack
+	.bootstack : {
+		_bootstack = .;
+	} > bootstack
+
+	.intrstack : {
+		_intrstack = .;
+	} > intrstack
 }
diff -ruN serial/startup.s serial2/startup.s
--- serial/startup.s	Sat Sep 19 16:58:54 2009
+++ serial2/startup.s	Sat Sep 19 19:57:14 2009
@@ -3,7 +3,7 @@
 	.global	_start
 	.type	_start,@function
 _start:
-	mov.l	#_stack,sp
+	mov.l	#_bootstack,sp
 	jsr	@_main
 
 _loop:
あとついでにブートローダーのメッセージをなんかそれっぽいものに変える修正.
@@ -121,10 +121,10 @@
 
   init();
 
-  puts("Hello World!\n");
+  puts("kzboot (kozos boot loader) started.\n");
 
   while (1) {
-    puts("> ");
+    puts("kzboot> ");
     gets(buf);
 
     if (!strcmp(buf, "load")) {
ブートローダー側の修正は以上.

次にOS側の対応.

まずは Makefile の修正.

--- h8_07/os/Makefile	Sat Sep 19 17:24:41 2009
+++ h8_08/os/Makefile	Sun Sep 20 00:34:04 2009
@@ -1,15 +1,24 @@
 OBJS += startup.o main.o interrupt.o
-OBJS += lib.o serial.o timer.o
+OBJS += lib.o serial.o
+#OBJS += timer.o
+
+# sources of kozos
+OBJS += thread.o syscall.o memory.o idle.o
+OBJS += extintr.o
+OBJS += kozos.o command.o
+
 #LIBOBJS += 
 #LIB = libxxx.a
-TARGET ?= hello
+TARGET ?= kozos
 #CC ?= gcc
 CFLAGS =
 CFLAGS += -Wall -mh -nostdinc -nostdlib -fno-builtin
 #CFLAGS += -mint32 # intを32ビットにすると掛算/割算ができなくなる
 CFLAGS += -I.
-CFLAGS += -g
+#CFLAGS += -g
 #CFLAGS += -O
+CFLAGS += -Os
+CFLAGS += -DKOZOS
 
 LFLAGS += -static -T ld.scr -L.
 
KOZOS移植で追加されるソースをコンパイル対象に追加して,あとターゲット名を helloからkozosに変更.さらに -g がついてるとデバッグ情報が付加されて リンカスクリプトで指定したメモリ上に入りきれなくてリンクエラーになって しまうので,まあどうせ strip でデバッグ情報は捨ててしまうので-gを削除. あとサイズを極力小さくするために,コンパイルオプションに -Os を追加 (これがけっこう威力がでかくて,これのおかげでなんとかメモリ上に収まった).

次にKOZOSのソースコードで大きな変更を行った部分やハマッた部分について 説明しよう.

まずはthread.cなどで,32ビットが必要な部分(ポインタの値を保存しているとか)で intを使っている部分を uint32 に変更.まあ本来なら -mint32 オプションで コンパイルすることで int を32ビットにできるのだけど,そもそも16ビットCPU なのだからネイティブな状態で動作させたいし,KOZOSの16ビット対応をする いい機会なので,ごっそり書き換えることにした.

ここで1箇所ハマッた部分があって,レディーキューのビットマップを立てる 以下の箇所

  readyque_bitmap |= (1 << current->pri);
上記はシフト演算が16ビットで行われてしまうので,優先度が16以上の スレッドが動作できないという問題が発覚(このためidleスレッドが動作できず, 動作可能なスレッドの検索に失敗して schedule() 内で停止してしまっていた). 以下のように修正した.
  readyque_bitmap |= ((uint32)1 << current->pri);
スレッドのコンテキストは,レジスタの内容をH8に合わせて thread.h の定義を変更.
typedef struct _kz_context {
  uint32 er[8];
  uint32 pc;
} kz_context;
あとは割り込み発生時に割り込み処理スタックの先頭にレジスタ情報が退避される ので,そっから情報を吸い上げてスレッドのコンテキストに保存する対応.
void thread_intr(int vec)
{
  ...
  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);
  current->context.er[0] = *(uint32 *)(current->context.er[7]);

  current->context.pc = *(uint32 *)(current->context.er[7] + 4);
  ...
あとちなみに INTR_STACK_START の定義だが,configure.h で
extern uint32 intrstack;
#define INTR_STACK_START   (&intrstack)
のようにしているけど,これも最初は
extern uint32 intrstack;
#define INTR_STACK_START   intrstack
のように書いてしまっていて,割り込みスタックからレジスタ情報をうまく 保存できなくてその後のスレッドのディスパッチに失敗するというバグでハマッた. ああまぬけ.configure.h では,スレッドのスタックの位置やサイズも調整してある.

startup.s にはディスパッチ用の処理を追加.

	.global	_dispatch
	.type	_dispatch,@function
_dispatch:
	mov.l   er0,er7
	mov.l	@er7+,er0
	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
	rte
上述したようにER0の値はスレッドのスタック上に退避されているので, そっちを利用するようにしてある.これも,最初はER0の値を読み出すのを忘れていた ためにスタックポインタがER0を指している状態で rte してしまって, おかしなアドレスにジャンプしていたというバグがあった.

あと,serial.c は3本のSCIを選択できるように引数に index を渡せるように修正 (これは,流用したPowerPCのKOZOSがそのようになっていたのでそれに合わせた). あと memory.c はもともとは湯水のようにメモリを使っていたのだけど, あんまし無駄にメモリ獲得しないように修正.extintr.c もバッファに無駄に 1024バイトとか使っていた部分があったので,修正.

extintr.c はとりあえずSCI1を利用するようにして, あと USE_MESSAGE を未指定にするようにした. これは USE_MESSAGE 有効だと,割り込み発生時にはメッセージ送信だけ行って 割り込みフラグを落とさずにスレッドのディスパッチに入ってしまい, ディスパッチ直後にまた割り込みが入ってしまって無限ループになって, そのうちメッセージ用のメモリの枯渇で固まる,というバグがあったから. これもハマッた.(PowerPCはスレッドレベルがゼロのスレッドは割り込み禁止で 動作する対応が入っていて,H8にも同じ対応を入れたのだけど,extintr の スレッドレベルをゼロにするのを忘れていて現状では USE_MESSAGE 有効だと うまく動作しない)

kozos.c はSCI1が利用されるように command1 スレッドを起動するように修正. これも,当初はメッセージが出なくてちょっと悩んだところだ.

あと puts() とか putxval() とかのライブラリが main.c にあったのだけど, lib.c に移動した.

あと thread.c の thread_run() で,従来はスタックをてきとうに確保して いたのだけど,下方伸長なので thread_stack を増加させてからスタックポインタを 設定するように修正.これでスタックひとつぶんのメモリが節約できる. thread_run()は他にもレジスタ周りの設定をH8に合わせて修正.

あとはまあこまごまと,メモリ節約のためにいろんなところを修正してある.

説明のためにソースを見返すと,なんというかちょっとなんだかなあな箇所が いっぱい.ちょっと細かいところをきれいにして,すっきりしたソースコードに したいところだ.

あとは気が向けば,puts() や putxval() などのライブラリはブートローダー側で ROM上にサービスルーチンとして用意して,そっちを呼び出すようにすることで OSのサイズを削減できる(BIOSっぽいものを作る).まあでもなるべくならBIOSに 依存せずにOS側ですべて行いたいので,これはほんとに気が向けば.

まあでもサービスルーチンをBIOS側で用意するっつーのは,それはそれで面白いかも. BIOSがどんなことをやっているのかの学習用にはいいかもしれん.

こー考えると,H8って内蔵フラッシュROMが512KBで内蔵RAMが16KBなのでROMのほうが ずっとサイズがでかいのだけど,これって基本的にはプログラムはROM上で動かして, RAMはスタックとデータ領域とBSSだけに使うっていう使いかたを想定して いるんだろうね.今回のKOZOSのように,ROMはブートローダーだけにして, すべてRAM上で動作させるっつー使いかたをするときは,RAMを増設して使うべき なのかもしれん.


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