このセクションでは、同期入出力転送を実行するための単純な方法を示します。この方法では、ハードウェアが、DMA を使用して一度に 1 つのデータバッファーのみを転送できる単純なディスク装置であることを前提にしています。また、ソフトウェアコマンドでディスクを起動および停止できることも前提にしています。デバイスドライバの strategy(9E) ルーチンは、新しい要求を受け付ける前に現在の要求の完了を待機します。デバイスは、転送が完了したときに割り込みます。デバイスはまた、エラーが発生した場合にも割り込みます。
ブロックドライバの同期データ転送を実行するための手順は次のとおりです。
無効な buf(9S) 要求をチェックします。
strategy(9E) に渡された buf(9S) 構造体の有効性をチェックします。すべてのドライバは、次の条件を確認します。
要求が有効なブロックで始まっている。ドライバは、 b_blkno フィールドを正しいデバイスオフセットに変換したあと、そのオフセットがデバイスで有効かどうかを判定します。
要求がデバイス上の最終ブロックを超えていない。
デバイス固有の要件が満たされている。
エラーが検出された場合、ドライバは bioerror(9F) を使用して該当するエラーを示します。ドライバはその後、biodone(9F) を呼び出すことによって要求を完了します。biodone () は、strategy (9E) の呼び出し元に転送が完了したことを通知します。この場合は、エラーのために転送が停止されました。
デバイスがビジー状態かどうかをチェックします。
同期データ転送では、デバイスへのシングルスレッドアクセスが許可されます。デバイスドライバがこのアクセスを実施する場合、次の 2 つの方法があります。
ドライバが、mutex によって保護されるビジー状態フラグを保持する。
デバイスがビジー状態の場合、ドライバは cv_wait(9F) を使用して条件変数に関して待機する。
デバイスがビジー状態の場合、スレッドは、割り込みハンドラによってデバイスがビジー状態でなくなったことが示されるまで待機します。使用可能なステータスは、cv_broadcast (9F) または cv_signal(9F) 関数のどちらかで示すことができます。条件変数の詳細については、Chapter 3, Multithreadingを参照してください。
デバイスがビジー状態でなくなった場合、strategy (9E) ルーチンは、そのデバイスを使用可能としてマークします。strategy() は次に、転送のためにバッファーとデバイスを準備します。
DMA のためのバッファーを設定します。
ddi_dma_alloc_handle(9F) を使用して DMA ハンドルを割り当てることにより、DMA 転送のためのデータバッファーを準備します。データバッファーをハンドルにバインドするには、ddi_dma_buf_bind_handle(9F) を使用します。DMA リソースおよび関連するデータ構造体の設定については、Chapter 9, Direct Memory Access (DMA)を参照してください。
転送を開始します。
この時点では、 buf(9S) 構造体へのポインタがデバイスの状態構造体に保存されています。それにより、割り込みルーチンは、biodone (9F) を呼び出すことによって転送を完了できます。
デバイスドライバは次に、データ転送を開始するためにデバイスレジスタにアクセスします。ほとんどの場合、ドライバが、mutex を使用してデバイスレジスタをほかのスレッドから保護します。この場合は、strategy(9E) がシングルスレッドであるため、デバイスレジスタを保護する必要はありません。データロックの詳細については、Chapter 3, Multithreadingを参照してください。
実行スレッドがデバイスの DMA エンジンを起動した場合、ドライバは、次のように呼び出し元のルーチンに実行制御を戻すことができます。
static int
xxstrategy(struct buf *bp)
{
    struct xxstate *xsp;
    struct device_reg *regp;
    minor_t instance;
    ddi_dma_cookie_t cookie;
    instance = getminor(bp->b_edev);
    xsp = ddi_get_soft_state(statep, instance);
    if (xsp == NULL) {
        bioerror(bp, ENXIO);
        biodone(bp);
        return (0);
    }
    /* validate the transfer request */
    if ((bp->b_blkno >= xsp->Nblocks) || (bp->b_blkno < 0)) {
        bioerror(bp, EINVAL);    
        biodone(bp);
        return (0);
    }
    /*
     * Hold off all threads until the device is not busy.
     */
    mutex_enter(&xsp->mu);
    while (xsp->busy) {
        cv_wait(&xsp->cv, &xsp->mu);
    }
    xsp->busy = 1;
    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;
    regp = xsp->regp;
    ddi_put32(xsp->data_access_handle, ®p->dma_addr,
        cookie.dmac_address);
    ddi_put32(xsp->data_access_handle, ®p->dma_size,
        (uint32_t)cookie.dmac_size);
    ddi_put8(xsp->data_access_handle, ®p->csr,
        ENABLE_INTERRUPTS | START_TRANSFER);
    return (0);
}
割り込んでいるデバイスを処理します。
デバイスがデータ転送を完了すると、そのデバイスは割り込みを生成します。それにより、最終的にドライバの割り込みルーチンが呼び出されます。ほとんどのドライバは、割り込みを登録するときに、割り込みルーチンへの引数としてデバイスの状態構造体を指定します。ddi_add_intr (9F) のマニュアルページおよびRegistering Interruptsを参照してください。それにより、割り込みルーチンは、転送されている buf(9S) 構造体に加え、状態構造体から取得できるほかのすべての情報にアクセスできます。
割り込みハンドラは、転送がエラーなしで完了したかどうかを判定するために、デバイスのステータスレジスタをチェックします。エラーが発生した場合、ハンドラは bioerror (9F) を使用して該当するエラーを示します。ハンドラはまた、デバイスの保留中の割り込みをクリアしたあと、 biodone(9F) を呼び出すことによって転送を完了します。
最後のタスクとして、ハンドラはビジー状態フラグをクリアします。ハンドラは次に、条件変数に関して cv_signal(9F) または cv_broadcast(9F) を呼び出すことにより、デバイスがビジー状態でなくなったことを通知します。この通知により、 strategy(9E) でこのデバイスを待機しているほかのスレッドが、次のデータ転送に進むことができるようになります。
次の例は、同期割り込みルーチンを示しています。
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;
    cv_signal(&xsp->cv);
    mutex_exit(&xsp->mu);
    return (DDI_INTR_CLAIMED);
}