JavaScript is required to for searching.
ナビゲーションリンクをスキップ
印刷ビューの終了
デバイスドライバの記述     Oracle Solaris 11.1 Information Library (日本語)
このドキュメントの評価
search filter icon
search icon

ドキュメントの情報

はじめに

パート I Oracle Solaris プラットフォーム用デバイスドライバの設計

1.  Oracle Solaris デバイスドライバの概要

2.  Oracle Solaris カーネルとデバイスツリー

3.  マルチスレッド

4.  プロパティー

5.  イベントの管理とタスクのキュー

6.  ドライバの自動構成

7.  デバイスアクセス: プログラム式入出力

8.  割り込みハンドラ

9.  ダイレクトメモリーアクセス (DMA)

10.  デバイスメモリーおよびカーネルメモリーのマッピング

11.  デバイスコンテキスト管理

12.  電源管理

13.  Oracle Solaris ドライバの強化

14.  階層化ドライバインタフェース (LDI)

パート II 特定の種類のデバイスドライバの設計

15.  文字デバイスのドライバ

16.  ブロックデバイスのドライバ

17.  SCSI ターゲットドライバ

18.  SCSI ホストバスアダプタドライバ

ホストバスアダプタドライバの概要

SCSI インタフェース

SCSA HBA インタフェース

SCSA HBA エントリポイントのサマリー

SCSA HBA データ構造体

scsi_hba_tran() 構造体

scsi_address 構造体

scsi_device 構造体

scsi_pkt 構造体 (HBA)

ターゲットインスタンスごとのデータ

トランスポート構造体の複製

SCSA HBA 関数

HBA ドライバの依存性と構成に関する問題

宣言と構造体

コマンド別構造体

モジュール初期化用のエントリポイント

_init() エントリポイント (SCSI HBA ドライバ)

_fini() エントリポイント (SCSI HBA ドライバ)

自動構成のエントリポイント

attach() エントリポイント (SCSI HBA ドライバ)

detach() エントリポイント (SCSI HBA ドライバ)

SCSA HBA ドライバのエントリポイント

ターゲットドライバインスタンスの初期化

tran_tgt_init() エントリポイント

tran_tgt_probe() エントリポイント

tran_tgt_free() エントリポイント

資源割り当て

tran_init_pkt() エントリポイント

scsi_pkt(9S) 構造体の割り当てと初期化

DMA 資源の割り当て

データ転送のための DMA 資源の再割り当て

tran_destroy_pkt() エントリポイント

tran_sync_pkt() エントリポイント

tran_dmafree() エントリポイント

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

tran_start() エントリポイント

割り込みハンドラとコマンドの完了

タイムアウトハンドラ

機能管理

tran_getcap() エントリポイント

tran_setcap() エントリポイント

中止およびリセット管理

tran_abort() エントリポイント

tran_reset() エントリポイント

tran_bus_reset() エントリポイント

tran_reset_notify() エントリポイント

動的再構成 (DR)

SCSI HBA ドライバに固有の問題

HBA ドライバのインストール

HBA の構成プロパティー

scsi-reset-delay プロパティー

scsi-options プロパティー

ターゲットごとの scsi-options

x86 ターゲットドライバの構成プロパティー

キューイングのサポート

19.  ネットワークデバイスのドライバ

20.  USB ドライバ

21.  SR-IOV ドライバ

パート III デバイスドライバの構築

22.  ドライバのコンパイル、ロード、パッケージ化、およびテスト

23.  デバイスドライバのデバッグ、テスト、およびチューニング

24.  推奨されるコーティング方法

パート IV 付録

A.  ハードウェアの概要

B.  Oracle Solaris DDI/DKI サービスのサマリー

C.  64 ビットデバイスドライバの準備

D.  コンソールフレームバッファードライバ

E.  pci.conf ファイル

索引

ドキュメントの品質向上のためのご意見をください
簡潔すぎた
読みづらかった、または難し過ぎた
重要な情報が欠けていた
内容が間違っていた
翻訳版が必要
その他
Your rating has been updated
貴重なご意見を有り難うございました!

あなたの貴重なご意見はより良いドキュメント作成の手助けとなります 内容の品質向上と追加コメントのためのアンケートに参加されますか?

SCSA HBA ドライバのエントリポイント

HBA ドライバは、SCSA インタフェースを介してターゲットドライバとともに動作できます。SCSA インタフェースでは、HBA ドライバは、scsi_hba_tran(9S) 構造体を介して呼び出し可能なエントリポイントをいくつか提供する必要があります。

これらのエントリポイントは、次の 5 つの機能グループに分けられます。

次の表に、SCSA HBA のエントリポイントを機能グループ別に示します。

表 18-3 SCSA エントリポイント

機能グループ
グループ内のエントリポイント
説明
ターゲットドライバインスタンスの初期化
ターゲットごとの初期化を実行します (省略可能)
ターゲットが存在するかどうか SCSI バスを調べます (省略可能)
ターゲットごとの解放を実行します (省略可能)
資源割り当て
SCSI パケットと DMA 資源を割り当てます
SCSI パケットと DMA 資源を解放します
DMA の前後にメモリーを同期させます
DMA 資源を解放します
コマンドのトランスポート
SCSI コマンドをトランスポートします
機能管理
機能の値について問い合わせます
機能の値を設定します
中止とリセット
未処理の SCSI コマンドを中止します
ターゲットデバイスまたは SCSI バスをリセットします
SCSI バスをリセットします
バスのリセットをターゲットに知らせるように要求します (省略可能)
動的再構成 (DR)
バス上の動作を停止します
バス上の動作を再開します

ターゲットドライバインスタンスの初期化

以降のセクションでは、ターゲットエントリポイントについて説明します。

tran_tgt_init() エントリポイント

tran_tgt_init(9E) エントリポイントを使用すると、HBA はターゲットごとの資源を割り当てて初期化できます。また、tran_tgt_init() を使用すると、HBA はデバイスのアドレスをその特定の HBA で有効かつサポート可能であるとみなすことができます。DDI_FAILURE を返すことにより、そのデバイスのターゲットドライバのインスタンスにはプローブも接続も行われません。

tran_tgt_init() は必須ではありません。tran_tgt_init() を指定しない場合、フレームワークでは該当するターゲットドライバの可能なインスタンスをすべてプローブおよび接続しようとします。

static int
isp_tran_tgt_init(
    dev_info_t            *hba_dip,
    dev_info_t            *tgt_dip,
    scsi_hba_tran_t       *tran,
    struct scsi_device    *sd)
{
    return ((sd->sd_address.a_target < N_ISP_TARGETS_WIDE &&
        sd->sd_address.a_lun < 8) ? DDI_SUCCESS : DDI_FAILURE);
}

tran_tgt_probe() エントリポイント

tran_tgt_probe(9E) エントリポイントを使用すると、HBA は必要に応じて scsi_probe(9F) の操作をカスタマイズできます。このエントリポイントは、ターゲットドライバが scsi_probe() を呼び出した場合にのみ呼び出されます。

