A20 gateとkeyboard controller(xv6を例にして)

はじめに

xv6でのA20 gateをenableしている過程をソースコードレベルで詳しく追っていく。A20 gateの概要に関しては、reference[8] が詳しいので省略。keyboard controllerについては後述する。

Note) 大目標としてはkeyboard入力の際に、どのような過程をたどって、コンソールに文字が出力されるのかを見たい。実はA20 gateをONにするときに、IRQ1を暗にenableにしているっぽい(ただし、boot時はcliなので、keyboard入力によるinterrupt(IRQ1)が無視される。) なので、本記事はいつIRQ 1をenableしているのかの記事にもなっている。

reference

概要

コード

本記事で説明する部分はここだけだ。簡単ではあるが、シンプルではない。

.code16                       # Assemble for 16-bit mode
.globl start
start:
  cli                         # BIOS enabled interrupts; disable

  # Zero data segment registers DS, ES, and SS.
  # <skip>

  # Physical address line A20 is tied to zero so that the first PCs 
  # with 2 MB would run software that assumed 1 MB.  Undo that.
seta20.1:
  inb     $0x64,%al               # Wait for not busy
  testb   $0x2,%al
  jnz     seta20.1

  movb    $0xd1,%al               # 0xd1 -> port 0x64
  outb    %al,$0x64

seta20.2:
  inb     $0x64,%al               # Wait for not busy
  testb   $0x2,%al
  jnz     seta20.2

  movb    $0xdf,%al               # 0xdf -> port 0x60
  outb    %al,$0x60

説明

まず、cliでinterruptをignoreする1。これは、eflags.IF bitをoffにすることでProcessor側が外部から来たinterruptを(blockするのではなく、)無視する2という命令だ。

その後、segment registersを初期化して、A20 lineのenableに移る。A20のenableはkeyboard controllerがkeyboard unit(キーボード本体)にA20 lineをenableする様に設定することで実現できる。 具体的には、keyboard controller(8042)のOutput Portのpin 1をhigh(1)に設定する。(movb $0xdf,%al, outb %al,$0x60に当たる部分)

上記のことをソースコードレベルで追うために、keyboard controllerについて軽く触れた後、上記のinstructionの意味について述べたいと思う。

Note) なぜ、A20の操作にkeyboard controllerが関係しているのかと言うと、ただ単に大昔keyboard controllerに空きポートが合ったため、そこにA20 gateのportと設置したためらしい。 reference[8] も参照のこと。

keyboard controllerについて

もともと(1980年代くらい?)、keyboard controllerは8042という単体のシステムデバイスだったが、だいぶ前からlegacy device全般が一つのチップセット(自分のmacbookの実機ではsouthbridgeであるPIIX43, reference[4])としてまとめられている。8042相当の機能がsouthbridgeに含まれているというわけだ。(最も、PIIX4で外部から観測されるのは、I/O port 0x60 or 0x64を読み書きした際にX-BUS INTERFACEのKBCCS#(KEYBOARD CONTROLLER CHIP SELECT) signalがassertされるところだけなので、ほとんどブラックボックスになっている)

f:id:knknkn11626:20190609143139p:plain
CPU and its peripheral. See Reference[3]

本記事ではこれ以後、PIIX4中のkeyboard controllerというよりは8042(keyboard controller)単体と、CPU(Processor)、keyboard(上図でKBDと書いてあるやつ, keyboard unitと呼ぶこともある。)との関係を見ていきたい。keyboard (unit)の中にもマイコンが内蔵されていて、双方向にやり取りできるということは頭の片隅においた方が良いかもしれない。

また、keyboard controller(こちらはchipsetの中)とkeyboard unit(つまりkeyboard本体)は異なることに注意。

f:id:knknkn11626:20190609151304j:plain
keyboard unit(8048, 6805など)とkeyboard controller(8042)の関係 Reference[5]より

Note) レガシーなキーボードインタフェースとしては、内蔵型の他に、PS/2タイプのものがある。以下の写真のようなやつ。今はusb or wifi接続がほとんどだと思うので、店頭では見かけないと思う(自分はamazonで買った)

f:id:knknkn11626:20190609175316j:plain
PS/2 keyboard

A20 lineのenableのフロー

全てCPU <-> 8042(port 0x60, port 0x64)とのやり取り(in, out instructionを使用)でA20 lineのenableが行われている。 本小節では、Reference[2]のkeyboard controllerの詳細を参考にした。

なお、input, outputという用語はすべてCPU(Processor)からみた方向であることに注意する:

  1. status registerを読んで、IBF(Input Buffer Full) flagがONでないかどうかを確かめる(inb $0x64,%altestb $0x2,%al)
  2. ONならば1に戻って再度チェック。OFFであれば、3にすすむ(jnz seta20.1)
  3. 0xD1 (Write output port)のcommandをkeyboard controllerに投げる。これでoutput portに所要のbitをON/OFFする準備ができた(outb %al,$0x64 where %al = $0xd1)
  4. status registerを読んで、IBF(Input Buffer Full) flagがONでないかどうかを確かめる(inb $0x64,%altestb $0x2,%al)
  5. ONならば4に戻って再度チェック。OFFであれば、6にすすむ(jnz seta20.2)
  6. output portに所要の値を書き込む。(outb %al,$0x60 where %al = $0xdf)

