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:
Use ddi_intr_get_supported_types(9F) to determine which types of interrupts are supported.
Use ddi_intr_get_nintrs(9F) to determine the number of supported interrupt types.
Use kmem_zalloc(9F) to allocate memory for DDI interrupt handles.
For each interrupt type that you allocate, take the following steps:
Use ddi_intr_get_pri(9F) to get the priority for the interrupt.
If you need to set a new priority for the interrupt, use ddi_intr_set_pri(9F).
Use mutex_init(9F) to initialize the lock.
Use ddi_intr_add_handler(9F) to register the handler for the interrupt.
Use ddi_intr_enable(9F) to enable the interrupt.
Take the following steps to free each interrupt:
Disable each interrupt using ddi_intr_disable(9F).
Remove the interrupt handler using ddi_intr_remove_handler(9F).
Remove the lock using mutex_destroy(9F).
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:
Use ddi_intr_get_supported_types(9F) to determine which types of interrupts are supported.
Use ddi_intr_get_nintrs(9F) to determine the number of supported MSI interrupt types.
Use ddi_intr_alloc(9F) to allocate memory for the MSI interrupts.
For each interrupt type that you allocate, take the following steps:
Use ddi_intr_get_pri(9F) to get the priority for the interrupt.
If you need to set a new priority for the interrupt, use ddi_intr_set_pri(9F).
Use mutex_init(9F) to initialize the lock.
Use ddi_intr_add_handler(9F) to register the handler for the interrupt.
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);
}