Writing Device Drivers

Chapter 8 Interrupt Handlers

This chapter describes mechanisms for handling interrupts, such as allocating, registering, servicing, and removing interrupts. This chapter provides information on the following subjects:

Interrupt Handler Overview

An interrupt is a hardware signal from a device to a CPU. An interrupt tells the CPU that the device needs attention and that the CPU should stop any current activity and respond to the device. If the CPU is not performing a task that has higher priority than the priority of the interrupt, then the CPU suspends the current thread. The CPU then invokes the interrupt handler for the device that sent the interrupt signal. The job of the interrupt handler is to service the device and stop the device from interrupting. When the interrupt handler returns, the CPU resumes the work it was doing before the interrupt occurred.

The Solaris DDI/DKI provides interfaces for performing the following tasks:

Device Interrupts

I/O buses implement interrupts in two common ways: vectored and polled. Both methods commonly supply a bus-interrupt priority level. Vectored devices also supply an interrupt vector. Polled devices do not supply interrupt vectors.

To stay current with changing bus technologies, the Solaris OS has been enhanced to accommodate both newer types of interrupts and more traditional interrupts that have been in use for many years. Specifically, the operating system now recognizes three types of interrupts:


Note –

Some newer bus technologies such as PCI Express require MSIs but can accommodate legacy interrupts by using INTx emulation. INTx emulation is used for compatibility purposes, but is not considered to be good practice.


High-Level Interrupts

A bus prioritizes a device interrupt at a bus-interrupt level. The bus interrupt level is then mapped to a processor-interrupt level. A bus interrupt level that maps to a CPU interrupt priority above the scheduler priority level is called a high-level interrupt. High-level interrupt handlers are restricted to calling the following DDI interfaces:

A bus-interrupt level by itself does not determine whether a device interrupts at a high level. A particular bus-interrupt level can map to a high-level interrupt on one platform, but map to an ordinary interrupt on another platform.

A driver is not required to support devices that have high-level interrupts. However, the driver is required to check the interrupt level. If the interrupt priority is greater than or equal to the highest system priority, the interrupt handler runs in high-level interrupt context. In this case, the driver can fail to attach, or the driver can use a two-level scheme to handle interrupts. For more information, see Handling High-Level Interrupts.

Legacy Interrupts

The only information that the system has about a device interrupt is the priority level of the bus interrupt and the interrupt request number. An example of the priority level for a bus interrupt is the IPL on an SBus in a SPARC machine. An example of an interrupt request number is the IRQ on an ISA bus in an x86 machine.

When an interrupt handler is registered, the system adds the handler to a list of potential interrupt handlers for each IPL or IRQ. When the interrupt occurs, the system must determine which device actually caused the interrupt, among all devices that are associated with a given IPL or IRQ. The system calls all the interrupt handlers for the designated IPL or IRQ until one handler claims the interrupt.

The following buses are capable of supporting polled interrupts:

Standard and Extended Message-Signaled Interrupts

Both standard (MSI) and extended (MSI-X) message-signaled interrupts are implemented as in-band messages. A message-signaled interrupt is posted as a write with an address and value that are specified by the software.

MSI Interrupts

Conventional PCI specifications include optional support for Message Signaled Interrupts (MSI). An MSI is an in-band message that is implemented as a posted write. The address and the data for the MSI are specified by software and are specific to the host bridge. Because the messages are in-band, the receipt of the message can be used to “push” data that is associated with the interrupt. By definition, MSI interrupts are unshared. Each MSI message that is assigned to a device is guaranteed to be a unique message in the system. PCI functions can request 1, 2, 4, 8, 16, or 32 MSI messages. Note that the system software can allocate fewer MSI messages to a function than the function requested. The host bridge can be limited in the number of unique MSI messages that are allocated for devices.

MSI-X Interrupts

MSI-X interrupts are enhanced versions of MSI interrupts that have the same features as MSI interrupts with the following key differences:

With MSI-X interrupts, an unallocated interrupt vector of a device can use a previously added or initialized MSI-X interrupt vector to share the same vector address, vector data, interrupt handler, and handler arguments. Use the ddi_intr_dup_handler(9F) function to alias the resources provided by the Solaris OS to the unallocated interrupt vectors on an associated device. For example, if 2 MSI-X interrupts are allocated to a driver and 32 interrupts are supported on the device, then the driver can use ddi_intr_dup_handler() to alias the 2 interrupts it received to the 30 additional interrupts on the device.

The ddi_intr_dup_handler() function can duplicate interrupts that were added with ddi_intr_add_handler(9F) or initialized with ddi_intr_enable(9F).

A duplicated interrupt is disabled initially. Use ddi_intr_enable() to enable the duplicated interrupt. You cannot remove the original MSI-X interrupt handler until all duplicated interrupt handlers that are associated with this original interrupt handler are removed. To remove a duplicated interrupt handler, first call ddi_intr_disable(9F), and then call ddi_intr_free(9F). When all duplicated interrupt handlers that are associated with this original interrupt handler are removed, then you can use ddi_intr_remove_handler(9F) to remove the original MSI-X interrupt handler. See the ddi_intr_dup_handler(9F) man page for examples.

Software Interrupts

The Solaris DDI/DKI supports software interrupts, also known as soft interrupts. Soft interrupts are initiated by software rather than by a hardware device. Handlers for these interrupts must also be added to and removed from the system. Soft interrupt handlers run in interrupt context and therefore can be used to do many of the tasks that belong to an interrupt handler.

Hardware interrupt handlers must perform their tasks quickly, because the handlers might have to suspend other system activity while doing these tasks. This requirement is particularly true for high-level interrupt handlers, which operate at priority levels greater than the priority level of the system scheduler. High-level interrupt handlers mask the operations of all lower-priority interrupts, including the interrupt operations of the system clock. Consequently, the interrupt handler must avoid involvement in activities that might cause it to sleep, such as acquiring a mutex.

