LECTURE 11 PCI デバイスの情報を取得する

○ はじめに

今の PC には、PCI カードという形で様々なデバイス ( ネットワークカードやグラフィックカード、サウンドカード等 ) が接続されています。シリアルポートやパラレルポートを搭載した PC がどんどん減って来ると、 USB や PCI のデバイスを制御したいことが増えて来ると思います。

PCI のデバイスを制御する場合も、I/O アドレスと割り込み番号が分かれば、後はどこにどんな値を書き込めばいいのかマニュアルを見れば制御できます。また、デバイス自身の検索もベンダー ID とデバイス ID を使って検索することができます。これらの値も、通常はマニュアルに載っています。

ここでは、デバイスの検索や I/O アドレスや割り込み番号といった基本情報を取得するための方法について説明します。

○ 設定レジスタ

PCI デバイスには、256 バイトの設定レジスタがあります。このうち、先頭から 64 バイトは PCI 規格で標準化されていて、以下の様になっています。この設定レジスタの内容をカーネルの関数を使って読み込むことで、必要な情報を取得することになります。

バイト数 項目名 簡単な説明 ( 僕が知っている所だけ書きます m(__)m )
2 ベンダ ID ハードウェアメーカーの識別子。 PCI Special Interest Group によって管理されている。Linux® カーネルソースコードの drivers/pci/devlist.h を見ると、ベンダ ID およびデバイス ID の組が マッピングされるデバイスの名称が分かります ( 全てではないと思いますが )
2 デバイス ID メーカーがデバイスに固有に付ける ID
2 コマンドレジスタ -
2 ステータスレジスタ -
1 リビジョン ID -
3 クラスコード デバイスの種類を表す値。後ろの 2 バイトがベースクラスとサブクラスという 2 種類の値を表していて、この組でデバイスの種類が決定される。 クラス値も PCI Special Interest Group が管理している様である。Linux カーネルソースコードの drivers/pci/classlist.h を見ると、2 バイトの組がどういうデバイスとして 定義されているか分かります ( 全てではないと思いますが )。 最初の 1 バイトは良く分かりません
1 キャッシュライン -
1 レイテンシタイマ -
1 ヘッダタイプ -
1 BIST ( Built In Self Test ) -
4 ベースアドレス 0 最初の I/O 領域情報
4 ベースアドレス 1 2 番目の I/O 領域情報
4 ベースアドレス 2 3 番目の I/O 領域情報
4 ベースアドレス 3 4 番目の I/O 領域情報
4 ベースアドレス 4 5 番目の I/O 領域情報
4 ベースアドレス 5 6 番目の I/O 領域情報
4 CardBus CIS pointer -
2 サブシステムベンダ ID -
2 サブシステムデバイス ID -
4 拡張 ROM ベースアドレス -
8 予約 将来のために予約されている領域
1 IRQ 番号 割り込み番号
1 IRQ ピン番号 割り込み信号に使われているピン番号
1 Min_Gnt -
1 Max_Lat -

○ サンプルプログラム

PCI デバイスの情報を取得して、カーネルログに出力するサンプルプログラムpci.c です。 はじめに のページから、全てのサンプルプログラムを 固めた tar ボールをダウンロードすることもできます。

insmod で組み込むとエラー終了しますが、これは init_module でわざと 0 以外を返しているからです。PCI デバイス情報を表示した後に組み込まれている必要が無いので、この様にしています。

カーネルログを見ると、以下の様な感じで PCI デバイスの情報が出力されていると思います(実際の表示は、接続されているデバイスによって異なります)。

