Go to main content
Oracle® Solaris 11.3 デバイスドライバの記述

印刷ビューの終了

更新: 2016 年 11 月
 
 

コマンドの構築とトランスポート

ホストバスアダプタドライバは、デバイスへのコマンドの転送を担当します。さらに、ドライバは低レベル SCSI プロトコルの処理も担当します。scsi_transport(9F) ルーチンは、転送のためにパケットをホストバスアダプタドライバに渡します。ターゲットドライバは、有効な scsi_pkt(9S) 構造体の作成を担当します。

コマンドの構築

ルーチンscsi_init_pkt(9F) は、この例で示すように、SCSI CDB に領域を割り当て、必要な場合には DMA リソースを割り当てて、pkt_flags フィールドを設定します。

pkt = scsi_init_pkt(&sdp->sd_address, NULL, bp,
    CDB_GROUP0, 1, 0, 0, SLEEP_FUNC, NULL);

この例では、渡されたbuf(9S) 構造体ポインタで指定されているように DMA リソースを割り当てるとともに、新しいパケットを作成しています。SCSI CDB はグループ 0 (6 バイト) のコマンドに割り当てられています。pkt_flags フィールドは 0 に設定されていますが、pkt_private フィールドに割り当てられた領域はありません。scsi_init_pkt(9F) に対するこの呼び出しは、SLEEP_FUNC パラメータのため、使用できるリソースが現在ない場合はリソースを待機し続けます。

次のステップは、scsi_setup_cdb(9F) 関数を使用して SCSI CDB を初期化することです。

if (scsi_setup_cdb((union scsi_cdb *)pkt->pkt_cdbp,
    SCMD_READ, bp->b_blkno, bp->b_bcount >> DEV_BSHIFT, 0) == 0)
    goto failed;

この例では、グループ 0 のコマンド記述子ブロックを構築しています。この例の pkt_cdbp フィールドには次のように値が格納されます。

  • コマンド自体はバイト 0 に格納されます。コマンドはパラメータ SCMD_READ から設定されます。

  • アドレスフィールドはバイト 1、バイト 2、およびバイト 3 の 0-4 ビット内です。アドレスは bp->b_blkno から設定されます。

  • カウントフィールドはバイト 4 内です。カウントは最後のパラメータから設定されます。この場合、count は bp->b_bcount >> DEV_BSHIFT に設定されます。ここで DEV_BSHIFT はブロックの数に変換される転送のバイトカウントです。


注 - scsi_setup_cdb(9F) は、ターゲットデバイスの論理ユニット番号 (LUN) を、SCSI コマンドブロックのバイト 1 の 5-7 ビットで設定することをサポートしていません。この要件は SCSI-1 で定義されています。コマンドブロック内に LUN ビットが設定されている必要がある SCSI-1 デバイスの場合は、scsi_setup_cdb(9F) ではなく、makecom_g0(9F) または同等の関数を使用します。

SCSI CDB を初期化したら、パケット内にあるほかの 3 つのフィールドを初期化し、パケットへのポインタとして状態構造体内に格納します。

pkt->pkt_private = (opaque_t)bp;
pkt->pkt_comp = xxcallback;
pkt->pkt_time = 30;
xsp->pkt = pkt;

buf(9S) ポインタは、後から完了ルーチンで使用するため、pkt_private フィールドに保存されます。

ターゲット機能の設定

ターゲットドライバはscsi_ifsetcap(9F) を使用して、ホストバスアダプタドライバの機能を設定します。cap は名前と値の組で、NULL で終わる文字列と整数値から構成されます。機能の現在の値は、scsi_ifgetcap(9F) を使用して取得できます。scsi_ifsetcap(9F) では、バス上のすべてのターゲットに対して機能を設定できます。

ただし、一般に、ターゲットドライバによって所有されていないターゲットの機能を設定することは推奨されません。この運用方法は、HBA ドライバで汎用的にサポートされているわけではありません。切断と同期などの一部の機能は、デフォルトでは HBA ドライバによって設定できます。その他の機能は、ターゲットドライバによって明示的に設定する必要が生じる可能性があります。たとえば、wide-xfer とタグ付きキューイングはターゲットドライバで設定する必要があります。

コマンドのトランスポート

scsi_pkt(9S) 構造体に値が格納されたら、scsi_transport(9F) を使用して、構造体をバスアダプタドライバに渡します。

if (scsi_transport(pkt) != TRAN_ACCEPT) {
    bp->b_resid = bp->b_bcount;
    bioerror(bp, EIO);
    biodone(bp);
}

scsi_transport(9F) からのほかの戻り値は次のとおりです。

  • TRAN_BUSY – 指定されたターゲットのコマンドはすでに処理中です。

  • TRAN_BADPKT – パケット内の DMA カウントが大きすぎるか、ホストバスアダプタドライバが何らかの理由でこのパケットを拒否しました。

  • TRAN_FATAL_ERROR – ホストアダプタドライバはこのパケットを受け取ることができません。


注 - scsi_device(9S) 構造体内の sd_mutex という mutex は、scsi_transport(9F) の呼び出しを越えて保持してはいけません。

