デバイスドライバは、サービスのコンシューマとしてもプロバイダとしても、Oracle Solaris OS と互換性がある必要があります。このセクションでは、デバイスドライバを設計する上で考慮する点として、次の項目について説明します。
Oracle Solaris DDI/DKI インタフェースは、ドライバの移植性のために提供されています。DDI/DKI を使用すると、開発者はハードウェアやプラットフォームの違いについて心配することなく標準的な方法でドライバコードを作成できます。このセクションでは、DDI/DKI インタフェースのさまざまな局面について説明します。
DDI インタフェースを使用すると、ドライバは持続的な一意の ID をデバイスに割り当てることができます。デバイス ID は、デバイスを識別したり、検出したりするために使用できます。この ID は、デバイスの名前や番号 (dev_t) には依存していません。アプリケーションでは、libdevid(3LIB) で定義された関数を使用すると、ドライバが登録したデバイス ID を読み取り、操作できます。
デバイスまたはデバイスドライバの属性は、プロパティーによって指定されます。プロパティーは名前-値ペアです。名前は、対応付けられた値を使ってプロパティーを識別する文字列です。プロパティーは、自己識別デバイスの FCode またはハードウェア構成ファイル (driver.conf(4) のマニュアルページを参照) で定義することも、ドライバ自身が ddi_prop_update(9F) ファミリのルーチンを使用して定義することもできます。
DDI/DKI では、デバイス割り込み処理の次の局面に対応します。
システムへのデバイス割り込みの登録
デバイス割り込みの削除
割り込みハンドラへの割り込みのディスパッチ
デバイス割り込みのソースは、interrupt と呼ばれるプロパティーに含まれています。このプロパティーは、自己識別デバイスとしての PROM、ハードウェア構成ファイル、または x86 プラットフォームでのブートシステムによって提供されます。
一部の DDI メカニズムには、コールバックメカニズムが備わっています。DDI 関数には、条件が満たされたときにコールバックをスケジュールするためのメカニズムが備わっています。コールバック関数は、次の一般的な条件に対して使用できます。
転送が完了した
リソースが利用できるようになった
タイムアウト期間が経過した
コールバック関数は、割り込みハンドラなどのエントリポイントに多少似ています。コールバックを許可する DDI 関数は、コールバック関数が特定のタスクを実行するものとみなします。DMA ルーチンの場合、コールバック関数は、障害が発生した場合にコールバック関数を再スケジュールする必要があるかどうかを示す値を返します。
コールバック関数は、単独の割り込みスレッドとして実行されます。コールバックは、通常のマルチスレッド問題をすべて処理する必要があります。
状態構造体を割り当てる際にデバイスドライバの作成者を支援するために、DDI/DKI ではソフトウェア状態管理ルーチン (soft-state ルーチン) と呼ばれる一連のメモリー管理ルーチンを提供します。これらのルーチンは、指定されたサイズのメモリー項目の動的な割り当て、取得、および破棄を行い、リスト管理の詳細を非表示にします。インスタンス番号は、目的のメモリー項目を特定するために使われます。この番号は通常、システムによって割り当てられるインスタンス番号です。
ルーチンは、次のタスクに対して提供されます。
ドライバのソフト状態リストを初期化する
ドライバのソフト状態のインスタンスに領域を割り当てる
ドライバのソフト状態のインスタンスを指すポインタを取得する
ドライバのソフト状態のインスタンスのメモリーを解放する
ドライバのソフト状態リストの使用を終了する
これらのルーチンの使用方法の例については、ロード可能なドライバインタフェースを参照してください。
プログラム式入出力デバイスアクセスとは、ホストの CPU からデバイスレジスタやデバイスメモリーの読み書きを行う動作のことです。Oracle Solaris DDI には、カーネルによってデバイスのレジスタやメモリーをマッピングするためのインタフェースのほかに、ドライバからデバイスメモリーの読み書きを行うためのインタフェースも備わっています。これらのインタフェースを使用すると、デバイスとホストのエンディアンネスのあらゆる違いを自動的に管理したり、デバイスで設定されたメモリーとストアのシーケンス要件を適用したりすることによって、プラットフォームやバスに依存しないドライバを開発できます。
Oracle Solaris プラットフォームでは、DMA 対応デバイスをサポートするための、アーキテクチャーに依存しないハイレベルなモデルを定義しています。Oracle Solaris DDI は、プラットフォーム固有の詳細からドライバを遮蔽します。この概念を使えば、複数のプラットフォームやアーキテクチャー上で 1 つの共通のドライバを動作させることができます。
DDI/DKI には、階層化デバイスインタフェース (LDI) と呼ばれるインタフェースグループがあります。このインタフェースを使用すると、Oracle Solaris カーネルの内部からデバイスにアクセスできます。この機能を使用すると、開発者はカーネルデバイスの使用を監視するアプリケーションを作成できます。たとえば、prtconf(1M) と fuser(1M) の両方のコマンドで LDI を使用すると、システム管理者はデバイス使用のさまざまな局面を追跡できます。LDI については、階層化ドライバインタフェース (LDI)で詳しく説明しています。
ドライバコンテキストとは、ドライバが現在動作している状況のことを意味します。コンテキストは、ドライバが実行できる操作を制限します。ドライバコンテキストは、呼び出される実行コードによって異なります。ドライバコードは、次の 4 つのコンテキストで実行されます。
ユーザーコンテキスト。同期方式でユーザースレッドによって呼び出された場合、ドライバのエントリポイントにユーザーコンテキストがあります。つまり、ユーザースレッドは、呼び出されたエントリポイントからシステムが復帰するのを待ちます。たとえば、ドライバの read(9E) エントリポイントが read(2) システムコールから呼び出された場合、そのエントリポイントにユーザーコンテキストがあります。この場合、ドライバはデータをユーザースレッドにコピーしたりユーザースレッドからコピーしたりするためにユーザー領域にアクセスできます。
カーネルコンテキスト。カーネルの一部から呼び出された場合、ドライバ関数はカーネルコンテキストで動作します。ブロック型デバイスドライバでは、デバイスにページを書き込むために、strategy(9E) エントリポイントが pageout デーモンによって呼び出されることがあります。このページデーモンは現在のユーザースレッドとは関係がないため、この場合、strategy(9E) はカーネルコンテキストで動作します。
割り込みコンテキスト。割り込みコンテキストはカーネルコンテキストのより制限された形式です。割り込みコンテキストは、割り込みが処理された結果、呼び出されます。ドライバ割り込みルーチンは、関連付けられた割り込みレベルを使って割り込みコンテキストで動作します。コールバックルーチンも同様に割り込みコンテキストで動作します。詳細は、割り込みハンドラを参照してください。
高レベルの割り込みコンテキスト。高レベルの割り込みコンテキストは割り込みコンテキストのより制限された形式です。割り込みが高レベルであることをddi_intr_hilevel(9F) が示している場合、ドライバ割り込みハンドラは高レベルの割り込みコンテキストで動作します。詳細は、割り込みハンドラを参照してください。
セクション 9F のマニュアルページには、各関数に使用できるコンテキストが記載されています。たとえば、カーネルコンテキストでは、ドライバは copyin(9F) を呼び出してはいけません。
デバイスドライバは通常、メッセージを出力しませんが、データの破損などの予期しないエラーが発生した場合は例外です。ドライバのエントリポイントは代わりにエラーコードを返して、アプリケーションがエラーの処理方法を決定できるようにします。cmn_err(9F) 関数を使用してメッセージをシステムログに書き出せば、そのあとでコンソールに表示できます。
cmn_err(9F) によって解釈される書式文字列指定子は printf(3C) 書式文字列指定子に似ていますが、ビットフィールドを出力する %b 書式が追加されています。この書式文字列の最初の文字には特別な意味を持たせることができます。cmn_err (9F) の呼び出しでは、出力される重要度ラベルを示すメッセージレベル (level) も指定します。詳細は、cmn_err(9F) のマニュアルページを参照してください。
CE_PANIC レベルには、システムをクラッシュさせるという副作用があります。このレベルは、システムが、続行することによってより多くの問題が発生するような不安定な状態である場合にだけ使用されます。このレベルは、デバッグ時にシステムコアダンプを取るためにも使用できます。CE_PANIC は本稼働デバイスドライバでは使用しません。
デバイスドライバは、自分で動作させることができるすべての接続デバイスを同時に処理する準備ができている必要があります。ドライバが処理するデバイスの数には制限がありません。デバイスごとの情報をすべて動的に割り当てる必要があります。
void *kmem_alloc(size_t size, int flag);
標準的なカーネルメモリー割り当てルーチンは、kmem_alloc(9F) です。kmem_alloc() は C ライブラリルーチン malloc(3C) に似ていますが、flag 引数が追加されています。flag 引数には、要求されたサイズが使用できない場合に呼び出し側がブロックするかどうかを示す KM_SLEEP または KM_NOSLEEP を指定できます。KM_NOSLEEP が設定されていて、メモリーが使用できない場合、kmem_alloc(9F) は NULL を返します。
kmem_zalloc(9F) は kmem_alloc(9F) に似ていますが、割り当てられたメモリーの内容もクリアします。
void kmem_free(void *cp, size_t size);
kmem_alloc(9F) または kmem_zalloc(9F) によって割り当てられたメモリーは、kmem_free(9F) を使ってシステムに返されます。kmem_free() は C ライブラリルーチン free(3C) に似ていますが、size 引数が追加されています。ドライバは、あとで kmem_free(9F) を呼び出すために、割り当てられた各オブジェクトのサイズを追跡して記録する必要があります。
このマニュアルには、ホットプラグによる取り付けに関する情報は記載されていません。このドキュメントに記載されたデバイスドライバ作成のための規則や提案に従えば、ドライバでホットプラグによる取り付けを扱えるようになります。特に、自動構成 (ドライバの自動構成を参照) とdetach(9E) の両方がドライバで正しく機能することを確認してください。また、電源管理を使用するドライバを設計している場合は、電源管理に記載された情報に従うようにしてください。SCSI HBA ドライバでは、ホットプラグによる取り付け機能を利用するために、cb_ops 構造体をその dev_ops 構造体 (SCSI ホストバスアダプタドライバを参照) に追加することが必要な場合があります。
以前の Oracle Solaris OS バージョンでは、DT_HOTPLUG プロパティーを組み込むためにホットプラグ対応ドライバが必要でしたが、このプロパティーは必要なくなりました。ただし、ドライバの作成者が適切と思えば、DT_HOTPLUG プロパティーを組み込んで使用しても構いません。
デバイスが正常に機能するには、デバイスのファームウェアとデバイスドライバの互換性がなければなりません。デバイスドライバをインストールまたはアップグレードする前に、ファームウェアとの互換性をチェックする必要があります。互換性のあるファームウェアが見つからない場合は、デバイスドライバの実装によって、デバイスドライバがインストールまたはアップグレードされないように保証する必要があります。
好ましいデバイスドライバの設計では、1 つ以上の ELF (実行可能リンク形式) モジュール内にファームウェアをカプセル化し、attach() 関数を使用してファームウェアのバージョンを管理します。ELF モジュール内にファームウェアをカプセル化するには、elfwrap(1) コマンドを使用します。ファームウェア ELF モジュールは、misc モジュールとしてモジュールリンケージスタブとリンクされます。ファームウェアモジュールは /kernel/misc ディレクトリに存在します。
次のフローチャートに、デバイスドライバの attach() 関数でファームウェアのバージョンを管理する方法を示します。
図 1 attach() 関数でのファームウェアのバージョン管理
詳細は、attach(9E)、ddi_modopen(9F)、ddi_modsym(9F)、およびddi_modclose(9F) のマニュアルページを参照してください。
ファームウェアのバージョンをチェックする attach() 関数の一般的な疑似コードは次のとおりです。
static int xxxx_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { ..... ddi_modhandle_t modfn; int errno; uint32_t *fwvp; ..... long fw_version; ..... /* Check if firmware update is disabled. * If firmware update is disabled, return. * The setting is stored in a config file. */ if(dip->firmware_disable_update){ return (0); } /* Get the current firmware version. */ fw_version = xxxx_get_firmware_version(dip,...); /* Check if the firmware version is correct and if force update is disabled. */ if(fw_version == REQUIRED_FIRMWARE_VERSION){ if(dip->firmware_force_update == 0){ return (0); } /* Even though the firmware version matches, update is forced. */ } /* Open the firmware file by using ddi_modopen. */ modfn = ddi_modopen(FIRMWARE_FILENAME, KRTLD_MODE_FIRST, &errno); if (errno){ /* handle error */ } /* locate the firmware */ fwvp = ddi_modsym(modfn, FIRMWARE_VERSION_NAME, &errno); if (errno){ /* handle error */ (void) ddi_modclose(modfn); } /* Check if the firmware version in the module is correct. */ if (*fwvp != REQUIRED_FIRMWARE_VERSION){ if(dip->firmware_force_update == 0){ /* close the firmware module file */ (void) ddi_modclose(modfn); return (0); } /* Even tough the firmware version in the module does not match, * update is forced. */ } /* Update the firmware. */ xxx_update_firmware(dip,....); /* Close the FIRMWARE file */ (void) ddi_modclose(modfn); ..... // resume attach }