[LinuxFocus-icon]
ホーム  |  マップ  |  一覧  |  検索

ニュース | アーカイブ | リンク | LFについて
[an error occurred while processing this directive]
convert to palmConvert to GutenPalm
or to PalmDoc

[Photo of the Author]
by Dr. B. Thangaraju
<balasubramanian.thangaraju(at)wipro.com>

著者紹介:
Dr. B. Thangaraju は Tamil Nadu にある Bharathidasan University で物理学の博士号を取得し、 インドの Indian Institute of Science で助手として 5 年間勤務しました。 透明導電膜 (Transparent and Conducting Oxide (TCO) thin films) 、 スプレー熱分解、光音響法、カルコゲナイドガラスの研究を行いました。 有名な国際誌で 10 個の論文を発表しました。 また、研究成果を国内外のカンファレンスで 7 回あまり発表しました。

現在はインドの Talent Transformation, Wipro Technologies で Manager として働いています。 現在は Linux カーネル、デバイスドライバ、リアルタイム Linux の分野で調査、研究、普及活動を行っています。

日本語訳:
須藤 賢一 <deep_blue(at)users.sourceforge.jp>

目次:

 

Linux のデバイスドライバで、安全に I/O ポートを割り当てる方法

[Illustration]

要約:

デバイスドライバを書くのはとても挑戦しがいのある、冒険に満ちた作業です。 init_module ルーチンの中でドライバを登録したら、 次はデバイスが使うリソースを割り当てないといけません。 デバイスが使う主要なリソースのひとつが I/O ポートです。 動的にリンクされたドライバでは、 プログラマは自分のデバイス用に未使用の範囲を注意深く割り当てなければなりません。 ドライバは、ある範囲のポートが使用中かそうでないかをまず探ることになります。 デバイス用にポートの割り当てを要求するのはその後で行うようにしないといけません。 逆に、ドライバモジュールがカーネルから取り外される際には、ポートを開放する必要があります。 この記事では Linux のデバイスドライバが安全確実にポートを割り当てるための複雑な方法を説明します。

_________________ _________________ _________________

 

はじめに

デバイスドライバの開発者の頭を悩ませるのがデバイス用のリソースの割り当てです。 リソースには I/O ポート、メモリ、IRQ があります。 この記事では I/O サブシステムの基礎と、リソース割り当ての重要性を説明します。 リソースについては主に I/O ポートを扱います。 デバイス用のポートをどうやって探し、割り当て、解放するのかも説明します。

ハードウェアの基本部分であるポートやバスやデバイスコントローラーは、 いろんな種類の I/O デバイスに対応しています。 デバイスドライバーは I/O サブシステムに対して統一的なデバイスアクセスのためのインターフェースを提供しています。 ちょうどシステムコールがアプリケーションとオペレーティングシステムの間での標準的なインターフェースを提供しているのと同じです。 コンピューターにはたくさんのデバイスが接続されています。 ディスク、テープ、CD-ROM、フロッピーディスクといった記憶装置もあれば、 キーボード、マウス、スクリーンといったヒューマンインターフェース装置もあり、 ネットワークカードやモデムといった伝送装置もあります。 このように I/O デバイスの種類はいくらでもあるわけですが、デバイスがどのように接続され、ソフトウェアがどうやってハードウェアを制御するかという、基礎的なことさえ勉強すれば十分です。

 

基本的な概念

デバイスは 2 つの部分から成り立っています。 デバイスコントローラーと呼ばれる電子的な部分と、機械的な部分です。 コントローラーはシステムバスを通じてシステムとつながっています。 一般に、各コントローラーにはポートアドレス (衝突がないもの) が割り当てられます。 I/O ポートはステータスレジスタ、コントロールレジスタ、 データインレジスタ、データアウトレジスタという 4 つのレジスタの組から成ります。 ステータスレジスタにはホストが読み込み可能なビットがあり、 指示したコマンドが完了したかどうか、 データの読み書きが可能かどうか、エラーが起きていないかなどを示します。 コントロールレジスタはホストが書き込みを行うレジスタで、 コマンドを開始したりデバイスの状態を変更したりするために使われます。 データインレジスタは入力を読み込むためのレジスタで、 データアウトレジスタはシステムに出力を行うためのものです。