<6>module [pci.o] : loaded  into kernel
<6>module [pci.o] : vendor id = 32902, device id = 29072
<6>module [pci.o] : class = 1536
<6>module [pci.o] : subsystem
<6>module [pci.o] :   vendor id = 0, device id = 0
<6>module [pci.o] : IRQ = 0
<6>module [pci.o] : Base address 0xe0000000 - 0xe3ffffff
<6>module [pci.o] :   Memory
<6>module [pci.o] :   Prefetchable
<6>module [pci.o] : 
<6>module [pci.o] : 
<6>module [pci.o] : vendor id = 32902, device id = 29073
<6>module [pci.o] : class = 1540
<6>module [pci.o] : subsystem
<6>module [pci.o] :   vendor id = 0, device id = 0
<6>module [pci.o] : IRQ = 0
<6>module [pci.o] : 
<6>module [pci.o] : vendor id = 32902, device id = 28944
<6>module [pci.o] : class = 1537
<6>module [pci.o] : subsystem
<6>module [pci.o] :   vendor id = 0, device id = 0
<6>module [pci.o] : IRQ = 0
<6>module [pci.o] : 
<6>module [pci.o] : vendor id = 32902, device id = 28945
<6>module [pci.o] : class = 257
<6>module [pci.o] : subsystem
<6>module [pci.o] :   vendor id = 0, device id = 0
<6>module [pci.o] : IRQ = 0
<6>module [pci.o] : Base address 0xf000 - 0xf00f
<6>module [pci.o] :   I/O port
<6>module [pci.o] : 
<6>module [pci.o] : 
<6>module [pci.o] : vendor id = 32902, device id = 28946
<6>module [pci.o] : class = 3075
<6>module [pci.o] : subsystem
<6>module [pci.o] :   vendor id = 0, device id = 0
<6>module [pci.o] : IRQ = 10
<6>module [pci.o] : Base address 0xe000 - 0xe01f
<6>module [pci.o] :   I/O port
<6>module [pci.o] : 
<6>module [pci.o] : 
<6>module [pci.o] : vendor id = 32902, device id = 28947
<6>module [pci.o] : class = 1664
<6>module [pci.o] : subsystem
<6>module [pci.o] :   vendor id = 0, device id = 0
<6>module [pci.o] : IRQ = 0
<6>module [pci.o] : 
<6>module [pci.o] : vendor id = 4358, device id = 12355
<6>module [pci.o] : class = 512
<6>module [pci.o] : subsystem
<6>module [pci.o] :   vendor id = 4697, device id = 41221
<6>module [pci.o] : IRQ = 5
<6>module [pci.o] : Base address 0xe400 - 0xe47f
<6>module [pci.o] :   I/O port
<6>module [pci.o] : 
<6>module [pci.o] : Base address 0xeb000000 - 0xeb00007f
<6>module [pci.o] :   Memory
<6>module [pci.o] : 
<6>module [pci.o] : 
<6>module [pci.o] : vendor id = 4354, device id = 2
<6>module [pci.o] : class = 1025
<6>module [pci.o] : subsystem
<6>module [pci.o] :   vendor id = 4354, device id = 32
<6>module [pci.o] : IRQ = 12
<6>module [pci.o] : Base address 0xe800 - 0xe81f
<6>module [pci.o] :   I/O port
<6>module [pci.o] : 
<6>module [pci.o] : 
<6>module [pci.o] : vendor id = 4354, device id = 28674
<6>module [pci.o] : class = 2432
<6>module [pci.o] : subsystem
<6>module [pci.o] :   vendor id = 4354, device id = 32
<6>module [pci.o] : IRQ = 0
<6>module [pci.o] : Base address 0xec00 - 0xec07
<6>module [pci.o] :   I/O port
<6>module [pci.o] : 
<6>module [pci.o] : 
<6>module [pci.o] : vendor id = 4139, device id = 1317
<6>module [pci.o] : class = 768
<6>module [pci.o] : subsystem
<6>module [pci.o] :   vendor id = 4139, device id = 6648
<6>module [pci.o] : IRQ = 11
<6>module [pci.o] : Base address 0xe8000000 - 0xe9ffffff
<6>module [pci.o] :   Memory
<6>module [pci.o] :   Prefetchable
<6>module [pci.o] : 
<6>module [pci.o] : Base address 0xe4000000 - 0xe4003fff
<6>module [pci.o] :   Memory
<6>module [pci.o] : 
<6>module [pci.o] : Base address 0xe5000000 - 0xe57fffff
<6>module [pci.o] :   Memory
<6>module [pci.o] : 
<6>module [pci.o] : 