HBA ドライバは、scsi_hba_probe(9F) を呼び出し、その戻り値を返すことで、scsi_probe() の通常の操作を保持できます。

このエントリポイントは必須ではありません。必要がない場合、HBA ドライバは、scsi_hba_tran(9S) 構造体の tran_tgt_probe ベクトルが scsi_hba_probe() を指すように設定します。

scsi_probe() は、scsi_inquiry(9S) 構造体を割り当て、scsi_device(9S) 構造体の sd_inq フィールドが scsi_inquiry のデータを指すように設定します。scsi_hba_probe() はこのタスクを自動的に処理します。次に、scsi_unprobe(9F)scsi_inquiry データを解放します。

scsi_inquiry データの割り当てを除き、同じ SCSI デバイスが tran_tgt_probe() を何度も呼び出す可能性があるため、tran_tgt_probe() をステートレスにする必要があります。通常、scsi_inquiry データの割り当ては scsi_hba_probe () によって処理されます。


注 - scsi_inquiry(9S) 構造体の割り当ては scsi_hba_probe() によって自動的に処理されます。この情報は、カスタムの scsi_probe() 処理が必要な場合にだけ関係があります。


static int
isp_tran_tgt_probe(
    struct scsi_device    *sd,
    int                   (*callback)())
{
    /*
     * Perform any special probe customization needed.
     * Normal probe handling.
     */
    return (scsi_hba_probe(sd, callback));
}

tran_tgt_free() エントリポイント

tran_tgt_free(9E) エントリポイントを使用すると、HBA はターゲットのインスタンスに対して解放またはクリーンアップの手順を実行できます。このエントリポイントは省略可能です。

static void
isp_tran_tgt_free(
    dev_info_t            *hba_dip,
    dev_info_t            *tgt_dip,
    scsi_hba_tran_t       *hba_tran,
    struct scsi_device    *sd)
{
    /*
     * Undo any special per-target initialization done
     * earlier in tran_tgt_init(9F) and tran_tgt_probe(9F)
     */
}

資源割り当て

以降のセクションでは、資源の割り当てについて説明します。

tran_init_pkt() エントリポイント

tran_init_pkt(9E) エントリポイントは、ターゲットドライバのリクエストに応じて scsi_pkt(9S) 構造体と DMA 資源の割り当てと初期化を行います。

tran_init_pkt(9E) エントリポイントは、ターゲットドライバが SCSA 関数 scsi_init_pkt(9F) を呼び出したときに呼び出されます。

tran_init_pkt(9E) エントリポイントの各呼び出しは、次の 3 つの可能なサービスの 1 つ以上を実行するよう要求するものです。

scsi_pkt(9S) 構造体の割り当てと初期化

tran_init_pkt(9E) エントリポイントは、pktNULL の場合、scsi_hba_pkt_alloc(9F) を介して scsi_pkt(9S) 構造体を割り当てる必要があります。

scsi_hba_pkt_alloc(9F) では、次の項目に領域を割り当てます。

scsi_pkt(9S) 構造体のメンバー (pkt など) は、次のメンバーを除いてゼロに初期化する必要があります。

次の図に示すように、これらのメンバーはフィールドの値が格納されるメモリー空間を指すポインタです。詳細については、scsi_pkt 構造体 (HBA)」を参照してください。

図 18-5 scsi_pkt(9S) 構造体のポインタ

image:図は、ゼロに初期化されるのではなく、値を指しているメンバーを含む scsi_pkt 構造体を示しています。

次の例は、scsi_pkt 構造体の割り当てと初期化を示しています。

例 18-2 HBA ドライバでの SCSI パケット構造体の初期化

static struct scsi_pkt                 *
isp_scsi_init_pkt(
    struct scsi_address    *ap,
    struct scsi_pkt        *pkt,
    struct buf             *bp,
    int                    cmdlen,
    int                    statuslen,
    int                    tgtlen,
    int                    flags,
    int                    (*callback)(),
    caddr_t                arg)
{
    struct isp_cmd         *sp;
    struct isp             *isp;
    struct scsi_pkt        *new_pkt;

    ASSERT(callback == NULL_FUNC || callback == SLEEP_FUNC);

    isp = (struct isp *)ap->a_hba_tran->tran_hba_private;
    /*
     * First step of isp_scsi_init_pkt:  pkt allocation
     */
    if (pkt == NULL) {
        pkt = scsi_hba_pkt_alloc(isp->isp_dip, ap, cmdlen,
            statuslen, tgtlen, sizeof (struct isp_cmd),
            callback, arg);
        if (pkt == NULL) {
            return (NULL);
        }

        sp = (struct isp_cmd *)pkt->pkt_ha_private;
        /*
         * Initialize the new pkt
         */
        sp->cmd_pkt         = pkt;
        sp->cmd_flags       = 0;
        sp->cmd_scblen      = statuslen;
        sp->cmd_cdblen      = cmdlen;
        sp->cmd_dmahandle   = NULL;
        sp->cmd_ncookies    = 0;
        sp->cmd_cookie      = 0; 
        sp->cmd_cookiecnt   = 0;
        sp->cmd_nwin        = 0;
        pkt->pkt_address    = *ap;
        pkt->pkt_comp       = (void (*)())NULL;
        pkt->pkt_flags      = 0;
        pkt->pkt_time       = 0;
        pkt->pkt_resid      = 0;
        pkt->pkt_statistics = 0;
        pkt->pkt_reason     = 0;
        new_pkt = pkt;
    } else {
        sp = (struct isp_cmd *)pkt->pkt_ha_private;
        new_pkt = NULL;
    }
    /*
     * Second step of isp_scsi_init_pkt:  dma allocation/move
     */
    if (bp && bp->b_bcount != 0) {
        if (sp->cmd_dmahandle == NULL) {
            if (isp_i_dma_alloc(isp, pkt, bp, flags, callback) == 0) {
                if (new_pkt) {
                    scsi_hba_pkt_free(ap, new_pkt);
                }
                return ((struct scsi_pkt *)NULL);
            }
        } else {
            ASSERT(new_pkt == NULL);
            if (isp_i_dma_move(isp, pkt, bp) == 0) {
                return ((struct scsi_pkt *)NULL);
            }
        }
    }
    return (pkt);
}

DMA 資源の割り当て

tran_init_pkt(9E) エントリポイントは、次の条件が真の場合、データ転送用の DMA 資源を割り当てる必要があります。

HBA ドライバは、DMA 資源が特定のコマンドに対してどのように割り当てられるかを追跡する必要があります。この割り当ては、パケットごとの HBA ドライバの非公開データに含まれるフラグビットまたは DMA ハンドルを使って行われます。

pkt に含まれる PKT_DMA_PARTIAL フラグを使用すると、ターゲットドライバはデータ転送を複数の SCSI コマンドに分けることで完全な要求に対応できます。この方法は、HBA ハードウェアの分散および集中機能または DMA 資源が 1 つの SCSI コマンドで要求を完了できない場合に役立ちます。

