Writing Device Drivers

Responsibilities of an Interrupt Handler

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:

  1. 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.

  2. 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. Most vectored devices, on the other hand, stop interrupting after the bus interrupt-acknowledge cycle; however, their internal state still indicates that they have interrupted but have not yet been serviced.

  3. 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. The following points apply to interrupt processing:

    • Do only what absolutely requires interrupt context.

  4. Do any additional processing that could save another interrupt, for example, read the next data from the device.

  5. Return DDI_INTR_CLAIMED.

    Example 6-2 shows an interrupt routine.


    Example 6-2 Interrupt Routine

    static u_int
    xxintr(caddr_t arg)
    {
    	struct xxstate *xsp = (struct xxstate *)arg;
    	uint8_t 		status, 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);
    	temp = ddi_get8(xsp->data_access_handle, &xsp->regp->csr);
    	perform any I/O related and synchronization processing
    	signal waiting threads (biodone(9F) or cv_signal(9F)
    	mutex_exit(&xsp->mu);
    	return (DDI_INTR_CLAIMED);
    }

When the system detects an interrupt on a bus architecture that does not support vectored hardware, it calls the driver interrupt handler function for each device that could have issued the interrupt. The interrupt handler must determine whether the device it handles issued an interrupt.

On architectures supporting vectored interrupts, this step is unnecessary but not harmful, and it enhances portability. The syntax and semantics of the interrupt-handling routine therefore can be the same for both vectored interrupts and polling interrupts.

In the example presented here, the argument passed to xxintr() is a pointer to the state structure for the device that may have issued the interrupt. This was set up by passing a pointer to the state structure as the intr_handler_arg argument to ddi_add_intr(9F) in attach(9E).

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.