--- pci.c ---

    1    #include <asm/types.h>    /* u8, ... */
    2    #include <linux/errno.h>
    3    #include <linux/ioport.h> /* IORESOURCE_IO, ... */
    4    #include <linux/module.h> /* module 作成には必須 */
    5    #include <linux/kernel.h> /* printk */
    6    #include <linux/pci.h>
    7    
    8    
    9    MODULE_LICENSE( "GPL" );
   10    
   11    static char* msg = "module [pci.o]";
   12    
   13    
   14    /*
   15     * PCI バスに接続されているデバイスの情報を表示する。
   16     */
   17    static void 
   18    print_pci_information( void )
   19    {
   20        struct pci_dev* device = NULL;
   21    
   22        while ( 1 ) {
   23            /*
   24             * vendor id および device id に PCI_ANY_ID を指定することで
   25             * 接続されている全ての PCI デバイスを対象としている。
   26             */
   27            device = pci_find_device( PCI_ANY_ID, PCI_ANY_ID, device );
   28    
   29            if ( device == NULL )
   30                break;
   31    
   32    
   33            {
   34                #define BASE_ADDRESS_NUM 6
   35    
   36                u16 vendor_id;
   37                u16 device_id;
   38                u16 class;
   39                u16 sub_vendor_id;
   40                u16 sub_device_id;
   41                u8  irq;
   42    
   43                unsigned long resource_start;
   44                unsigned long resource_end;
   45                unsigned long resource_flag;
   46    
   47                int i;
   48    
   49    
   50                pci_read_config_word( device, PCI_VENDOR_ID,           &vendor_id );
   51                pci_read_config_word( device, PCI_DEVICE_ID,           &device_id );
   52                pci_read_config_word( device, PCI_CLASS_DEVICE,        &class );
   53                pci_read_config_word( device, PCI_SUBSYSTEM_VENDOR_ID, &sub_vendor_id );
   54                pci_read_config_word( device, PCI_SUBSYSTEM_ID,        &sub_device_id );
   55                pci_read_config_byte( device, PCI_INTERRUPT_LINE,      &irq );
   56    
   57    
   58                printk( KERN_INFO "%s : vendor id = %i, device id = %i\n",
   59                    msg, vendor_id, device_id );
   60    
   61                printk( KERN_INFO "%s : class = %i\n", msg, class );
   62                printk( KERN_INFO "%s : subsystem\n", msg );
   63    
   64                printk( KERN_INFO "%s :   vendor id = %i, device id = %i\n",
   65                    msg, sub_vendor_id, sub_device_id );
   66    
   67                printk( KERN_INFO "%s : IRQ = %i\n", msg, irq );
   68    
   69    
   70                for ( i = 0; i < BASE_ADDRESS_NUM; i ++ ) {
   71                    resource_start = pci_resource_start( device, i );
   72                    resource_end   = pci_resource_end( device, i );
   73                    resource_flag  = pci_resource_flags( device, i );
   74    
   75                    if ( resource_start != 0 || resource_end != 0 ) {
   76                        printk( KERN_INFO "%s : Base address 0x%0x - 0x%0x\n", 
   77                            msg, resource_start, resource_end );
   78    
   79                        if ( resource_flag & IORESOURCE_IO )
   80                            printk( KERN_INFO "%s :   I/O port\n", msg );
   81    
   82                        if ( resource_flag & IORESOURCE_MEM )
   83                            printk( KERN_INFO "%s :   Memory\n", msg );
   84    
   85                        if ( resource_flag & IORESOURCE_PREFETCH )
   86                            printk( KERN_INFO "%s :   Prefetchable\n", msg );
   87    
   88                        if ( resource_flag & IORESOURCE_READONLY )
   89                            printk( KERN_INFO "%s :   Read Only\n", msg );
   90    
   91                        printk( KERN_INFO "%s : \n", msg );
   92                    }
   93                }
   94    
   95                printk( KERN_INFO "%s : \n", msg );
   96            }
   97        }
   98    }
   99    
  100    
  101    /*
  102     * モジュールの初期処理
  103     * insmod 時に呼ばれる
  104     */
  105    int 
  106    init_module( void )
  107    {
  108        printk( KERN_INFO "%s : loaded  into kernel\n", msg );
  109    
  110        print_pci_information();
  111    
  112        /* 組み込まれる意味が無いので、常にエラーを返すことで
  113           組み込まれない様にしている */
  114    
  115        return -ENODEV;
  116    }
  117    
  118    
  119    /*
  120     * モジュールの解放処理
  121     * モジュールの参照数が 0 であれば、rmmod 時に呼ばれる
  122     */
  123    void 
  124    cleanup_module( void )
  125    {
  126        printk( KERN_INFO "%s : removed from kernel\n", msg );
  127    }
  128    
  129    
  130    /* End of pci.c */

○ サンプルプログラムの説明