scsi_transport(9F) が TRAN_ACCEPT を返す場合、パケットはホストバスアダプタドライバで管理されるものになっています。パケットは、コマンド完了ルーチンが呼び出されるまで、ターゲットドライバからアクセスしてはいけません。

同期 scsi_transport() 関数

パケット内に FLAG_NOINTR が設定されている場合、コマンドが完了するまで scsi_transport(9F) は復帰しません。コールバックは実行されません。


注 - 割り込みのコンテキストで FLAG_NOINTR を使わないでください。

コマンドの完了

ホストバスアダプタドライバがコマンドを完了すると、ドライバはパケットの完了コールバックルーチンを呼び出します。ドライバは次に、scsi_pkt(9S) 構造体へのポインタをパラメータとして渡します。パケットのデコード後、完了ルーチンが適切な操作を実行します。

使用例 96は、簡単な完了コールバックルーチンを表しています。このコードは、トランスポートの失敗をチェックします。失敗の場合、ルーチンはコマンドを再試行するのではなく、実行を断念します。ターゲットがビジー状態の場合、あとでコマンドを再送信するために、追加のコードが必要です。

コマンドがチェック条件になった場合、要求の自動検知が有効になっている場合を除き、ターゲットドライバが要求検知コマンドを送信する必要があります。

その他の場合、コマンドは成功したことになります。コマンドの処理の最後に、コマンドはパケットを破棄し、biodone(9F) を呼び出します。

バスのリセットやパリティーの問題など、トランスポートエラーが発生した場合、ターゲットドライバはscsi_transport(9F) を使用してパケットを再送信できます。再送信の前に、パケット内の値を変更する必要はありません。

次の例では、完了しなかったコマンドの再試行は試みていません。


注 - 割り込みのコンテキストでは、通常、ターゲットドライバのコールバック関数が呼び出されます。結果としてコールバック関数は、スリープすることがあってはなりません。
使用例 96  SCSI ドライバの完了ルーチン
static void
xxcallback(struct scsi_pkt *pkt)
{
    struct buf        *bp;
    struct xxstate    *xsp;
    minor_t           instance;
    struct scsi_status *ssp;
    /*
     * Get a pointer to the buf(9S) structure for the command
     * and to the per-instance data structure.
     */
    bp = (struct buf *)pkt->pkt_private;
    instance = getminor(bp->b_edev);
    xsp = ddi_get_soft_state(statep, instance);
    /*
     * Figure out why this callback routine was called
     */
    if (pkt->pkt_reason != CMP_CMPLT) {
        bp->b_resid = bp->b_bcount;
        bioerror(bp, EIO);
        scsi_destroy_pkt(pkt);          /* Release resources */
        biodone(bp);                    /* Notify waiting threads */ ;
    } else {
        /*
         * Command completed, check status.
         * See scsi_status(9S)
         */
        ssp = (struct scsi_status *)pkt->pkt_scbp;
        if (ssp->sts_busy) {
            /* error, target busy or reserved */
        } else if (ssp->sts_chk) {
            /* Send a request sense command. */
        } else {
            bp->b_resid = pkt->pkt_resid;  /* Packet completed OK */
            scsi_destroy_pkt(pkt);
            biodone(bp);
       }
    }
}

パケットの再利用

ターゲットドライバは次の方法でパケットを再利用できます。

  • 変更されていないパケットを再送信します。

  • scsi_sync_pkt(9F) を使用してデータを同期します。次に、ドライバでデータを処理します。最後に、パケットを再送信します。

  • scsi_dmafree(9F) を使用して DMA リソースを解放し、pkt ポインタをscsi_init_pkt(9F) に渡して新しい bp にバインドします。ターゲットドライバがパケットの再初期化を行う必要があります。CDB の長さは前の CDB と同じになっている必要があります。

  • scsi_init_pkt(9F) への最初の呼び出しで、不完全な DMA のみが割り当てられた場合、以後のscsi_init_pkt(9F) の呼び出しは同じパケットに対して行うことができます。bp に対しても呼び出しを行い、転送の次の部分に対する DMA リソースを調整できます。

自動要求検知モード

キューイングが使用される場合、タグ付きのキューキングであるか、タグなしのキューイングであるかにかかわらず、自動要求検知モードを使用することが推奨されます。CAC (Contingent Allegiance Condition) は後続のコマンドによってクリアされ、結果として検知データは失われます。ほとんどの HBA ドライバは、ターゲットドライバのコールバックを実行する前に次のコマンドを開始します。その他の HBA ドライバは、別の優先順位が低いスレッドを使用してコールバックを実行できます。この方法では、パケットがチェック条件で完了したことをターゲットドライバに通知するために必要な時間が増加する可能性があります。この場合、ターゲットドライバは、検知データの取得に間に合うように要求検知コマンドを送信できないことがあります。

この検知データの損失を回避するには、チェック条件が検出された場合に HBA ドライバまたはコントローラが要求検知コマンドを発行する必要があります。このモードは、自動要求検知モードと呼ばれます。すべての HBA ドライバが自動要求検知モードに対応しているわけではなく、一部のドライバは自動要求検知モードが有効な場合にのみ動作可能であることに注意してください。