PKT_DMA_PARTIAL フラグを使用すると、HBA ドライバは DDI_DMA_PARTIAL フラグを設定できます。DDI_DMA_PARTIAL フラグは、この SCSI コマンドの DMA 資源が割り当てられるときに役立ちます。たとえば、ddi_dma_buf_bind_handle(9F)) コマンドを使用して DMA 資源を割り当てることができます。DMA 資源の割り当て時に使われる DMA 属性には、DMA を実行する HBA ハードウェアの機能に対する制約が正確に記述されています。システムが要求の一部にしか DMA 資源を割り当てられない場合、ddi_dma_buf_bind_handle(9F)DDI_DMA_PARTIAL_MAP を返します。

tran_init_pkt(9E) エントリポイントは、この転送に割り当てられていない DMA 資源の量を pkt_resid フィールドに返す必要があります。

ターゲットドライバは、tran_init_pkt(9E) への 1 回の要求で、scsi_pkt(9S) 構造体とその pkt の DMA 資源を同時に割り当てることがあります。このときに HBA ドライバが DMA 資源を割り当てられない場合、HBA ドライバは割り当てられている scsi_pkt(9S) を解放してから復帰する必要があります。scsi_pkt(9S) を解放するには、scsi_hba_pkt_free(9F) を呼び出します。

ターゲットドライバは、最初に scsi_pkt(9S) を割り当て、あとでこの pkt に DMA 資源を割り当てることがあります。この場合、HBA ドライバは DMA 資源を割り当てられなくても、pkt を解放してはいけません。この場合は、ターゲットドライバが pkt の解放を担当します。

例 18-3 HBA ドライバでの DMA 資源の割り当て

static int
isp_i_dma_alloc(
    struct isp         *isp,
    struct scsi_pkt    *pkt,
    struct buf         *bp,
    int                flags,
    int                (*callback)())
{
    struct isp_cmd     *sp  = (struct isp_cmd *)pkt->pkt_ha_private;
    int                dma_flags;
    ddi_dma_attr_t     tmp_dma_attr;
    int                (*cb)(caddr_t);
    int                i;

    ASSERT(callback == NULL_FUNC || callback == SLEEP_FUNC);

    if (bp->b_flags & B_READ) {
        sp->cmd_flags &= ~CFLAG_DMASEND;
        dma_flags = DDI_DMA_READ;
    } else {
        sp->cmd_flags |= CFLAG_DMASEND;
        dma_flags = DDI_DMA_WRITE;
    }
    if (flags & PKT_CONSISTENT) {
        sp->cmd_flags |= CFLAG_CMDIOPB;
        dma_flags |= DDI_DMA_CONSISTENT;
    }
    if (flags & PKT_DMA_PARTIAL) {
        dma_flags |= DDI_DMA_PARTIAL;
    }

    tmp_dma_attr = isp_dma_attr;
    tmp_dma_attr.dma_attr_burstsizes = isp->isp_burst_size;

    cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP;

    if ((i = ddi_dma_alloc_handle(isp->isp_dip, &tmp_dma_attr,
      cb, 0, &sp->cmd_dmahandle)) != DDI_SUCCESS) {
        switch (i) {
          case DDI_DMA_BADATTR:
              bioerror(bp, EFAULT);
              return (0);
          case DDI_DMA_NORESOURCES:
              bioerror(bp, 0);
              return (0);
        }
    }

    i = ddi_dma_buf_bind_handle(sp->cmd_dmahandle, bp, dma_flags,
    cb, 0, &sp->cmd_dmacookies[0], &sp->cmd_ncookies);

    switch (i) {
      case DDI_DMA_PARTIAL_MAP:
          if (ddi_dma_numwin(sp->cmd_dmahandle, &sp->cmd_nwin) == DDI_FAILURE) {
              cmn_err(CE_PANIC, "ddi_dma_numwin() failed\n");
          }

          if (ddi_dma_getwin(sp->cmd_dmahandle, sp->cmd_curwin,
            &sp->cmd_dma_offset, &sp->cmd_dma_len, &sp->cmd_dmacookies[0], 
            &sp->cmd_ncookies) == DDI_FAILURE) {
              cmn_err(CE_PANIC, "ddi_dma_getwin() failed\n");
          }
          goto get_dma_cookies;

      case DDI_DMA_MAPPED:
          sp->cmd_nwin = 1;
          sp->cmd_dma_len = 0;
          sp->cmd_dma_offset = 0;

      get_dma_cookies:
          i = 0;
          sp->cmd_dmacount = 0;
          for (;;) {
              sp->cmd_dmacount += sp->cmd_dmacookies[i++].dmac_size;
              if (i == ISP_NDATASEGS || i == sp->cmd_ncookies)
                  break;
              ddi_dma_nextcookie(sp->cmd_dmahandle,
              &sp->cmd_dmacookies[i]);
          }
          sp->cmd_cookie = i;
          sp->cmd_cookiecnt = i;
          sp->cmd_flags |= CFLAG_DMAVALID;
          pkt->pkt_resid = bp->b_bcount - sp->cmd_dmacount;
          return (1);

      case DDI_DMA_NORESOURCES:
          bioerror(bp, 0);
          break;

      case DDI_DMA_NOMAPPING:
          bioerror(bp, EFAULT);
          break;

      case DDI_DMA_TOOBIG:
          bioerror(bp, EINVAL);
          break;

      case DDI_DMA_INUSE:
          cmn_err(CE_PANIC, "ddi_dma_buf_bind_handle:"
            " DDI_DMA_INUSE impossible\n");

      default:
          cmn_err(CE_PANIC, "ddi_dma_buf_bind_handle:"
            " 0x%x impossible\n", i);
    }
    ddi_dma_free_handle(&sp->cmd_dmahandle);
    sp->cmd_dmahandle = NULL;
    sp->cmd_flags &= ~CFLAG_DMAVALID;
    return (0);
}

データ転送のための DMA 資源の再割り当て

前回割り当てたパケットにまだ転送されていないデータが残っている場合は、次の条件に適合したときに tran_init_pkt(9E) エントリポイントで DMA 資源を再割り当てする必要があります。

転送の次回分に DMA 資源を再割り当てするとき、tran_init_pkt(9E) は、この転送に割り当てられていない DMA 資源の量を pkt_resid フィールドに返す必要があります。

DMA 資源を移動しようとしているときにエラーが発生した場合、tran_init_pkt(9E)scsi_pkt(9S) を解放してはいけません。この場合は、ターゲットドライバがパケットの解放を担当します。

コールバックパラメータが NULL_FUNC である場合、tran_init_pkt(9E) エントリポイントは関数をスリープしたり、スリープの可能性のある関数を呼び出したりしてはいけません。コールバックパラメータが SLEEP_FUNC であり、資源がすぐに利用できない場合、tran_init_pkt(9E) エントリポイントはスリープになります。要求を満たすことが不可能でないかぎり、tran_init_pkt() は資源が利用できるようになるまでスリープになります。

