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

印刷ビューの終了

更新: 2014 年 9 月
 
 

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

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 つの要件に基づいて操作する必要があります。

  • 新しいコマンドがキューに入っている場合は、できるだけ速くハードウェア上でそのコマンドを起動します。

  • コマンド完了コールバックを呼び出します。コールバックは、コマンドが完了したときにターゲットドライバに通知するために、ターゲットドライバによって 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_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 ハードウェアから返されなかった場合は、問題が発生しています。ハードウェアが正しく機能していないため、リセットする必要があります。