実機環境を作る シリアル編
「自作OS Advent Calendar 2014」の13日目です。
(bits http://biosbits.org/ について書こうと思っていたが特に何もわからなかったのでやめた)
いつも環境作るの面倒でやめてしまうのでこの機会にまとめておこうと思う。
実機では、ログを取るのも面倒なので、シリアルで繋ぐのがよいと思う。シリアルで繋げば、大量にprintf出しても、ホスト側であとでゆっくり閲覧できる。
あとEFI環境だと、Video BIOS使えないけど、そういう環境でもシリアルなら出せるので助かる場合も多いと思う。
まあシリアル出てるマザーボードもあまり無いので、面倒な感じは否めないが、慣れてくると、マザボ選びの基準にCOMポートを入れるようになるので大丈夫(と、言いつつ今家の中でシリアルケーブル探すの30分ぐらいかかった)。ATXのでかいマザボなら、ポート付いてなくても、COMヘッダだけ出ていることもあって、http://www.amazon.com/dp/B005NKXS1U こういうのを使えば出せたりするのだけど、これのピンアサインの流派がふたつあって、名前も付いてなくてほぼ区別付かないので、リスキーな部分もある。
ホストマシンは、USB-Serial変換(SerialBus - Serial変換)で繋いでおけばよい。まあ、環境によっては、USBシリアルって良くない現象を呼ぶことが多くて、何故かUSBハブ入れたら動きましたとかあるのだけど、相手が電源まともなマザボとかなら多分大丈夫でしょう…
まず、シリアル繋がってるか確認する。(クロスかストレートか目視で判別できなくて不安が多いので)
Linux に minicom とか入れて、115200 8N1 にする。いつも忘れるが、minicom は Ctrl-A O で設定できる。
# cat ~/.minirc.dfl # Machine-generated file - use setup menu in minicom to change parameters. pu port /dev/ttyUSB0
でも良い。
お互いに入力した文字が見えたら良い。ファームウェアの設定で止まってる可能性があるのでファームウェア設定も確認する
PCに付いてるシリアルは、ほぼ確実にns16550互換なので、次のようにして使うと使える。
- FCR(0x3fa) に 3 を書いてfifo を enable (しなくてもよいけど…)
- LCR(0x3fb) に 0x80 を入れて DLAB = 1 にする(これで周波数のdividerが設定できる)
- DLL(0x3f8), DLM(0x3f9) に divier の値を入れる。現代では 115200 以外を選ぶ理由は無いと思うので、DLL=1, DLM=0 で良い
- LCR(0x3fb) の DLAB = 0にしてdividerの設定終了
- LCR(0x3fb) に転送方法指定。ホストの設定とあわせる
これで初期化。
送信キューに空きがあると、LCR の 5bit 目が立つ。送信前にはこれをチェックする。
受信キューにデータがあると、LCR の 1bit 目が立つ。これを監視すればデータの入力をチェックできる。
まあ割り込み入れるとその他色々が必要になるけど、デバッグ出力用ならいらないんじゃないかな…
今テストプログラム書いてみたらデバイスドライバっぽいコードは100byteぐらいだった。MBRに突っこめる大きさだね!
use16 org 7c00h %define BASE 0x3f8 %define RBR BASE + 0 %define THR BASE + 0 %define DLL BASE + 0 %define DLM BASE + 1 %define IER BASE + 1 %define FCR BASE + 2 %define IIR BASE + 2 %define LCR BASE + 3 %define MCR BASE + 4 %define LSR BASE + 5 %define MSR BASE + 6 %define SCR BASE + 7 %macro outbyte 2 ; port, data mov dx, %1 mov al, %2 out dx, al %endmacro %macro outbyteA 1 ; port mov dx, %1 out dx, al %endmacro %macro inbyte 1 ; port mov dx, %1 in al, dx %endmacro start: mov ax, cs mov ds, ax call init_uart mov si, data call out_str mov si, data call out_str_video .loop: call getchar cmp al, 'a' jl .not_lower cmp al, 'z' jg .not_lower sub al, 32 .not_lower: cmp al, 13 jne .out mov al, 10 call putchar mov al, 13 .out: call putchar jmp .loop out_str_video: ; si = ptr to str .loop: lodsb test al, al jz .end mov bl, 7 mov bh, 0, mov ah, 0xe int 10h jmp .loop .end: ret init_uart: outbyte LCR, 0 ; clear DLAB outbyte FCR, 0xc7 ; clear & enable fifo outbyte MCR, 0x3 ; set DTR, RTS outbyte IER, 0 outbyte LCR, 0x80 ; set DLAB outbyte DLL, 1 ; 115200 outbyte DLM, 0 ; 115200 outbyte LCR, 0 ; clear DLAB outbyte LCR, 3 ; 1 stop, 8bit, non par ret out_str: ; si = ptr to str .loop: inbyte LSR test al, 1 << 5 jz .loop lodsb test al, al jz .end outbyteA THR jmp .loop .end: ret getchar: .loop: inbyte LSR test al, 1<<0 jz .loop inbyte RBR ret putchar: ; al = out byte push ax .loop: inbyte LSR test al, 1<<5 jz .loop pop ax outbyteA THR ret data: db 'Hello, World!', 13, 10, 0 times (510 - ($ - $$)) db 0 sig: dw 0xAA55
ただH61N-D2Vとかいうマザボだと動かなかったのでなんか初期化とか忘れてる可能性はある(ので動かなかったらすいません)。Linuxからだと動いてたけど。
あとbochs で動作確認するときは、適当なxterm開いて、tty コマンドで ptsの番号確認して、bochsrc に
com1: enabled=1, mode=term, dev="/dev/pts/8"
と書く。
次回、TFTP ブート編に続く(かもしれない)