Z80,000やZ80320と同じように入手を諦めていたのですが、たまたまネットで見つけ、ちょっと高価でしたが購入してしまいました。
このCPUはかなり高機能で、以下のような特徴があります。
- 32本の32bit汎用レジスタ
- 浮動小数点演算命令
- V20/V30エミュレーション
- デマンドページングをサポートできるMMU
Z80,000やZ80320と同じように入手を諦めていたのですが、たまたまネットで見つけ、ちょっと高価でしたが購入してしまいました。
このCPUはかなり高機能で、以下のような特徴があります。
ブートローダの2段目は、C言語とアセンブリ言語で書かれたbootで、そのソースコードは、/usr/src/cmd/standaloneにあります。M.sにあるstartから実行が開始され、MMUの設定とコードの移動が行われたのち、C言語でかかれたmain()に実行が移ります。
/ set kernel I+D to physical 0 and IO page
clr r1 ! start from 0x00000
mov $77406,r2 ! 127 blocks R/W
mov $KISA0,r3
mov $KISD0,r4
jsr pc,setseg
mov $IO,-(r3)
clr r1
mov $KDSA0,r3
mov $KDSD0,r4
jsr pc,setseg
mov $IO,-(r3)
/ set user I+D to physical 64K (words) and IO page
mov $4000,r1 ! start from 0x20000
mov $UISA0,r3
mov $UISD0,r4
jsr pc,setseg
mov $IO,-(r3)
mov $4000,r1
mov $UDSA0,r3
mov $UDSD0,r4
jsr pc,setseg
mov $IO,-(r3)
/ enable map
mov $65,SSR3 / 22-bit map ! Split I+D for Kernel and User
bit $20,SSR3
beq 1f ! Check if 22-bit actived
mov $3,MSCR
1:
mov $30340,PS
inc SSR0 ! Enable MMUブートローダは、1-24行でカーネルとユーザのIとD空間用にMMUを設定したのち、MMUを有効にします。この段階ではブートローダは カーネルモードで実行されているので、PSWを現在カーネル、以前をユーザに32行で設定しています。
このままでは、ローダ自身がじゃまでUNIXカーネルを読み込めないので、ユーザ空間に移動させます。
/ copy program to user I space
mov $_end,r0
asr r0
clr r1
1:
mov (r1),-(sp)
mtpi (r1)+
sob r0,1b
/ continue execution in user space copy
mov $140004,sp ! 0xc004
clr *$KDSA6
mov $140340,-(sp) ! PSW:0b1100000011100000
mov $user,-(sp) ! Return address
rtt ! Return from trap7行目のmtpi命令は、以前いたモードのI空間にデータをスタック経由でデータを書き込みます。繰り返し命令を使って、カーネルモードから以前のユーザモードのI空間にローダのコードがコピーされます。
14-16行目は、スタックにカーネルからユーザモードに戻るようなPSWと、userのアドレスを積み、rtt命令でトラップからの復帰を装ってモード切替とジャンプを実行しています。
この後は、C言語で書かれた main()に実行が移ります。
新しくZ8001CPUボードを作製しました。今までは、ラッピングワイヤを直接はんだ付けして配線していましたが、今回初めて、ユニバーサル基板にピンヘッダをたてワイヤラッピングで配線してみました。この方法は、ピンヘッダとICソケットとの間を配線するのが単調で根気がいります。とくにPLCCソケットは苦痛でした。ラッピングでの配線はやっていて楽しいのですが、間違いに気づいたときは修正が大変です。ほどきたい配線の上にすでに配線されていると、それもほどかないといけません。一度ほどくとワイヤが傷んでしまっているので、2回目巻きなおすと折れたりするので気を使います。年で眼も弱ってきているので、そろそろPCBが作れるようにならないとだめですね。
今回のボードは、MMUを搭載しCP/M-8000以外のOSも移植することを考えています。初代のボードでは、Z8010 MMUを使ってメモリー管理ができるように考えていたのですが、何故かうまく動作せず。結局、CPLDでセグメントアドレスを変換するロジックを組んで、CP/M-8000が動作するようにしました。しかし、この方法ではアドレス変換が固定で融通が利かず、CP/M-8000以外のOSを動かすのは難しくなります。
新しいボードでは、Z8010を使うことは諦めて、MMUを自前で組むことにしました。これに先立って、Z8002 CPUを使ったボードで、MMUを自作できそうか簡単な回路を組んで試してあったのですが、実際に作ってみると配線量の多さからかなり苦戦しました。MMU以外は、最初のボードとほぼ同じです。
自作MMUの仕様は下のようになっています。
PDP-11が起動し最初にディスクから読み出されるコードは、ディスクの先頭に書かれているブートプログラムです。そのソースコードは、/usr/mdec/rpuboot.s です。UNIX V6だと、このプログラムがUNIXカーネルをディスクから読み出して実行するのですが、V7では、/bootを読み込んで実行します。そしてbootがUNIXカーネルを読み込んで実行します。bootのソースコードは、/usr/src/cmd/standaloneにあります。
ブートプログラムはアセンブリ言語で書かれています。コードのメインの部分を見ていきます。
/ now start reading the inodes / starting at the root and / going through directories 1: mov $names,r1 mov $2,r0 / ルートディレクトリのinode番号 1: clr bno jsr pc,iget tst (r1) / namesの終端か beq 1f 2: jsr pc,rmblk / br start / rmblkが正常の終了の場合はスキップ mov $buf,r2 / r2 = buf[] 3: mov r1,r3 / r3 = names[] mov r2,r4 / r4 = buf[] add $16.,r2 / r2 = 次のディレクトリエントリ tst (r4)+ / inodeは空か beq 5f 4: cmpb (r3)+,(r4)+ / ファイル名の比較 bne 5f cmp r4,r2 / blo 4b mov -16.(r2),r0 / r0 = inode番号 add $14.,r1 / br 1b 5: cmp r2,$buf+512. / blo 3b / buf[]の最後でない br 2b / buf[]の最後 / read file into core until / a mapping error, (no disk address) 1: clr r1 / r1 = 0 1: jsr pc,rmblk br 1f mov $buf,r2 2: mov (r2)+,(r1)+ cmp r2,$buf+512. blo 2b br 1b 1: clr r1
6−9行で、ルートディレクトリのinodeを読み込んでいます。igetは、r0で指定した番号のinodeをinode[]にコピーします。
13行は、rmblkでルートディレクトリをbuf[]に読み込みます。15行目からは、1エントリごとにファイル名とnames[]を比較します。
一致した場合は、27行でinode番号をr0に読み込みます。r1に14を足して8行目に飛び、igetでファイル名が一致したファイルのinodeを読み込みます。
11行でファイル名が終わっていることを確認し、38行目からinodeのdi_addr[]に沿ってファイルをメモリーに読み込んでいきます。
次のリストは igetです。
/ get the inode specified in r0 iget: add $15.,r0 mov r0,r5 ash $-3.,r0 / r0 = (r0 + 15) / 8 bic $!17777,r0 / r0 &= 0x01fff mov r0,dno clr r0 jsr pc,rblk bic $!7,r5 / r5 &= 0x0007 ash $6,r5 / r5 *= 64 add $buf,r5 / r5 += buf mov $inod,r4 1: mov (r5)+,(r4)+ cmp r4,$inod+64. blo 1b rts pc
3-6行でinode番号をinodeが存在しているブロック番号に変換し、9行でそのブロックをbuf[]に読み込んでいます。
10-12行で目的のinodeのbuf[]内でのオフセットを求め、13ー17行でinode[]にコピーしています。
次のrmblkは、bnoで指定されたブロックを、inode内のdi_addr[]に従ってbuff[]に読み込みます。
/ read a mapped block / offset in file is in bno. / skip if success, no skip if fail / the algorithm only handles a single / indirect block. that means that / files longer than 10+128 blocks cannot / be loaded. rmblk: add $2,(sp) / リターンアドレスを2増やす mov bno,r0 cmp r0,$10. blt 1f mov $10.,r0 / 間接参照 1: mov r0,-(sp) / asl r0 / add (sp)+,r0 / r0 = bno * 3 add $addr+1,r0 / r0 += di_addr + 1 movb (r0)+,dno movb (r0)+,dno+1 / dno = ブロック番号の下位2バイト movb -3(r0),r0 / ブロック番号の上位1バイト bne 1f tst dno beq 2f 1: jsr pc,rblk / ブロック読み込み mov bno,r0 inc bno sub $10.,r0 blt 1f / 直接参照ならリターン ash $2,r0 / r0 *= 4 mov buf+2(r0),dno / dno = ディスク番号の下位2バイト mov buf(r0),r0 / r0 = ディスク番号の上位2バイト bne rblk tst dno bne rblk 2: sub $2,(sp) 1: rts pc
ブロック番号が0から9までは直接参照で10は間接参照になりますが、コメントにあるとおり、2重間接参照には対応していません。
13行でブロック番号10以上は、間接参照に対応するため10に固定しています。
15-18行で、ブロック番号からdi_addr[]内でのディスク番号下位2バイトのオフセットを求めています。
22-24行は、ブロック番号が0だった場合、rmblkを呼び出した直後のアドレスにリターンします。
26-30行で、ブロックを読み込んだ後、直接参照の場合はリターンします。
31行目からは間接参照の処理で、29行で10引かれたディスク番号を4倍しオフセットを求め、先に読み込んだブロックからブロック番号を取り出します。
34-36行で、番号が0でなければブロックを読み出します。
ブロックが正常に読み込まれた場合は、9行でリターンアドレスが+2されているので、rmblkを呼び出した次の命令をスキップします。
これで、UNIXが起動する第一段階を追いかけられました。次は、2段階目の/bootになります。
ディレクトリは、/usr/sys/h/dir.hで定義されているdirect構造体のリストです。
#ifndef DIRSIZ
#define DIRSIZ 14
#endif
struct direct
{
ino_t d_ino;
char d_name[DIRSIZ];
};direct構造体のサイズは16バイトで,、最初の2バイトはinode番号です。続く14バイトがファイル名です。
(inode番号 - 1)×dinod構造体のサイズ (64バイト)+ inodeブロックの先頭アドレス(0x400)
計算すると、rl2tunixのi-nodeのアドレスは0x2300になります。
92ブロックの間接参照のブロック番号が書かれています。直接参照と合わせると、102ブロックに渡ってrl2unixが書き込まれていることがわかります。
これでUNIXカーネルのファイルまでたどり着くことができました。次は、ブートプログラムを見ていきます。
struct dinode
{
unsigned short di_mode;/* mode and type of file */
short di_nlink; /* number of links to file */
short di_uid; /* owner's user id */
short di_gid; /* owner's group id */
off_t di_size; /* number of bytes in file */
char di_addr[40]; /* disk block addresses */
time_t di_atime; /* time last accessed */
time_t di_mtime; /* time last modified */
time_t di_ctime; /* time created */
};
#define INOPB 8 /* 8 inodes per block */
/*
* the 40 address bytes:
* 39 used; 13 addresses
* of 3 bytes each.
*/
};inode番号0は未使用で、ルートディレクトリのinode番号は2なので、ルートのinodeの位置は、ディスクイメージでは0x0440からです。
ブートローダの移植から始めようとしているわけですが、ディスクドライブからカーネルを読み込まないといけないので、まずはファイルシステムを理解する必要があります。この辺りは参考になりそうな書籍やネット上の情報があるので助かります。
技術評論社から出ている「はじめてのOSコードリーディング」は、UNIX V6の説明ですが、基本はV7とほぼ同じなので参考になります。コードだけを読んで理解するのは難しいので、ディスクイメージと照らし合わせながら理解していきます。SIMHのサイトからUNIX V7のSoftware Kitesをダウンロードし、ZIPファイルに含まれているunix_v7_rl.dskを使います。
http://simh.trailing-edge.com/kits/uv7swre.zip
このディスクイメージのブロックサイズは512バイトです。
先頭のブロックはブートブロックで、システム起動時に最初に読み込まれるブートプログラムが格納されています。ディスクからカーネルを読み出すコードですが、とりあえずは手をつけずに置いておきます。
ブートブロックに続く1ブロック、ディスクイメージのダンプリストで0x0200から0x03FFまでがスーパーブロックになります。
/*
* Structure of the super-block
*/
struct filsys {
unsigned short s_isize; /* size in blocks of i-list */
daddr_t s_fsize; /* size in blocks of entire volume */
short s_nfree; /* number of addresses in s_free */
daddr_t s_free[NICFREE];/* free block list */
short s_ninode; /* number of i-nodes in s_inode */
ino_t s_inode[NICINOD];/* free i-node list */
char s_flock; /* lock during free list manipulation */
char s_ilock; /* lock during i-list manipulation */
char s_fmod; /* super block modified flag */
char s_ronly; /* mounted read-only flag */
time_t s_time; /* last super block update */
/* remainder not maintained by this version of the system */
daddr_t s_tfree; /* total free blocks*/
ino_t s_tinode; /* total free inodes */
short s_m; /* interleave factor */
short s_n; /* " " */
char s_fname[6]; /* file system name */
char s_fpack[6]; /* file system pack name */
};