例 18-4 HBA ドライバでの DMA 資源の再割り当て

static int
isp_i_dma_move(
    struct isp         *isp,
    struct scsi_pkt    *pkt,
    struct buf         *bp)
{
    struct isp_cmd     *sp  = (struct isp_cmd *)pkt->pkt_ha_private;
    int                i;

    ASSERT(sp->cmd_flags & CFLAG_COMPLETED);
    sp->cmd_flags &= ~CFLAG_COMPLETED;
    /*
     * If there are no more cookies remaining in this window,
     * must move to the next window first.
     */
    if (sp->cmd_cookie == sp->cmd_ncookies) {
        /*
         * For small pkts, leave things where they are
         */
        if (sp->cmd_curwin == sp->cmd_nwin && sp->cmd_nwin == 1)
            return (1);
        /*
         * At last window, cannot move
         */
        if (++sp->cmd_curwin >= sp->cmd_nwin)
            return (0);
        if (ddi_dma_getwin(sp->cmd_dmahandle, sp->cmd_curwin,
          &sp->cmd_dma_offset, &sp->cmd_dma_len,
          &sp->cmd_dmacookies[0], &sp->cmd_ncookies) == DDI_FAILURE)
            return (0);
        sp->cmd_cookie = 0;
    } else {
        /*
         * Still more cookies in this window - get the next one
         */
        ddi_dma_nextcookie(sp->cmd_dmahandle, &sp->cmd_dmacookies[0]);
    }
    /*
     * Get remaining cookies in this window, up to our maximum
     */
    i = 0;
    for (;;) {
        sp->cmd_dmacount += sp->cmd_dmacookies[i++].dmac_size;
        sp->cmd_cookie++;
        if (i == ISP_NDATASEGS || sp->cmd_cookie == sp->cmd_ncookies)
            break;
        ddi_dma_nextcookie(sp->cmd_dmahandle, &sp->cmd_dmacookies[i]);
    }
    sp->cmd_cookiecnt = i;
    pkt->pkt_resid = bp->b_bcount - sp->cmd_dmacount;
    return (1);
}

tran_destroy_pkt() エントリポイント

tran_destroy_pkt(9E) エントリポイントは、scsi_pkt(9S) 構造体を解放する HBA ドライバの関数です。tran_destroy_pkt() エントリポイントは、ターゲットドライバが scsi_destroy_pkt(9F) を呼び出したときに呼び出されます。

tran_destroy_pkt() エントリポイントは、パケットに割り当てられているすべての DMA 資源を解放する必要があります。転送が完了したあと、DMA 資源が解放され、キャッシュされたデータが残っている場合は、暗黙的に DMA の同期が行われます。tran_destroy_pkt() エントリポイントは、scsi_hba_pkt_free(9F) を呼び出して、SCSI パケットを解放します。

例 18-5 HBA ドライバの tran_destroy_pkt(9E) エントリポイント

static void
isp_scsi_destroy_pkt(
    struct scsi_address    *ap,
    struct scsi_pkt    *pkt)
{
    struct isp_cmd *sp = (struct isp_cmd *)pkt->pkt_ha_private;
    /*
     * Free the DMA, if any
     */
    if (sp->cmd_flags & CFLAG_DMAVALID) {
        sp->cmd_flags &= ~CFLAG_DMAVALID;
        (void) ddi_dma_unbind_handle(sp->cmd_dmahandle);
        ddi_dma_free_handle(&sp->cmd_dmahandle);
        sp->cmd_dmahandle = NULL;
    }
    /*
     * Free the pkt
     */
    scsi_hba_pkt_free(ap, pkt);
}

tran_sync_pkt() エントリポイント

tran_sync_pkt(9E) エントリポイントは、DMA 転送の前またはあとで、scsi_pkt(9S) 構造体に割り当てられた DMA オブジェクトを同期させます。tran_sync_pkt() エントリポイントは、ターゲットドライバが scsi_sync_pkt(9F) を呼び出したときに呼び出されます。

データ転送の方向がデバイスからメモリーへの DMA 読み取りの場合、tran_sync_pkt() は CPU のデータビューを同期させる必要があります。データ転送の方向がメモリーからデバイスへの DMA 書き込みの場合、tran_sync_pkt() はデバイスのデータビューを同期させる必要があります。

例 18-6 HBA ドライバの tran_sync_pkt(9E) エントリポイント

static void
isp_scsi_sync_pkt(
    struct scsi_address    *ap,
    struct scsi_pkt        *pkt)
{
    struct isp_cmd *sp = (struct isp_cmd *)pkt->pkt_ha_private;

    if (sp->cmd_flags & CFLAG_DMAVALID) {
        (void)ddi_dma_sync(sp->cmd_dmahandle, sp->cmd_dma_offset,
        sp->cmd_dma_len,
        (sp->cmd_flags & CFLAG_DMASEND) ?
        DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU);
    }
}

tran_dmafree() エントリポイント

tran_dmafree(9E) エントリポイントは、scsi_pkt(9S) 構造体に割り当てられている DMA 資源を解放します。tran_dmafree() エントリポイントは、ターゲットドライバが scsi_dmafree(9F) を呼び出したときに呼び出されます。

tran_dmafree() は、scsi_pkt(9S) 構造体に割り当てられた DMA 資源のみを解放し、scsi_pkt(9S) そのものは解放しません。DMA 資源が解放されると、暗黙的に DMA の同期が行われます。


注 - scsi_pkt(9S) の解放は、tran_destroy_pkt(9E) への別の要求で行われます。tran_destroy_pkt() では DMA 資源の解放も行う必要があるため、HBA ドライバは scsi_pkt() 構造体に DMA 資源が割り当てられているかどうかの正確な記録を保持する必要があります。


例 18-7 HBA ドライバの tran_dmafree (9E) エントリポイント

static void
isp_scsi_dmafree(
    struct scsi_address    *ap,
    struct scsi_pkt        *pkt)
{
    struct isp_cmd    *sp = (struct isp_cmd *)pkt->pkt_ha_private;

    if (sp->cmd_flags & CFLAG_DMAVALID) {
        sp->cmd_flags &= ~CFLAG_DMAVALID;
        (void)ddi_dma_unbind_handle(sp->cmd_dmahandle);
        ddi_dma_free_handle(&sp->cmd_dmahandle);
        sp->cmd_dmahandle = NULL;
    }
}

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

HBA ドライバは、コマンドのトランスポートの一環として、次の手順を実行します。

  1. ターゲットドライバからコマンドを受け入れます。

  2. そのコマンドをデバイスハードウェアに発行します。

  3. 発生する割り込みをすべて処理します。

  4. タイムアウトを管理します。

tran_start() エントリポイント

