このセクションでは、非同期入出力転送を実行するための方法を示します。ドライバは入出力要求をキューに入れたあと、呼び出し元に制御を戻します。ここでも、ハードウェアが、一度に 1 つの転送が可能な単純なディスク装置であることを前提にしています。デバイスは、データ転送が完了したときに割り込みます。また、エラーが発生した場合にも割り込みが実行されます。非同期データ転送を実行するための基本的な手順は次のとおりです。
無効な buf(9S) 要求をチェックします。
要求をキューに登録します。
最初の転送を開始します。
割り込んでいるデバイスを処理します。
同期の場合と同様に、デバイスドライバは、strategy(9E) に渡された buf(9S) 構造体の有効性をチェックします。詳細については、同期データ転送 (ブロックドライバ)を参照してください。
同期データ転送とは異なり、ドライバは非同期要求の完了を待機しません。代わりに、ドライバはその要求をキューに追加します。キューの先頭は、現在の転送である場合があります。キューの先頭はまた、使用例 89 に示すように、アクティブな要求を保持するための状態構造体の別のフィールドである場合もあります。
キューが最初に空である場合は、ハードウェアがビジー状態ではないため、strategy(9E) は復帰する前に転送を開始します。それ以外の場合、空でないキューで転送が完了すると、割り込みルーチンは新しい転送を開始します。使用例 89 では、新しい転送を開始するかどうかの判定を便宜上、別のルーチンで行なっています。
ドライバは、buf(9S) 構造体の av_forw メンバーと av_back メンバーを使用して転送要求のリストを管理できます。1 つのポインタを使用して片方向リンクリストを管理することも、両方のポインタを一緒に使用して両方向リンクリストを構築することもできます。デバイスのハードウェア仕様によって、デバイスのパフォーマンスを最適化するために (ポリシーの挿入などの) どのタイプのリスト管理を使用するかが指定されます。転送リストはデバイスごとのリストであるため、このリストの先頭と最後尾は状態構造体に格納されます。
次の例は、転送リストなどのドライバの共有データにアクセスできる複数のスレッドを示しています。共有データを識別し、mutex を使用してそのデータを保護する必要があります。mutex のロックの詳細については、マルチスレッドを参照してください。
使用例 89 ブロックドライバに対するデータ転送要求のキューへの登録static int
xxstrategy(struct buf *bp)
{
struct xxstate *xsp;
minor_t instance;
instance = getminor(bp->b_edev);
xsp = ddi_get_soft_state(statep, instance);
/* ... */
/* validate transfer request */
/* ... */
/*
* Add the request to the end of the queue. Depending on the device, a sorting
* algorithm, such as disksort(9F) can be used if it improves the
* performance of the device.
*/
mutex_enter(&xsp->mu);
bp->av_forw = NULL;
if (xsp->list_head) {
/* Non-empty transfer list */
xsp->list_tail->av_forw = bp;
xsp->list_tail = bp;
} else {
/* Empty Transfer list */
xsp->list_head = bp;
xsp->list_tail = bp;
}
mutex_exit(&xsp->mu);
/* Start the transfer if possible */
(void) xxstart((caddr_t)xsp);
return (0);
}
キューへの登録を実装しているデバイスドライバは通常、start() ルーチンを備えています。start() は次の要求をキューから取り出し、デバイスとの間のデータ転送を開始します。この例では、start() はデバイスの状態 (ビジー状態か空いているか) には関係なく、すべての要求を処理します。
start() は、アイドルのデバイスを起動できるように strategy() が要求をキューに入れるたびに strategy(9E) によって呼び出されます。デバイスがビジー状態の場合、start() はただちに復帰します。
start() はまた、取り込まれた割り込みから割り込みハンドラが復帰する前に、その割り込みハンドラからも呼び出されるため、空でないキューを処理できます。キューが空の場合、start() はただちに復帰します。
start() は非公開のドライバルーチンであるため、start() は任意の引数を取り、任意の型を返すことができます。次のコーディング例は、DMA コールバックとして使用するために記述されています (ただし、その部分は示されていません)。したがって、この例では caddr_t を引数として取り、int を返す必要があります。DMA コールバックルーチンの詳細については、リソース割り当てエラーの処理を参照してください。
使用例 90 ブロックドライバに対する最初のデータ要求の開始static int
xxstart(caddr_t arg)
{
struct xxstate *xsp = (struct xxstate *)arg;
struct buf *bp;
mutex_enter(&xsp->mu);
/*
* If there is nothing more to do, or the device is
* busy, return.
*/
if (xsp->list_head == NULL || xsp->busy) {
mutex_exit(&xsp->mu);
return (0);
}
xsp->busy = 1;
/* Get the first buffer off the transfer list */
bp = xsp->list_head;
/* Update the head and tail pointer */
xsp->list_head = xsp->list_head->av_forw;
if (xsp->list_head == NULL)
xsp->list_tail = NULL;
bp->av_forw = NULL;
mutex_exit(&xsp->mu);
/*
* If the device has power manageable components,
* mark the device busy with pm_busy_components(9F),
* and then ensure that the device
* is powered up by calling pm_raise_power(9F).
*
* Set up DMA resources with ddi_dma_alloc_handle(9F) and
* ddi_dma_buf_bind_handle(9F).
*/
xsp->bp = bp;
ddi_put32(xsp->data_access_handle, &xsp->regp->dma_addr,
cookie.dmac_address);
ddi_put32(xsp->data_access_handle, &xsp->regp->dma_size,
(uint32_t)cookie.dmac_size);
ddi_put8(xsp->data_access_handle, &xsp->regp->csr,
ENABLE_INTERRUPTS | START_TRANSFER);
return (0);
}
割り込みルーチンは非同期バージョンに似ていますが、start() の呼び出しが追加され、cv_signal(9F) の呼び出しが削除されています。
使用例 91 非同期割り込みのためのブロックドライバルーチンstatic u_int
xxintr(caddr_t arg)
{
struct xxstate *xsp = (struct xxstate *)arg;
struct buf *bp;
uint8_t status;
mutex_enter(&xsp->mu);
status = ddi_get8(xsp->data_access_handle, &xsp->regp->csr);
if (!(status & INTERRUPTING)) {
mutex_exit(&xsp->mu);
return (DDI_INTR_UNCLAIMED);
}
/* Get the buf responsible for this interrupt */
bp = xsp->bp;
xsp->bp = NULL;
/*
* This example is for a simple device which either
* succeeds or fails the data transfer, indicated in the
* command/status register.
*/
if (status & DEVICE_ERROR) {
/* failure */
bp->b_resid = bp->b_bcount;
bioerror(bp, EIO);
} else {
/* success */
bp->b_resid = 0;
}
ddi_put8(xsp->data_access_handle, &xsp->regp->csr,
CLEAR_INTERRUPT);
/* The transfer has finished, successfully or not */
biodone(bp);
/*
* If the device has power manageable components that were
* marked busy in strategy(9F), mark them idle now with
* pm_idle_component(9F)
* Release any resources used in the transfer, such as DMA
* resources (ddi_dma_unbind_handle(9F) and
* ddi_dma_free_handle(9F)).
*
* Let the next I/O thread have access to the device.
*/
xsp->busy = 0;
mutex_exit(&xsp->mu);
(void) xxstart((caddr_t)xsp);
return (DDI_INTR_CLAIMED);
}