PCIデバイスの検出
/proc/pciの利用でお話ししたように、/proc/pci をみることでPCIデバイスのアドレス情報は得られます。 とはいえ、モジュールをつくると直接的に内部情報が得られるので、それをつかうに越したことはありません。 例によって? init_module のみのモジュールをつくって情報を抜き出してみます。 // gcc -c pcitest.c -Wall -Wstrict-prototypes -O -pipe -m486 #define MODULE #define __KERNEL__ #include #include #include #include #if LINUX_VERSION_CODE < 0x20155 || defined(CARDBUS) #include #endif static int vendorid=0x8086; // とりあえず intel static int deviceid=0x7190; // とりあえず 440BX #if LINUX_VERSION_CODE > 0x20115 MODULE_PARM(vendorid, "i"); MODULE_PARM(deviceid, "i"); #endif // PCI のI/Oメモリ領域のベースアドレスを取得して表示 static void PrintPCIBaseAddress(unsigned char bus,unsigned char dev_fn,int i) { unsigned int baseaddr; // ベースアドレスの取得 if(pcibios_read_config_dword (bus, dev_fn,PCI_BASE_ADDRESS_0+i*4,&baseaddr)) { printk("pcitest: cannot get base addr(%d)\n",i); return ; } if((baseaddr&PCI_BASE_ADDRESS_SPACE)==PCI_BASE_ADDRESS_SPACE_IO) { これで I/O か メモリかの区別をします printk("pcitest: found base(%d) I/O address: %04lX\n",i, baseaddr&PCI_BASE_ADDRESS_IO_MASK); } else { printk("pcitest: found base(%d) ",i); switch(baseaddr&PCI_BASE_ADDRESS_MEM_TYPE_MASK) { メモリには種類があります case PCI_BASE_ADDRESS_MEM_TYPE_32: printk("32bit Memory "); break; case PCI_BASE_ADDRESS_MEM_TYPE_1M: printk("32bit 1M< Memory "); break; case PCI_BASE_ADDRESS_MEM_TYPE_64: printk("64bit Memory "); break; } if(baseaddr&PCI_BASE_ADDRESS_MEM_PREFETCH) printk("Prifetchable "); printk("from %08lX\n",baseaddr&PCI_BASE_ADDRESS_MEM_MASK); } } // PCI の割り込み情報を表示 static void PrintPCIInterrupt(unsigned char bus,unsigned char dev_fn) { // 割り込み番号取得 : 2.0.x のあと、変ったらしい...。 #if LINUX_VERSION_CODE >= 0x20155 struct pci_dev *pdev = pci_find_slot(bus, dev_fn); if (!pdev) { printk("pcitest: cannot get IRQ\n"); return ; } else printk("pcitest: found IRQ %d\n",pdev->irq); #else unsigned char IRQ; if(pcibios_read_config_byte(bus, dev_fn,PCI_INTERRUPT_LINE,&IRQ)) { printk("pcitest: cannot get IRQ\n"); return ; } else printk("pcitest: found IRQ %d\n",IRQ); #endif } int init_module(void) { int i,r; unsigned char bus,dev_fn; for(i=0;i<0xff;i++) { r=pcibios_find_device(vendorid,deviceid,i,&bus,&dev_fn); if(r!=PCIBIOS_SUCCESSFUL) { // これ以上ドライバ見つからず if(r!=PCIBIOS_DEVICE_NOT_FOUND) printk("pcitest's error: %X: see linux/pci.h\n",r); break; } printk("pcitest: found device(%04X,%04X,%d) on" " Bus:%d Device:%d Function:%d\n", vendorid,deviceid,i,bus,dev_fn>>3,dev_fn&0x7); PrintPCIBaseAddress(bus,dev_fn,0); PrintPCIBaseAddress(bus,dev_fn,1); PrintPCIBaseAddress(bus,dev_fn,2); PrintPCIBaseAddress(bus,dev_fn,3); PrintPCIBaseAddress(bus,dev_fn,4); PrintPCIBaseAddress(bus,dev_fn,5); PrintPCIInterrupt(bus,dev_fn); printk("\n"); } if(i==0) { printk("There is no device vendor:%04X device:%04X\n", vendorid,deviceid); } return 1; // insmod がこけるように } void cleanup_module(void) { } 基本的に、冒頭のbios32.h あたりは、Linux 2.2 でも互換性は残してあるようですが、warningが出たりするのでまじめに回避します。 コンパイルして実行してみます。 ただし、これは BP6使用のパソコンでの結果ですので、各自の環境の /proc/pci, linux/pci.h あたりを参考にvendorid, deviceidを変えてみてください。 % gcc -c pcitest.c -Wall -Wstrict-prototypes -O -pipe -m486 # insmod pcitest デフォルトで 440BX ./pcitest.o: init_module: Device or resource busy % dmesg pcitest: found device(8086,7190,0) on Bus:0 Device:0 Function:0 pcitest: found base(0) 32bit Memory Prifetchable from E0000000 pcitest: found base(1) 32bit Memory from 00000000 このへんはごみ pcitest: found base(2) 32bit Memory from 00000000 pcitest: found base(3) 32bit Memory from 00000000 pcitest: found base(4) 32bit Memory from 00000000 pcitest: found base(5) 32bit Memory from 00000000 pcitest: found IRQ 0 % cat /proc/pci PCI devices found: Bus 0, device 0, function 0: Host bridge: Intel 440BX - 82443BX Host (rev 3). Medium devsel. Master Capable. Latency=32. Prefetchable 32 bit memory at 0xe0000000 [0xe0000008]. # insmod pcitest vendorid=0x1103 deviceid=4 1103,0004 を指定:標準搭載の(つかってない)ATA66 ./pcitest.o: init_module: Device or resource busy pcitest: found device(1103,0004,0) on Bus:0 Device:19 Function:0 デバイス(0x1103,0x0004) その0 pcitest: found base(0) I/O address: D800 pcitest: found base(1) I/O address: DC00 pcitest: found base(2) 32bit Memory from 00000000 pcitest: found base(3) 32bit Memory from 00000000 pcitest: found base(4) I/O address: E000 pcitest: found base(5) 32bit Memory from 00000000 pcitest: found IRQ 11 pcitest: found device(1103,0004,1) on Bus:0 Device:19 Function:1 デバイス(0x1103,0x0004) その1 pcitest: found base(0) I/O address: E400 pcitest: found base(1) I/O address: E800 pcitest: found base(2) 32bit Memory from 00000000 pcitest: found base(3) 32bit Memory from 00000000 pcitest: found base(4) I/O address: EC00 pcitest: found base(5) 32bit Memory from 00000000 pcitest: found IRQ 11 % cat /proc/pci Bus 0, device 19, function 0: Unknown mass storage controller: Triones Technologies, Inc. Unknown device (rev 1). Vendor id=1103. Device id=4. Medium devsel. IRQ 11. Master Capable. Latency=120. Min Gnt=8.Max Lat=8. I/O at 0xd800 [0xd801]. I/O at 0xdc00 [0xdc01]. I/O at 0xe000 [0xe001]. Bus 0, device 19, function 1: Unknown mass storage controller: Triones Technologies, Inc. Unknown device (rev 1). Vendor id=1103. Device id=4. Medium devsel. IRQ 11. Master Capable. Latency=120. Min Gnt=8.Max Lat=8. I/O at 0xe400 [0xe401]. I/O at 0xe800 [0xe801]. I/O at 0xec00 [0xec01]. 原則として、PCIデバイスを扱うときは PCI の仕組みあたりを知っておくとよいと思いますが、/proc/pci の情報からアドレスを取得する、程度なら、このようなプログラムをブラックボックスとして使っても問題にはならないでしょう。 それぞれの base address に何があるかはデバイス(ハード)次第なので、この先の使い道はハード(チップ)のマニュアルを見てご検討下さい。