If the handler sleeps, then the system might hang because the clock is masked and incapable of scheduling the sleeping thread. For this reason, high-level interrupt handlers normally perform a minimum amount of work at high-priority levels and delegate other tasks to software interrupts, which run below the priority level of the high-level interrupt handler. Because software interrupt handlers run below the priority level of the system scheduler, software interrupt handlers can do the work that the high-level interrupt handler was incapable of doing.

DDI Interrupt Functions

The Solaris OS provides a framework for registering and unregistering interrupts and provides support for Message Signaled Interrupts (MSIs). Interrupt management interfaces enable you to manipulate priorities, capabilities, and interrupt masking, and to obtain pending information.

Interrupt Capability Functions

Use the following functions to obtain interrupt information:

ddi_intr_get_navail(9F)

Returns the number of interrupts available for a specified hardware device and interrupt type.

ddi_intr_get_nintrs(9F)

Returns the number of interrupts that the device supports for the specified interrupt type.

ddi_intr_get_supported_types(9F)

Returns the hardware interrupt types that are supported by both the device and the host.

ddi_intr_get_cap(9F)

Returns interrupt capability flags for the specified interrupt.

Interrupt Initialization and Destruction Functions

Use the following functions to create and remove interrupts:

ddi_intr_alloc(9F)

Allocates system resources and interrupt vectors for the specified type of interrupt.

ddi_intr_free(9F)

Releases the system resources and interrupt vectors for a specified interrupt handle.

ddi_intr_set_cap(9F)

Sets the capability of the specified interrupt through the use of the DDI_INTR_FLAG_LEVEL and DDI_INTR_FLAG_EDGE flags.

ddi_intr_add_handler(9F)

Adds an interrupt handler.

ddi_intr_dup_handler(9F)

Use with MSI-X only. Copies an address and data pair for an allocated interrupt vector to an unused interrupt vector on the same device.

ddi_intr_remove_handler(9F)

Removes the specified interrupt handler.

ddi_intr_enable(9F)

Enables the specified interrupt.

ddi_intr_disable(9F)

Disables the specified interrupt.

ddi_intr_block_enable(9F)

Use with MSI only. Enables the specified range of interrupts.

ddi_intr_block_disable(9F)

Use with MSI only. Disables the specified range of interrupts.

ddi_intr_set_mask(9F)

Sets an interrupt mask if the specified interrupt is enabled.

ddi_intr_clr_mask(9F)

Clears an interrupt mask if the specified interrupt is enabled.

ddi_intr_get_pending(9F)

Reads the interrupt pending bit if such a bit is supported by either the host bridge or the device.

Priority Management Functions

Use the following functions to obtain and set priority information:

ddi_intr_get_pri(9F)

Returns the current software priority setting for the specified interrupt.

ddi_intr_set_pri(9F)

Sets the interrupt priority level for the specified interrupt.

ddi_intr_get_hilevel_pri(9F)

Returns the minimum priority level for a high-level interrupt.

Soft Interrupt Functions

Use the following functions to manipulate soft interrupts and soft interrupt handlers:

ddi_intr_add_softint(9F)

Adds a soft interrupt handler.

ddi_intr_trigger_softint(9F)

Triggers the specified soft interrupt.

ddi_intr_remove_softint(9F)

Removes the specified soft interrupt handler.

ddi_intr_get_softint_pri(9F)

Returns the soft interrupt priority for the specified interrupt.

ddi_intr_set_softint_pri(9F)

Changes the relative soft interrupt priority for the specified soft interrupt.

Interrupt Function Examples

This section provides examples for performing the following tasks:


Example 8–1 Changing Soft Interrupt Priority

Use the ddi_intr_set_softint_pri(9F) function to change the soft interrupt priority to 9.

if (ddi_intr_set_softint_pri(mydev->mydev_softint_hdl, 9) != DDI_SUCCESS)
    cmn_err (CE_WARN, "ddi_intr_set_softint_pri failed");


Example 8–2 Checking for Pending Interrupts

Use the ddi_intr_get_pending(9F) function to check whether an interrupt is pending.

if (ddi_intr_get_pending(mydevp->htable[0], &pending) != DDI_SUCCESS)
    cmn_err(CE_WARN, "ddi_intr_get_pending() failed");
else if (pending)
    cmn_err(CE_NOTE, "ddi_intr_get_pending(): Interrupt pending");


Example 8–3 Setting Interrupt Masks

Use the ddi_intr_set_mask(9F) function to set interrupt masking to prevent the device from receiving interrupts.

if ((ddi_intr_set_mask(mydevp->htable[0]) != DDI_SUCCESS))
    cmn_err(CE_WARN, "ddi_intr_set_mask() failed");


Example 8–4 Clearing Interrupt Masks

Use the ddi_intr_clr_mask(9F) function to clear interrupt masking. The ddi_intr_clr_mask(9F) function fails if the specified interrupt is not enabled. If the ddi_intr_clr_mask(9F) function succeeds, the device starts generating interrupts.

if (ddi_intr_clr_mask(mydevp->htable[0]) != DDI_SUCCESS)
    cmn_err(CE_WARN, "ddi_intr_clr_mask() failed");

Registering Interrupts

Before a device driver can receive and service interrupts, the driver must call ddi_intr_add_handler(9F) to register an interrupt handler with the system. Registering interrupt handlers provides the system with a way to associate an interrupt handler with an interrupt specification. The interrupt handler is called when the device might have been responsible for the interrupt. The handler has the responsibility of determining whether it should handle the interrupt and, if so, of claiming that interrupt.


Tip –

Use the ::interrupts command in the mdb or kmdb debugger to retrieve the registered interrupt information of a device on supported SPARC and x86 systems.


Registering Legacy Interrupts

