はじめてのOSコードリーディング ~UNIX V6で学ぶカーネルのしくみにインスパイアされてxv6 OSを読んでます。
UNIX V6自体はx86CPUでは動作しないため、基本的には、UNIXv6をX86アーキテクチャで動くようにしたxv6 OSのリポジトリをForkしたkash1064/xv6-public: xv6 OSのソースコードを読んでいくことにしました。
前回はmain
関数で実行されるconsoleinit
関数の動きを確認しました。
今回はuartinit
関数の挙動を追っていきます。
もくじ
uartinit関数
uartinit
関数はuart.c
関数で定義された関数で、シリアルポート関連の初期化を行っています。
#define COM1 0x3f8
static int uart; // is there a uart?
void uartinit(void)
{
char *p;
// Turn off the FIFO
outb(COM1+2, 0);
// 9600 baud, 8 data bits, 1 stop bit, parity off.
outb(COM1+3, 0x80); // Unlock divisor
outb(COM1+0, 115200/9600);
outb(COM1+1, 0);
outb(COM1+3, 0x03); // Lock divisor, 8 data bits.
outb(COM1+4, 0);
outb(COM1+1, 0x01); // Enable receive interrupts.
// If status is 0xFF, no serial port.
if(inb(COM1+5) == 0xFF) return;
uart = 1;
// Acknowledge pre-existing interrupt conditions;
// enable interrupts.
inb(COM1+2);
inb(COM1+0);
ioapicenable(IRQ_COM1, 0);
// Announce that we're here.
for(p="xv6...\n"; *p; p++) uartputc(*p);
}
UARTとは
そもそもUARTとは何かですが、**Universal Asynchronous Receiver/Transmitter**(汎用非同期送受信機)
を指すようです。
UARTは、2つのデバイス間でシリアルデータを交換するためのプロトコルを指します。
参考:Universal asynchronous receiver-transmitter - Wikipedia
近年では主として使われていないようですが、UARTはシリアルポートによる通信で使用されていたプロトコルみたいです。
uartinit
関数では、いわゆるCOMポートをセットアップしているようですね。
通信にシリアルポートを使用する
OSが通信にシリアルポートを使用する場合は、初めにシリアルポートを初期化する必要があります。
ソースコードを順に見ていきます。
#define COM1 0x3f8
// Turn off the FIFO
outb(COM1+2, 0);
COM1
は0x3f8
として定義されています。
これは、固定されたCOM1ポートのIOポートのアドレスです。
各COMポートのレジスタについては、IOポートアドレスからのオフセットでアクセスできます。
COM1+2
はInterrupt Identification
とFIFO control registers
を指します。
ここでは、0をセットすることでUART内のFIFOの動作を無効化しています。
FIFOが無効化されている場合、受信したデータはReceiver buffer register
に受け渡されます。
続いて、以下のコードを見てみます。
// 9600 baud, 8 data bits, 1 stop bit, parity off.
outb(COM1+3, 0x80); // Unlock divisor
outb(COM1+0, 115200/9600);
outb(COM1+1, 0);
outb(COM1+3, 0x03); // Lock divisor, 8 data bits.
outb(COM1+4, 0);
outb(COM1+1, 0x01); // Enable receive interrupts.
COM1+3
はLine control register
に該当します。
これは、セットされたbitに応じて通信パラメータを変更します。
参考画像:Serial UART, an in depth tutorial - Lammert Bies
outb(COM1+3, 0x80);
は8bit目をセットしてDLAB
を有効化しています。
これによって、次のCOM1+0
とCOM1+1
への書き込みアクセスがDivisor latch registers (R/W)
に変化します。
ここに115200/9600
と0をそれぞれ書き込むことでUARTのタイムベースを9,600bpsに設定していることになります。
続くoutb(COM1+3, 0x03);
では、LRCのDLAB
を解除するとともに8 data bits
をセットしています。
その後、Modem control register
に0をセットした後、割込みを有効化してます。
一通りの初期化処理を行った後、Line status register
のチェックを行ってシリアルポートが利用可能であることを確認しています。
// If status is 0xFF, no serial port.
if(inb(COM1+5) == 0xFF) return;
uart = 1;
最後に、RBRとIIRの情報を参照して現在の割込み状態を確認した後、割込みを有効化して終了です。
// Acknowledge pre-existing interrupt conditions;
// enable interrupts.
inb(COM1+2);
inb(COM1+0);
ioapicenable(IRQ_COM1, 0);
まとめ
今回はシリアルポートの初期化処理を追いかけてました。
次回はpinit
関数です。