ナビゲーションリンクをスキップ | |
印刷ビューの終了 | |
![]() |
デバイスドライバの記述 Oracle Solaris 11.1 Information Library (日本語) |
パート I Oracle Solaris プラットフォーム用デバイスドライバの設計
2. Oracle Solaris カーネルとデバイスツリー
標準メッセージシグナル割り込みと拡張メッセージシグナル割り込み
22. ドライバのコンパイル、ロード、パッケージ化、およびテスト
23. デバイスドライバのデバッグ、テスト、およびチューニング
このセクションでは、多数の異なる割り込み条件を生成する可能性のあるデバイスのドライバで、割り込みリソース管理機能を利用して割り込みベクターの割り当てを最適化する方法について説明します。
割り込みリソース管理機能を使用すると、ドライバの割り込み構成を動的に管理することで、デバイスドライバで使用する割り込みリソースを増やすことができます。割り込みリソース管理機能を使用しないと、割り込み処理の構成は通常、ドライバの attach (9E) ルーチンでのみ行われます。割り込みリソース管理機能では、システムの変更を監視し、その変更に応答して各デバイスに許可する割り込みベクターの数を再計算して、ドライバの新しい割り込みベクター割り当ての影響を受ける、関連するドライバに通知します。関連するドライバとは、コールバックハンドラを登録したドライバのことです (「コールバックのインタフェース」を参照)。割り込みベクターの再割り当ての原因になる可能性のある変更には、デバイスの追加や削除、明示的な要求が含まれます (「要求する割り込みベクターの数の変更」を参照)。
割り込みリソース管理機能は、すべての Oracle Solaris プラットフォームで利用できるわけではありません。この機能は、MSI-X 割り込みを利用する PCIe デバイスでのみ利用できます。
注 - 割り込みリソース管理機能を利用するドライバは、この機能が利用できないときに適切に順応できる必要があります。
割り込みリソース管理機能が利用できる場合、ドライバは、この機能が利用できない場合に割り当てられるよりも多くの割り込みベクターにアクセスできます。ドライバで利用できる割り込みベクターの数が増えると、ドライバが割り込み条件を処理する効率が向上することがあります。
割り込みリソース管理機能では、次の制約に応じて、関連するドライバに許可される割り込みベクターの数を動的に調整します。
利用可能な合計数。システムでは割り込みベクターの数に制限があります。
要求される合計数。ドライバにはこれより少ない数を許可できますが、ドライバが要求した割り込みベクターよりも多くの割り込みベクターを許可することはできません。
ほかのドライバとの公平性。利用可能な割り込みベクターの合計数は、各ドライバが要求する合計数と関連して公平な方法で、多数のドライバによって共有されます。
指定された任意の時点でデバイスで利用できるようになる割り込みベクターの数は、次の条件により異なることがあります。
ほかのデバイスが動的にシステムに追加されたり削除されたりする
ロードに応答してドライバが要求する割り込みベクターの数はドライバにより動的に変更される
ドライバは、割り込みリソース管理機能を利用するために次のサポートを提供する必要があります。
コールバックサポート。ドライバは、利用可能な割り込みの数がシステムよって変更されたときに通知されるように、コールバックハンドラを登録する必要があります。ドライバは、割り込みの使用率を増やしたり減らしたりできる必要があります。
割り込みの要求。ドライバは、使用する割り込みの数を指定する必要があります。
割り込みの使用率。ドライバは、次の点に基づいて、任意の時点で正確な数の割り込みを要求する必要があります。
ドライバのハードウェアが生成する可能性のある割り込み条件
それらの条件を並列で処理するために使用できるプロセッサの数
割り込みの柔軟性。ドライバには、利用可能な割り込みの現在の数に最適な方法で、1 つ以上の割り込み条件を各割り込みベクターに割り当てるために十分な柔軟性が必要です。ドライバでは、利用可能な割り込みの数が任意の時点で増減したときに、この割り当てを再構成する必要が生じる場合があります。
ドライバは、次のインタフェースを使用してコールバックサポートを登録する必要があります。
表 8-1 コールバックサポートのインタフェース
|
ドライバのコールバックハンドラ関数を登録するときは、ddi_cb_register(9F) 関数を使用します。
int ddi_cb_register (dev_info_t *dip, ddi_cb_flags_t cbflags, ddi_cb_func_t cbfunc, void *arg1, void *arg2, ddi_cb_handle_t *ret_hdlp);
ドライバは、1 つのコールバック関数のみを登録できます。この 1 つのコールバック関数が、個々のすべてのコールバックアクションを処理するために使用されます。cbflags パラメータは、アクションが発生したときにドライバが受信するアクションのタイプを判定します。cbfunc() ルーチンは、関連するアクションをドライバが処理するときに常に呼び出されます。ドライバは、cbfunc() ルーチンを実行するたびに自分に送信する 2 つのプライベート引数 (arg1 と arg2) を指定します。
cbflags() パラメータは、ドライバがサポートするアクションを指定する列挙型です。
typedef enum { DDI_CB_FLAG_INTR } ddi_cb_flags_t;
割り込みリソース管理アクションのサポートを登録するには、ドライバでハンドラを登録して DDI_CB_FLAG_INTR フラグを含める必要があります。コールバックハンドラが正常に登録されると、ret_hdlp パラメータを介して不透明なハンドルが返されます。ドライバでコールバックハンドラの処理が終了すると、ドライバは ret_hdlp パラメータを使用してコールバックハンドラの登録を解除できます。
コールバックハンドラをドライバの attach(9F) エントリポイントで登録します。不透明なハンドルをドライバのソフト状態に保存します。ドライバの detach(9F) エントリポイントでコールバックハンドラの登録を解除します。
ドライバのコールバックハンドラ関数の登録を解除するときは、ddi_cb_unregister(9F) 関数を使用します。
int ddi_cb_unregister (ddi_cb_handle_t hdl);
この呼び出しは、ドライバの detach(9F) エントリポイントで行います。この呼び出しのあと、ドライバはコールバックアクションを受信しなくなります。
ドライバは、登録されたコールバック処理関数があることによって取得した、システムによる追加サポートも失います。たとえば、ドライバで前に利用できるようになった一部の割り込みベクターは、ドライバがコールバック処理関数の登録を解除するとすぐに元に戻されます。正常に復帰する前に、ddi_cb_unregister() 関数は、システムのサポートを失うことによって発生する、最終的なアクションをドライバに通知します。
コールバックアクションを受信し、処理する各アクションに固有の引数を受信するときは、登録されたコールバック処理関数を使用します。
typedef int (*ddi_cb_func_t)(dev_info_t *dip, ddi_cb_action_t cbaction, void *cbarg, void *arg1, void *arg2);
cbaction パラメータは、ドライバがコールバックを受信するときの処理対象となるアクションを指定します。
typedef enum { DDI_CB_INTR_ADD, DDI_CB_INTR_REMOVE } ddi_cb_action_t;
DDI_CB_INTR_ADD アクションは、ドライバで使用可能な割り込みが増えることを意味します。DDI_CB_INTR_REMOVE アクションは、ドライバで使用可能な割り込みが減ることを意味します。追加または削除された割り込みの数を判定するときは、cbarg パラメータを int にキャストします。cbarg 値は、利用可能な割り込みの数の変化を表します。
たとえば、利用可能な割り込みの数の変化を取得する場合は、次のようになります。
count = (int)(uintptr_t)cbarg;
cbaction が DDI_CB_INTR_ADD である場合は、cbarg 個の割り込みベクターを追加します。cbaction が DDI_CB_INTR_REMOVE である場合は、cbarg 個の割り込みベクターを解放します。
arg1 および arg2 の説明については、ddi_cb_register(9F) を参照してください。
コールバック処理関数は、関数が登録される期間全体を通じて、正しく実行できる必要があります。コールバック関数は、コールバック関数の登録が正常に解除される前に破棄される可能性のあるデータ構造体に依存することはできません。
コールバック処理関数は、次のいずれかの値を返す必要があります。
アクションを正しく処理した場合は DDI_SUCCESS
内部エラーが発生した場合は DDI_FAILURE
認識できないアクションを受信した場合は DDI_ENOTSUP
ドライバは、次のインタフェースを使用して、システムからの割り込みベクターを要求する必要があります。
表 8-2 割り込みベクター要求のインタフェース
|
最初に割り込みを割り当てるときは、ddi_intr_alloc(9F) 関数を使用します。
int ddi_intr_alloc (dev_info_t *dip, ddi_intr_handle_t *h_array, int type, int inum, int count, int *actualp, int behavior);
ドライバは、この関数を呼び出す前に、要求する数の割り込みを格納するのに十分な大きさの空のハンドル配列を割り当てる必要があります。ddi_intr_alloc() 関数は、count 個の割り込みハンドルを割り当てて、inum パラメータで指定されたオフセットから始まる割り当て済みの割り込みベクターを使って配列を初期化しようとします。actualp パラメータは、割り当てられた割り込みベクターの実際の数を返します。
ドライバは、次の 2 つの方法で ddi_intr_alloc() 関数を使用できます。
ドライバは、ddi_intr_alloc() 関数を複数回呼び出して、割り込みハンドル配列の個々のメンバーに別々の手順で割り込みベクターを割り当てることができます。
ドライバは、ddi_intr_alloc() 関数を 1 回呼び出して、デバイスのすべての割り込みベクターを一度に割り当てることができます。
割り込みリソース管理機能を使用している場合は、 ddi_intr_alloc() を 1 回呼び出して、すべての割り込みベクターを一度に割り当てます。count パラメータは、ドライバが要求する割り込みベクターの合計数です。actualp の値が count の値より小さい場合、システムは要求を完全に満たすことはできません。割り込みリソース管理機能では、この要求 (count が nreq になる - 下記を参照) を保存するため、あとでさらに多くの割り込みベクターをこのドライバに割り当てることができるようになる場合があります。
注 - 割り込みリソース管理機能を使用すると、ddi_intr_alloc() を追加で呼び出しても、要求される割り込みベクターの合計数は変化しません。要求する割り込みベクターの数を変更するときは、ddi_intr_set_nreq(9F) 関数を使用します。
要求する割り込みベクターの数を変更するときは、ddi_intr_set_nreq(9F) 関数を使用します。
int ddi_intr_set_nreq (dev_info_t *dip, int nreq);
割り込みリソース管理機能が利用できるとき、ドライバは ddi_intr_set_nreq() 関数を使用して、要求する割り込みベクターの合計数を動的に調整できます。ドライバは、ドライバが接続されると存在するようになる実際のロードに応答してこれを行うことができます。
ドライバは、最初に ddi_intr_alloc(9F) を呼び出して、割り込みベクターの最初の数を要求する必要があります。ddi_intr_alloc() 呼び出しのあとで、ドライバは ddi_intr_set_nreq() を呼び出してその要求サイズをいつでも変更できます。指定した nreq 値は、ドライバの、要求する割り込みベクターの新しい合計数です。割り込みリソース管理機能では、この新しい要求に応答して、システムの各ドライバに割り当てられた割り込みの数のバランスをとり直すことがあります。割り込みリソース管理機能によって、ドライバに割り当てられた割り込みの数のバランスがとり直されると、影響を受ける各ドライバは常に、ドライバが使用可能な割り込みベクターが増えたまたは減ったというコールバック通知を受信します。
たとえば、処理中の特定のトランザクションに関連して割り込みを使用する場合、ドライバは要求する割り込みベクターの合計数を動的に調整することがあります。ストレージドライバは、DMA エンジンを進行中の各トランザクションに関連付け、その理由で割り込みベクターを要求することがあります。ドライバは、open(9F) および close(9F) ルーチンで ddi_intr_set_nreq() を呼び出して、ドライバの実際の使用に応じて割り込みの使用率を調整することがあります。
多数の異なる割り込み条件をサポートするデバイスのドライバは、それらの条件を任意の数の割り込みベクターにマッピングできる必要があります。そのようなドライバは、割り当てられる割り込みベクターが利用可能なままであると想定することはできません。現在利用可能な一部の割り込みは、システム内のほかのドライバの必要に対応するため、あとでシステムによって元に戻されることがあります。
ドライバは、次のことができる必要があります。
ハードウェアがサポートする割り込みの数を判定する。
使用に適した割り込みの数を判定する。たとえば、システムのプロセッサの合計数がこの評価に影響することがあります。
必要な割り込みの数と、指定された任意の時点で利用可能な割り込みの数とを比較する。
まとめると、ドライバは、一連の割り込み処理関数を選択し、ハードウェアをプログラムして必要性と割り込みの可用性に応じて割り込みを生成できる必要があります。場合によっては、複数の割り込みが同じベクターのターゲットになることがあります。また、その割り込みベクターの割り込みハンドラは、発生した割り込みを判定する必要があります。デバイスのパフォーマンスは、ドライバが割り込みベクターに割り込みをマッピングする能力の影響を受けることがあります。
割り込みリソース管理の好例となるデバイスドライバのタイプの 1 つは、ネットワークデバイスのドライバです。ネットワークデバイスのハードウェアは、複数の送受信チャネルをサポートしています。
ネットワークデバイスは、デバイスがいずれかの受信チャネルでパケットを受信するか、いずれかの送信チャネルでパケットを送信すると常に、一意の割り込み条件を生成します。ハードウェアは、発生する可能性のあるイベントごとに特定の MSI-X 割り込みを送信できます。ハードウェア内のテーブルによって、イベントごとに生成する MSI-X 割り込みが判定されます。
パフォーマンスを最適化するために、ドライバはシステムからの十分な割り込みを要求して、別個の各割り込みに独自の割り込みベクターを提供します。ドライバは、attach(9F) ルーチンで最初に ddi_intr_alloc(9F) を呼び出すときにこの要求を行います。
次に、ドライバは、actualp の ddi_intr_alloc() から受信した割り込みの実際の数を評価します。ドライバは、要求したすべての割り込みを受信することもあり、要求より少ない割り込みを受信することもあります。
ドライバ内の別個の関数は、利用可能な割り込みの合計数を使用して、イベントごとに生成する MSI-X 割り込みを計算します。この関数は、その結果に従ってハードウェア内のテーブルをプログラムします。
要求したすべての割り込みベクターをドライバが受信した場合、ハードウェアのテーブル内の各エントリに、独自の一意の MSI-X 割り込みがあることになります。割り込み条件と割り込みベクターの間には、1 対 1 のマッピングが存在します。ハードウェアは、イベントのタイプごとに一意の MSI-X 割り込みを生成します。
ドライバが持つ利用可能な割り込みベクターが少ない場合は、一部の MSI-X 割り込み番号をハードウェアのテーブルで複数回使用する必要があります。ハードウェアは、複数のタイプのイベントで同じ MSI-X 割り込みを生成します。
ドライバには、2 つの異なる割り込みハンドラ関数があるはずです。
1 つの割り込みハンドラ関数は、割り込みに応答して特定のタスクを実行します。この単純な関数が、可能性のあるハードウェアイベントのうちただ 1 つのイベントによって生成される割り込みを処理します。
もう 1 つの割り込みハンドラ関数はさらに複雑です。この関数は、複数の割り込みが同じ MSI-X 割り込みベクターにマッピングされる場合を扱います。
このセクションのドライバ例では、関数 xx_setup_interrupts() は、利用可能な割り込みベクターの数を使用してハードウェアをプログラムし、それらの割り込みベクターごとに適切な割り込みハンドラを呼び出します。xx_setup_interrupts() 関数は、2 か所で呼び出されます。ddi_intr_alloc() が xx_attach () で呼び出されたあとと、割り込みベクター割り当てが xx_cbfunc() コールバックハンドラ関数で調整されたあとです。
int xx_setup_interrupts(xx_state_t *statep, int navail, xx_intrs_t *xx_intrs_p);
xx_setup_interrupts() 関数は、xx_intrs_t データ構造体の配列を使って呼び出されます。
typedef struct { ddi_intr_handler_t inthandler; void *arg1; void *arg2; } xx_intrs_t;
この xx_setup_interrupts() 機能は、割り込みリソース管理機能が利用できるかどうかに関係なくドライバに存在している必要があります。ドライバは、接続時に要求する数より少ない割り込みベクターで機能できる必要があります。割り込みリソース管理機能が利用できる場合は、利用可能な割り込みベクターの新しい数に動的に調整されるようにドライバを変更できます。
割り込みリソース管理機能が利用できるかどうかに関係なくドライバが提供する必要のある別の機能には、ハードウェアを休止および再開する機能が含まれます。休止と再開は、電源管理とホットプラグによる取り付けに関係する特定のイベントに必要です。休止と再開は、割り込みコールバックアクションの処理にも必要です。
休止関数は、xx_detach() で呼び出されます。
int xx_quiesce(xx_state_t *statep);
再開関数は、xx_attach() で呼び出されます。
int xx_resume(xx_state_t *statep);
このデバイスドライバを拡張して割り込みリソース管理機能を使用するときは、次の変更を行います。
コールバックハンドラの登録。ドライバは、利用可能な割り込みが多い時と少ない時を示すアクションのために登録を行う必要があります。
コールバックの処理。ドライバは、コールバックアクションに応答してハードウェアを休止し、割り込み処理をプログラムし、ハードウェアを再開する必要があります。
/* * attach(9F) routine. * * Creates soft state, registers callback handler, initializes * hardware, and sets up interrupt handling for the driver. */ xx_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { xx_state_t *statep = NULL; xx_intr_t *intrs = NULL; ddi_intr_handle_t *hdls; ddi_cb_handle_t cb_hdl; int instance; int type; int types; int nintrs; int nactual; int inum; /* Get device instance */ instance = ddi_get_instance(dip); switch (cmd) { case DDI_ATTACH: /* Get soft state */ if (ddi_soft_state_zalloc(state_list, instance) != 0) return (DDI_FAILURE); statep = ddi_get_soft_state(state_list, instance); ddi_set_driver_private(dip, (caddr_t)statep); statep->dip = dip; /* Initialize hardware */ xx_initialize(statep); /* Register callback handler */ if (ddi_cb_register(dip, DDI_CB_FLAG_INTR, xx_cbfunc, statep, NULL, &cb_hdl) != 0) { ddi_soft_state_free(state_list, instance); return (DDI_FAILURE); } statep->cb_hdl = cb_hdl; /* Select interrupt type */ ddi_intr_get_supported_types(dip, &types); if (types & DDI_INTR_TYPE_MSIX) { type = DDI_INTR_TYPE_MSIX; } else if (types & DDI_INTR_TYPE_MSI) { type = DDI_INTR_TYPE_MSI; } else { type = DDI_INTR_TYPE_FIXED; } statep->type = type; /* Get number of supported interrupts */ ddi_intr_get_nintrs(dip, type, &nintrs); /* Allocate interrupt handle array */ statep->hdls_size = nintrs * sizeof (ddi_intr_handle_t); statep->hdls = kmem_zalloc(statep->hdls_size, KMEM_SLEEP); /* Allocate interrupt setup array */ statep->intrs_size = nintrs * sizeof (xx_intr_t); statep->intrs = kmem_zalloc(statep->intrs_size, KMEM_SLEEP); /* Allocate interrupt vectors */ ddi_intr_alloc(dip, hdls, type, 0, nintrs, &nactual, 0); statep->nactual = nactual; /* Configure interrupt handling */ xx_setup_interrupts(statep, statep->nactual, statep->intrs); /* Install and enable interrupt handlers */ for (inum = 0; inum < nactual; inum++) { ddi_intr_add_handler(&hdls[inum], intrs[inum].inthandler, intrs[inum].arg1, intrs[inum].arg2); ddi_intr_enable(hdls[inum]); } break; case DDI_RESUME: /* Get soft state */ statep = ddi_get_soft_state(state_list, instance); if (statep == NULL) return (DDI_FAILURE); /* Resume hardware */ xx_resume(statep); break; } return (DDI_SUCESS); } /* * detach(9F) routine. * * Stops the hardware, disables interrupt handling, unregisters * a callback handler, and destroys the soft state for the driver. */ xx_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { xx_state_t *statep = NULL; int instance; int inum; /* Get device instance */ instance = ddi_get_instance(dip); switch (cmd) { case DDI_DETACH: /* Get soft state */ statep = ddi_get_soft_state(state_list, instance); if (statep == NULL) return (DDI_FAILURE); /* Stop device */ xx_uninitialize(statep); /* Disable and free interrupts */ for (inum = 0; inum < statep->nactual; inum++) { ddi_intr_disable(statep->hdls[inum]); ddi_intr_remove_handler(statep->hdls[inum]); ddi_intr_free(statep->hdls[inum]); } /* Unregister callback handler */ ddi_cb_unregister(statep->cb_hdl); /* Free interrupt handle array */ kmem_free(statep->hdls, statep->hdls_size); /* Free interrupt setup array */ kmem_free(statep->intrs, statep->intrs_size); /* Free soft state */ ddi_soft_state_free(state_list, instance); break; case DDI_SUSPEND: /* Get soft state */ statep = ddi_get_soft_state(state_list, instance); if (statep == NULL) return (DDI_FAILURE); /* Suspend hardware */ xx_quiesce(statep); break; } return (DDI_SUCCESS); } /* * (*ddi_cbfunc)() routine. * * Adapt interrupt usage when availability changes. */ int xx_cbfunc(dev_info_t *dip, ddi_cb_action_t cbaction, void *cbarg, void *arg1, void *arg2) { xx_state_t *statep = (xx_state_t *)arg1; int count; int inum; int nactual; switch (cbaction) { case DDI_CB_INTR_ADD: case DDI_CB_INTR_REMOVE: /* Get change in availability */ count = (int)(uintptr_t)cbarg; /* Suspend hardware */ xx_quiesce(statep); /* Tear down previous interrupt handling */ for (inum = 0; inum < statep->nactual; inum++) { ddi_intr_disable(statep->hdls[inum]); ddi_intr_remove_handler(statep->hdls[inum]); } /* Adjust interrupt vector allocations */ if (cbaction == DDI_CB_INTR_ADD) { /* Allocate additional interrupt vectors */ ddi_intr_alloc(dip, statep->hdls, statep->type, statep->nactual, count, &nactual, 0); /* Update actual count of available interrupts */ statep->nactual += nactual; } else { /* Free removed interrupt vectors */ for (inum = statep->nactual - count; inum < statep->nactual; inum++) { ddi_intr_free(statep->hdls[inum]); } /* Update actual count of available interrupts */ statep->nactual -= count; } /* Configure interrupt handling */ xx_setup_interrupts(statep, statep->nactual, statep->intrs); /* Install and enable interrupt handlers */ for (inum = 0; inum < statep->nactual; inum++) { ddi_intr_add_handler(&statep->hdls[inum], statep->intrs[inum].inthandler, statep->intrs[inum].arg1, statep->intrs[inum].arg2); ddi_intr_enable(statep->hdls[inum]); } /* Resume hardware */ xx_resume(statep); break; default: return (DDI_ENOTSUP); } return (DDI_SUCCESS); }