SCSI HBA ドライバの tran_start(9E) エントリポイントは、アドレス指定されたターゲットに SCSI コマンドをトランスポートするために呼び出されます。SCSI コマンドは、ターゲットドライバが tran_init_pkt(9E) エントリポイントを介して割り当てた scsi_pkt(9S) 構造体の中に完全に記述されます。コマンドがデータ転送を伴う場合は、scsi_pkt(9S) 構造体に対して DMA 資源も割り当てられている必要があります。

tran_start() エントリポイントは、ターゲットドライバが scsi_transport(9F) を呼び出したときに呼び出されます。

tran_start() は、基本的なエラーチェックと、コマンドが必要とする初期化を行います。tran_start() の動作は、scsi_pkt(9S) 構造体の pkt_flags フィールドに設定される FLAG_NOINTR フラグの影響を受けることがあります。FLAG_NOINTR が設定されていない場合、tran_start() はハードウェア上の実行用コマンドをキューに入れて、すぐに復帰する必要があります。コマンドが完了すると同時に、HBA ドライバは pkt 完了ルーチンを呼び出します。

FLAG_NOINTR が設定されている場合、HBA ドライバは pkt 完了ルーチンを呼び出してはいけません。

次の例は、tran_start(9E) エントリポイントの処理方法を示しています。ISP ハードウェアは、ターゲットデバイスごとにキューを提供しています。アクティブな未処理のコマンドを 1 つしか管理できないデバイスの場合、ドライバは通常、ターゲットごとのキューを管理する必要があります。次に、ラウンドロビン方式で現在のコマンドが完了すると同時にドライバは新しいコマンドを起動します。

例 18-8 HBA ドライバの tran_start (9E) エントリポイント

static int
isp_scsi_start(
    struct scsi_address    *ap,
    struct scsi_pkt        *pkt)
{
    struct isp_cmd         *sp;
    struct isp             *isp;
    struct isp_request     *req;
    u_long                 cur_lbolt;
    int                    xfercount;
    int                    rval = TRAN_ACCEPT;
    int                    i;

    sp = (struct isp_cmd *)pkt->pkt_ha_private;
    isp = (struct isp *)ap->a_hba_tran->tran_hba_private;

    sp->cmd_flags = (sp->cmd_flags & ~CFLAG_TRANFLAG) |
                CFLAG_IN_TRANSPORT;
    pkt->pkt_reason = CMD_CMPLT;
    /*
     * set up request in cmd_isp_request area so it is ready to
     * go once we have the request mutex
     */
    req = &sp->cmd_isp_request;

    req->req_header.cq_entry_type = CQ_TYPE_REQUEST;
    req->req_header.cq_entry_count = 1;
    req->req_header.cq_flags        = 0;
    req->req_header.cq_seqno = 0;
    req->req_reserved = 0;
    req->req_token = (opaque_t)sp;
    req->req_target = TGT(sp);
    req->req_lun_trn = LUN(sp);
    req->req_time = pkt->pkt_time;
    ISP_SET_PKT_FLAGS(pkt->pkt_flags, req->req_flags);
    /*
     * Set up data segments for dma transfers.
     */
    if (sp->cmd_flags & CFLAG_DMAVALID) {
        if (sp->cmd_flags & CFLAG_CMDIOPB) {
            (void) ddi_dma_sync(sp->cmd_dmahandle,
            sp->cmd_dma_offset, sp->cmd_dma_len,
            DDI_DMA_SYNC_FORDEV);
        }

        ASSERT(sp->cmd_cookiecnt > 0 &&
            sp->cmd_cookiecnt <= ISP_NDATASEGS);

        xfercount = 0;
        req->req_seg_count = sp->cmd_cookiecnt;
        for (i = 0; i < sp->cmd_cookiecnt; i++) {
            req->req_dataseg[i].d_count =
            sp->cmd_dmacookies[i].dmac_size;
            req->req_dataseg[i].d_base =
            sp->cmd_dmacookies[i].dmac_address;
            xfercount +=
            sp->cmd_dmacookies[i].dmac_size;
        }

        for (; i < ISP_NDATASEGS; i++) {
            req->req_dataseg[i].d_count = 0;
            req->req_dataseg[i].d_base = 0;
        }

        pkt->pkt_resid = xfercount;

        if (sp->cmd_flags & CFLAG_DMASEND) {
            req->req_flags |= ISP_REQ_FLAG_DATA_WRITE;
        } else {
            req->req_flags |= ISP_REQ_FLAG_DATA_READ;
        }
    } else {
        req->req_seg_count = 0;
        req->req_dataseg[0].d_count = 0;
    }
    /*
     * Set up cdb in the request
     */
    req->req_cdblen = sp->cmd_cdblen;
    bcopy((caddr_t)pkt->pkt_cdbp, (caddr_t)req->req_cdb,
    sp->cmd_cdblen);
    /*
     * Start the cmd.  If NO_INTR, must poll for cmd completion.
     */
    if ((pkt->pkt_flags & FLAG_NOINTR) == 0) {
        mutex_enter(ISP_REQ_MUTEX(isp));
        rval = isp_i_start_cmd(isp, sp);
        mutex_exit(ISP_REQ_MUTEX(isp));
    } else {
        rval = isp_i_polled_cmd_start(isp, sp);
    }
    return (rval);
}

割り込みハンドラとコマンドの完了

割り込みハンドラは、デバイスのステータスをチェックして、問題になっている割り込みがデバイスによって生成されていることを確認する必要があります。また、割り込みハンドラはエラーが発生していないかどうかをチェックし、デバイスによって生成された割り込みを処理する必要もあります。

データが転送された場合は、実際に転送されたデータ量を調べるためにハードウェアがチェックされます。scsi_pkt(9S) 構造体の pkt_resid フィールドにはこの転送の残りが設定されます。

tran_init_pkt(9E) を介して DMA 資源が割り当てられるときに PKT_CONSISTENT フラグでマーク付けされたコマンドは、特別な処理を行います。HBA ドライバは、ターゲットドライバのコマンド完了コールバックが実行される前に、必ずそのコマンドのデータ転送が正しく同期されるよう保証する必要があります。

コマンドが完了したら、2 つの要件に基づいて操作する必要があります。

可能な場合は、PKT_COMP コマンド完了コールバックを呼び出す前に、新しいコマンドをハードウェア上で起動するようにしてください。コマンド完了処理には、かなりの時間を要することがあります。通常、ターゲットドライバは biodone(9F) や場合によっては scsi_transport(9F) などの関数を呼び出して新しいコマンドを開始します。

この割り込みがこのドライバから要求された場合、割り込みハンドラは DDI_INTR_CLAIMED を返す必要があります。それ以外の場合は、 DDI_INTR_UNCLAIMED を返します。

次の例は、SCSI HBA ドライバ isp の割り込みハンドラを示しています。割り込みハンドラが attach(9E) で追加されるときに、caddr_t パラメータが設定されます。このパラメータは通常、状態構造体を指すポインタであり、インスタンスごとに割り当てられます。

