第 1 部分针对 Oracle Solaris 平台设计设备驱动程序
9. 直接内存访问 (Direct Memory Access, DMA)
设备驱动程序必须首先通过调用 ddi_intr_add_handler(9F) 向系统注册中断处理程序,然后才能接收和服务中断。注册中断处理程序会为系统提供一种将中断处理程序与中断规范相关联的方法。如果设备可能负责中断,则会调用中断处理程序。此处理程序负责确定其是否应处理中断,如果是,则负责声明该中断。
提示 - 可在 mdb 或 kmdb 调试器中使用 ::interrupts 命令检索支持的 SPARC 和 x86 系统上设备的已注册中断信息。
要注册驱动程序的中断处理程序,驱动程序通常会在其 attach(9E) 入口点执行以下步骤。
使用 ddi_intr_get_supported_types(9F) 确定支持的中断类型。
使用 ddi_intr_get_nintrs(9F) 确定支持的中断类型的数量。
使用 kmem_zalloc(9F) 为 DDI 中断句柄分配内存。
对于分配的每个中断类型,执行以下步骤:
使用 ddi_intr_get_pri(9F) 获取中断的优先级。
如果需要为中断设置新的优先级,请使用 ddi_intr_set_pri(9F)。
使用 mutex_init(9F) 将锁初始化。
使用 ddi_intr_add_handler(9F) 注册中断的处理程序。
使用 ddi_intr_enable(9F) 启用中断。
执行以下步骤以释放每个中断:
使用 ddi_intr_disable(9F) 禁用每个中断。
使用 ddi_intr_remove_handler(9F) 删除中断处理程序。
使用 mutex_destroy(9F) 删除锁。
使用 ddi_intr_free(9F) 和 kmem_free(9F) 释放中断,从而释放为 DDI 中断句柄分配的内存。
示例 8-5 注册传统中断
以下示例说明如何为名为 mydev 的设备安装中断处理程序。此示例假设 mydev 仅支持一个中断。
/* 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); }
示例 8-6 删除传统中断
以下示例说明如何删除传统中断。
/* 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));
要注册驱动程序的中断处理程序,驱动程序通常会在其 attach(9E) 入口点执行以下步骤。
使用 ddi_intr_get_supported_types(9F) 确定支持的中断类型。
使用 ddi_intr_get_nintrs(9F) 确定支持的 MSI 中断类型的数量。
使用 ddi_intr_alloc(9F) 为 MSI 中断分配内存。
对于分配的每个中断类型,执行以下步骤:
使用 ddi_intr_get_pri(9F) 获取中断的优先级。
如果需要为中断设置新的优先级,请使用 ddi_intr_set_pri(9F)。
使用 mutex_init(9F) 将锁初始化。
使用 ddi_intr_add_handler(9F) 注册中断的处理程序。
使用以下函数之一启用所有中断:
使用 ddi_intr_block_enable(9F) 启用某个块中的所有中断。
在循环中使用 ddi_intr_enable(9F) 单独启用每个中断。
示例 8-7 注册一组 MSI 中断
以下示例说明如何为名为 mydev 的设备注册 MSI 中断。
/* 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); }
示例 8-8 删除 MSI 中断
以下示例说明如何删除 MSI 中断。
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); }