PCIのメモ.

2010/02/10追記:下の日記が,ちゃんと動く版です〜
http://d.hatena.ne.jp/masami256/20100208/1265644385

最近だらだらと自作カーネルを書いているんですが,
PCIについて,OSDevとかこちらを読んで,現状分かったことを忘れないうちにメモっときます.

PCIでどんなデバイスがあるかを調べるには,CONFIG_ADDRESS(アドレス:0x0cf8)とCONFIG_DATA(アドレス:0xcfc)の2個を使って,
検索をするようです.

CONFIG_ADDRESSは32bitで,各ビットの意味はこんな感じです.
31 : イネーブルビット
30 - 24: 予約済み
23 - 16: バス番号
15 - 11: デバイス番号
10 - 8 : 機能番号
7 - 2 : レジスタのアクセス
1 - 0 : 0で固定
CONFIG_ADDRESSはデータを読み込むというよりも,書き込みがメインです.
CONFIG_ADDRESSはどんなデータをCONFIG_DATAから読み出したいかを指定するためのモノという感じでしょうか.

CONFIG_DATAからデータを読み込む前に,31bit目のイネーブルビットを1にしてあげる必要があります.

PCIデバイスはバス番号(0〜255)とデバイス番号(0〜31)の組み合わせで探していけます.
と,いうことで,こんな感じでfor文を回してみました.

void find_pci_device(void)
{
	int bus, dev;

	printk("start find_pci_device\n");

	for (bus = 0; bus < PCI_BUS_MAX; bus++) {
		for (dev = 0; dev < PCI_DEVICE_MAX; dev++) {
			read_pci_data(bus, dev);
		}
	}
}

read_pci_data()は以下のコードです.

static u_int32_t read_pci_data(u_int8_t bus, u_int8_t dev)
{
	u_int32_t data;
	struct pci_configuration_register reg;

	memset(&reg, 0, sizeof(reg));
	reg.bus_num = bus;
	reg.dev_num = dev;
	reg.enable_bit = 1;

struct pci_configuration_registerはCONFIG_ADDRESSの構造を,cの構造体にしただけです.
バス番号とデバイス番号,それにイネーブルビットの値を設定します.

	// write data to CONFIG_ADDRESS.
	write_pci_config_address(&reg);

そうしたら,CONFIG_ADDRESSにデータを書き込みます.

CONFIG_ADDRESSにデータを書き込んでるのは以下のコードになります.

static void write_pci_config_address(struct pci_configuration_register *reg)
{
	u_int32_t data;

	data = (reg->enable_bit << 31) |
		(reg->reserved << 24) | 
		(reg->bus_num << 16) | 
		(reg->dev_num << 11) | 
		(reg->func_num << 8) |
		(reg->reg_num << 2) |
		reg->bit0;

	outl(PCI_CONFIG_ADDRESS, data);	
}

CONFIG_ADDRESSに書き込むデータは32bitなので,構造体のデータを1つの32bitのデータにまとめてたうえで書き込みを行います.
outl()はアセンブラのoutl命令を実行する関数です.

	data = inl(CONFIG_DATA_1);

	if (data != 0xffffffff) {

inl()もアセンブラのinl命令を実行する関数です.
0xcfcを読み込んだ結果が0xffffffffの場合は,デバイスが存在しないということになります.
CONFIG_ADDRESSでレジスタ番号を0にしてCONFIG_DATAを読み込むと,32bitの上位16bitがベンダーID,下位16bitがデバイス番号になります.

		u_int32_t class;

		reg.enable_bit = 0;
		write_pci_config_address(&reg);

この処理が必要なのか良く分かってませんが(´・ω`・)エッ? イネーブルビットを0にしてます.

		reg.enable_bit = 1;
		reg.reg_num = 0x8;

		write_pci_config_address(&reg);
		class = inl(CONFIG_DATA_1);
		printk("Found Device: Vender is 0x%x : Device is 0x%x : Class is 0x%x\n", data & 0xffff, (data >> 16) & 0xffff, class);

こんどは,レジスタ番号に0x08を指定(デバイス番号,バス番号は変更なし)して,再度CONFIG_ADDRESSに書き込みを行います.
詳細な意味は,OS-Wikiを参照してもらうとして・・・
デバイスのクラスコードを読み出します.

0x08で読み出した場合,32bit中の下位8bitがリビジョンID,上位24bitがクラスコードになってるんですが,
今のところは一緒くたにして表示してます.

	}

	reg.enable_bit = 0;
	write_pci_config_address(&reg);


	return 0;
}

最後にイネーブルビットを0にして終了です.

このコードをEeePCで動かしたら,こんな感じになりました.