例 18-9 HBA ドライバの割り込みハンドラ

static u_int
isp_intr(caddr_t arg)
{
    struct isp_cmd         *sp;
    struct isp_cmd         *head, *tail;
    u_short                response_in;
    struct isp_response    *resp;
    struct isp             *isp = (struct isp *)arg;
    struct isp_slot        *isp_slot;
    int                    n;

    if (ISP_INT_PENDING(isp) == 0) {
        return (DDI_INTR_UNCLAIMED);
    }

    do {
again:
        /*
         * head list collects completed packets for callback later
         */
        head = tail = NULL;
        /*
         * Assume no mailbox events (e.g., mailbox cmds, asynch
         * events, and isp dma errors) as common case.
         */
        if (ISP_CHECK_SEMAPHORE_LOCK(isp) == 0) {
            mutex_enter(ISP_RESP_MUTEX(isp));
            /*
             * Loop through completion response queue and post
             * completed pkts.  Check response queue again
             * afterwards in case there are more.
             */
            isp->isp_response_in =
            response_in = ISP_GET_RESPONSE_IN(isp);
            /*
             * Calculate the number of requests in the queue
             */
            n = response_in - isp->isp_response_out;
            if (n < 0) {
                n = ISP_MAX_REQUESTS -
                isp->isp_response_out + response_in;
            }
            while (n-- > 0) {
                ISP_GET_NEXT_RESPONSE_OUT(isp, resp);
                sp = (struct isp_cmd *)resp->resp_token;
                /*
                 * Copy over response packet in sp
                 */
                isp_i_get_response(isp, resp, sp);
            }
            if (head) {
                tail->cmd_forw = sp;
                tail = sp;
                tail->cmd_forw = NULL;
            } else {
                tail = head = sp;
                sp->cmd_forw = NULL;
            }
            ISP_SET_RESPONSE_OUT(isp);
            ISP_CLEAR_RISC_INT(isp);
            mutex_exit(ISP_RESP_MUTEX(isp));

            if (head) {
                isp_i_call_pkt_comp(isp, head);
            }
        } else {
            if (isp_i_handle_mbox_cmd(isp) != ISP_AEN_SUCCESS) {
                return (DDI_INTR_CLAIMED);
            }
            /*
             * if there was a reset then check the response
             * queue again
             */
            goto again;    
        }

    } while (ISP_INT_PENDING(isp));

    return (DDI_INTR_CLAIMED);
}

static void
isp_i_call_pkt_comp(
    struct isp             *isp,
    struct isp_cmd         *head)
{
    struct isp             *isp;
    struct isp_cmd         *sp;
    struct scsi_pkt        *pkt;
    struct isp_response    *resp;
    u_char                 status;

    while (head) {
        sp = head;
        pkt = sp->cmd_pkt;
        head = sp->cmd_forw;

        ASSERT(sp->cmd_flags & CFLAG_FINISHED);

        resp = &sp->cmd_isp_response;

        pkt->pkt_scbp[0] = (u_char)resp->resp_scb;
        pkt->pkt_state = ISP_GET_PKT_STATE(resp->resp_state);
        pkt->pkt_statistics = (u_long)
            ISP_GET_PKT_STATS(resp->resp_status_flags);
        pkt->pkt_resid = (long)resp->resp_resid;
        /*
         * If data was xferred and this is a consistent pkt,
         * do a dma sync
         */
        if ((sp->cmd_flags & CFLAG_CMDIOPB) &&
            (pkt->pkt_state & STATE_XFERRED_DATA)) {
                (void) ddi_dma_sync(sp->cmd_dmahandle,
                sp->cmd_dma_offset, sp->cmd_dma_len,
                DDI_DMA_SYNC_FORCPU);
        }

        sp->cmd_flags = (sp->cmd_flags & ~CFLAG_IN_TRANSPORT) |
            CFLAG_COMPLETED;
        /*
         * Call packet completion routine if FLAG_NOINTR is not set.
         */
        if (((pkt->pkt_flags & FLAG_NOINTR) == 0) &&
            pkt->pkt_comp) {
                (*pkt->pkt_comp)(pkt);
        }
    }
}

タイムアウトハンドラ

HBA ドライバは、タイムアウトの適用も担当します。scsi_pkt(9S) 構造体でタイムアウトがゼロに指定されていないかぎり、コマンドは指定時間内に完了する必要があります。

コマンドがタイムアウトになると、HBA ドライバは、CMD_TIMEOUT に設定されたpkt_reasonと、STAT_TIMEOUT との論理和を取得した pkt_statisticsscsi_pkt(9S) をマーク付けします。また、HBA ドライバはターゲットとバスの回復も試みないといけません。この回復処理を正常に実行できる場合、ドライバは、STAT_BUS_RESET または STAT_DEV_RESET との論理和を取得した pkt_statistics を使用して、scsi_pkt(9S) をマーク付けします。

回復の試行が完了したあとで、HBA ドライバはコマンド完了コールバックを呼び出します。


注 - 回復処理が正常に行われなかったり、試されなかったりした場合、ターゲットドライバは scsi_reset(9F) を呼び出してタイムアウトから回復しようとすることがあります。


ISP ハードウェアでは、コマンドのタイムアウトを直接管理し、タイムアウトしたコマンドを必要なステータスとともに返します。isp サンプルドライバのタイムアウトハンドラでは、60 秒ごとに 1 回だけアクティブなコマンドのタイムアウト状態をチェックします。

isp サンプルドライバは、timeout(9F) 機能を使用して、カーネルが 60 秒ごとにタイムアウトハンドラを呼び出すように設定します。caddr_t 引数は、attach(9E) の実行時にタイムアウトが初期化されるときに設定されるパラメータです。この場合、caddr_t 引数は、ドライバのインスタンスごとに割り当てられた状態構造体を指すポインタです。

タイムアウトが発生したときに、タイムアウトしたコマンドが ISP ハードウェアから返されなかった場合は、問題が発生しています。ハードウェアが正しく機能していないため、リセットする必要があります。

機能管理

以降のセクションでは、機能管理について説明します。

tran_getcap() エントリポイント

SCSI HBA ドライバの tran_getcap(9E) エントリポイントは、scsi_ifgetcap(9F) によって呼び出されます。ターゲットドライバは、SCSA 定義機能セットのいずれかの現在の値を調べるために scsi_ifgetcap() を呼び出します。

ターゲットドライバは、whom パラメータをゼロ以外の値に設定することで、特定のターゲットの機能の現在の設定を要求できます。whom 値をゼロに設定することは、SCSI バスまたはアダプタハードウェアの一般的な機能の現在の設定を要求することを意味します。

tran_getcap() エントリポイントは、-1 (機能が未定義の場合) または要求された機能の現在の値を返します。

HBA ドライバは、scsi_hba_lookup_capstr(9F) 関数を使用して、機能文字列を定義済みの標準的な機能セットと比較できます。

