Writing Device Drivers

Handling High-Level Interrupts

High-level interrupts are those that interrupt at the level of the scheduler and above. This level does not allow the scheduler to run; therefore, high-level interrupt handlers cannot be preempted by the scheduler, nor can they rely on the scheduler (that is, they cannot block because of the scheduler)—they can only use mutual exclusion locks for locking.

Because of this, the driver must use ddi_intr_hilevel(9F) to determine if it is using high-level interrupts. If ddi_intr_hilevel(9F) returns true, the driver can fail to attach, or it can use a two-level scheme to handle interrupts.

The suggested method is to add a high-level interrupt handler, which simply triggers a lower-priority software interrupt to handle the device. The driver should allow more concurrency by using a separate mutex for protecting data from the high-level handler.

High-level Mutexes

A mutex initialized with the interrupt block cookie that represents a high-level interrupt is known as a high-level mutex. While holding a high-level mutex, the driver is subject to the same restrictions as a high-level interrupt handler.

High-Level Interrupt Handling Example

In the example presented in Example 7–3, the high-level mutex (xsp->high_mu) is used only to protect data shared between the high-level interrupt handler and the soft interrupt handler. This includes a queue that the high-level interrupt handler appends data to (and the low-level handler removes data from), and a flag that indicates the low-level handler is running. A separate low-level mutex (xsp->low_mu) protects the rest of the driver from the soft interrupt handler.


Example 7–3 attach(9E) Routine Handling High-Level Interrupts

static int
xxattach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
        struct xxstate *xsp;
        ...
        if (ddi_intr_hilevel(dip, inumber)) {
            ddi_get_iblock_cookie(dip, inumber,
                &xsp->high_iblock_cookie);
            mutex_init(&xsp->high_mu, NULL, MUTEX_DRIVER,
                (void *)xsp->high_iblock_cookie);
            if (ddi_add_intr(dip, inumber, &xsp->high_iblock_cookie,
                &xsp->high_idevice_cookie, xxhighintr, (caddr_t)xsp)
                != DDI_SUCCESS)
                goto failed;
            ddi_get_soft_iblock_cookie(dip, DDI_SOFTINT_HI,
                &xsp->low_iblock_cookie)
            mutex_init(&xsp->low_mu, NULL, MUTEX_DRIVER,
                (void *)xsp->low_iblock_cookie);
            if (ddi_add_softintr(dip, DDI_SOFTINT_HI, &xsp->id,
                &xsp->low_iblock_cookie, NULL,
                xxlowintr, (caddr_t)xsp) != DDI_SUCCESS)
                goto failed;
        } else {
            add normal interrupt handler
        }
        cv_init(&xsp->cv, NULL, CV_DRIVER, NULL);
        ...
        return (DDI_SUCCESS);
    failed:
            free allocated resources, remove interrupt handlers
        return (DDI_FAILURE);
}

The high-level interrupt routine services the device, and enqueues the data. The high-level routine triggers a software interrupt if the low-level routine is not running, as Example 7–4 demonstrates.


Example 7–4 High-level Interrupt Routine

static uint_t
xxhighintr(caddr_t arg)
{
    struct xxstate    *xsp = (struct xxstate *)arg;
    uint8_t        status;
    volatile  uint8_t  temp;
    int        need_softint;
    
    mutex_enter(&xsp->high_mu);
    /* read status */
    status = ddi_get8(xsp->data_access_handle, &xsp->regp->csr);
    if (!(status & INTERRUPTING)) {
        mutex_exit(&xsp->high_mu);
        return (DDI_INTR_UNCLAIMED); /* dev not interrupting */
    }

    ddi_put8(xsp->data_access_handle,&xsp->regp->csr,
        CLEAR_INTERRUPT | ENABLE_INTERRUPTS);
        /* flush store buffers */
    temp = ddi_get8(xsp->data_access_handle, &xsp->regp->csr);
    read data from device and queue the data for the low-level interrupt handler;
    if (xsp->softint_running)
        need_softint = 0;
    else {
        xsp->softint_count++;
        need_softint = 1;
    }
    mutex_exit(&xsp->high_mu);
    /* read-only access to xsp->id, no mutex needed */
    if (need_softint)
        ddi_trigger_softintr(xsp->id);
    return (DDI_INTR_CLAIMED);
}

The low-level interrupt routine is started by the high-level interrupt routine triggering a software interrupt. Once running, it should continue to do so until there is nothing left to process, as Example 7–5 shows.


Example 7–5 Low-level Interrupt Routine

static uint_t
xxlowintr(caddr_t arg)
{
    struct xxstate *xsp = (struct xxstate *)arg;
    ....
    mutex_enter(&xsp->low_mu);
    mutex_enter(&xsp->high_mu);
    if (xsp->softint_count > 1) {
        xsp->softint_count--;
        mutex_exit(&xsp->high_mu);
        mutex_exit(&xsp->low_mu);
        return (DDI_INTR_CLAIMED);
    }
    if (queue empty) {
        mutex_exit(&xsp->high_mu);
        mutex_exit(&xsp->low_mu);
        return (DDI_INTR_UNCLAIMED);
    }
    xsp->softint_running = 1;
    while (data on queue) {
        ASSERT(mutex_owned(&xsp->high_mu);
        dequeue data from high-level queue;
        mutex_exit(&xsp->high_mu);
        normal interrupt processing
        mutex_enter(&xsp->high_mu);
    }
    xsp->softint_running = 0;
    xsp->softint_count = 0;
    mutex_exit(&xsp->high_mu);
    mutex_exit(&xsp->low_mu);
    return (DDI_INTR_CLAIMED);
}