第4回 APIC Timer

3回で飽きたと思われかねないこのペース。ええ、大丈夫です。大丈夫です。ご安心ください。まだやる気はありまする。まあ、二週間に一回くらいは書くペースで…、って、それだと、一年ぐらい続ける計算になるのだった…まあ、なんとかします…

というわけで、今度こそ、APICの番です。いやーここまで辿り着くまで大変でしたね、おもにやる気的な意味でっ!!嘘、嘘です。ご安心ください。まだやる気はあります…ちょっとだけ…


まずは、今回から、想定読者のハードルが高いというのがあるなー。
と、いうのは、今回の想定読者としては、前回書いたSerialで読み書きできるPCを用意してて、かつ、x86の割り込みの仕組みを理解している人、というのがあるなー。
多分想定読者に該当する人は日本には100人単位ぐらいしかおらなくて、かつ、これを読んでる人でAndをとると、ひとりも残らないという気がするなー。

でも気にしないで続きを書くアルよ。あのOS自作本だって、「プログラマ初心者向け」を掲げながら、5日目にして、GDTとIDTの説明をするという勇気ある構成になってるのなー。適当なwebページがIDTの説明を前提知識にするのだって許されるはずですの。


おぉっと、あと、シリアルの説明を飛ばしていた。しかし、シリアルの説明は地味で書く気が起きないのでスキップするのだった。



まずは、APICとは何かをおさらいするなー。APICは、Advanced Programmable Interrupt Controlerの略で、よーするに、アドバンスドでプログラマブルな割り込みコントローラのことなー。意味不明なー。


Intelは、スグに「Advanced」とか「Smart」とか「Turbo」とかいうプレフィクスを付けたがるけど、基本的にあんまり意味は無いなー。
あと、現代のプログラマが出会うInterrupt Controlerはまず間違いなくProgrammableなので、「Programmable」という単語にもあまり意味は無いなー。


つまり、「Advanced」「Programmable」のことは忘れて、APICというのは「割り込みコントローラ」という理解で全く問題無いアルよー。
あと、APICには、Local APICと、I/O APICがあるが、以下、「APIC」というと「Local APIC」のことを指す、としておくよ!忘れないでね!


さて、APICに割り込みを発生させるのは以下なー

  • Locally connected I/O devices : 直接プロセッサのINTピンに繋がってるデバイスが出す割り込み
  • Externally connected I/O devices : I/O APIC経由で飛んでくる割り込み
  • Inter-processor interrupts (IPIs) : プロセッサ間割り込み
  • APIC timer generated interrupts : APICタイマ割り込み
  • Performance monitoring counter interrupts : パフォーマンスカウンタが出す割り込み
  • Thermal Sensor interrupts : 温度計
  • APIC internal error interrupts : 謎

で、今回は一番簡単なAPIC タイマについて説明する回なのなー。

おっと、簡単とゆっても、APICタイマというのは、x86割り込みベクタと、APICの設定の上に成り立っておる存在であり、x86割り込みベクタの説明無しに説明できるものではないのなー。

(赤い部分が設定しないといけない部分)

しかし!x86割り込みベクタの説明はできる自信が無いので、ここはスキップ!!一応このへんは、「はじめて読む486」というよくできた本があるのでそちらを参考にすべきだろ。


ともかく、以下では、君が「idt descriptorとidt entryを適切に設定後」、という文の意味を理解できてると仮定して進めていくなー。


まずは、APICのサポートについて。プロセッサがAPICをサポートするかどうかは、EAX=1 で CPUIDを実行後、EDXの9bit目を見るとわかるとのことです。

次に、APICを有効にする。これは、SVR(Spurious-Interrupt Vector Register)のbit 8を立てればよいとのことです。詳細は、「9.4.3 Enabling or Disabling the Local APIC」あたり。


で、タイマをセットする。

セットする値は、以下5つです

  • クロックをいくらで割るか(分周をいくらにするか)
  • 今のカウンタ値をいくらにするか
  • カウンタリセット後の値をいくらにするか
  • どの割り込みベクタに割り込み入れるか
  • ワンショットと周期動作(Periodic)のどっちのモードを使うか

値を決めたら設定していくんだ!


まず、分周いくらにするかだなー。ちなみに割る前のクロックはバスクロックだそうです。ここで!バスクロックのとりかたがわからないので、分周いくらに設定するかどうか以前の問題なのだった!
とりあえず初期値は1/2なので、そのままにしておくとよいでしょう。


次にカウンタの設定です!ほんとはここで「コンピュータにおいてタイマとはどういう動きをするか」という説明が入るとよいのですが、時間という相対的概念の都合により、カットされます。

(まともに説明するとしたらこんな感じの図が入るいめ〜じ)

で、設定方法だけ書くと、リセット後の値は、「initial count」、現在値は、「current count」レジスタに書き込めば設定できます。


OK、以上みっつの値を設定後、LVT Timer レジスタに書きこみます。LVT Timerレジスタには、「タイマのモード」と「割り込みベクタ」、そして、「Timer割り込みの有効/無効」が設定できます。


設定したらGo!その直後から、APIC タイマ割り込みが入ることが確認できることでしょう!


そんで、タイマ割り込みを処理したあとは、EOIレジスタに任意の値を書き込んで、割り込みをつつがなく終了させます。(ほんとはここで「割り込みコントローラ」とはなんぞや?という説明があるとよい!よいんだがっ…!)


OK!できたでしょうか?とてもこの説明で理解できる人がいるとは思えないアルね!


以上を確認するためにつくったプログラム
起動すると、シリアルからコマンドが打てるようになります。
"ltimer" コマンドを打って、適当な値を入れると、その値でタイマが動くようになります。
まあ、読んで理解できるとは思えないがー。何かに行き詰まったときにでも参考になるかもしれんよ…


それでは、APICの動作も確認できたので、次回は(多分)マルチプロセッサをやるでしょう…多分。

必要なメモリマップ

以上の説明で出てきたレジスタのアドレスは以下のん。

  • SVR : 0xFEE000F0
  • DIVIDE : 0xFEE003E0
  • INITIAL COUNT : 0xFEE00380
  • CURRENT COUNT : 0xFEE00390
  • LVT TIMER : 0xFEE00320
  • EOI : 0xFEE000B0

全部メモリマップドです。


それでは検討(なんの?)を祈る。