割り込みリソース管理の好例となるデバイスドライバのタイプの 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);
}