例 18-10 HBA ドライバの tran_getcap (9E) エントリポイント

static int
isp_scsi_getcap(
    struct scsi_address    *ap,
    char                   *cap,
    int                    whom)
{
    struct isp             *isp;
    int                    rval = 0;
    u_char                 tgt = ap->a_target;
    /*
      * We don't allow getting capabilities for other targets
    */
    if (cap == NULL || whom  == 0) {
        return (-1);
    }
    isp = (struct isp *)ap->a_hba_tran->tran_hba_private;
    ISP_MUTEX_ENTER(isp);

    switch (scsi_hba_lookup_capstr(cap)) {
      case SCSI_CAP_DMA_MAX:
          rval = 1 << 24; /* Limit to 16MB max transfer */
          break;
      case SCSI_CAP_MSG_OUT:
          rval = 1;
          break;
      case SCSI_CAP_DISCONNECT:
          if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_DR) == 0) {
              break;
          } else if (
              (isp->isp_cap[tgt] & ISP_CAP_DISCONNECT) == 0) {
                  break;
          }
          rval = 1;
          break;
      case SCSI_CAP_SYNCHRONOUS:
          if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_SYNC) == 0) {
              break;
          } else if (
              (isp->isp_cap[tgt] & ISP_CAP_SYNC) == 0) {
                  break;
          }
          rval = 1;
          break;
      case SCSI_CAP_WIDE_XFER:
          if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_WIDE) == 0) {
              break;
          } else if (
              (isp->isp_cap[tgt] & ISP_CAP_WIDE) == 0) {
                  break;
          }
          rval = 1;
          break;
      case SCSI_CAP_TAGGED_QING:
          if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_DR) == 0 ||
            (isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_TAG) == 0) {
              break;
          } else if (
              (isp->isp_cap[tgt] & ISP_CAP_TAG) == 0) {
                  break;
          }
          rval = 1;
          break;
      case SCSI_CAP_UNTAGGED_QING:
          rval = 1;
          break;
      case SCSI_CAP_PARITY:
          if (isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_PARITY) {
              rval = 1;
          }
          break;
      case SCSI_CAP_INITIATOR_ID:
          rval = isp->isp_initiator_id;
          break;
      case SCSI_CAP_ARQ:
          if (isp->isp_cap[tgt] & ISP_CAP_AUTOSENSE) {
              rval = 1;
          }
          break;
      case SCSI_CAP_LINKED_CMDS:
          break;
      case SCSI_CAP_RESET_NOTIFICATION:
         rval = 1;
          break;
      case SCSI_CAP_GEOMETRY:
          rval = (64 << 16) | 32;
          break;
      default:
          rval = -1;
          break;
    }
    ISP_MUTEX_EXIT(isp);
    return (rval);
}

tran_setcap() エントリポイント

SCSI HBA ドライバの tran_setcap(9E) エントリポイントは、scsi_ifsetcap(9F) によって呼び出されます。ターゲットドライバは、SCSA 定義機能セットのいずれかの現在の値を変更するために scsi_ifsetcap() を呼び出します。

ターゲットドライバは、whom パラメータをゼロ以外の値に設定することで、特定のターゲットに新しい値が設定されるように要求できます。whom 値をゼロに設定することは、一般に SCSI バスまたはアダプタハードウェアの新しい値を設定するよう要求することを意味します。

tran_setcap() は必要に応じて次の値を返します。

HBA ドライバは、scsi_hba_lookup_capstr(9F) 関数を使用して、機能文字列を定義済みの標準的な機能セットと比較できます。

例 18-11 HBA ドライバの tran_setcap (9E) エントリポイント

static int
isp_scsi_setcap(
    struct scsi_address    *ap,
    char                   *cap,
    int                    value,
    int                    whom)
{
    struct isp             *isp;
    int                    rval = 0;
    u_char                 tgt = ap->a_target;
    int                    update_isp = 0;
    /*
     * We don't allow setting capabilities for other targets
     */
    if (cap == NULL || whom == 0) {
        return (-1);
    }

    isp = (struct isp *)ap->a_hba_tran->tran_hba_private;
    ISP_MUTEX_ENTER(isp);

    switch (scsi_hba_lookup_capstr(cap)) {
      case SCSI_CAP_DMA_MAX:
      case SCSI_CAP_MSG_OUT:
      case SCSI_CAP_PARITY:
      case SCSI_CAP_UNTAGGED_QING:
      case SCSI_CAP_LINKED_CMDS:
      case SCSI_CAP_RESET_NOTIFICATION:
          /*
           * None of these are settable through
           * the capability interface.
           */
          break;
      case SCSI_CAP_DISCONNECT:
          if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_DR) == 0) {
                break;
          } else {
              if (value) {
                  isp->isp_cap[tgt] |= ISP_CAP_DISCONNECT;
              } else {
                isp->isp_cap[tgt] &= ~ISP_CAP_DISCONNECT;
              }
          }
          rval = 1;
          break;
      case SCSI_CAP_SYNCHRONOUS:
          if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_SYNC) == 0) {
                break;
          } else {
              if (value) {
                  isp->isp_cap[tgt] |= ISP_CAP_SYNC;
              } else {
                  isp->isp_cap[tgt] &= ~ISP_CAP_SYNC;
              }
          }
          rval = 1;
          break;
      case SCSI_CAP_TAGGED_QING:
          if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_DR) == 0 ||
            (isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_TAG) == 0) {
                break;
          } else {
              if (value) {
                  isp->isp_cap[tgt] |= ISP_CAP_TAG;
              } else {
                  isp->isp_cap[tgt] &= ~ISP_CAP_TAG;
              }
          }
          rval = 1;
          break;
      case SCSI_CAP_WIDE_XFER:
          if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_WIDE) == 0) {
                break;
          } else {
              if (value) {
                  isp->isp_cap[tgt] |= ISP_CAP_WIDE;
              } else {
                  isp->isp_cap[tgt] &= ~ISP_CAP_WIDE;
              }
          }
          rval = 1;
          break;
      case SCSI_CAP_INITIATOR_ID:
          if (value < N_ISP_TARGETS_WIDE) {
              struct isp_mbox_cmd mbox_cmd;
              isp->isp_initiator_id = (u_short) value;
              /*
               * set Initiator SCSI ID
               */
              isp_i_mbox_cmd_init(isp, &mbox_cmd, 2, 2,
                ISP_MBOX_CMD_SET_SCSI_ID,
                isp->isp_initiator_id,
                0, 0, 0, 0);
              if (isp_i_mbox_cmd_start(isp, &mbox_cmd) == 0) {
                  rval = 1;
              }
          }
          break;
      case SCSI_CAP_ARQ:
          if (value) {
              isp->isp_cap[tgt] |= ISP_CAP_AUTOSENSE;
          } else {
              isp->isp_cap[tgt] &= ~ISP_CAP_AUTOSENSE;
          }
          rval = 1;
          break;
      default:
          rval = -1;
          break;
    }
    ISP_MUTEX_EXIT(isp);
    return (rval);
}

