OS起動編⑨ uartinit() (Xv6を読む~OSコードリーディング~)

前回
jupiteroak.hatenablog.com
トップページ
jupiteroak.hatenablog.com




main.c
https://github.com/mit-pdos/xv6-public/blob/master/main.c#L28

int
main(void)
{
  ...
  uartinit();      // serial port
  ...

uart.c
https://github.com/mit-pdos/xv6-public/blob/master/uart.c#L19

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);
}

uartinitでは、シリアル通信で使用する8250UARTの初期設定を行なっています。


処理の内容

FIFOバッファを無効化する

outb(COM1+2, 0);

out命令(ポート出力命令)でI/OポートアドレスCOM1+2→0x3f8+2を指定することにより、UART が持つFIFO Control Register(8bit幅)への書き込みを行なっています。
設定値は0x00→0b 0000 0000です。bit0に0をセットすることでFIFOバッファを無効化しています(8250UARTにFIFOバッファは存在しないので、挙動に支障が出ない0の値で初期化している思われます)。

UARTのボーレートを設定する

outb(COM1+3, 0x80); 

out命令(ポート出力命令)でポートアドレス COM1+3(0x3f8+3)を指定することにより、UART が持つLine Control Register(8bit幅)への書き込みを行なっています。
設定値は0x80→0b 1000 0000です。bit7のDLAB(Divisor Latch Access Bit)に1をセットすることで、次の命令からDLL(Divisor Latch LSB register)とDLM(Divisor Latch MSB register)にアクセスできるようにしています。

outb(COM1+0, 115200/9600); 
outb(COM1+1, 0);

1つ目のout命令(ポート出力命令)でポートアドレス COM1+0→0x3f8+0を指定することにより、UART が持つDLL(Divisor Latch LSB register 8bit幅)への書き込みを行ない、2つ目のout命令(ポート出力命令)でポートアドレス COM+1→0x3f8+1を指定することにより、UART が持つDLM(Divisor Latch MSB register 8bit幅)への書き込みを行なっています。
DLL(Divisor Latch LSB register)とDLM(Divisor Latch MSB register)はUARTのボーレート(通信速度)を調整するために使用されるレジスタです。UARTのボーレート(通信速度)の最大値115200 bpsをDLL(Divisor Latch LSB register)とDLMによって指定された除数で割ることで、ボーレート(通信速度)を調整します。DLL(Divisor Latch LSB register)には除数の下位位8bitを設定し、DLM(Divisor Latch MSB register)には除数の下位8bitを設定します。
ボーレート(通信速度)を9600bpsに設定する場合、指定される除数は0x000c(12 = 115200/9600)になるので、DLL(Divisor Latch LSB register)には0x0c(115200/9600)が設定され、DLM(Divisor Latch MSB register)には0x00が設定されます。

シリアル通信のプロトコルを設定する

outb(COM1+3, 0x03);

out命令(ポート出力命令)でポートアドレスCOM1+3(0x3f8+3)を指定することにより、UART が持つLine Control Register(8bit幅)への書き込みを行なっています。設定値は0x03→0b 0000 0011です。
bit7のDLAB(Divisor Latch Access Bit)を0にセットしてDLL(Divisor Latch LSB register)とDLM(Divisor Latch MSB register)にアクセスできないようにしています。
bit5-0はシリアル通信のプロトコル設定に関与しているフラグです。
bit5-4-3に000をセットすることにより、シリアル通信で送信されるデータにパリティビットが含まれないようにしています。
bit2に0をセットすることにより、シリアル通信で送信されるデータのストップビット値を1にしています。
bit1-0に11をセットすることにより、シリアル通信で送信されるデータの単位を8bitに指定しています。

ハードウェアフロー制御を設定する

outb(COM1+4, 0);

out命令(ポート出力命令)でポートアドレス COM1+4→0x3f8+4を指定することにより、UART が持つModem Control Register(8bit幅)への書き込みを行なっています。Modem Control Registerはハードウェアフロー制御に関与しているレジスタです。
設定値は0x00→0b 0000 0000です。

割り込みを発生させるタイミングを設定する

outb(COM1+1, 0x01);

out命令(ポート出力命令)でポートアドレス COM1+1→0x3f8+1を指定することにより、UART が持つInterrupt Enable Registerへの書き込みを行なっています。設定値は0x01→0b 0000 0000です。
bit0に1をセットすることで、UARTからデータが回収可能な時に割り込みを発生させるようにしています。

シリアルポートが存在することを確認する

if(inb(COM1+5) == 0xFF) return;
     uart = 1;

in命令(ポート入力命令)でポートアドレス COM1+5→0x3f8+5を指定することにより、UART が持つLine Status Register(8bit幅)の読み取りを行なっています。
Line Status Registerは、主にUART内で生じたエラー情報を示すために使用されています。
inb(COM1+5) == 0xFF が真となる場合→Line Status Registerの全てのbitが1にセットされている場合は、シリアルポートが存在しないとみなされ、処理を終了します。
inb(COM1+5) == 0xFF が偽となる場合は、uartに1を代入し、後の処理でUARTの有無を示すフラグとして利用します。

UART内に残存しているデータを処理しておく

 inb(COM1+2);
 inb(COM1+0);


inb(COM1+2);
in命令(ポート入力命令)でポートアドレス COM1+2→0x3f8+2)を指定することにより、UART が持つInterrupt Identification Register(8bit幅)の読み取りを行なっています。このレジスタは、割り込みを発生させたUARTを識別したり、割り込みの種類を識別するために使用されます。


inb(COM1+0);
in命令(ポート入力命令)でポートアドレス COM1+0→0x3f8+0を指定することにより、UART が持つReceiver Buffer Register(8bit幅)の読み取りを行なっています。このレジスタは、シリアル通信において受信用のバッファとして使用されます。

UARTからI/OAPICが持つIRQピンへの割り込みについての設定を行う

ioapicenable(IRQ_COM1, 0);

ioapicenable関数を呼び出して、ピン番号がIRQ_COM1→4であるIRQピン(I/OAPICが持つIRQピン)に送信される割り込みを有効化しています。
ピン番号IRQ_COM1→4のIRQピンにはUARTからの割り込みが送信されるので、その割り込みのベクタ番号は36(T_IRQ0 32 + IRQ_COM1)となります。

シリアル通信を行う

for(p="xv6...\n"; *p; p++) uartputc(*p);

for文とuartputc関数を使って、文字列"xv6...\n"をシリアル通信で送信します。
p="xv6...\n"で、文字列リテラル"xv6...\n"の先頭アドレスを代入しています。
間接参照演算子*とchar型へのポインタpを使って文字列"xv6...\n"の先頭1文字分(x)のデータを参照し、その文字データをuartputc関数に渡してシリアル通信で送信しています。
uartputc関数の処理(シリアル通信の送信)が終わったらpをインクリメントし、次の1文字分(v)のデータを参照します。
この繰り返しによって、*pの値が\0'(NULL)になった(文字列の終端まできた)時点で、for分内の処理が終了します。




次回
jupiteroak.hatenablog.com