では、サンプルプログラムについて説明していきます。

  • 1 〜 6 行目

    必要なヘッダファイルをインクルードしています。asm/types.h は、 u8 や u16 等の型を使うために必要です。また、PCI 関連の関数を使うために linux/pci.h をインクルードしています。

  • 14 〜 98 行目 ( print_pci_information )

    pci.c のメインと言える関数です。 init_module 内でこの関数を実行して、PCI 情報を表示させています。 while ループで全ての PCI デバイス情報を取得し、表示しています。

    • 23 〜 30 行目

      pci_find_device 関数を使って、PCI デバイス情報を検索しています。

      #include <linux/pci.h>
      struct pci_dev* 
      pci_find_device( 
        unsigned int vendor, unsigned int device, const struct pci_dev* from );
      
      

      pci_find_device は、ベンダ ID とデバイス ID を指定して該当する PCI デバイス情報を検索するための関数です。該当する PCI デバイスが存在しない場合は NULL を返します。from に NULL を指定すると、PCI デバイスリストの先頭から 検索が実行され、最初に該当したデバイス情報が返ります。取得した struct pci_dev へのポインタを from に指定すると、PCI デバイスリスト中の次のデバイスから 検索が実行されます。

      ここでは PCI_ANY_ID という特定の ID を指定しないための ID を使用しているため、 ベンダ ID とデバイス ID に関係なく接続されているデバイス情報が順に返りますが、 PCI デバイスを制御する場合は、通常はマニュアル等でベンダ ID とデバイス ID を指定してデバイス情報を検索します。 また、制御する際には取得した struct pci_dev に対して、pci_enable_device を実行してデバイスを有効化してください。

      #include <linux/pci.h>
      int pci_enable_device( struct pci_dev* dev );
      
      

      pci_enable_device は成功すると 0 が、失敗するとそれ以外が返ります。

      なお、参考までにデバイスクラスを指定して PCI デバイス情報を取得するためには、 pci_find_class を使います。

      #include <linux/pci.h>
      struct pci_dev* pci_find_class( unsigned int class, const struct pci_dev* from );
      
      

      class は、ベースクラスとサブクラスを表す 16 ビット値を指定します。

    • 33 〜 96 行目

      取得した PCI デバイス情報を使って、設定レジスタの内容を読み込み カーネルログに出力しています。

      ベンダ ID / デバイス ID / クラス / サブシステムベンダ ID / サブシステム ID / 割り込み番号を設定レジスタから読み込みために、 pci_read_config_byte / pci_read_config_word を使用しています。

      #include <linux/pci.h>
      int pci_read_config_byte( struct pci_dev* dev, int where, u8* ptr );
      int pci_read_config_word( struct pci_dev* dev, int where, u16* ptr );
      int pci_read_config_dword( struct pci_dev* dev, int where, u32* ptr );
      
      

      where に、設定レジスタ中の読みたい場所を表すマクロを指定し、 ptr に値を格納する領域へのポインタを指定します。複数バイトの値については、 関数内でエンディアンの変換が行われるので気にする必要はありません ( 設定レジスタ内はリトルエンディアン )。なお、マクロは linux/pci.h で定義されていますので、設定レジスタの内容と見比べながら使ってみてください。

      なお、設定レジスタに対して書き込みを行う場合 ( コマンドレジスタへの書き込み等 ) は、pci_write_config_xxxx を使います。

      #include <linux/pci.h>
      int pci_write_config_byte( struct pci_dev* dev, int where, u8 val );
      int pci_write_config_word( struct pci_dev* dev, int where, u16 val );
      int pci_write_config_dword8 struct pci_dev* dev, int where, u32 val );
      
      

      書き込みを行う前に、pci_enable_device でデバイスを有効化するのを 忘れないでください。

    • 70 〜 93 行目

      6 つのベースアドレスに対して、 I/O 領域の先頭アドレス / 最後のアドレス / I/O 領域の種類 を取得して表示しています。これらはそれぞれ以下の関数で取得します。

      #include <linux/pci.h>
      unsigned long pci_resource_start( struct pci_dev* dev, int bar );
      unsigned long pci_resource_end( struct pci_dev* dev, int bar );
      unsigned long pci_resource_flags( struct pci_dev* dev, int bar );
      
      

      bar に 0 - 5 を指定することで、ベースアドレス 0 - 5 を選択します。 pci_resource_start と pci_resource_end は、ベースアドレスがマッピング されていない(要するに PCI デバイス上に存在しない I/O 領域である ) 場合は 0 を返す様です。

      pci_resource_flags は、I/O 領域に関する情報を論理和で返します。 linux/ioport.h に定義されている定数の論理和となっていますが、 pci.c で判定している定数は以下の通りです。

      定数名 内容
      IORESOURCE_IO I/O 領域は I/O ポートである
      IORESOURCE_MEM I/O 領域はメモリ領域 (メモリマッピングされている) である
      IORESOURCE_PREFETCH メモリ領域がプリフェッチできるかどうか ( アクセスによる副作用が無く、CPU がキャッシュできるかどうか )

以上で pci.c の解説は終わりです。ここまでの内容で I/O 領域のアドレスと割り込み番号を取得することができたので、マニュアルでデバイスの制御方法をじっくり読めば PCI カードを制御するためのデバイスドライバを書くのは簡単ですね!