To register a driver's interrupt handler, the driver typically performs the following steps in its attach(9E) entry point:

  1. Use ddi_intr_get_supported_types(9F) to determine which types of interrupts are supported.

  2. Use ddi_intr_get_nintrs(9F) to determine the number of supported interrupt types.

  3. Use kmem_zalloc(9F) to allocate memory for DDI interrupt handles.

  4. For each interrupt type that you allocate, take the following steps:

    1. Use ddi_intr_get_pri(9F) to get the priority for the interrupt.

    2. If you need to set a new priority for the interrupt, use ddi_intr_set_pri(9F).

    3. Use mutex_init(9F) to initialize the lock.

    4. Use ddi_intr_add_handler(9F) to register the handler for the interrupt.

    5. Use ddi_intr_enable(9F) to enable the interrupt.

  5. Take the following steps to free each interrupt:

    1. Disable each interrupt using ddi_intr_disable(9F).

    2. Remove the interrupt handler using ddi_intr_remove_handler(9F).

    3. Remove the lock using mutex_destroy(9F).

    4. Free the interrupt using ddi_intr_free(9F) and kmem_free(9F) to free memory that was allocated for DDI interrupt handles.


Example 8–5 Registering a Legacy Interrupt

The following example shows how to install an interrupt handler for a device called mydev. This example assumes that mydev supports one interrupt only.

/* Determine which types of interrupts supported */
ret = ddi_intr_get_supported_types(mydevp->mydev_dip, &type);

if ((ret != DDI_SUCCESS) || (!(type & DDI_INTR_TYPE_FIXED))) {
    cmn_err(CE_WARN, "Fixed type interrupt is not supported");
    return (DDI_FAILURE);
}

/* Determine number of supported interrupts */
ret = ddi_intr_get_nintrs(mydevp->mydev_dip, DDI_INTR_TYPE_FIXED, 
    &count);

/*
 * Fixed interrupts can only have one interrupt. Check to make
 * sure that number of supported interrupts and number of
 * available interrupts are both equal to 1.
 */
if ((ret != DDI_SUCCESS) || (count != 1)) {
    cmn_err(CE_WARN, "No fixed interrupts");
    return (DDI_FAILURE);
}

/* Allocate memory for DDI interrupt handles */
mydevp->mydev_htable = kmem_zalloc(sizeof (ddi_intr_handle_t), 
    KM_SLEEP);
ret = ddi_intr_alloc(mydevp->mydev_dip, mydevp->mydev_htable,
    DDI_INTR_TYPE_FIXED, 0, count, &actual, 0);

if ((ret != DDI_SUCCESS) || (actual != 1)) {
    cmn_err(CE_WARN, "ddi_intr_alloc() failed 0x%x", ret);
    kmem_free(mydevp->mydev_htable, sizeof (ddi_intr_handle_t));

    return (DDI_FAILURE);
}

/* Sanity check that count and available are the same. */
ASSERT(count == actual);

/* Get the priority of the interrupt */
if (ddi_intr_get_pri(mydevp->mydev_htable[0], &mydevp->mydev_intr_pri)) {
    cmn_err(CE_WARN, "ddi_intr_alloc() failed 0x%x", ret);

    (void) ddi_intr_free(mydevp->mydev_htable[0]);
    kmem_free(mydevp->mydev_htable, sizeof (ddi_intr_handle_t));

    return (DDI_FAILURE);
}

cmn_err(CE_NOTE, "Supported Interrupt pri = 0x%x", mydevp->mydev_intr_pri);

/* Test for high level mutex */
if (mydevp->mydev_intr_pri >= ddi_intr_get_hilevel_pri()) {
    cmn_err(CE_WARN, "Hi level interrupt not supported");

    (void) ddi_intr_free(mydevp->mydev_htable[0]);
    kmem_free(mydevp->mydev_htable, sizeof (ddi_intr_handle_t));

    return (DDI_FAILURE);
}

/* Initialize the mutex */
mutex_init(&mydevp->mydev_int_mutex, NULL, MUTEX_DRIVER,
    DDI_INTR_PRI(mydevp->mydev_intr_pri));

/* Register the interrupt handler */
if (ddi_intr_add_handler(mydevp->mydev_htable[0], mydev_intr, 
   (caddr_t)mydevp, NULL) !=DDI_SUCCESS) {
    cmn_err(CE_WARN, "ddi_intr_add_handler() failed");

    mutex_destroy(&mydevp->mydev_int_mutex);
    (void) ddi_intr_free(mydevp->mydev_htable[0]);
    kmem_free(mydevp->mydev_htable, sizeof (ddi_intr_handle_t));

    return (DDI_FAILURE);
}

/* Enable the interrupt */
if (ddi_intr_enable(mydevp->mydev_htable[0]) != DDI_SUCCESS) {
    cmn_err(CE_WARN, "ddi_intr_enable() failed");

    (void) ddi_intr_remove_handler(mydevp->mydev_htable[0]);
    mutex_destroy(&mydevp->mydev_int_mutex);
    (void) ddi_intr_free(mydevp->mydev_htable[0]);
    kmem_free(mydevp->mydev_htable, sizeof (ddi_intr_handle_t));

    return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}


Example 8–6 Removing a Legacy Interrupt

The following example shows how legacy interrupts are removed.

/* disable interrupt */
(void) ddi_intr_disable(mydevp->mydev_htable[0]);

/* Remove interrupt handler */
(void) ddi_intr_remove_handler(mydevp->mydev_htable[0]);

/* free interrupt handle */
(void) ddi_intr_free(mydevp->mydev_htable[0]);

/* free memory */
kmem_free(mydevp->mydev_htable, sizeof (ddi_intr_handle_t));

Registering MSI Interrupts

To register a driver's interrupt handler, the driver typically performs the following steps in its attach(9E) entry point:

  1. Use ddi_intr_get_supported_types(9F) to determine which types of interrupts are supported.

  2. Use ddi_intr_get_nintrs(9F) to determine the number of supported MSI interrupt types.

  3. Use ddi_intr_alloc(9F) to allocate memory for the MSI interrupts.

  4. For each interrupt type that you allocate, take the following steps:

    1. Use ddi_intr_get_pri(9F) to get the priority for the interrupt.

    2. If you need to set a new priority for the interrupt, use ddi_intr_set_pri(9F).

    3. Use mutex_init(9F) to initialize the lock.

    4. Use ddi_intr_add_handler(9F) to register the handler for the interrupt.

  5. Use one of the following functions to enable all the interrupts:


Example 8–7 Registering a Set of MSI Interrupts

