Oracle® Solaris 11.2 デバイスドライバの記述

印刷ビューの終了

更新: 2014 年 9 月
 
 

リソース割り当て

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

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) 構造体の割り当てと初期化

  • データ転送のための DMA リソースの割り当て

  • データ転送の次回分のための DMA リソースの再割り当て

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

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

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

  • scsi_pkt(9S)

  • 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 Structure (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 リソースを割り当てる必要があります。

  • 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);
}

データ転送のための DMA リソースの再割り当て

前回割り当てたパケットにまだ転送されていないデータが残っている場合は、次の条件に適合したときに 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() エントリポイント

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;
    }
}