Sun Microsystems, Inc.
spacerspacer
spacer www.sun.com docs.sun.com |
spacer
black dot
 
 
7.  Interrupt Handlers Registering Interrupts  Previous   Contents   Next 
   
 

Interrupt Handler Responsibilities

The interrupt handler has a set of responsibilities to perform. Some are required by the framework, and some are required by the device. All interrupt handlers are required to do the following:

  • Determine if the device is interrupting and possibly reject the interrupt.

    The interrupt handler must first examine the device and determine if it has issued the interrupt. If it has not, the handler must return DDI_INTR_UNCLAIMED. This step allows the implementation of device polling: it tells the system whether this device, among a number of devices at the given interrupt priority level, has issued the interrupt.

  • Inform the device that it is being serviced.

    This is a device-specific operation, but it is required for the majority of devices. For example, SBus devices are required to interrupt until the driver tells them to stop. This guarantees that all SBus devices interrupting at the same priority level will be serviced.

  • Perform any I/O request-related processing.

    Devices interrupt for different reasons, such as transfer done or transfer error. This step may involve using data access functions to read the device's data buffer, examine the device's error register, and set the status field in a data structure accordingly. Interrupt dispatching and processing are relatively time consuming.

  • Do any additional processing that could prevent another interrupt.

    For example, read the next item of data from the device.

  • Return DDI_INTR_CLAIMED.

The following example shows an interrupt routine.


Example 7-2 Interrupt Example

static uint_t
xxintr(caddr_t arg)
{
    struct xxstate *xsp = (struct xxstate *)arg;
    uint8_t         status; 
    volatile  uint8_t  temp;

    /*
     * Claim or reject the interrupt.This example assumes
     * that the device's CSR includes this information.
     */
    mutex_enter(&xsp->high_mu);
    /* use data access routines to 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 */
    }
    /*
     * Inform the device that it is being serviced, and re-enable
     * interrupts. The example assumes that writing to the
     * CSR accomplishes this. The driver must ensure that this data
     * access operation makes it to the device before the interrupt
     * service routine returns. For example, using the data access
     * functions to read the CSR, if it does not result in unwanted
     * effects, can ensure this.
     */
    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);
    
    mutex_exit(&xsp->mu);
    return (DDI_INTR_CLAIMED);
}

Most of the steps performed by the interrupt routine depend on the specifics of the device itself. Consult the hardware manual for the device to determine the cause of the interrupt, detect error conditions, and access the device data registers.

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

 
 
 
  Previous   Contents   Next