The following example illustrates how to register an MSI interrupt for a device called mydev.

/* Get supported interrupt types */
if (ddi_intr_get_supported_types(devinfo, &intr_types) != DDI_SUCCESS) {
    cmn_err(CE_WARN, "ddi_intr_get_supported_types failed");
    goto attach_fail;
}

if (intr_types & DDI_INTR_TYPE_MSI) 
    mydev_add_msi_intrs(mydevp);

/* Check count, available and actual interrupts */
static int
mydev_add_msi_intrs(mydev_t *mydevp)
{
    dev_info_t    *devinfo = mydevp->devinfo;
    int           count, avail, actual;
    int           x, y, rc, inum = 0;

    /* Get number of interrupts */
    rc = ddi_intr_get_nintrs(devinfo, DDI_INTR_TYPE_MSI, &count);
    if ((rc != DDI_SUCCESS) || (count == 0)) {
        cmn_err(CE_WARN, "ddi_intr_get_nintrs() failure, rc: %d, "
            "count: %d", rc, count);

        return (DDI_FAILURE);
    }
    /* Get number of available interrupts */
    rc = ddi_intr_get_navail(devinfo, DDI_INTR_TYPE_MSI, &avail);
    if ((rc != DDI_SUCCESS) || (avail == 0)) {
        cmn_err(CE_WARN, "ddi_intr_get_navail() failure, "
            "rc: %d, avail: %d\n", rc, avail);
        return (DDI_FAILURE);
    }
    if (avail < count) {
        cmn_err(CE_NOTE, "nitrs() returned %d, navail returned %d",
            count, avail);
    }
    /* Allocate memory for MSI interrupts */
    mydevp->intr_size = count * sizeof (ddi_intr_handle_t);
    mydevp->htable = kmem_alloc(mydevp->intr_size, KM_SLEEP);

    rc = ddi_intr_alloc(devinfo, mydevp->htable, DDI_INTR_TYPE_MSI, inum,
        count, &actual, DDI_INTR_ALLOC_NORMAL);

    if ((rc != DDI_SUCCESS) || (actual == 0)) {
        cmn_err(CE_WARN, "ddi_intr_alloc() failed: %d", rc);

        kmem_free(mydevp->htable, mydevp->intr_size);
        return (DDI_FAILURE);
    }

    if (actual < count) {
        cmn_err(CE_NOTE, "Requested: %d, Received: %d", count, actual);
    }

    mydevp->intr_cnt = actual;
    /*
     * Get priority for first msi, assume remaining are all the same
     */
    if (ddi_intr_get_pri(mydevp->htable[0], &mydev->intr_pri) !=
        DDI_SUCCESS) {
        cmn_err(CE_WARN, "ddi_intr_get_pri() failed");

        /* Free already allocated intr */
        for (y = 0; y < actual; y++) {
            (void) ddi_intr_free(mydevp->htable[y]);
        }

        kmem_free(mydevp->htable, mydevp->intr_size);
        return (DDI_FAILURE);
    }
    /* Call ddi_intr_add_handler() */
    for (x = 0; x < actual; x++) {
        if (ddi_intr_add_handler(mydevp->htable[x], mydev_intr,
           (caddr_t)mydevp, NULL) != DDI_SUCCESS) {
            cmn_err(CE_WARN, "ddi_intr_add_handler() failed");

            /* Free already allocated intr */
            for (y = 0; y < actual; y++) {
                (void) ddi_intr_free(mydevp->htable[y]);
            }

            kmem_free(mydevp->htable, mydevp->intr_size);
            return (DDI_FAILURE);
        }
    }

    (void) ddi_intr_get_cap(mydevp->htable[0], &mydevp->intr_cap);
    if (mydev->m_intr_cap & DDI_INTR_FLAG_BLOCK) {
        /* Call ddi_intr_block_enable() for MSI */
        (void) ddi_intr_block_enable(mydev->m_htable, mydev->m_intr_cnt);
    } else {
        /* Call ddi_intr_enable() for MSI non block enable */
        for (x = 0; x < mydev->m_intr_cnt; x++) {
            (void) ddi_intr_enable(mydev->m_htable[x]);
        }
    }
    return (DDI_SUCCESS);
}


Example 8–8 Removing MSI Interrupts

The following example shows how to remove MSI interrupts.

static void
mydev_rem_intrs(mydev_t *mydev)
{
    int        x;

    /* Disable all interrupts */
    if (mydev->m_intr_cap & DDI_INTR_FLAG_BLOCK) {
        /* Call ddi_intr_block_disable() */
        (void) ddi_intr_block_disable(mydev->m_htable, mydev->m_intr_cnt);
    } else {
        for (x = 0; x < mydev->m_intr_cnt; x++) {
            (void) ddi_intr_disable(mydev->m_htable[x]);
        }
    }

    /* Call ddi_intr_remove_handler() */
    for (x = 0; x < mydev->m_intr_cnt; x++) {
        (void) ddi_intr_remove_handler(mydev->m_htable[x]);
        (void) ddi_intr_free(mydev->m_htable[x]);
    }

    kmem_free(mydev->m_htable, mydev->m_intr_size);
}    

Interrupt Resource Management

This section discusses how a driver for a device that can generate many different interruptible conditions can utilize the Interrupt Resource Management feature to optimize its allocation of interrupt vectors.

The Interrupt Resource Management Feature

The Interrupt Resource Management feature can enable a device driver to use more interrupt resources by dynamically managing the driver's interrupt configuration. When the Interrupt Resource Management feature is not used, configuration of interrupt handling usually only occurs in a driver's attach(9E) routine. The Interrupt Resource Management feature monitors the system for changes, recalculates the number of interrupt vectors to grant to each device in response to those changes, and notifies each affected participating driver of the driver's new allocation of interrupt vectors. A participating driver is a driver that has registered a callback handler as described in Callback Interfaces. Changes that can cause interrupt vector reallocation include adding or removing devices, or an explicit request as described in Modify Number of Interrupt Vectors Requested.