使った関数について触れておきます。 linux/bios32.h, linux/pci.h linux/pci.h 情報取得関数 int pcibios_find_device (unsigned short vendor, unsigned short dev_id, unsigned short index, unsigned char *bus, unsigned char *dev_fn); vendor: ベンダID, device: デバイスID, index: 複数あるときの番号 (0,1,2,..) bus: バス番号(識別に使用) dev_fn: デバイス・ファンクション番号(識別に使用) PCIコンフィギュレーション 読み取り関数 int pcibios_read_config_byte (unsigned char bus, unsigned char dev_fn, unsigned char where, unsigned char *val); int pcibios_read_config_word (unsigned char bus, unsigned char dev_fn, unsigned char where, unsigned short *val); bus, dev_fn: pcibios_find_device で得られる値を使用 where: コンフィグレーションの種類 val: 値 PCIコンフィギュレーション 書き込み関数:書き換える必要があるとき使用 int pcibios_read_config_dword (unsigned char bus, unsigned char dev_fn, unsigned char where, unsigned int *val); int pcibios_write_config_byte (unsigned char bus, unsigned char dev_fn, unsigned char where, unsigned char val); int pcibios_write_config_word (unsigned char bus, unsigned char dev_fn, unsigned char where, unsigned short val); int pcibios_write_config_dword (unsigned char bus, unsigned char dev_fn, unsigned char where, unsigned int val); 補足ですが、Linux 2.2 では多少便利になっています。 上のソースでは見た目、割り込み(IRQ) の値を得る部分でpci_find_slot()なる別の関数を呼ぶ必要があったりして面倒に見えますが、互換部分を使っているためです。

