第6回 ACPI の "C" その1

さて、前回は予告どおりやってしまったので、今回は予告を無視して、ACPIをやるのなー。(まちがえないようにっ!!!APICではないっ!!!!)
ACPI は "Advanced Configuration and Power Interface"の略で、現在ver.3.0…とか言ってる間にver.4.0が出ていた。
http://www.acpi.info/


ACPIは現代のPCを語るうえで見ないわけにはいかない項目であるな。WindowsのHALがACPIか否かで分かれてることからもわかるように、
いや、いまいちよくわからないのでこの文は忘れてもらって構わない…


ACPIは現代のPCの仕様の中でも、最も複雑なものに入るなー。というのは、ハードウェアとソフトウェアの間のインターフェースを決めてるからだっ。ハードウェアの仕様は、基本的にそんなに大きくならないし、ソフトウェアの仕様なんて適当に守ってればよいのだが、ACPIの仕様は、ハードウェアのインターフェースぐらいきっちりしたのが、720ページにもわたって書いてあるなー。途中で挫折しがちである。
まあ、みなさんが挫折しない説明になるように努力するので頑張ってね。


さて、ACPIつーと電源管理のイメージがあるがー、"Configuration and Power"という名前からもわかるように、実際には、「設定と電源管理」の両方の機能があるのであるので。(そして例のごとく、Intel の言う"Advanced"に意味は無い)
今日は、"Power"ではなくて、"Configuration"の話をする予定ですね。というのは、HPETとI/O APICのアドレスをハードコーディングするのが許せなかったからだ…


まずはACPISpec40.pdfを開くべしべし。(Intelは資料をwebに公開するので立派だと思う)
色々と書いてあるが、1-4章は無視だっ。1-3章は、http://ja.wikipedia.org/wiki/Advanced_Configuration_and_Power_Interface ここに書いてある情報ぐらいしか書いていないっ。4章は、またあとで。5章を開くんだっ!


とりあえず、ACPIの仕様は膨大すぎるので、目標を立てよう。目標は、「HPETとI/O APICのアドレスを取得する」だ!


ソフトウェア的に気にすべきことは、5章から書いてあるなー。まずは、「RSD PTR」を探す。「RSD PTR」っつーのは、ACPIの情報の一番根っこである。必要な情報は全てここから始まるのだった…


RSD PTRの探しかたは、5.2.5.1 に書いてあるのぅ…

  • Extended BIOS Data Area(EBDA)の最初の1KBのうちのどっか。
  • 0xE0000h 〜 0xFFFFFh のどっか。

結構適当…ちなみに、EBDAは40:0ehらへんに書いてあるアドレスを読めばわかるとのこと。
この中から、

  • 先頭8byte が "RSD PTR "(3byte目と7byte目は空白0x20)
  • 先頭20byteを足し合わせると0x00になる(8byte目にチェックサムが入ってるので)

という領域を探すのら。


んで、このRSD PTRの中の、16byte目に32bitの「RsdtAddress」というのが書いてあって、これを辿ると、先頭4byteが"RSDT"になってる、「RSDT(Root System Description Table)」というのが見つかるなー。
あと、64bitマシンで、ACPIのデータが32bitを越える領域に置いてある場合は、もういっこ、「XSDT」というのがあるのだが、とりあえず今は忘れておこぅ。(うちが理解していないので)


OK、ACPIはテーブルの名前(というか全体的に名前)を4文字に含めようと努力した結果として、わかりにくくなる部分があるん。今は、「RSDT」にいるよ。「RSD PTR」を辿って、「RSD」にいるよ。


次に、RSDTの中には、いくつかのエントリへのポインタがある。エントリポインタの数は、RSDTの長さ(4byte目)から、エントリ以外の長さ(36byte)を除いて、それを4で割った数だよ。このエントリを見ていこう。


このエントリポインタを辿ったさきは、最初の4byteが名前になってて、これを見ることで、そのエントリの種類がわかるんだなー。ここで、目標にしてるのは、「HPETのアドレス」と「I/O APICのアドレス」で、これは、それぞれ、"HPET"、"APIC"に含まれておる。
HPETのエントリの構造は、APICSpecには含まれてないなー。こちらの構造の仕様は、HPETの仕様に含まれておられます。http://www.intel.com/hardwaredesign/hpetspec_1.pdf(PDF注意)。こちらのp30「3.2.4 ACPI 2.0 HPET Description Table」でございますわ。
これによると、40byte目に、ACPI仕様に含まれる「Generic Address Structure」の構造をした12byteのアドレスが含まれておられる、とのことですな。
Generic Address Structureの構造は、ACPIの「5.2.3.1 Generic Address Structure」のとこにあって、要約すると、つまり、4byte目にアドレスが入っておる。(64bitアドレスの下位32bitとして)