The Interrupt Resource Management feature is not available on every Solaris platform. The feature is only available to PCIe devices that utilize MSI-X interrupts.


Note –

A driver that utilizes the Interrupt Resource Management feature must be able to adapt correctly when the feature is not available.


When the Interrupt Resource Management feature is available, it can enable a driver to gain access to more interrupt vectors than the driver might otherwise be allocated. A driver might process interrupt conditions more efficiently when utilizing a larger number of interrupt vectors.

The Interrupt Resource Management feature dynamically adjusts the number of interrupt vectors granted to each participating driver depending upon the following constraints:

The number of interrupt vectors made available to a device at any given time can vary:

A driver must provide the following support to use the Interrupt Resource Management feature:

Callback Interfaces

A driver must use the following interfaces to register its callback support.

Table 8–1 Callback Support Interfaces

Interface 

Data Structures 

Description 

ddi_cb_register()

ddi_cb_flags_t, ddi_cb_handle_t

Register a callback handler function to receive specific types of actions. 

ddi_cb_unregister()

ddi_cb_handle_t

Unregister a callback handler function. 

(*ddi_cb_func_t)()

ddi_cb_action_t

Receive callback actions and specific arguments relevant to each action to be processed. 

Register a Callback Handler Function

Use the ddi_cb_register(9F) function to register a callback handler function for a driver.

int
ddi_cb_register (dev_info_t *dip, ddi_cb_flags_t cbflags,
                 ddi_cb_func_t cbfunc, void *arg1, void *arg2,
                 ddi_cb_handle_t *ret_hdlp);

The driver can register only one callback function. This one callback function is used to handle all individual callback actions. The cbflags parameter determines which types of actions should be received by the driver when they occur. The cbfunc() routine is called whenever a relevant action should be processed by the driver. The driver specifies two private arguments (arg1 and arg2) to send to itself during each execution of its cbfunc() routine.

The cbflags() parameter is an enumerated type that specifies which actions the driver supports.

typedef enum {
        DDI_CB_FLAG_INTR
} ddi_cb_flags_t;

To register support for Interrupt Resource Management actions, a driver must register a handler and include the DDI_CB_FLAG_INTR flag. When the callback handler is successfully registered, an opaque handle is returned through the ret_hdlp parameter. When the driver is finished with the callback handler, the driver can use the ret_hdlp parameter to unregister the callback handler.

Register the callback handler in the driver's attach(9F) entry point. Save the opaque handle in the driver's soft state. Unregister the callback handler in the driver's detach(9F) entry point.

Unregister a Callback Handler Function

Use the ddi_cb_unregister(9F) function to unregister a callback handler function for a driver.

int
ddi_cb_unregister (ddi_cb_handle_t hdl);

Make this call in the driver's detach(9F) entry point. After this call, the driver no longer receives callback actions.

The driver also loses any additional support from the system that it gained from having a registered callback handling function. For example, some interrupt vectors previously made available to the driver are immediately taken back when the driver unregisters its callback handling function. Before returning successfully, the ddi_cb_unregister() function notifies the driver of any final actions that result from losing support from the system.

Callback Handler Function

Use the registered callback handling function to receive callback actions and receive arguments that are specific to each action to be processed.

typedef int (*ddi_cb_func_t)(dev_info_t *dip, ddi_cb_action_t cbaction,
                             void *cbarg, void *arg1, void *arg2);

The cbaction parameter specifies what action the driver is receiving a callback to process.

typedef enum {
        DDI_CB_INTR_ADD,
        DDI_CB_INTR_REMOVE
} ddi_cb_action_t;

A DDI_CB_INTR_ADD action means that the driver now has more interrupts available to use. A DDI_CB_INTR_REMOVE action means that the driver now has fewer interrupts available to use. Cast the cbarg parameter to an int to determine the number of interrupts added or removed. The cbarg value represents the change in the number of interrupts that are available.

For example, get the change in the number of interrupts available:

count = (int)(uintptr_t)cbarg;

If the cbaction is DDI_CB_INTR_ADD, add cbarg number of interrupt vectors. If the cbaction is DDI_CB_INTR_REMOVE, free cbarg number of interrupt vectors.

See ddi_cb_register(9F) for an explanation of arg1 and arg2.

The callback handling function must be able to perform correctly for the entire time that the function is registered. The callback function cannot depend upon any data structures that might be destroyed before the callback function is successfully unregistered.

The callback handling function must return one of the following values:

Interrupt Request Interfaces

A driver must use the following interfaces to request interrupt vectors from the system.

Table 8–2 Interrupt Vector Request Interfaces

Interface 

Data Structures 

Description 

ddi_intr_alloc()

ddi_intr_handle_t

Allocate interrupts. 

ddi_intr_set_nreq()

 

Change number of interrupt vectors requested. 

Allocate an Interrupt

Use the ddi_intr_alloc(9F) function to initially allocate interrupts.

int
ddi_intr_alloc (dev_info_t *dip, ddi_intr_handle_t *h_array, int type,
                int inum, int count, int *actualp, int behavior);

Before calling this function, the driver must allocate an empty handle array large enough to contain the number of interrupts requested. The ddi_intr_alloc() function attempts to allocate count number of interrupt handles, and initialize the array with the assigned interrupt vectors beginning at the offset specified by the inum parameter. The actualp parameter returns the actual number of interrupt vectors that were allocated.

A driver can use the ddi_intr_alloc() function in two ways:

If you are using the Interrupt Resource Management feature, call ddi_intr_alloc() one time to allocate all interrupt vectors at once. The count parameter is the total number of interrupt vectors requested by the driver. If the value in actualp is less than the value of count, then the system is not able to fulfill the request completely. The Interrupt Resource Management feature saves this request (count becomes nreq - see below) and might be able to allocate more interrupt vectors to this driver at a later time.


Note –

When you use the Interrupt Resource Management feature, additional calls to ddi_intr_alloc() do not change the total number of interrupt vectors requested. Use the ddi_intr_set_nreq(9F) function to change the number of interrupt vectors requested.


Modify Number of Interrupt Vectors Requested

