Writing Device Drivers

Interrupt Handler and Command Completion

The interrupt handler must check the status of the device to be sure the device is generating the interrupt in question. It must also check for any errors that have occurred and service any interrupts generated by the device.

If data was transferred, the hardware should be checked to determine how much data was actually transferred, and the pkt_resid field in the scsi_pkt(9S) structure should be set to the residual of the transfer.

For commands marked with the PKT_CONSISTENT flag when DMA resources were allocated through tran_init_pkt(9E), the HBA driver must ensure that the data transfer for the command is correctly synchronized before the target driver's command completion callback is performed.

Once a command has completed, there are two requirements:

It is important to start a new command on the hardware, if possible, before calling the PKT_COMP command completion callback. The command completion handling can take considerable time, as the target driver will typically call functions such as biodone(9F) and possibly scsi_transport(9F) to begin a new command.

The interrupt handler must return DDI_INTR_CLAIMED if this interrupt is claimed by this driver; otherwise, the handler returns DDI_INTR_UNCLAIMED.

Example 15-10 shows an interrupt handler for the SCSI HBA isp driver. The caddr_t argument is the parameter set up when the interrupt handler was added in attach(9E) and is typically a pointer to the state structure allocated per instance.


Example 15-10 HBA Driver Interrupt Handler

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,
         * we need to 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);
        }
    }
}