Input Bufferというのはkeyboard (unit)にて文字を打ち込んだ際にkeyboard controllerに向けて送出されるscan codeのこと。(正確には、AT互換keyboardの場合、scan code setが2なのだが、keyboard controller側でscan code setが1のものにconvertされて、input bufferにそのデータが格納される。ここはkeyboard入力の記事で詳しめに説明したい)

keyboard controllerに対する操作は、I/O port 0x60, 0x64を介して行える:

Port Read/Write Function instruction
0x60 Read Read Input Buffer inb $0x60, %al
0x60 Write Write Output Buffer outb %al,$0x60
0x64 Read Read Status Register inb $0x64, %al
0x64 Write Send Command outb %al, $0x64

これより、1,4番のstatus registerの読み込みはinb $0x64,%alに当てはまる事がわかる。status registerの各bitの意味はReference[6]の8042の仕様書を見ればよい。(簡易的にはbochs(x86エミュレータ)のport I/O 一覧, reference[7]を見ればよい)

これによると、input buffer full flagというものがbit1の位置にあるので、これを1,4番で取り出して、2,5番でチェックしている。

Send Commandの部分は少しむずかしい。これは命令を投げる(write 0x64) -> parameterを書き込む(write 0x60)という2段階構造になっているからだ。上記の3番でI/O portの0x64に0xd1を書き込んでいるが、reference[2]によれば

0xD1 (Write output port) - Write parameter to output port (see Output Port definition.)

ということで、8042のoutput portに値を書き込むのを要請している。

Note) port 0x64へのwriteは"Writing to port 0x64 doesn't write to any specific register, but sends a command for the 8042 to interpret."とあるとおり、どのregisterにも書き込むことはない。ただ命令として伝えるだけ。

その後、input buffer full flagのチェック後にparameter(0xdf)をI/O port 0x60に書き込む。output portは以下のようになっている:

f:id:knknkn11626:20190609163050p:plain
Reference[2]のAT-compatible modeの表より

bit0(System Reset): 1(Normal)
bit1(Gate A20): 1(enable)
bit2,3(undefined): 1
bit4(Input Buffer Full): 1(New input can be read from port 0x60: When set, IRQ 1 is generated when data is available in the input buffer.)
bit5(Output Buffer Empty): 0(not empty)
bit6(Keyboard Clock): 1(Pull Clock low)
bit7(keyboard data): 1(Pull Data low) 

bit1がONになっているので、A20 lineをenableにしていることになる。bit7:6のkeyboard data/clockについては、8042 -> keyboardへの出力を強制的にOFFしているようだ。(keyboard (unit)の中にもマイコンが内蔵されていて、双方向にやり取りできるということを思い出そう。)

bit4を1にすることで、IRQ 1の送出を許可しているようだ。(IRQの番号1はlegacy deviceでは固定になっていることが多い)

Note) なぜ、lowにすることでoffにできるのかは自明ではないので、自分の理解できた範囲で述べたいと思う。

f:id:knknkn11626:20190609165828j:plain
Reference[5]よりkeyboard unitへのデータ送信

上図で意味するところは、CLKがH->Lになると言うのが合図になって、dataがその後送信されるということを意味する。

keyboard clockをPull Clock lowにすると、(オープンコレクタ出力の仕組みにより)、トランジスタをOFF -> プルアップ抵抗によりclkの電圧が常にHighになる。clkがいつまで経ってもlowにならない限り、dataが送られているのが認識できない様になるというわけだ。ということで、keyboard unitへのデータ送信ができなくなる。

Note) ところで、なぜ0x64, 0x60に書き込む前にinput buffer full(IBF) flagをチェックするのだろうか? というのも今回はinput bufferをreadしているわけではない(つまりinb 0x60 %alを実行していない)ので一見すると奇妙である。むしろoutput buffer bull(OBF: bit position0)を確認すべきなのでは? これについては私もよく分からなかったのだが、https://www.win.tue.nl/~aeb/linux/kbd/A20.htmlClassical A20 control, via the keyboard controllerに沿っているようだ。


  1. https://github.com/mit-pdos/xv6-public/blob/b818915f793cd20c5d1e24f668534a9d690f3cc8/proc.c#L331 あたりで再びenableにしている。

  2. Intel SDM vol.2には Clearing the IF flag causes the processor to ignore maskable external interrupts.とある。

  3. 実機のsouthbridge(northbridgeもだけど)の確認方法はPCIの知識が必要だ。簡単に言うと、CONFIG_ADDRESS(port I/O 0x0CF8)に所要の値を入れ、CONFIG_DATA(port I/O 0x0CFC)がPIIX4のvendor ID(8086h)とDevice ID(7110h: function0)にマッチした場合、このデバイスが存在する事がわかる。次回アクセスする場合は、このCONFIG_ADDRESSを使用すればよい。 https://wiki.osdev.org/PCI#Enumerating_PCI_Buses にある通り総当たりに近いやり方で探す。ここあたりはPCIに特化した記事を別途書きたいと思う。