中止およびリセット管理

以降のセクションでは、SCSI HBA の中止とリセットのエントリポイントについて説明します。

tran_abort() エントリポイント

SCSI HBA の tran_abort(9E) エントリポイントは、特定のターゲットに対して現在トランスポート中のコマンドを中止するために呼び出されます。このエントリポイントは、ターゲットドライバが scsi_abort(9F) を呼び出したときに呼び出されます。

tran_abort() エントリポイントは、pkt パラメータで示されたコマンドの中止を試みます。pkt パラメータが NULL の場合、tran_abort() は特定のターゲットまたは論理ユニットのトランスポート層にある未処理のコマンドをすべて中止しようとします。

正常に中止された各コマンドは、pkt_reason CMD_ABORTED と、STAT_ABORTED との論理和を取得した pkt_statistics でマーク付けされます。

tran_reset() エントリポイント

SCSI HBA ドライバの tran_reset(9E) エントリポイントは、SCSI バスまたは特定の SCSI ターゲットデバイスをリセットするために呼び出されます。このエントリポイントは、ターゲットドライバが scsi_reset(9F) を呼び出したときに呼び出されます。

tran_reset() エントリポイントは、レベルが RESET_ALL の場合に SCSI バスをリセットする必要があります。レベルが RESET_TARGET の場合は、特定のターゲットまたは論理ユニットのみをリセットする必要があります。

リセットの影響を受けるアクティブなコマンドは、pkt_reason CMD_RESET でマーク付けされます。リセットの種類によって、pkt_statistics の論理和を取得するために STAT_BUS_RESET または STAT_DEV_RESET のどちらが使われるかが決まります。

トランスポート層にあり、まだターゲット上でアクティブになっていないコマンドは、pkt_reason CMD_RESET と、STAT_ABORTED との論理和が取得された pkt_statistics でマーク付けされる必要があります。

tran_bus_reset() エントリポイント

tran_bus_reset(9E) は、ターゲットをリセットしないで SCSI バスをリセットする必要があります。

#include <sys/scsi/scsi.h>

int tran_bus_reset(dev_info_t *hba-dip, int level);

各表記の意味は次のとおりです。

*hba-dip

SCSI HBA に関連付けられたポインタです

level

ターゲットはリセットされず、SCSI バスのみがリセットされるように、RESET_BUS に設定します

scsi_hba_tran(9S) 構造体の tran_bus_reset() ベクトルは、HBA ドライバの attach(9E) の実行中に初期化されます。このベクトルは、ユーザーがバスのリセットを開始したときに呼び出される予定の HBA エントリポイントを指します。

実装方法はハードウェアによって異なります。HBA ドライバがターゲットに影響を与えずに SCSI バスをリセットできない場合、ドライバは RESET_BUS に失敗するか、このベクトルの初期化を行いません。

tran_reset_notify() エントリポイント

tran_reset_notify(9E) エントリポイントは、SCSI バスのリセットが行われたときに使用します。この関数は、コールバックによってターゲットドライバに通知するように SCSI HBA ドライバに要求します。

例 18-12 HBA ドライバの tran_reset_notify (9E) エントリポイント

isp_scsi_reset_notify(
    struct scsi_address    *ap,
    int                    flag,
    void                   (*callback)(caddr_t),
    caddr_t                arg)
{
    struct isp                       *isp;
    struct isp_reset_notify_entry    *p, *beforep;
    int                              rval = DDI_FAILURE;

    isp = (struct isp *)ap->a_hba_tran->tran_hba_private;
    mutex_enter(ISP_REQ_MUTEX(isp));
    /*
     * Try to find an existing entry for this target
     */
    p = isp->isp_reset_notify_listf;
    beforep = NULL;

    while (p) {
        if (p->ap == ap)
            break;
        beforep = p;
        p = p->next;
    }

    if ((flag & SCSI_RESET_CANCEL) && (p != NULL)) {
        if (beforep == NULL) {
            isp->isp_reset_notify_listf = p->next;
        } else {
            beforep->next = p->next;
        }
        kmem_free((caddr_t)p, sizeof (struct isp_reset_notify_entry));
        rval = DDI_SUCCESS;
    } else if ((flag & SCSI_RESET_NOTIFY) && (p == NULL)) {
        p = kmem_zalloc(sizeof (struct isp_reset_notify_entry),
          KM_SLEEP);
        p->ap = ap;
        p->callback = callback;
        p->arg = arg;
        p->next = isp->isp_reset_notify_listf;
        isp->isp_reset_notify_listf = p;
        rval = DDI_SUCCESS;
    }
    mutex_exit(ISP_REQ_MUTEX(isp));
    return (rval);
}

動的再構成 (DR)

最小限のホットプラグ操作をサポートするために、ドライバはバスの休止、バスの休止解除、およびバスのリセットのサポートを実装することが必要な場合があります。scsi_hba_tran(9S) 構造体は、これらの操作をサポートします。ハードウェアが休止、休止解除、またはリセットを必要としない場合、ドライバの変更は必要ありません。

scsi_hba_tran 構造体には、次のフィールドがあります。

int (*tran_quiesce)(dev_info_t *hba-dip);
int (*tran_unquiesce)(dev_info_t *hba-dip);
int (*tran_bus_reset)(dev_info_t *hba-dip, int level);

これらのインタフェースは、SCSI バスを休止および休止解除します。

#include <sys/scsi/scsi.h>

int prefixtran_quiesce(dev_info_t *hba-dip);
int prefixtran_unquiesce(dev_info_t *hba-dip);

tran_quiesce(9E) および tran_unquiesce(9E) は、ホットプラグ用に設計されていない SCSI デバイスに使用します。HBA ドライバで動的再構成 (DR) をサポートするためには、これらの関数を実装する必要があります。

scsi_hba_tran(9S) 構造体の tran_quiesce() および tran_unquiesce() ベクトルは、attach(9E) の実行中に HBA エントリポイントを指すように初期化されます。これらの関数は、ユーザーが休止および休止解除操作を開始したときに呼び出されます。

tran_quiesce() エントリポイントは、SCSI バスに接続されているデバイスの再構成前または再構成中に SCSI バス上のすべての動作を停止します。tran_unquiesce() エントリポイントは、SCSA フレームワークによって呼び出され、再構成操作が完了したあとで、SCSI バス上の動作を再開します。

HBA ドライバは、すべての未処理コマンドが完了するのを待ってから成功を返すことで、tran_quiesce() を処理する必要があります。ドライバがバスを休止したあとは、対応する tran_unquiesce() エントリポイントを SCSA フレームワークが呼び出すまで、新しい入出力要求をすべてキューに入れる必要があります。

HBA ドライバは、キューに入っているターゲットドライバの入出力要求を開始することで、tran_unquiesce() の呼び出しを処理します。