Writing Device Drivers

High-Level Interrupt Handling Example

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.


Example 6-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, "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.


Example 6-4 High-level Interrupt Routine

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.


Example 6-5 Low-level Interrupt Routine

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