xv6の力を借りてPCIデバイスの情報を取得しよう
コンピュータには周辺機器とやり取りするためにPCIという仕組みがあります。
OSはこの仕組みでデバイスの初期化や設定をしてusbやネットワークデバイスなどを利用できるように環境を整備します。
PCIについての情報は以下のページに載っています。
https://www.intel.com/content/dam/www/programmable/us/en/pdfs/literature/ug/ug_a10_pcie_avmm.pdf
https://wiki.osdev.org/PCI
今回はxv6に処理を追加してqemuで実行します。
qemuがエミュレートするPCIデバイスの情報を取得するということです。
xv6の用意
ますはxv6を手に入れます。
https://github.com/mit-pdos/xv6-public
今更言う必要はないと思いますが、xv6はunixv6をx86アーキテクチャ用に書き直したものです。
xv6への処理の追加
PCIへはout命令とin命令を使ってアクセスします。
使うI/OPortは0xcf8と0xcfcです。
Bus Number,Device Number,Function Number, Offsetを指定してポート0xcf8に書き込み、
in命令でポート0xcfcからデータを取得します。
どちらも32bitのデータを扱います。
これらをソースコードにするとこのようになりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
uint readpcidata(uint bus, uint device, uint func, uint offset) { uint address = (uint)( (bus << 16) | (device << 11) | (func << 8) | offset | (uint)0x80000000 ); outl(0xCF8, address); return inl(0xcfc); } |
この関数自体はどのファイルに追加しても問題ないと思いますが、main.cにでも追加しておきましょうか。
xv6には32bitのデータをやり取りするためのout命令とin命令の記述が見つからなかったのでこれも実装しておきました。
追加したファイルはx86.hです
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
static inline int inl(ushort port) { int data; asm volatile("in %1,%0" : "=a" (data) : "d" (port)); return data; } static inline void outl(ushort port, int data) { asm volatile("out %0,%1" : : "a" (data), "d" (port)); } |
これらの関数を呼び出してpciデバイスを出力する処理です。
この関数をmain.cのmain関数内などで呼び出します。
とりあえず重要そうな情報を取得して出力しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
void lsallpcidevice() { for (uint b=0; b<255; b++) { for (uint d=0; d<32; d++) { uint data = readpcidata(b, d, 0, 0); if (data != 0xffffffff) { ushort vendor = data & 0xffff; ushort device = (data >> 16) & 0xffff; cprintf("-------------------------------------------------\n"); cprintf("vendor id => %x device id %x\n", vendor, device); data = readpcidata(b, d, 0, 0x8); uchar class_code = (data >> 24) & 0xff; uchar sub_class = (data >> 16) & 0xff; data = readpcidata(b,d, 0, 0xc); uchar header_type = (data >> 16) & 0xff; cprintf("class => %x sub_class => %x header type %x\n", class_code, sub_class, header_type); data = readpcidata(b, d, 0, 0x3c); uchar i_pin = (data >> 8) & 0xff; uchar i_line = data & 0xff; uint bar0 = readpcidata(b, d, 0, 0x10); cprintf("i_pin => %x i_line => %x bar => %x \n\n", i_pin, i_line, bar0); } } } } |
実行
実行するとこうなりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
------------------------------------------------- vendor id => 8086 device id 1237 class => 6 sub_class => 0 header type 0 i_pin => 0 i_line => 0 bar => 0 ------------------------------------------------- vendor id => 8086 device id 7000 class => 6 sub_class => 1 header type 80 i_pin => 0 i_line => 0 bar => 0 ------------------------------------------------- vendor id => 1234 device id 1111 class => 3 sub_class => 0 header type 0 i_pin => 0 i_line => 0 bar => fd000008 ------------------------------------------------- vendor id => 8086 device id 100e class => 2 sub_class => 0 header type 0 i_pin => 1 i_line => b bar => febc0000 |
vendor idが8086のものが3つありますがこれはintelのことを指しているそうです。
そして一番下にdeviceが100eのものがありますがこれは82540EM Gigabit Ethernet Controllerという名前のデバイスを指しています。
以下のページにその記述がありました。
https://pci-ids.ucw.cz/read/PC/8086/100e
classコードが2、sub classコードが0となっているため、このデバイスがNetworkControllerというクラスに属するEthernetControllerであるということがわかります。
https://wiki.osdev.org/PCI#The_PCI_Bus
他にもclassが6となっているものはBridgeDevice、3となっているものはDisplayControllerを表しているみたいですね。
これらの情報はすべてインターネットから集めたものなので間違いがないとは言えませんが、
vendor idやdevice idなど実際に該当するものもあるので大丈夫でしょう。
今回実装した方法以外にもPCIへアクセスするためのメカニズムはいろいろあるみたいです。
I/OPortは使わずにメモリを通してアクセスもできるみたいです。
ちなみに初めはOSを作れるところまで作ろうと思ったのですが、
僕にはもうそんな体力はないようでxv6を少しいじるのが関の山でした。