読者です 読者をやめる 読者になる 読者になる

PCI コンフィグレーション・メカニズム1でPCIにアクセス.

自作OS

前のコードでバグってるところを見つけたので、修正版です.
全てのソースコードgithubにあります.

PCIのバス番号と、デバイス番号からPCIデバイスを探す関数.

static u_int32_t find_pci_data(u_int8_t bus, u_int8_t dev)
{
	u_int32_t data;
	u_int32_t status;
	u_int32_t class;
	u_int32_t header;
	u_int32_t subsystem;

	int i;
	struct pci_configuration_register reg;
	bool b;

	// At first, check function number zero.
	memset(&reg, 0, sizeof(reg));
	reg.bus_num = bus;
	reg.dev_num = dev;

	// Check all function numbers.
	for (i = 0; i < PCI_FUNCTION_MAX; i++) {
		reg.func_num = i;		
		data = read_pci_reg00(&reg);
		if (data != 0xffffffff) {

			class = read_pci_class(&reg);
			header = read_pci_header_type(&reg);
			subsystem = read_pci_sub_system(&reg);
			status = read_pci_command_register(&reg);

			b = store_pci_device_to_list(bus, dev, data, i, class, header, subsystem);
			if (!b) {
				printk("kmalloc failed %s:%s at %d\n", __FILE__, __FUNCTION__, __LINE__);
				while (1);
			}

			// if it's not a multi function, we need to search other function.
			if (i == 0 && !is_multi_function(header))
				return 0;
		}
	}
	
	return 0;
}

read_pci_XXX()はレジスタ番号を設定して,read_pci_data()を呼び出してます.
実際の処理は,こっちがやります.

static inline u_int32_t read_pci_class(struct pci_configuration_register *reg)
{
	reg->reg_num = 0x8;

	return read_pci_data(reg);
}

引数のstruct pci_configuration_registerはこんなやつです.

// This structure represents PCI's CONFIG_ADDRESS register.
struct pci_configuration_register {
	u_int32_t enable_bit;      // 31: enable bit.
	u_int32_t reserved;        // 24-30: reserved.
	u_int32_t bus_num;         // 16-23: bus number.
	u_int32_t dev_num;         // 11-15: device number.
	u_int32_t func_num;        // 8-10: function number.
	u_int32_t reg_num;         // 2-7: regster number.
	u_int32_t bit0;            // 0-1: always 0.
};

read_pci_data()は最初にwrite_pci_config_address()を使って,CONFIG_ADDRESSに必要なデータを書き込んで,次に0xcfcから32bitのデータを読み込みます.
読み終わったら,finish_access_to_config_data()を呼んで,イネーブルビットを0に戻してあげます.

static u_int32_t read_pci_data(struct pci_configuration_register *reg)
{
	u_int32_t data;

	// Enable bit should be 1 before read PCI_DATA.
	reg->enable_bit = 1;

	// write data to CONFIG_ADDRESS.
	write_pci_config_address(reg);
	
	data = inl(CONFIG_DATA_1);

	finish_access_to_config_data(reg);

	return data;
}

write_pci_config_address()はCONFIG_ADDRESSに対してデータ(バス,デバイス,ファンクションとか)を書き込んで,
CONFIG_DATAを読む準備をします.

static inline void write_pci_config_address(const struct pci_configuration_register *reg)
{
	u_int32_t data = 0;

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

	outl(PCI_CONFIG_ADDRESS, data);	
}

store_pci_device_to_list()は見つかったデバイスの情報を連結リストに入れてるだけです.

store_pci_device_to_list(u_int8_t bus, u_int8_t devfn, 
			 u_int32_t data, u_int8_t func, 
			 u_int32_t class, u_int32_t header,
			 u_int32_t subsystem)
{
	struct pci_device_list *p;

	p = kmalloc(sizeof(*p));
	if (!p)
		return false;

	p->data.bus = bus;
	p->data.devfn = devfn;
	p->data.vender = data & 0xffff;
	p->data.devid = (data >> 16) & 0xffff;
	p->data.pg_if = (class >> 8) & 0xff;
	p->data.sub_class = (class >> 16) & 0xff;
	p->data.base_class = (class >> 24) & 0xff;
	p->data.func = func;
	p->data.header_type = ((header >> 16) & 0xff) & 0x7f;
	p->data.multi = (header >> 16) >> 7;
	p->data.sub_vender = subsystem & 0xffff;
	p->data.sub_devid = (subsystem >> 16) & 0xffff;


	p->next = pci_device_head.next;
	pci_device_head.next = p;

	return true;
}

PCIデバイスは今のところ,こんな感じでまとめてます.

// Store PCI device information.
struct pci_device {
	u_int8_t bus;             // bus number.
	u_int8_t devfn;           // device number.
	u_int8_t func;            // function number.
	// 0x0
	u_int16_t vender;         // vender id.
	u_int16_t devid;          // device id.

	// 0x08
	u_int8_t revid;           // revision id.
	u_int8_t pg_if;           // program interface.
	u_int32_t sub_class;	  // sub class.
	u_int32_t base_class;     // base class.

	// 0x0c 
	u_int8_t header_type;     // header type.
	u_int8_t multi;           // multi device.

	// 0x2c
	u_int16_t sub_vender;     // sub system vender id.
	u_int16_t sub_devid;      // sub system device id.
};

struct pci_device_list {
	struct pci_device data;
	struct pci_device_list *next;
};

kvm上のLinuxPCIのバス0:デバイス番号1:ファンクション番号1のデータを見たときの絵がこれです.

bochsで動かした状態がこちらです.

kvmのほうで説明すると(x86なのでリトルエンディアンです),
オフセット0の最初の2バイトがベンダーID,次の2バイトがデバイスIDになります.
ベンダーID:8086,デバイスID:7010です.
次にちょっと飛んで,8〜11バイト目がクラスコードなどです.
クラスはCONFIG_DATAのオフセット0x04のところにあって、こんな構造になってます.

31-24 23-16 18-8 7-0
ベースクラス サブクラス プログラムインターフェース リビジョン

kvmのほうで「00 80 01 01」となってるところです.
これを上の表に当てはめると,

31-24 23-16 15-8 7-0
ベースクラス サブクラス プログラムインターフェース リビジョン
01 01 80 0

こんなような感じになって,ベースクラス:0x01,サブクラス:0x01ってのがわかります.

bochsの方で見ると,「Found Device〜」の2行めが,上で説明したデバイスになってます.

あとは,CONFIG_DATAのオフセット0x0cの23bit目が1ならマルチファンクションデバイスなので,
1〜7のファンクション番号を調べるとかありますが,大体こんな感じで,PCIを利用できます.