よって、プロセッサとデバイスの間での基本的なインターフェースはコントロールレジスタとステータスレジスタを通じて行います。 プロセッサがプログラムを実行中にデバイス関連の命令を見つけると、 適切なデバイスにコマンドを発行して命令を実行します。 コントローラーは要求された動作を行い、ステータスレジスタの対応するビットを立てて待機します。 処理が完了するまで定期的にデバイスの状態をチェックするのはプロセッサの役目です。 例えば、パラレルポートのドライバ (プリンタが利用) は一般に定期的にプリンターに問い合わせを行って、プリンタが受け付け可能な状態になっているかをチェックします。 プリンタが受け付け可能でないと、(プロセッサが別の仕事をできるように) ドライバはしばらく休眠します 。 そしてプリンタが利用可能になるまで何度も試します。 このポーリング機構によりシステムの性能が向上します。 これがないと、システムは無駄にデバイスを待つことになり、 他のことができなくなってしまいます。

各レジスタは I/O 空間の中で明確なアドレスを持っています。 一般にこのアドレスは、設定ファイルの中で指定されたパラメーターを使って、 ブート時に割り当てられます。 デバイスが静的に接続されている場合、各デバイスには連続したアドレスが割り当てられることもあります。 つまり、カーネルが既存のデバイスに対するデバイスドライバを含んでいるということです。 割り当てられた I/O ポートの範囲はL proc ディレクトリに保存されます。 現在割り当て済みのポートアドレスの範囲は $cat /proc/ioports で確認することができます。 出力の最初の列がポートの範囲を、次の列がそのポートを使っているデバイスを示します。 オペレーティングシステムによっては、 動作中にデバイスドライバモジュールをロードできるものがあります。 そのため、システムが動いている最中に新たにデバイスを接続することが可能で、 動的に読み込まれたデバイスドライバを使って新たに接続されたデバイスを制御したりアクセスしたりすることができます。

デバイスドライバの概念はとても抽象的ですが、 デバイスが持つハード的な機能と密接に関連しているため、 コンピューターのソフトウェアとしては最も下位に属します。 デバイスドライバはみな 1 種類のデバイスを管理します。 キャラクタデバイス、ブロックデバイス、ネットワークデバイスなどの種類があります。 アプリケーションがデバイスを要求すると、 カーネルが適切なデバイスドライバに処理を依頼します。 するとデバイスドライバはデバイスに対してコマンドを発行します。 デバイスドライバは関数の集まりです。 open、close、read、write、ioctl、llseek などの多数のエントリを持っています。 モジュールを読み込むと init_module ( ) 関数が呼び出され、 モジュールが開放されると cleanup_module ( ) 関数が呼び出されます。 デバイスはデバイスドライバの init_module ( ) ルーチンの中で登録されます。

init_module ( ) の中でデバイスが登録されると、 その関数の中で I/O ポートやメモリ、IRQ といったリソースが割り当てられます。 これらはドライバが正しくデバイスを操作するのに必要です。 デバイスに対しておかしなメモリアドレスを割り当てた時には、カーネルは segmentation fault というエラーメッセージを表示しますが、 I/O ポートの場合には wrong I/O port などといったエラーメッセージを表示してはくれません。 かわりに、既に他のデバイスが使っているポートを割り当て、システムをクラッシュさせます。 モジュールを取り外す際には、cleanup_module ( ) 関数の中でデバイスの登録を解除し、 メジャー番号とリソースを解放してやる必要があります。

デバイスドライバのもっぱらの仕事は I/O ポートの読み書きです。 したがって、デバイスが使っているポートアドレスを他に誰も使っていないことをしっかりと確認しなければなりません。 他のデバイスはそのアドレス範囲を使ってはいけないのです。 確実を期すため、ドライバはまず使いたいポートアドレスが既に使われていないかどうかを探る必要があります。 使われていないことが分かったら、カーネルにそのアドレス範囲を自分のデバイス用に割り当てるよう要求します。

 

安全確実なポートの割り当て

ではカーネル関数を使って実際にリソースの割り当てと解放を行う方法を見てみましょう。 ここで示す実践的な方法は Linux 2.4 カーネルで試しました。 プログラム例はそのままでは Linux オペレーティングシステムにしか使えませんが、 ある程度は他の UNIX にも応用できます。

まず、目的のポート範囲を自分のデバイスが使って良いかどうか調べます。

int check_region (unsigned long start, unsigned long len);

この関数はそのポートアドレス範囲が使用可能ならばゼロを返し、 アドレスが使用中ならばゼロより小さい値か負のエラーコード ( -EBUSY または -EINVAL) を返します。 関数の引数は 2 つです。 start は連続範囲 (I/O ポートの範囲) のはじまり、 len はポートの数です。