Use the ddi_intr_set_nreq(9F) function to change the number of interrupt vectors requested.

int
ddi_intr_set_nreq (dev_info_t *dip, int nreq);

When the Interrupt Resource Management feature is available, a driver can use the ddi_intr_set_nreq() function to dynamically adjust the total number of interrupt vectors requested. The driver might do this in response to the actual load that exists once the driver is attached.

A driver must first call ddi_intr_alloc(9F) to request an initial number of interrupt vectors. Any time after the ddi_intr_alloc()call, the driver can call ddi_intr_set_nreq() to change its request size. The specified nreq value is the driver's new total number of requested interrupt vectors. The Interrupt Resource Management feature might rebalance the number of interrupts allocated to each driver in the system in response to this new request. Whenever the Interrupt Resource Management feature rebalances the number of interrupts allocated to drivers, each affected driver receives a callback notification that more or fewer interrupt vectors are available for the driver to use.

A driver might dynamically adjust its total number of requested interrupt vectors if, for example, it uses interrupts in conjunction with specific transactions that it is processing. A storage driver might associate a DMA engine with each ongoing transaction, thus requiring interrupt vectors for that reason. A driver might make calls to ddi_intr_set_nreq() in its open(9F) and close(9F) routines to scale its interrupt usage in response to actual use of the driver.

Interrupt Usage and Flexibility

A driver for a device that supports many different interruptible conditions must be able to map those conditions to an arbitrary number of interrupt vectors. The driver cannot assume that interrupt vectors that are allocated will remain available. Some currently available interrupts might later be taken back by the system to accommodate the needs of other drivers in the system.

A driver must be able to:

In summary, the driver must be able to select a mixture of interrupt handling functions and program its hardware to generate interrupts according to need and interrupt availability. In some cases multiple interrupts might be targeted to the same vector, and the interrupt handler for that interrupt vector must determine which interrupts occurred. The performance of the device can be affected by how well the driver maps interrupts to interrupt vectors.

Example Implementation of Interrupt Resource Management

One type of device driver that is an excellent candidate for interrupt resource management is a network device driver. The network device hardware supports multiple transmit and receive channels.

The network device generates a unique interrupt condition whenever the device receives a packet on one of its receive channels or transmits a packet on one of its transmit channels. The hardware can send a specific MSI-X interrupt for each event that can occur. A table in the hardware determines which MSI-X interrupt to generate for each event.

To optimize performance, the driver requests enough interrupts from the system to give each separate interrupt its own interrupt vector. The driver makes this request when it first calls ddi_intr_alloc(9F) in its attach(9F) routine.

The driver then evaluates the actual number of interrupts it received from ddi_intr_alloc() in actualp. It might receive all the interrupts it requested, or it might receive fewer interrupts.

A separate function inside the driver uses the total number of available interrupts to calculate which MSI-X interrupts to generate for each event. This function programs the table in the hardware accordingly.

The driver should have two different interrupt handler functions.

In the example driver in this section, the function xx_setup_interrupts() uses the number of available interrupt vectors to program the hardware and calls the appropriate interrupt handler for each of those interrupt vectors. The xx_setup_interrupts() function is called in two places: after ddi_intr_alloc() is called in xx_attach(), and after interrupt vector allocations are adjusted in the xx_cbfunc() callback handler function.

int
xx_setup_interrupts(xx_state_t *statep, int navail, xx_intrs_t *xx_intrs_p);

The xx_setup_interrupts() function is called with an array of xx_intrs_t data structures.

typedef struct {
        ddi_intr_handler_t      inthandler;
        void                    *arg1;
        void                    *arg2;
} xx_intrs_t;

This xx_setup_interrupts() functionality must exist in the driver independent of whether the Interrupt Resource Management feature is available. Drivers must be able to function with fewer interrupt vectors than the number requested during attach. If the Interrupt Resource Management feature is available, you can modify the driver to dynamically adjust to a new number of available interrupt vectors.

Other functionality that the driver must provide independent of whether the Interrupt Resource Management feature is available includes the ability to quiesce the hardware and resume the hardware. Quiesce and resume are needed for certain events related to power management and hotplugging. Quiesce and resume also are required to handle interrupt callback actions.

The quiesce function is called in xx_detach().

int
xx_quiesce(xx_state_t *statep);

The resume function is called in xx_attach().

int
xx_resume(xx_state_t *statep);

Make the following modifications to enhance this device driver to use the Interrupt Resource Management feature:

/*
 * attach(9F) routine.
 *
 * Creates soft state, registers callback handler, initializes
 * hardware, and sets up interrupt handling for the driver.
 */