ここに、おそらく、値「0xfed00000」が入ってるはずなん。ちなみに俺の知るかぎり、HPETのアドレスが0xfed00000でないマザーボードはない。(よーするに、この作業は無駄だったということだ)


続けて、無駄その2なー。I/O APIC、これも、何も見なくても、0xfec00000にある、ということはわかっているのだが…だが…、まあ、一応調べておこう。APICに関する情報は、"APIC"シグネチャで始まるエントリに入ってるなー。このエントリは、MADT(Multiple APIC Description Table)と呼ばれてて、MADTの構造は、ACPISpecに含まれていて、「5.2.12 Multiple APIC Description Table (MADT)」に書いてあるのよ。そんで、この44byte目以降、APIC Structureのどっかだ。

めんどいのでコード見ろ。

	uintptr_t apic = find_acpi_description_entry(ACPI_SIG('A','P','I','C')); // "APIC"シグネチャを探す
	int len;
	int off;

	if (!apic) {
		puts("no APIC entry");
		return;
	}
	len = ACPI_R32(apic, 4); // 長さをとってくる(エントリ数はこれ見ないとわからない)

	len -= 44;		/* offset to APIC structure */
	off = 44;

	while (len > 0) {
		enum acpi_apic_type_code code = ACPI_R8(apic, off);
		int str_len = ACPI_R8(apic, off+1);

		switch (code) {
		case ACPI_PROCESSOR_LOCAL_APIC: // local apic 
			puts("local apic");
			printf(" id = %d\n", ACPI_R8(apic, off + 3));
			printf(" flags = %08x\n", ACPI_R32(apic, off + 4));
			break;

		case ACPI_IO_APIC: // io apic
			puts("io apic");
			printf(" id = %d\n", ACPI_R8(apic, off + 2));
			printf(" addr = %08x\n", ACPI_R32(apic, off + 4));
			printf(" global system int base = %08x\n", ACPI_R32(apic, off + 8));
			break;

		case ACPI_INTERRUPT_SOURCE_OVERRIDE:
			puts("source override");
			printf(" bus = %d (maybe 0)\n", ACPI_R8(apic, off+2));
			printf(" source = %d\n", ACPI_R8(apic, off+3));
			printf(" global source int = %d\n", ACPI_R32(apic, off+4));
			printf(" flags = %d\n", ACPI_R16(apic, off+8));
			break;

		case ACPI_NMI_SOURCE:
			puts("nmi source");
			printf(" flags = %x\n", ACPI_R16(apic, off+2));
			printf(" global system int = %d\n", ACPI_R32(apic, off+4));
			break;

		case ACPI_LOCAL_APIC_NMI_STRUCTURE:
			puts("nmi structure");
			printf(" id = %d\n", ACPI_R8(apic, off+2));
			printf(" flags = %x\n", ACPI_R16(apic, off+3));
			printf(" lint = %d\n", ACPI_R8(apic, off+5));
			break;
			
		case ACPI_LOCAL_APIC_ADDRESS_OVERRIDE_STRUCTURE:
			puts("lapic address over");
			break;

		case ACPI_IO_SAPIC:
			puts("io sapic");
			break;

		case ACPI_LOCAL_SAPIC:
			puts("local sapic");
			break;

		case ACPI_PLATFORM_INTERRUPT_SOURCES:
			puts("platform int source");
			break;

		default:
			puts("unknown apic desctiption");
			break;
		}

		off += str_len;
		len -= str_len;
	}

こんな感じでわかるなー(説明になってない)。
APICについては、個人のPCの場合はアドレス決めうちでもよいと思うがー、NUMA組んで複数I/O APICがある、とかになると、真面目にこのエントリを読む必要が出てくるで。

あと、ポイントとして、Local APICごとにエントリがあって、それ見るとLocal APICの数がわかって、プロセッサの数がわかるというのがある。


さあぁ、これで「HPET」と「I/O APIC」のアドレスがわかるようになったよ!調べるまでもなく、0xfed00000と、0xfec00000だね。


「ACPIの"C" その1」は、ここまでである。ACPIといっても、テーブルとポインタを辿っていくだけ、そんなに難しくないことがわかっておられますでしょうか?
しかし、君達は、ACPIの全貌のうちの、ほんの氷山の一角に触れたにすぎない…君はまだ「プログラミング言語AML」に触れてすらいないのだから…(気が向いたときに 「ACPIの"C"その2」へ続く)


上のを確認するプログラム
初期化時にrsdtをとってくる。
acpiapic コマンドでMADTの内容を表示するですよ。