All Articles

xv6OSを真面目に読みこんでカーネルを完全に理解する -シリアルポート 編-

はじめての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つのデバイス間でシリアルデータを交換するためのプロトコルを指します。

参考:UARTの概要 | Rohde & Schwarz

参考:Universal asynchronous receiver-transmitter - Wikipedia

近年では主として使われていないようですが、UARTはシリアルポートによる通信で使用されていたプロトコルみたいです。

uartinit関数では、いわゆるCOMポートをセットアップしているようですね。

通信にシリアルポートを使用する

OSが通信にシリアルポートを使用する場合は、初めにシリアルポートを初期化する必要があります。

参考:Serial Ports - OSDev Wiki

ソースコードを順に見ていきます。

#define COM1    0x3f8

// Turn off the FIFO
outb(COM1+2, 0);

COM10x3f8として定義されています。

これは、固定されたCOM1ポートのIOポートのアドレスです。

各COMポートのレジスタについては、IOポートアドレスからのオフセットでアクセスできます。

COM1+2Interrupt IdentificationFIFO 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+3Line control registerに該当します。

これは、セットされたbitに応じて通信パラメータを変更します。

2022/02/image-54.png

参考画像:Serial UART, an in depth tutorial - Lammert Bies

outb(COM1+3, 0x80);は8bit目をセットしてDLABを有効化しています。

これによって、次のCOM1+0COM1+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関数です。

参考書籍