xx_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
        xx_state_t              *statep = NULL;
        xx_intr_t               *intrs = NULL;
        ddi_intr_handle_t       *hdls;
        ddi_cb_handle_t         cb_hdl;
        int                     instance;
        int                     type;
        int                     types;
        int                     nintrs;
        int                     nactual;
        int                     inum;

        /* Get device instance */
        instance = ddi_get_instance(dip);

        switch (cmd) {
        case DDI_ATTACH:

                /* Get soft state */
                if (ddi_soft_state_zalloc(state_list, instance) != 0)
                        return (DDI_FAILURE);
                statep = ddi_get_soft_state(state_list, instance);
                ddi_set_driver_private(dip, (caddr_t)statep);
                statep->dip = dip;

                /* Initialize hardware */
                xx_initialize(statep);

                /* Register callback handler */
                if (ddi_cb_register(dip, DDI_CB_FLAG_INTR, xx_cbfunc,
                    statep, NULL, &cb_hdl) != 0) {
                        ddi_soft_state_free(state_list, instance);
                        return (DDI_FAILURE);
                }
                statep->cb_hdl = cb_hdl;

                /* Select interrupt type */
                ddi_intr_get_supported_types(dip, &types);
                if (types & DDI_INTR_TYPE_MSIX) {
                        type = DDI_INTR_TYPE_MSIX;
                } else if (types & DDI_INTR_TYPE_MSI) {
                        type = DDI_INTR_TYPE_MSI;
                } else {
                        type = DDI_INTR_TYPE_FIXED;
                }
                statep->type = type;

                /* Get number of supported interrupts */
                ddi_intr_get_nintrs(dip, type, &nintrs);

                /* Allocate interrupt handle array */
                statep->hdls_size = nintrs * sizeof (ddi_intr_handle_t);
                statep->hdls = kmem_zalloc(statep->hdls_size, KMEM_SLEEP);

                /* Allocate interrupt setup array */
                statep->intrs_size = nintrs * sizeof (xx_intr_t);
                statep->intrs = kmem_zalloc(statep->intrs_size, KMEM_SLEEP);

                /* Allocate interrupt vectors */
                ddi_intr_alloc(dip, hdls, type, 0, nintrs, &nactual, 0);
                statep->nactual = nactual;

                /* Configure interrupt handling */
                xx_setup_interrupts(statep, statep->nactual, statep->intrs);

                /* Install and enable interrupt handlers */
                for (inum = 0; inum < nactual; inum++) {
                        ddi_intr_add_handler(&hdls[inum],
                            intrs[inum].inthandler,
                            intrs[inum].arg1, intrs[inum].arg2);
                        ddi_intr_enable(hdls[inum]);
                }

                break;

        case DDI_RESUME:

                /* Get soft state */
                statep = ddi_get_soft_state(state_list, instance);
                if (statep == NULL)
                        return (DDI_FAILURE);

                /* Resume hardware */
                xx_resume(statep);

                break;
        }

        return (DDI_SUCESS);
}

/*
 * detach(9F) routine.
 *
 * Stops the hardware, disables interrupt handling, unregisters
 * a callback handler, and destroys the soft state for the driver.
 */
xx_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
        xx_state_t      *statep = NULL;
        int             instance;
        int             inum;

        /* Get device instance */
        instance = ddi_get_instance(dip);

        switch (cmd) {
        case DDI_DETACH:

                /* Get soft state */
                statep = ddi_get_soft_state(state_list, instance);
                if (statep == NULL)
                        return (DDI_FAILURE);

                /* Stop device */
                xx_uninitialize(statep);

                /* Disable and free interrupts */
                for (inum = 0; inum < statep->nactual; inum++) {
                        ddi_intr_disable(statep->hdls[inum]);
                        ddi_intr_remove_handler(statep->hdls[inum]);
                        ddi_intr_free(statep->hdls[inum]);
                }

                /* Unregister callback handler */
                ddi_cb_unregister(statep->cb_hdl);

                /* Free interrupt handle array */
                kmem_free(statep->hdls, statep->hdls_size);

                /* Free interrupt setup array */
                kmem_free(statep->intrs, statep->intrs_size);

                /* Free soft state */
                ddi_soft_state_free(state_list, instance);

                break;

        case DDI_SUSPEND:

                /* Get soft state */
                statep = ddi_get_soft_state(state_list, instance);
                if (statep == NULL)
                        return (DDI_FAILURE);

                /* Suspend hardware */
                xx_quiesce(statep);

                break;
        }

        return (DDI_SUCCESS);
}

/*
 * (*ddi_cbfunc)() routine.
 *
 * Adapt interrupt usage when availability changes.
 */
int
xx_cbfunc(dev_info_t *dip, ddi_cb_action_t cbaction, void *cbarg,
    void *arg1, void *arg2)
{
        xx_state_t      *statep = (xx_state_t *)arg1;
        int             count;
        int             inum;
        int             nactual;

        switch (cbaction) {
        case DDI_CB_INTR_ADD:
        case DDI_CB_INTR_REMOVE:

                /* Get change in availability */
                count = (int)(uintptr_t)cbarg;

                /* Suspend hardware */
                xx_quiesce(statep);

                /* Tear down previous interrupt handling */
                for (inum = 0; inum < statep->nactual; inum++) {
                        ddi_intr_disable(statep->hdls[inum]);
                        ddi_intr_remove_handler(statep->hdls[inum]);
                }

                /* Adjust interrupt vector allocations */
                if (cbaction == DDI_CB_INTR_ADD) {

                        /* Allocate additional interrupt vectors */
                        ddi_intr_alloc(dip, statep->hdls, statep->type,
                            statep->nactual, count, &nactual, 0);

                        /* Update actual count of available interrupts */
                        statep->nactual += nactual;

                } else {

                        /* Free removed interrupt vectors */
                        for (inum = statep->nactual - count;
                            inum < statep->nactual; inum++) {
                                ddi_intr_free(statep->hdls[inum]);
                        }

                        /* Update actual count of available interrupts */
                        statep->nactual -= count;
                }

                /* Configure interrupt handling */
                xx_setup_interrupts(statep, statep->nactual, statep->intrs);

                /* Install and enable interrupt handlers */
                for (inum = 0; inum < statep->nactual; inum++) {
                        ddi_intr_add_handler(&statep->hdls[inum],
                            statep->intrs[inum].inthandler,
                            statep->intrs[inum].arg1,
                            statep->intrs[inum].arg2);
                        ddi_intr_enable(statep->hdls[inum]);
                }

                /* Resume hardware */
                xx_resume(statep);

                break;

        default:
                return (DDI_ENOTSUP);
        }

        return (DDI_SUCCESS);
}

Interrupt Handler Functionality

The driver framework and the device each place demands on the interrupt handler. All interrupt handlers are required to do the following tasks:

The following example shows an interrupt routine for a device called mydev.


Example 8–9 Interrupt Example

