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.
If the interrupt priority returned from ddi_intr_get_pri(9F) is greater than or equal to the priority returned from ddi_intr_get_hilevel_pri(9F), the driver can fail to attach, or the driver can implement a high-level interrupt handler. The high-level interrupt handler uses a lower-priority software interrupt to handle the device. To allow more concurrency, use a separate mutex to protect data from the high-level handler.
If the interrupt priority returned from ddi_intr_get_pri(9F) is less than the priority returned from ddi_intr_get_hilevel_pri(9F), the attach(9E) entry point falls through to regular interrupt registration. In this case, a soft interrupt is not necessary.
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);
}