上で使った関数群は pci.h では"Don't use these in new code"と記されています。 新しいコードではpci_find_device()を使うようにとの指示です。 実際、それらをつかうと、PCIのコンフィギュレーションに直接触る必要もなくなるので、便利ですらあります。

以下に、その例を示します。外見上は上のコードと同じ動作をします。 // gcc -c pcitest.c -Wall -Wstrict-prototypes -O -pipe -m486 // 2.2 以降専用 #define MODULE #define __KERNEL__ #include #include #include #include #if LINUX_VERSION_CODE < 0x20200 || defined(CARDBUS) #error This must use with Linux 2.2 #endif static int vendorid=0x8086; static int deviceid=0x7190; #if LINUX_VERSION_CODE > 0x20115 MODULE_PARM(vendorid, "i"); MODULE_PARM(deviceid, "i"); #endif // PCI のI/Oメモリ領域のベースアドレスを取得して表示 static void PrintPCIBaseAddress(unsigned long baseaddr,int i) { if((baseaddr&PCI_BASE_ADDRESS_SPACE)==PCI_BASE_ADDRESS_SPACE_IO) { printk("pcitest: found base(%d) I/O address: %04lX\n",i, baseaddr&PCI_BASE_ADDRESS_IO_MASK); } else { printk("pcitest: found base(%d) ",i); switch(baseaddr&PCI_BASE_ADDRESS_MEM_TYPE_MASK) { case PCI_BASE_ADDRESS_MEM_TYPE_32: printk("32bit Memory "); break; case PCI_BASE_ADDRESS_MEM_TYPE_1M: printk("32bit 1M< Memory "); break; case PCI_BASE_ADDRESS_MEM_TYPE_64: printk("64bit Memory "); break; } if(baseaddr&PCI_BASE_ADDRESS_MEM_PREFETCH) printk("Prifetchable "); printk("from %08lX\n",baseaddr&PCI_BASE_ADDRESS_MEM_MASK); } } int init_module(void) { int i; struct pci_dev *pdev=NULL; for(i=0;;i++) { pdev=pci_find_device(vendorid,deviceid,pdev); if(!pdev) break; // これ以上ドライバ見つからず printk("pcitest: found device(%04X,%04X,%d) on" " Bus:%d Device:%d Function:%d\n", vendorid,deviceid,i, pdev->bus->number,pdev->devfn>>3,pdev->devfn&0x7); PrintPCIBaseAddress(pdev->base_address[0],0); PrintPCIBaseAddress(pdev->base_address[1],1); PrintPCIBaseAddress(pdev->base_address[2],2); PrintPCIBaseAddress(pdev->base_address[3],3); PrintPCIBaseAddress(pdev->base_address[4],4); PrintPCIBaseAddress(pdev->base_address[5],5); printk("pcitest: found IRQ %d\n",pdev->irq); printk("\n"); } if(i==0) { printk("There is no device vendor:%04X device:%04X\n", vendorid,deviceid); } return 1; // insmod がこけるように } void cleanup_module(void) { }