ポートが利用可能ならば、デバイスに割り当てます。 割り当てには request_region 関数を使います。

struct resource *request_region (unsigned long start, unsigned long len, char *name);

最初の 2 つの引数は上で説明したのと同じです。 キャラクタ型のポインタ変数 name は、 割り当てたポートアドレスを使うデバイスの名前です。 関数は resource 構造体へのポインタを返します。 resource 構造体はリソースの範囲を示すもので、 <linux/ioport.h> で定義されています。 この構造体の定義は以下のようになっています。

struct resource {
        const char *name;  , India. His current areas of research, study
    and knowledge dissemination are Linux Kernel, Device Drivers
    and Real Time Linux.

        unsigned long start, end;
        unsigned long flags;
        struct resource *parent, *sibiling, *child;
};
モジュールがカーネルから取り外される際には、 別のデバイスがこのポートを使えるよう、解放してやる必要があります。 それには cleanup_module ( ) の中で release_regin ( ) 関数を呼び出します。 関数の形式は以下の通りです。

void release_region ( unsigned long start, unsigned long len);

2 つの引数の説明は前回と同じです。 ここで説明した 3 つの関数は実際にはマクロになっていて、 <linux/ioport.h> で定義されています。  

デバイスのポートを割り当てるドライバのコード例

動的にロードされたデバイス用にポートの割り当てと解放を行うプログラムの例を示します。
#include <linux/fs.h.>
#include <linux/ioport.h.>

struct file_operations fops;
unsigned long start, len;

int init_module (void)
{
 int status;
 start = 0xff90;
 len   = 0x90;

 register_chrdev(254,"your_device",&fops);

 status =  check_region (start, len);
 if (status == 0) {
     printk ("The ports are available in that range.\n");
     request_region(start,len,"your_device");
 } else {
     printk ("The ports are already in use. Try other range.\n");
     return (status);
 }
 return 0;
}

void cleanup_module (void)
{
 release_region(start, len);
 printk (" ports are freed successfully\n");
 unregister_chrdev(254,"your_device");}
 printk (" your device is unregistered\n");
}

念のため言っておきますが、この例ではエラー判定やメジャー番号の動的な割り当ては省略してあります。 ポートの割り当てに成功したら、proc ディレクトリで確認してみましょう。
$cat /proc/ioports  

ドライバから利用できる、I/O ポート関連のカーネル関数

Linux には、I/O ポートから読み書きを行うための関数がポートのサイズに応じて用意されています。 ポートは 8、16、32 ビットのどれかです。 Linux カーネルヘッダー <asm/io.h> には、 I/O ポートにアクセスするためのインライン関数が定義されています。 8 ビット、16 ビット、32 ビットのポートに対して読み込み (inx)、 書き込み (outx) を行うための関数はそれぞれ以下の通りです。



__u8 inb (unsigned int port);
void outb (__u8 data, unsigned int port);

__u16 inw (unsigned int port);
void outw(__u16 data, unsigned int port);

__u32 inl (unsigned int prot);
void outl (__u32 data, unsigned int port);


一度に複数のデータを効率良く転送するための文字列バージョンは以下の通りです。


void insb(unsigned int port, void *addr, unsigned long count);
void outsb(unsigned int port, void *addr, unsigned long count);


addr はデータ格納先または転送元となるメモリ上の場所で、 count は転送単位の数です。 データは port で指定されたポートに対して読み書きされます。


void insw(unsigned int port, void *addr, unsigned long count);
void outsw(unsigned int port, void *addr, unsigned long count);

16 ビットの値を指定された 16 ビットポートに対して読み書きします。


void insl(unsigned int port, void *addr, unsigned long count);
void outsl(unsigned int port, void *addr, unsigned long count);

32 ビットの値を指定された 32 ビットポートに対して読み書きします。  

謝辞

原稿を丁寧にお読みいただいた インド Wipro Technologies 社 Talent Transformation 部門マネージャー Mr. Jayasurya V 氏に感謝します。  

参考文献


Webpages maintained by the LinuxFocus Editor team
© Dr. B. Thangaraju, FDL
LinuxFocus.org
翻訳履歴:
en --> -- : Dr. B. Thangaraju <balasubramanian.thangaraju(at)wipro.com>
en --> jp: 須藤 賢一 <deep_blue(at)users.sourceforge.jp>

2003-04-04, generated by lfparser version 2.36