PCIデバイスの検出

[| ]  最終更新: 2023/02/14 18:31:47

PCIデバイスの情報取得

/proc/pciの利用でお話ししたように、/proc/pci をみることでPCIデバイスのアドレス情報は得られます。 とはいえ、モジュールをつくると直接的に内部情報が得られるので、それをつかうに越したことはありません。 例によって? init_module のみのモジュールをつくって情報を抜き出してみます。
// gcc -c pcitest.c -Wall -Wstrict-prototypes -O -pipe -m486

#define MODULE
#define __KERNEL__

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/pci.h>
#if LINUX_VERSION_CODE < 0x20155  ||  defined(CARDBUS)
#include <linux/bios32.h>
#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 に何があるかはデバイス(ハード)次第なので、この先の使い道はハード(チップ)のマニュアルを見てご検討下さい。

使った関数について触れておきます。

 Linux2.0: linux/bios32.h, linux/pci.h  
 Linux2.2: 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 専用でもかまわない場合

補足ですが、Linux 2.2 では多少便利になっています。 上のソースでは見た目、割り込み(IRQ) の値を得る部分でpci_find_slot()なる別の関数を呼ぶ必要があったりして面倒に見えますが、互換部分を使っているためです。

上で使った関数群は Linux2.2: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 <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/pci.h>
#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)
{
}



熊谷正朗 [→連絡]
東北学院大学 工学部 機械知能工学科 RDE
[| ]