In the example presented in Example 6-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.
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, "xx high mutex", 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, "xx low mutex", 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, "xx condvar", 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 6-4 demonstrates.
static u_int xxhighintr(caddr_t arg) { struct xxstate *xsp = (struct xxstate *)arg; uint8_t status, 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); 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 need_softint = 1; mutex_exit(&xsp->high_mutex); /* 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 6-5 shows.
static u_int xxlowintr(caddr_t arg) { struct xxstate *xsp = (struct xxstate *)arg; .... mutex_enter(&xsp->low_mu); mutex_enter(&xsp->high_mu); if (queue empty|| xsp->softint_running) { 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; mutex_exit(&xsp->high_mu); mutex_exit(&xsp->low_mu); return (DDI_INTR_CLAIMED); }