static uint_t
mydev_intr(caddr_t arg1, caddr_t arg2)
{
    struct mydevstate *xsp = (struct mydevstate *)arg1;
    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 interrupts 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. High-level interrupts cannot block because of the scheduler. High-level interrupts can only use mutual exclusion locks for locking.

The driver must determine whether the device is using high-level interrupts. Do this test in the driver's attach(9E) entry point when you register interrupts. See High-Level Interrupt Handling Example.

High-Level Mutexes

A mutex initialized with an interrupt priority 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 following example, 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. The protected data includes a queue used by both the high-level interrupt handler and the low-level handler, and a flag that indicates that 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 8–10 Handling High-Level Interrupts With attach()

static int
mydevattach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
    struct mydevstate *xsp;
    /* ... */

    ret = ddi_intr_get_supported_types(dip, &type);
    if ((ret != DDI_SUCCESS) || (!(type & DDI_INTR_TYPE_FIXED))) {
        cmn_err(CE_WARN, "ddi_intr_get_supported_types() failed");
        return (DDI_FAILURE);
    }

    ret = ddi_intr_get_nintrs(dip, DDI_INTR_TYPE_FIXED, &count);

    /*
     * Fixed interrupts can only have one interrupt. Check to make
     * sure that number of supported interrupts and number of
     * available interrupts are both equal to 1.
     */
    if ((ret != DDI_SUCCESS) || (count != 1)) {
    cmn_err(CE_WARN, "No fixed interrupts found");
            return (DDI_FAILURE);
    }

    xsp->xs_htable = kmem_zalloc(count * sizeof (ddi_intr_handle_t),
        KM_SLEEP);

    ret = ddi_intr_alloc(dip, xsp->xs_htable, DDI_INTR_TYPE_FIXED, 0,
        count, &actual, 0);

    if ((ret != DDI_SUCCESS) || (actual != 1)) {
    cmn_err(CE_WARN, "ddi_intr_alloc failed 0x%x", ret");
        kmem_free(xsp->xs_htable, sizeof (ddi_intr_handle_t));
        return (DDI_FAILURE);
    }

    ret = ddi_intr_get_pri(xsp->xs_htable[0], &intr_pri);
    if (ret != DDI_SUCCESS) {
        cmn_err(CE_WARN, "ddi_intr_get_pri failed 0x%x", ret");
        (void) ddi_intr_free(xsp->xs_htable[0]);
        kmem_free(xsp->xs_htable, sizeof (ddi_intr_handle_t));
        return (DDI_FAILURE);
    }

    if (intr_pri >= ddi_intr_get_hilevel_pri()) {

    mutex_init(&xsp->high_mu, NULL, MUTEX_DRIVER,
        DDI_INTR_PRI(intr_pri));

    ret = ddi_intr_add_handler(xsp->xs_htable[0],
        mydevhigh_intr, (caddr_t)xsp, NULL);
    if (ret != DDI_SUCCESS) {
        cmn_err(CE_WARN, "ddi_intr_add_handler failed 0x%x", ret");
        mutex_destroy(&xsp>xs_int_mutex);
            (void) ddi_intr_free(xsp->xs_htable[0]);
            kmem_free(xsp->xs_htable, sizeof (ddi_intr_handle_t));
        return (DDI_FAILURE);
    }

    /* add soft interrupt */
    if (ddi_intr_add_softint(xsp->xs_dip, &xsp->xs_softint_hdl,
        DDI_INTR_SOFTPRI_MAX, xs_soft_intr, (caddr_t)xsp) !=
        DDI_SUCCESS) {
        cmn_err(CE_WARN, "add soft interrupt failed");
        mutex_destroy(&xsp->high_mu);
        (void) ddi_intr_remove_handler(xsp->xs_htable[0]);
            (void) ddi_intr_free(xsp->xs_htable[0]);
            kmem_free(xsp->xs_htable, sizeof (ddi_intr_handle_t));
        return (DDI_FAILURE);
    }


    xsp->low_soft_pri = DDI_INTR_SOFTPRI_MAX;

    mutex_init(&xsp->low_mu, NULL, MUTEX_DRIVER,
        DDI_INTR_PRI(xsp->low_soft_pri));

    } else {
    /*
     * regular interrupt registration continues from here
     * do not use a soft interrupt
     */
    }

    return (DDI_SUCCESS);
}

The high-level interrupt routine services the device and queues the data. The high-level routine triggers a software interrupt if the low-level routine is not running, as the following example demonstrates.


Example 8–11 High-level Interrupt Routine

static uint_t
mydevhigh_intr(caddr_t arg1, caddr_t arg2)
{
   struct mydevstate    *xsp = (struct mydevstate *)arg1;
   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, queue data for 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) {
        ret = ddi_intr_trigger_softint(xsp->xs_softint_hdl, NULL);
        if (ret == DDI_EPENDING) {
            cmn_err(CE_WARN, "ddi_intr_trigger_softint() soft interrupt "
                "already pending for this handler");
        } else if (ret != DDI_SUCCESS) {
            cmn_err(CE_WARN, "ddi_intr_trigger_softint() failed");
        }           
    }

    return (DDI_INTR_CLAIMED);
}

The low-level interrupt routine is started by the high-level interrupt routine, which triggers a software interrupt. The low-level interrupt routine runs until there is nothing left to process, as the following example shows.


Example 8–12 Low-Level Soft Interrupt Routine

static uint_t
mydev_soft_intr(caddr_t arg1, caddr_t arg2)
{
    struct mydevstate *mydevp = (struct mydevstate *)arg1;
    /* ... */
    mutex_enter(&mydevp->low_mu);
    mutex_enter(&mydevp->high_mu);
    if (mydevp->softint_count > 1) {
        mydevp->softint_count--;
        mutex_exit(&mydevp->high_mu);
        mutex_exit(&mydevp->low_mu);
        return (DDI_INTR_CLAIMED);
    }

    if ( /* queue empty */ ) {
        mutex_exit(&mydevp->high_mu);
        mutex_exit(&mydevp->low_mu);
        return (DDI_INTR_UNCLAIMED);
    }

    mydevp->softint_running = 1;
    while (EMBEDDED COMMENT:data on queue) {
        ASSERT(mutex_owned(&mydevp->high_mu);
        /* Dequeue data from high-level queue. */
        mutex_exit(&mydevp->high_mu);
        /* normal interrupt processing */
        mutex_enter(&mydevp->high_mu);
    }

    mydevp->softint_running = 0;
    mydevp->softint_count = 0;
    mutex_exit(&mydevp->high_mu);
    mutex_exit(&mydevp->low_mu);
    return (DDI_INTR_CLAIMED);
}