ナビゲーションリンクをスキップ | |
印刷ビューの終了 | |
![]() |
デバイスドライバの記述 Oracle Solaris 11.1 Information Library (日本語) |
パート I Oracle Solaris プラットフォーム用デバイスドライバの設計
2. Oracle Solaris カーネルとデバイスツリー
_init() エントリポイント (SCSI HBA ドライバ)
_fini() エントリポイント (SCSI HBA ドライバ)
attach() エントリポイント (SCSI HBA ドライバ)
detach() エントリポイント (SCSI HBA ドライバ)
22. ドライバのコンパイル、ロード、パッケージ化、およびテスト
23. デバイスドライバのデバッグ、テスト、およびチューニング
HBA ドライバは、SCSA インタフェースを介してターゲットドライバとともに動作できます。SCSA インタフェースでは、HBA ドライバは、scsi_hba_tran(9S) 構造体を介して呼び出し可能なエントリポイントをいくつか提供する必要があります。
これらのエントリポイントは、次の 5 つの機能グループに分けられます。
ターゲットドライバインスタンスの初期化
資源の割り当てと解放
コマンドのトランスポート
機能管理
中止およびリセット処理
動的再構成 (DR)
次の表に、SCSA HBA のエントリポイントを機能グループ別に示します。
表 18-3 SCSA エントリポイント
|
以降のセクションでは、ターゲットエントリポイントについて説明します。
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(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(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(9E) エントリポイントは、ターゲットドライバのリクエストに応じて scsi_pkt(9S) 構造体と DMA 資源の割り当てと初期化を行います。
tran_init_pkt(9E) エントリポイントは、ターゲットドライバが SCSA 関数 scsi_init_pkt(9F) を呼び出したときに呼び出されます。
tran_init_pkt(9E) エントリポイントの各呼び出しは、次の 3 つの可能なサービスの 1 つ以上を実行するよう要求するものです。
scsi_pkt(9S) 構造体の割り当てと初期化
データ転送のための DMA 資源の割り当て
データ転送の次回分のための DMA 資源の再割り当て
tran_init_pkt(9E) エントリポイントは、pkt が NULL の場合、scsi_hba_pkt_alloc(9F) を介して scsi_pkt(9S) 構造体を割り当てる必要があります。
scsi_hba_pkt_alloc(9F) では、次の項目に領域を割り当てます。
SCSI CDB (長さは cmdlen)
SCSI ステータスの完了領域 (長さは statuslen)
パケットごとのターゲットドライバの非公開データ領域 (長さは tgtlen)
パケットごとの HBA ドライバの非公開データ領域 (長さは hbalen )
scsi_pkt(9S) 構造体のメンバー (pkt など) は、次のメンバーを除いてゼロに初期化する必要があります。
pkt_scbp – ステータスの完了
pkt_cdbp – CDB
pkt_ha_private – HBA ドライバの非公開データ
pkt_private – ターゲットドライバの非公開データ
次の図に示すように、これらのメンバーはフィールドの値が格納されるメモリー空間を指すポインタです。詳細については、「scsi_pkt 構造体 (HBA)」を参照してください。
図 18-5 scsi_pkt(9S) 構造体のポインタ
次の例は、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); }
tran_init_pkt(9E) エントリポイントは、次の条件が真の場合、データ転送用の DMA 資源を割り当てる必要があります。
bp が null 以外である。
bp->b_bcount がゼロ以外である。
DMA 資源がこの scsi_pkt(9S) にまだ割り当てられていない。
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); }
前回割り当てたパケットにまだ転送されていないデータが残っている場合は、次の条件に適合したときに tran_init_pkt(9E) エントリポイントで DMA 資源を再割り当てする必要があります。
部分的な DMA 資源がすでに割り当てられている。
前回の tran_init_pkt(9E) の呼び出しで、ゼロ以外の pkt_resid が返された。
bp が null 以外である。
bp->b_bcount がゼロ以外である。
転送の次回分に 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(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(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(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 ドライバは、コマンドのトランスポートの一環として、次の手順を実行します。
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 つの要件に基づいて操作する必要があります。
新しいコマンドがキューに入っている場合は、できるだけ速くハードウェア上でそのコマンドを起動します。
コマンド完了コールバックを呼び出します。コールバックは、コマンドが完了したときにターゲットドライバに通知するために、ターゲットドライバによって scsi_pkt(9S) 構造体で設定されています。
可能な場合は、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_statistics で scsi_pkt(9S) をマーク付けします。また、HBA ドライバはターゲットとバスの回復も試みないといけません。この回復処理を正常に実行できる場合、ドライバは、STAT_BUS_RESET または STAT_DEV_RESET との論理和を取得した pkt_statistics を使用して、scsi_pkt(9S) をマーク付けします。
回復の試行が完了したあとで、HBA ドライバはコマンド完了コールバックを呼び出します。
ISP ハードウェアでは、コマンドのタイムアウトを直接管理し、タイムアウトしたコマンドを必要なステータスとともに返します。isp サンプルドライバのタイムアウトハンドラでは、60 秒ごとに 1 回だけアクティブなコマンドのタイムアウト状態をチェックします。
isp サンプルドライバは、timeout(9F) 機能を使用して、カーネルが 60 秒ごとにタイムアウトハンドラを呼び出すように設定します。caddr_t 引数は、attach(9E) の実行時にタイムアウトが初期化されるときに設定されるパラメータです。この場合、caddr_t 引数は、ドライバのインスタンスごとに割り当てられた状態構造体を指すポインタです。
タイムアウトが発生したときに、タイムアウトしたコマンドが ISP ハードウェアから返されなかった場合は、問題が発生しています。ハードウェアが正しく機能していないため、リセットする必要があります。
以降のセクションでは、機能管理について説明します。
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); }
SCSI HBA ドライバの tran_setcap(9E) エントリポイントは、scsi_ifsetcap(9F) によって呼び出されます。ターゲットドライバは、SCSA 定義機能セットのいずれかの現在の値を変更するために scsi_ifsetcap() を呼び出します。
ターゲットドライバは、whom パラメータをゼロ以外の値に設定することで、特定のターゲットに新しい値が設定されるように要求できます。whom 値をゼロに設定することは、一般に SCSI バスまたはアダプタハードウェアの新しい値を設定するよう要求することを意味します。
tran_setcap() は必要に応じて次の値を返します。
-1 (機能が未定義の場合)
0 (HBA ドライバが要求された値に機能を設定できない場合)
1 (HBA ドライバが要求された値に機能を設定できる場合)
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 の中止とリセットのエントリポイントについて説明します。
SCSI HBA の tran_abort(9E) エントリポイントは、特定のターゲットに対して現在トランスポート中のコマンドを中止するために呼び出されます。このエントリポイントは、ターゲットドライバが scsi_abort(9F) を呼び出したときに呼び出されます。
tran_abort() エントリポイントは、pkt パラメータで示されたコマンドの中止を試みます。pkt パラメータが NULL の場合、tran_abort() は特定のターゲットまたは論理ユニットのトランスポート層にある未処理のコマンドをすべて中止しようとします。
正常に中止された各コマンドは、pkt_reason CMD_ABORTED と、STAT_ABORTED との論理和を取得した pkt_statistics でマーク付けされます。
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(9E) は、ターゲットをリセットしないで SCSI バスをリセットする必要があります。
#include <sys/scsi/scsi.h> int tran_bus_reset(dev_info_t *hba-dip, int level);
各表記の意味は次のとおりです。
SCSI HBA に関連付けられたポインタです
ターゲットはリセットされず、SCSI バスのみがリセットされるように、RESET_BUS に設定します
scsi_hba_tran(9S) 構造体の tran_bus_reset() ベクトルは、HBA ドライバの attach(9E) の実行中に初期化されます。このベクトルは、ユーザーがバスのリセットを開始したときに呼び出される予定の HBA エントリポイントを指します。
実装方法はハードウェアによって異なります。HBA ドライバがターゲットに影響を与えずに SCSI バスをリセットできない場合、ドライバは RESET_BUS に失敗するか、このベクトルの初期化を行いません。
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); }
最小限のホットプラグ操作をサポートするために、ドライバはバスの休止、バスの休止解除、およびバスのリセットのサポートを実装することが必要な場合があります。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() の呼び出しを処理します。