実機環境を作る シリアル編

自作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互換なので、次のようにして使うと使える。

  1. FCR(0x3fa) に 3 を書いてfifo を enable (しなくてもよいけど…)
  2. LCR(0x3fb) に 0x80 を入れて DLAB = 1 にする(これで周波数のdividerが設定できる)
  3. DLL(0x3f8), DLM(0x3f9) に divier の値を入れる。現代では 115200 以外を選ぶ理由は無いと思うので、DLL=1, DLM=0 で良い
  4. LCR(0x3fb) の DLAB = 0にしてdividerの設定終了
  5. 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 ブート編に続く(かもしれない)