ターゲットドライバは、scsi_ifsetcap(9F) を使用して自動要求検知モードを有効にします。次に、自動要求検知を有効にする例を示します。

使用例 97  自動要求検知モードの有効化
static int
xxattach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
    struct xxstate *xsp;
    struct scsi_device *sdp = (struct scsi_device *)
    ddi_get_driver_private(dip);
    /*
     * Enable auto-request-sense. An auto-request-sense command might
     * fail due to a BUSY condition or transport error. Therefore,
     * it is recommended to allocate a separate request sense
     * packet as well.
     * Note that scsi_ifsetcap(9F) can return -1, 0, or 1
     */
    xsp->sdp_arq_enabled =
    ((scsi_ifsetcap(ROUTE, "auto-rqsense", 1, 1) == 1) ? 1 : 0);
    /*
     * If the HBA driver supports auto request sense then the
     * status blocks should be sizeof (struct scsi_arq_status).
     * Else, one byte is sufficient.
     */
    xsp->sdp_cmd_stat_size =  (xsp->sdp_arq_enabled ?
    sizeof (struct scsi_arq_status) : 1);
    /* ... */
}

scsi_init_pkt(9F) を使用してパケットが割り当てられていて、このパケットで自動要求検知が必要な場合、追加の領域が必要になります。ターゲットドライバは、自動要求検知構造体を保持するため、ステータスブロック用のこの領域を要求する必要があります。要求検知コマンドで使用される検知の長さは、struct scsi_extended_sensesizeof です。ステータスブロックに対して struct scsi_status から sizeof を割り当てることで、個々のパケットごとに自動要求検知を無効にできます。

パケットは通常どおり、scsi_transport(9F) を使用して送信されます。このパケットでチェック条件が発生すると、ホストバスアダプタドライバは次のステップを実行します。

  • コントローラが自動要求検知機能を備えていない場合は、要求検知コマンドを発行します。

  • 検知データを取得します。

  • パケットのステータスブロックに scsi_arq_status の情報を格納します。

  • パケットの pkt_state フィールドに STATE_ARQ_DONE を設定します。

  • パケットのコールバックハンドラ (pkt_comp()) を呼び出します。

ターゲットドライバのコールバックルーチンでは、pkt_state 内の STATE_ARQ_DONE ビットをチェックして、検知データを使用可能であることを確認する必要があります。STATE_ARQ_DONE は、チェック条件が発生したこと、および要求検知が実行されたことを意味します。パケットで自動要求検知が一時的に無効にされている場合、以降の検知データの取得を保証することはできません。

その後、ターゲットドライバで自動要求検知コマンドが正常に完了し、検知データがデコードされたかどうかを検証する必要があります。

ダンプの処理

dump(9E) エントリポイントは、システム障害やチェックポイント操作が発生した場合に、仮想アドレス空間の一部を、指定されたデバイスに直接コピーします。cpr(7)dump(9E) のマニュアルページを参照してください。dump(9E) エントリポイントは、割り込みを使わずにこの操作を実行できる必要があります。

dump() の引数は次のとおりです。

dev

ダンプデバイスのデバイス番号

addr

ダンプを開始するカーネルの仮想アドレス

blkno

デバイス上の最初の出力先ブロック

nblk

ダンプするブロックの数

使用例 98  dump (9E) ルーチン
static int
xxdump(dev_t dev, caddr_t addr, daddr_t blkno, int nblk)
{
    struct xxstate     *xsp;
    struct buf         *bp;
    struct scsi_pkt    *pkt;
    int    rval;
    int    instance;

    instance = getminor(dev);
    xsp = ddi_get_soft_state(statep, instance);

    if (tgt->suspended) {
        (void) pm_raise_power(DEVINFO(tgt), 0, 1);
    }

    bp = getrbuf(KM_NOSLEEP);
    if (bp == NULL) {
        return (EIO);
    }

/* Calculate block number relative to partition. */
    bp->b_un.b_addr = addr;
    bp->b_edev = dev;
    bp->b_bcount = nblk * DEV_BSIZE;
    bp->b_flags = B_WRITE | B_BUSY;
    bp->b_blkno = blkno;

    pkt = scsi_init_pkt(ROUTE(tgt), NULL, bp, CDB_GROUP1,
    sizeof (struct scsi_arq_status),
    sizeof (struct bst_pkt_private), 0, NULL_FUNC, NULL);
    if (pkt == NULL) {
        freerbuf(bp);
        return (EIO);
    }
    (void) scsi_setup_cdb((union scsi_cdb *)pkt->pkt_cdbp,
        SCMD_WRITE_G1, blkno, nblk, 0);
    /*
     * While dumping in polled mode, other cmds might complete
     * and these should not be resubmitted. we set the
     * dumping flag here which prevents requeueing cmds.
     */
    tgt->dumping = 1;
    rval = scsi_poll(pkt);
    tgt->dumping = 0;

    scsi_destroy_pkt(pkt);
    freerbuf(bp);

    if (rval != DDI_SUCCESS) {
        rval = EIO;
    }
    return (rval);
}