本章介绍用于处理中断的机制,如分配、注册、服务以及删除中断。本章介绍有关以下主题的信息:
中断是指设备发送给 CPU 的硬件信号。中断将通知 CPU 需要注意设备,并且 CPU 应该停止任何当前活动并对设备进行响应。如果 CPU 未在执行优先级比中断优先级高的任务,则 CPU 会暂停当前线程。然后,CPU 会调用发送中断信号的设备的中断处理程序。中断处理程序的工作是服务设备并防止此设备中断。中断处理程序返回后,CPU 便会恢复出现中断之前所执行的工作。
Solaris DDI/DKI 提供了用于执行以下任务的接口:
确定中断类型和注册要求
注册中断
服务中断
屏蔽中断
获取中断待处理信息
获取和设置优先级信息
I/O 总线以两种常用方法来实现中断:向量化和轮询。这两种方法通常都会提供总线中断优先级别。向量化设备还会提供中断向量。轮询设备则不提供中断向量。
为了与不断发展的总线技术保持同步,Solaris OS 已经得到了增强,可适应更新类型的中断以及已经使用多年的较为传统的中断。具体来说,操作系统目前可识别三种类型的中断:
传统中断-传统或固定中断是指使用早期总线技术的中断。使用这些技术,可通过一个或多个“带外”(即,独立于总线的主线)连线的外部管脚来发送中断信号。较新的总线技术(如 PCI Express)通过带内机制模拟传统中断来维持软件兼容性。主机 OS 将这些模仿中断视为传统中断。
消息告知中断-消息告知中断 (message-signalled interrupt, MSI) 使用带内消息而不是使用管脚,可在主桥 (host bridge) 中确定中断的地址。(有关主桥 (host bridge) 的更多信息,请参见PCI 局部总线。)MSI 可以将数据与中断消息一起发送。每个 MSI 都不是共享的,这样可以保证指定给某一设备的 MSI 在系统中是唯一的。一个 PCI 函数最多可以请求 32 条 MSI 消息。
扩展消息告知中断-扩展消息告知中断 (Extended message-signalled interrupt, MSI-X) 是 MSI 的增强版本。MSI-X 中断具有以下新增的优点:
支持 2048 条而不是 32 条消息
针对每条消息支持独立的消息地址和消息数据
支持按消息屏蔽
软件分配的向量少于硬件请求的向量时可具有更大灵活性。软件可以在多个 MSI-X 插槽中重用相同的 MSI-X 地址和数据。
一些较新的总线技术(如 PCI Express)要求使用 MSI,但是可以使用 INTx 仿真来处理传统中断。INTx 仿真用于实现兼容性,但是这并不被认为是好的做法。
总线会在总线中断级别设置设备中断的优先级。然后,总线中断级别将映射到处理器中断级别。映射到高于调度程序优先级别的 CPU 中断优先级的总线中断级别称为高级别中断。高级别中断处理程序仅限于调用以下 DDI 接口:
使用与高级别中断关联的中断优先级初始化的互斥锁上的 mutex_enter(9F) 和 mutex_exit(9F)
以下 DDI get 和 put 例程:ddi_get8(9F)、ddi_put8(9F)、ddi_get16(9F)、ddi_put16(9F)、ddi_get32(9F)、ddi_put32(9F)、ddi_get64(9F) 和 ddi_put64(9F)。
总线中断级别本身无法确定设备是否会发生高级别中断。特定的总线中断级别可以在一个平台映射到高级别中断,而在其他平台上则映射到普通中断。
不要求驱动程序来支持具有高级别中断的设备。但是,要求驱动程序检查中断级别。如果中断优先级高于或等于系统最高优先级,中断处理程序会在高级别中断环境下运行。在这种情况下,驱动程序可能无法连接,或者驱动程序可能会使用双级别方案来处理中断。有关更多信息,请参见处理高级别中断 。
系统仅有的有关设备中断的信息为总线中断的优先级别和中断请求编号。例如,SPARC 计算机中 S 总线上的 IPL 即是总线中断的优先级别;x86 计算机中 ISA 总线上的 IRQ 即是中断请求编号。
注册中断处理程序之后,系统会将其添加到每个 IPL 或 IRQ 的潜在中断处理程序的列表中。出现中断时,系统必须确定与给定的 IPL 或 IRQ 关联的所有设备中实际导致此中断的设备。系统会针对指定的 IPL 或 IRQ 调用所有中断处理程序,直到一个处理程序声明中断为止。
以下总线可以支持轮询中断:
S 总线
ISA
PCI
标准 (MSI) 和扩展 (MSI-X) 消息告知中断均作为带内消息实现。消息告知中断可作为使用软件指定的地址和值的写操作进行发送。
常规 PCI 规范包括可选的消息告知中断 (Message Signaled Interrupt, MSI) 支持。MSI 是作为发送的写操作实现的带内消息。MSI 的地址和数据由软件指定,并特定于主桥 (host bridge)。由于消息是带内消息,因此消息的接收可用于“推送”与中断关联的数据。根据定义,MSI 中断是独享的。指定给设备的每条 MSI 消息保证在系统中均为唯一消息。PCI 函数可以请求 1、2、4、8、16 或 32 条 MSI 消息。请注意,系统软件为函数分配的 MSI 消息数可以少于函数所请求的数量。可限制主桥 (host bridge) 中为设备分配的唯一 MSI 消息的数量。
MSI-X 中断是 MSI 中断的增强版本,与 MSI 中断有相同功能,具有以下关键区别:
每个设备最多支持 2048 个 MSI-X 中断向量。
每个中断向量的地址和数据项都是唯一的。
MSI-X 支持按函数屏蔽和按向量屏蔽。
利用 MSI-X 中断,未分配的设备中断向量可以使用先前添加或初始化的 MSI-X 中断向量共享相同的向量地址、向量数据、中断处理程序和处理程序参数。使用 ddi_intr_dup_handler(9F) 函数可相对于关联设备上未分配的中断向量为 Solaris OS 提供的资源设置别名。例如,如果为驱动程序分配了 2 个 MSI-X 中断,并且设备支持 32 个中断,则驱动程序可以使用 ddi_intr_dup_handler() 相对于设备上其他 30 个中断为其收到的 2 个中断设置别名。
ddi_intr_dup_handler() 函数可以复制使用 ddi_intr_add_handler(9F) 添加或使用 ddi_intr_enable(9F) 初始化的中断。
复制的中断最初处于禁用状态。可使用 ddi_intr_enable() 启用复制的中断。您不能删除原始 MSI-X 中断处理程序,除非删除了与此原始中断处理程序相关联的所有复制的中断处理程序。要删除复制的中断处理程序,请首先调用 ddi_intr_disable(9F),然后调用 ddi_intr_free(9F)。当删除与该原始中断处理程序相关联的所有复制的中断处理程序后,就可以使用 ddi_intr_remove_handler(9F) 删除该原始 MSI-X 中断处理程序。有关示例,请参见 ddi_intr_dup_handler (9F) 手册页。
Solaris DDI/DKI 支持软件中断(也称为软中断)。软中断通过软件而不是硬件设备启动。另外,还必须在系统中添加和删除这些中断的处理程序。软中断处理程序在中断上下文中运行,因此可用于执行许多属于中断处理程序的任务。
硬件中断处理程序必须快速执行其任务,因为它们可能必须在执行这些任务的同时暂停其他系统活动。对于高级别中断处理程序,更需要满足此要求,这些处理程序在高于系统调度程序的优先级别上运行。高级别中断处理程序将屏蔽所有较低优先级中断的操作,包括系统时钟的中断操作。因此,该中断处理程序必须避免涉及到可能导致其休眠的活动,如获取互斥锁。
如果处理程序休眠,则系统可能会挂起,因为时钟会被屏蔽,从而无法调度休眠线程。因此,高级别中断处理程序通常在高优先级别执行最少量的工作,并将其他任务委托给运行优先级别低于高级别中断处理程序的软件中断。由于软件中断处理程序运行的优先级别低于系统调度程序,因此软件中断处理程序可以执行高级别中断处理程序无法执行的操作。
Solaris OS 提供了用于注册和取消注册中断的框架,并且提供了对消息告知中断 (Message Signaled Interrupt, MSI) 的支持。通过中断管理界面,可以处理优先级、功能和中断屏蔽,并可获取待处理信息。
返回可用于指定硬件设备和中断类型的中断的数量。
返回设备支持的指定中断类型的中断的数量。
返回设备和主机均支持的硬件中断类型。
针对指定的中断返回中断功能标志。
为指定类型的中断分配系统资源和中断向量。
针对指定的中断句柄释放系统资源和中断向量。
通过使用 DDI_INTR_FLAG_LEVEL 和 DDI_INTR_FLAG_EDGE 标志来设置指定中断的功能。
添加中断处理程序。
仅适用于 MSI-X。将分配的中断向量的地址和数据对复制到同一设备上未使用的中断向量。
删除指定的中断处理程序。
启用指定的中断。
禁用指定的中断。
仅用于 MSI。启用指定范围的中断。
仅用于 MSI。禁用指定范围的中断。
如果已启用指定的中断,则设置中断屏蔽码。
如果已启用指定的中断,则清除中断屏蔽码。
如果主桥 (host bridge) 或设备支持这种中断待处理位,则读取此位。
返回指定中断的当前软件优先级设置。
设置指定中断的中断优先级别。
返回高级别中断的最低优先级别。
添加软中断处理程序。
触发指定的软中断。
删除指定的软中断处理程序。
返回指定中断的软中断优先级。
更改指定软中断的相对软中断优先级。
更改软中断优先级
检查待处理中断
设置中断屏蔽码
清除中断屏蔽码
使用 ddi_intr_set_softint_pri(9F) 函数将软中断优先级到更改为 9。
if (ddi_intr_set_softint_pri(mydev->mydev_softint_hdl, 9) != DDI_SUCCESS) cmn_err (CE_WARN, "ddi_intr_set_softint_pri failed");
使用 ddi_intr_get_pending(9F) 函数检查中断是否处于待处理状态。
if (ddi_intr_get_pending(mydevp->htable[0], &pending) != DDI_SUCCESS) cmn_err(CE_WARN, "ddi_intr_get_pending() failed"); else if (pending) cmn_err(CE_NOTE, "ddi_intr_get_pending(): Interrupt pending");
使用 ddi_intr_set_mask(9F) 函数设置中断屏蔽,以防止设备收到中断。
if ((ddi_intr_set_mask(mydevp->htable[0]) != DDI_SUCCESS)) cmn_err(CE_WARN, "ddi_intr_set_mask() failed");
使用 ddi_intr_clr_mask(9F) 函数清除中断屏蔽。如果没有启用指定的中断,ddi_intr_clr_mask(9F) 函数将失败。如果 ddi_intr_clr_mask(9F ) 函数成功,则设备将开始生成中断。
if (ddi_intr_clr_mask(mydevp->htable[0]) != DDI_SUCCESS) cmn_err(CE_WARN, "ddi_intr_clr_mask() failed");
设备驱动程序必须首先通过调用 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 中断句柄分配的内存。
以下示例说明如何为名为 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); }
以下示例说明如何删除传统中断。
/* 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) 单独启用每个中断。
以下示例说明如何为名为 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); }
以下示例说明如何删除 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); }
本节介绍可以生成多种不同可中断条件的设备驱动程序如何利用中断资源管理功能来优化其中断向量的分配。
中断资源管理功能可动态管理驱动程序的中断配置,从而使设备驱动程序能够使用多个中断资源。未使用中断资源管理功能时,中断处理的配置通常仅在驱动程序的 attach (9E) 例程内进行。中断资源管理功能监控系统更改,根据这些更改重新计算分配给各设备的中断向量数量,并向各受影响的参与驱动程序发出关于驱动程序新中断向量分配情况的通知。参与驱动程序是注册了回调处理程序的驱动程序,如回调接口中所述。可能导致中断向量重新分配的更改包括添加或删除设备,或者显式请求,如修改所请求的中断向量数量中所述。
中断资源管理功能在各 Solaris 平台上不可用。此功能仅对利用 MSI-X 中断的 PCIe 设备可用。
利用中断资源管理功能的驱动程序必须能够在该功能不可用时正确做出调整。
中断资源管理功能可用时,可使驱动程序访问更多的中断向量,超过可通过其他方式为此驱动程序分配的数量。驱动程序在利用更多中断向量时可以更有效地处理中断条件。
中断资源管理功能根据以下约束动态调整分配给各参与驱动程序的中断向量数量:
可用总数。系统中存在的限定数量的中断向量。
请求的总数。可为驱动程序分配较少的中断向量,但始终不能超过所请求的中断向量数量。
对其他驱动程序公平。多个驱动程序以与各驱动程序请求的中断向量总数相关的方式共享可用中断向量的总数。
在任何给定时间为一个设备提供的可用中断向量的数量可能会有所不同:
在系统中动态添加或删除其他设备
驱动程序根据负荷动态更改所请求中断向量的数量
驱动程序必须提供以下支持,以利用中断资源管理功能:
回调支持。驱动程序必须注册回调处理程序,从而在系统改变其可用中断数量时获得通知。驱动程序必须能够增加或减少其使用的中断。
中断请求。驱动程序必须指定需要使用的中断数量。
中断用法。在任何时候,驱动程序都必须请求正确数量的中断,基于:
其硬件能生成哪些可中断条件
有多少处理器可用于并行处理那些条件
中断灵活性。驱动程序必须足够灵活,能够以最适合各中断向量的当前可用中断数量的方式为其分配一个或多个可中断的条件。在可用中断的数量增加或减少时,驱动程序可能需要随时重新配置这些分配。
驱动程序必须使用以下接口来注册回调支持。
表 8–1 回调支持接口
接口 |
数据结构 |
说明 |
---|---|---|
ddi_cb_register() |
ddi_cb_flags_t、ddi_cb_handle_t |
注册回调处理程序函数,以接收特定类型的操作。 |
ddi_cb_unregister() |
ddi_cb_handle_t |
取消注册回调处理程序函数。 |
(*ddi_cb_func_t)() |
ddi_cb_action_t |
接收回调操作和与要处理的各操作相关的特定参数。 |
使用 ddi_cb_register(9F) 函数为驱动程序注册回调处理程序函数。
int ddi_cb_register (dev_info_t *dip, ddi_cb_flags_t cbflags, ddi_cb_func_t cbfunc, void *arg1, void *arg2, ddi_cb_handle_t *ret_hdlp);
驱动程序仅可注册一个回调函数。这是用于处理所有独立回调操作的回调函数。cbflags 参数确定驱动程序应在发生哪些类型的操作时接收这些操作。cbfunc() 例程将在驱动程序应处理相关操作时调用。在每次执行 cbfunc() 例程时,驱动程序都会指定应发送给其本身的两个专用参数(arg1 和 arg2)。
cbflags() 参数属于枚举类型,指定驱动程序支持哪些操作。
typedef enum { DDI_CB_FLAG_INTR } ddi_cb_flags_t;
为了注册对中断资源管理操作的支持,驱动程序必须注册处理程序,并包含 DDI_CB_FLAG_INTR 标志。 回调处理程序成功注册后,将通过 ret_hdlp 参数返回一个不透明的句柄。驱动程序使用完回调处理程序之后,驱动程序可使用 ret_hdlp 参数取消注册此回调处理程序。
在驱动程序的 attach(9F) 入口点中注册回调处理程序。在驱动程序的软状态中保存不透明的句柄。在驱动程序的 detach(9F) 入口点中取消注册回调处理程序。
使用 ddi_cb_unregister(9F) 函数为驱动程序取消注册回调处理程序函数。
int ddi_cb_unregister (ddi_cb_handle_t hdl);
在驱动程序的 detach(9F) 入口点中执行此调用。在此调用之后,驱动程序将不再接收回调操作。
驱动程序也会失去因拥有注册的回调处理函数而从系统中获得的其他所有支持。例如,此前为驱动程序提供的某些中断向量会在取消注册其回调处理函数后立即收回。成功返回之前,ddi_cb_unregister() 函数会通知驱动程序由于系统支持缺失所导致的任何最终操作。
使用注册的回调处理函数来接收回调操作,接收特定于要处理的各操作的参数。
typedef int (*ddi_cb_func_t)(dev_info_t *dip, ddi_cb_action_t cbaction, void *cbarg, void *arg1, void *arg2);
cbaction 参数指定驱动程序接收到的回调要处理哪种操作。
typedef enum { DDI_CB_INTR_ADD, DDI_CB_INTR_REMOVE } ddi_cb_action_t;
DDI_CB_INTR_ADD 操作表示驱动程序中断可用数量增加。DDI_CB_INTR_REMOVE 操作表示驱动程序中断可用数量减少。将 cbarg 参数的类型强制转换为 int,以确定添加或删除的中断数量。cbarg 值表示可用中断数量的变化。
例如,获得可用中断数量的变化:
count = (int)(uintptr_t)cbarg;
如果 cbaction 为 DDI_CB_INT _ADD,则应添加 cbarg 数量的中断向量。如果 cbaction 为 DDI_CB_INT _REMOVE,则应释放 cbarg 数量的中断向量。
有关 arg1 和 arg2 的说明, 请参见 ddi_cb_registe (9F)。
回调处理函数必须能够在函数注册的整个时间段内正确执行。回调函数不能依赖任何可能会在回调函数成功取消注册之前销毁的数据结构。
回调处理函数必须返回以下值之一:
DDI_SUCCESS(如果正确处理了操作)
DDI_FAILURE(如果遇到内部错误)
DDI_ENOTSUP(如果接收到无法识别的操作)
驱动程序必须使用以下接口从系统请求中断向量。
表 8–2 中断向量请求接口
接口 |
数据结构 |
说明 |
---|---|---|
ddi_intr_alloc() |
ddi_intr_handle_t |
分配中断。 |
ddi_intr_set_nreq() |
更改所请求的中断向量数量。 |
使用 ddi_intr_alloc(9F) 函数来初始分配中断。
int ddi_intr_alloc (dev_info_t *dip, ddi_intr_handle_t *h_array, int type, int inum, int count, int *actualp, int behavior);
调用此函数之前,驱动程序必须分配一个可包含所请求中断数量的足够大的空句柄数组。ddi_intr_alloc() 函数会尝试分配 count 数量的中断句柄,并使用以 inum 参数指定的偏移开头的指定中断向量初始化数组。 actualp 参数返回所分配的中断向量的实际数量。
驱动程序可通过两种方式使用 ddi_intr_alloc() 函数:
驱动程序可以在单独的步骤中多次调用 ddi_intr_alloc() 函数,为中断句柄数组中的各成员分配中断向量。
驱动程序可以一次性地调用 ddi_intr_alloc() 函数,一次为设备分配所有中断向量。
如果您正在使用中断资源管理功能,则调用一次 ddi_intr_alloc() 即可分配所有中断向量。count 参数是驱动程序请求的中断向量的总数。如果 actualp 中的值小于 count 值,则系统无法完全满足请求。中断资源管理功能将保存此请求(count 转为·nreq - 请参见下文),稍后还可能会为此驱动程序分配更多中断向量。
使用中断资源管理功能时,对 ddi_intr_alloc() 的其他调用不会更改请求的中断向量总数。使用 ddi_intr_set_nreq(9F) 函数更改请求的中断向量数量。
使用 ddi_intr_set_nreq(9F) 函数修改请求的中断向量数量。
int ddi_intr_set_nreq (dev_info_t *dip, int nreq);
中断资源管理功能可用时,驱动程序可使用 ddi_intr_set_nreq() 函数动态调整请求的中断向量总数。附加了驱动程序之后,驱动程序可能会以此响应存在的实际负载。
驱动程序必须首先调用 ddi_intr_alloc(9F) 来请求初始数量的中断向量。完成 ddi_intr_alloc() 调用之后,驱动程序可随时调用 ddi_intr_set_nreq() 来更改请求的大小。指定的 nreq 值是驱动程序请求的中断向量的新总数。中断资源管理功能可能会根据这个新请求重新平衡系统中分配给各驱动程序的中断数量。只要中断资源管理功能重新平衡了分配给驱动程序的中断数量,各受影响的驱动程序就会接收到该驱动程序可以使用的中断向量增加或减少的回调通知。
例如,如果驱动程序将中断与其处理的特定事务并行使用,则可动态调整所请求中断向量的总数。存储驱动程序必须将 DMA 引擎与各进行中的事务相关联,因而需要中断向量。驱动程序可在 open(9F) 和 close(9F) 例程中调用 ddi_intr_set_nreq(),以便根据驱动程序的实际使用情况调整中断使用的比例。
支持多种不同可中断条件的设备的驱动程序必须能够将这些条件映射到任意数量的中断向量。驱动程序不能假设已经分配的中断向量仍然可用。某些当前可用中断稍后可能会被系统收回,以满足系统中其他驱动程序的需求。
驱动程序必须能够:
确定其硬件支持的中断数量。
确定适合使用的中断数量。例如,系统中的处理器总数可能会影响此项评估。
随时将所需中断的数量与可用中断的数量相比较。
总而言之,驱动程序必须能够选择一系列中断处理函数,并为其硬件编程,使之能够根据需求和中断可用性生成中断。在某些情况下,可能有多个针对同一个向量的中断,该中断向量的中断处理程序必须确定发生的是哪个中断。驱动程序将中断映射到中断向量的情况可能会影响设备性能。
网络设备驱动程序是中断资源管理的一种理想的备选设备驱动程序类型。网络设备硬件支持多个传输和接收信道。
网络设备在一个接收信道接收到包或者在一个传输信道传输包时,设备将生成唯一的中断条件。硬件可以为可能发生的各事件发送特定的 MSI-X 中断。硬件中的表格确定为各事件生成哪个 MSI-X 中断。
为了优化性能,驱动程序会向系统请求足够多的中断,以使每个中断都有自己的中断向量。在 attach(9F) 例程中初次调用 ddi_intr_alloc(9F) 时,驱动程序会发出此请求。
随后,驱动程序评估通过 actualp 中的 ddi_intr_alloc() 接收到的实际中断数量。 可能会接收到所请求的全部中断,也可能会接收到较少的中断。
驱动程序内的独立函数使用可用中断总数计算为各事件生成哪些 MSI-X 中断。此函数会相应在硬件中对该表进行编程。
如果驱动程序接收了全部请求的中断向量,硬件表中的每个条目都将有自己唯一的 MSI-X 中断。中断条件和中断向量之间存在一对一的映射。硬件为每种类型的事件生成唯一的 MSI-X 中断。
如果驱动程序可用的中断向量更少,则硬件表中必定要多次出现某些 MSI-X 中断数。硬件为多种类型的事件生成相同的 MSI-X 中断。
驱动程序应有两个不同的中断处理程序函数。
一个中断处理程序执行特定任务来响应一项中断。这个简单的函数仅可处理由一种可能硬件事件所生成的中断。
第二个中断处理程序更为复杂。此函数用于处理有多个中断映射到同一个 MSI-X 中断向量的情况。
在这一部分的示例驱动程序中,xx_setup_interrupts() 函数使用可用中断向量的数量来为硬件编程,并为其中每一个中断向量调用相应的中断处理程序。将在两个位置调用 xx_setup_interrupts() 函数:在 xx_attach() 中调用 di_intr_alloc() 之后,在 xx_cbfunc() 回调处理程序函数中调整了中断向量分配之后。
int xx_setup_interrupts(xx_state_t *statep, int navail, xx_intrs_t *xx_intrs_p);
xx_setup_interrupts() 函数是通过 xx_intrs_t 数据结构的数组调用的。
typedef struct { ddi_intr_handler_t inthandler; void *arg1; void *arg2; } xx_intrs_t;
无论中断资源管理功能是否可用,驱动程序中都必须存在这种 xx_setup_interrupts() 功能。驱动程序必须能够在中断向量少于附加过程中请求的数量时正常工作。如果中断资源管理功能可用,您就可以修改驱动程序,使其动态适应新的可用中断向量数量。
驱动程序必须独立于中断资源管理功能的可用性提供的其他功能,包括停止硬件和恢复硬件的能力。某些与电源管理和热插拔相关的事件需要停止和恢复。在处理中断回调操作时也必须利用停止和恢复。
在 xx_detach() 中调用了停止函数。
int xx_quiesce(xx_state_t *statep);
在 xx_attach() 中调用了恢复函数。
int xx_resume(xx_state_t *statep);
通过以下修改增强此设备驱动程序,使其使用中断资源管理功能:
注册回调处理程序。驱动程序必须为指明何时将有更少或更多的中断可用的操作注册。
处理回调。驱动程序必须停止其硬件、重新编程中断处理、恢复硬件以响应各个此类回调操作。
/* * attach(9F) routine. * * Creates soft state, registers callback handler, initializes * hardware, and sets up interrupt handling for the driver. */ xx_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { xx_state_t *statep = NULL; xx_intr_t *intrs = NULL; ddi_intr_handle_t *hdls; ddi_cb_handle_t cb_hdl; int instance; int type; int types; int nintrs; int nactual; int inum; /* Get device instance */ instance = ddi_get_instance(dip); switch (cmd) { case DDI_ATTACH: /* Get soft state */ if (ddi_soft_state_zalloc(state_list, instance) != 0) return (DDI_FAILURE); statep = ddi_get_soft_state(state_list, instance); ddi_set_driver_private(dip, (caddr_t)statep); statep->dip = dip; /* Initialize hardware */ xx_initialize(statep); /* Register callback handler */ if (ddi_cb_register(dip, DDI_CB_FLAG_INTR, xx_cbfunc, statep, NULL, &cb_hdl) != 0) { ddi_soft_state_free(state_list, instance); return (DDI_FAILURE); } statep->cb_hdl = cb_hdl; /* Select interrupt type */ ddi_intr_get_supported_types(dip, &types); if (types & DDI_INTR_TYPE_MSIX) { type = DDI_INTR_TYPE_MSIX; } else if (types & DDI_INTR_TYPE_MSI) { type = DDI_INTR_TYPE_MSI; } else { type = DDI_INTR_TYPE_FIXED; } statep->type = type; /* Get number of supported interrupts */ ddi_intr_get_nintrs(dip, type, &nintrs); /* Allocate interrupt handle array */ statep->hdls_size = nintrs * sizeof (ddi_intr_handle_t); statep->hdls = kmem_zalloc(statep->hdls_size, KMEM_SLEEP); /* Allocate interrupt setup array */ statep->intrs_size = nintrs * sizeof (xx_intr_t); statep->intrs = kmem_zalloc(statep->intrs_size, KMEM_SLEEP); /* Allocate interrupt vectors */ ddi_intr_alloc(dip, hdls, type, 0, nintrs, &nactual, 0); statep->nactual = nactual; /* Configure interrupt handling */ xx_setup_interrupts(statep, statep->nactual, statep->intrs); /* Install and enable interrupt handlers */ for (inum = 0; inum < nactual; inum++) { ddi_intr_add_handler(&hdls[inum], intrs[inum].inthandler, intrs[inum].arg1, intrs[inum].arg2); ddi_intr_enable(hdls[inum]); } break; case DDI_RESUME: /* Get soft state */ statep = ddi_get_soft_state(state_list, instance); if (statep == NULL) return (DDI_FAILURE); /* Resume hardware */ xx_resume(statep); break; } return (DDI_SUCESS); } /* * detach(9F) routine. * * Stops the hardware, disables interrupt handling, unregisters * a callback handler, and destroys the soft state for the driver. */ xx_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { xx_state_t *statep = NULL; int instance; int inum; /* Get device instance */ instance = ddi_get_instance(dip); switch (cmd) { case DDI_DETACH: /* Get soft state */ statep = ddi_get_soft_state(state_list, instance); if (statep == NULL) return (DDI_FAILURE); /* Stop device */ xx_uninitialize(statep); /* Disable and free interrupts */ for (inum = 0; inum < statep->nactual; inum++) { ddi_intr_disable(statep->hdls[inum]); ddi_intr_remove_handler(statep->hdls[inum]); ddi_intr_free(statep->hdls[inum]); } /* Unregister callback handler */ ddi_cb_unregister(statep->cb_hdl); /* Free interrupt handle array */ kmem_free(statep->hdls, statep->hdls_size); /* Free interrupt setup array */ kmem_free(statep->intrs, statep->intrs_size); /* Free soft state */ ddi_soft_state_free(state_list, instance); break; case DDI_SUSPEND: /* Get soft state */ statep = ddi_get_soft_state(state_list, instance); if (statep == NULL) return (DDI_FAILURE); /* Suspend hardware */ xx_quiesce(statep); break; } return (DDI_SUCCESS); } /* * (*ddi_cbfunc)() routine. * * Adapt interrupt usage when availability changes. */ int xx_cbfunc(dev_info_t *dip, ddi_cb_action_t cbaction, void *cbarg, void *arg1, void *arg2) { xx_state_t *statep = (xx_state_t *)arg1; int count; int inum; int nactual; switch (cbaction) { case DDI_CB_INTR_ADD: case DDI_CB_INTR_REMOVE: /* Get change in availability */ count = (int)(uintptr_t)cbarg; /* Suspend hardware */ xx_quiesce(statep); /* Tear down previous interrupt handling */ for (inum = 0; inum < statep->nactual; inum++) { ddi_intr_disable(statep->hdls[inum]); ddi_intr_remove_handler(statep->hdls[inum]); } /* Adjust interrupt vector allocations */ if (cbaction == DDI_CB_INTR_ADD) { /* Allocate additional interrupt vectors */ ddi_intr_alloc(dip, statep->hdls, statep->type, statep->nactual, count, &nactual, 0); /* Update actual count of available interrupts */ statep->nactual += nactual; } else { /* Free removed interrupt vectors */ for (inum = statep->nactual - count; inum < statep->nactual; inum++) { ddi_intr_free(statep->hdls[inum]); } /* Update actual count of available interrupts */ statep->nactual -= count; } /* Configure interrupt handling */ xx_setup_interrupts(statep, statep->nactual, statep->intrs); /* Install and enable interrupt handlers */ for (inum = 0; inum < statep->nactual; inum++) { ddi_intr_add_handler(&statep->hdls[inum], statep->intrs[inum].inthandler, statep->intrs[inum].arg1, statep->intrs[inum].arg2); ddi_intr_enable(statep->hdls[inum]); } /* Resume hardware */ xx_resume(statep); break; default: return (DDI_ENOTSUP); } return (DDI_SUCCESS); }
驱动程序框架和设备各自将要求置于中断处理程序上。所有中断处理程序均要求执行以下任务:
中断处理程序首先会检查设备,确定其是否发出了中断。如果设备未发出中断,则处理程序必须返回 DDI_INTR_UNCLAIMED。通过此步骤可实现设备轮询。在给定中断优先级别的任何设备都可能发出了中断。设备轮询将通知系统此设备是否已发出了中断。
通知设备正在对其进行服务。
通知设备服务是大多数设备所需的特定于设备的操作。例如,需要将 S 总线设备中断,直到驱动程序通知 S 总线设备停止。此方法可保证对在同一优先级别中断的所有 S 总线设备都进行服务。
执行任何与 I/O 请求有关的处理。
设备会由于不同原因而发生中断,如传送完成或传送错误。此步骤可涉及使用数据访问函数来读取设备的数据缓冲区,检查设备的错误寄存器,以及在数据结构中相应地设置状态字段。中断分发和处理相对比较耗时。
执行可以防止其他中断的任何附加处理。
例如,从设备中读取数据的下一项。
必须始终声明 MSI 中断。
对于 MSI-X 中断,声明中断是可选的。在任一情况下都无需检查中断的拥有权,因为 MSI 和 MSI-X 中断不是与其他设备共享的。
支持热插拔和多个 MSI 或 MSI-X 中断的驱动程序应针对热插拔事件保留单独的中断,并针对此中断注册单独的 ISR(interrupt service routine,中断服务例程)。
static uint_t mydev_intr(caddr_t arg1, caddr_t arg2) { struct mydevstate *xsp = (struct mydevstate *)arg1; uint8_t status; volatile uint8_t 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); /* flush store buffers */ temp = ddi_get8(xsp->data_access_handle, &xsp->regp->csr); mutex_exit(&xsp->mu); return (DDI_INTR_CLAIMED); }
中断例程执行的大多数步骤都依赖于设备本身的特定信息。查询设备的硬件手册可确定中断原因,检测错误状态并访问设备数据寄存器。
高级别中断是指中断在调度程序级别或更高级别的那类中断。此级别不允许运行调度程序。因此,调度程序无法抢占高级别中断处理程序。高级别中断不会因为调度程序而阻塞。高级别中断只能使用互斥锁进行锁定。
驱动程序必须确定设备是否在使用高级别中断。注册中断时,请在驱动程序的 attach(9E) 入口点进行此测试。请参见高级别中断处理示例。
如果从 ddi_intr_get_pri(9F) 返回的中断优先级高于或等于从 ddi_intr_get_hilevel_pri(9F) 返回的优先级,则表明驱动程序无法连接,或者驱动程序可能实现高级别的中断处理程序。高级别的中断处理程序可使用优先级较低的软件中断来处理该设备。要允许更大的并发性,请使用单独的互斥锁来防止高级中断处理程序使用数据。
如果从 ddi_intr_get_pri(9F) 返回的中断优先级低于从 ddi_intr_get_hilevel_pri(9F) 返回的优先级,则 attach(9E) 入口点将进行常规中断注册。在这种情况下不需要软中断。
使用表示高级别中断的中断优先级初始化的互斥锁称为高级互斥锁。虽然持有高级互斥锁,但是驱动程序仍会受到与高级别中断处理程序相同的限制。
在以下示例中,高级互斥锁 (xsp->high_mu) 仅用于保护在高级别中断处理程序和软中断处理程序之间共享的数据。受保护的数据包括高级别中断处理程序和低级处理程序使用的队列,以及用于指示低级处理程序正在运行的标志。单独的低级互斥锁 (xsp->low_mu) 可防止软中断处理程序使用驱动程序的其余部分。
static int mydevattach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mydevstate *xsp; /* ... */ ret = ddi_intr_get_supported_types(dip, &type); if ((ret != DDI_SUCCESS) || (!(type & DDI_INTR_TYPE_FIXED))) { cmn_err(CE_WARN, "ddi_intr_get_supported_types() failed"); return (DDI_FAILURE); } ret = ddi_intr_get_nintrs(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 found"); return (DDI_FAILURE); } xsp->xs_htable = kmem_zalloc(count * sizeof (ddi_intr_handle_t), KM_SLEEP); ret = ddi_intr_alloc(dip, xsp->xs_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(xsp->xs_htable, sizeof (ddi_intr_handle_t)); return (DDI_FAILURE); } ret = ddi_intr_get_pri(xsp->xs_htable[0], &intr_pri); if (ret != DDI_SUCCESS) { cmn_err(CE_WARN, "ddi_intr_get_pri failed 0x%x", ret"); (void) ddi_intr_free(xsp->xs_htable[0]); kmem_free(xsp->xs_htable, sizeof (ddi_intr_handle_t)); return (DDI_FAILURE); } if (intr_pri >= ddi_intr_get_hilevel_pri()) { mutex_init(&xsp->high_mu, NULL, MUTEX_DRIVER, DDI_INTR_PRI(intr_pri)); ret = ddi_intr_add_handler(xsp->xs_htable[0], mydevhigh_intr, (caddr_t)xsp, NULL); if (ret != DDI_SUCCESS) { cmn_err(CE_WARN, "ddi_intr_add_handler failed 0x%x", ret"); mutex_destroy(&xsp>xs_int_mutex); (void) ddi_intr_free(xsp->xs_htable[0]); kmem_free(xsp->xs_htable, sizeof (ddi_intr_handle_t)); return (DDI_FAILURE); } /* add soft interrupt */ if (ddi_intr_add_softint(xsp->xs_dip, &xsp->xs_softint_hdl, DDI_INTR_SOFTPRI_MAX, xs_soft_intr, (caddr_t)xsp) != DDI_SUCCESS) { cmn_err(CE_WARN, "add soft interrupt failed"); mutex_destroy(&xsp->high_mu); (void) ddi_intr_remove_handler(xsp->xs_htable[0]); (void) ddi_intr_free(xsp->xs_htable[0]); kmem_free(xsp->xs_htable, sizeof (ddi_intr_handle_t)); return (DDI_FAILURE); } xsp->low_soft_pri = DDI_INTR_SOFTPRI_MAX; mutex_init(&xsp->low_mu, NULL, MUTEX_DRIVER, DDI_INTR_PRI(xsp->low_soft_pri)); } else { /* * regular interrupt registration continues from here * do not use a soft interrupt */ } return (DDI_SUCCESS); }
高级别中断例程用于服务设备并对数据进行排队。如果低级例程未运行,则高级例程会触发软件中断,如以下示例所示。
static uint_t mydevhigh_intr(caddr_t arg1, caddr_t arg2) { struct mydevstate *xsp = (struct mydevstate *)arg1; uint8_t status; volatile uint8_t 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); /* flush store buffers */ temp = ddi_get8(xsp->data_access_handle, &xsp->regp->csr); /* read data from device, queue data for low-level interrupt handler */ if (xsp->softint_running) need_softint = 0; else { xsp->softint_count++; need_softint = 1; } mutex_exit(&xsp->high_mu); /* read-only access to xsp->id, no mutex needed */ if (need_softint) { ret = ddi_intr_trigger_softint(xsp->xs_softint_hdl, NULL); if (ret == DDI_EPENDING) { cmn_err(CE_WARN, "ddi_intr_trigger_softint() soft interrupt " "already pending for this handler"); } else if (ret != DDI_SUCCESS) { cmn_err(CE_WARN, "ddi_intr_trigger_softint() failed"); } } return (DDI_INTR_CLAIMED); }
低级中断例程由用于触发软件中断的高级别中断例程启动。低级中断例程会一直运行,直到没有其他要处理的对象为止,如以下示例所示。
static uint_t mydev_soft_intr(caddr_t arg1, caddr_t arg2) { struct mydevstate *mydevp = (struct mydevstate *)arg1; /* ... */ mutex_enter(&mydevp->low_mu); mutex_enter(&mydevp->high_mu); if (mydevp->softint_count > 1) { mydevp->softint_count--; mutex_exit(&mydevp->high_mu); mutex_exit(&mydevp->low_mu); return (DDI_INTR_CLAIMED); } if ( /* queue empty */ ) { mutex_exit(&mydevp->high_mu); mutex_exit(&mydevp->low_mu); return (DDI_INTR_UNCLAIMED); } mydevp->softint_running = 1; while (EMBEDDED COMMENT:data on queue) { ASSERT(mutex_owned(&mydevp->high_mu); /* Dequeue data from high-level queue. */ mutex_exit(&mydevp->high_mu); /* normal interrupt processing */ mutex_enter(&mydevp->high_mu); } mydevp->softint_running = 0; mydevp->softint_count = 0; mutex_exit(&mydevp->high_mu); mutex_exit(&mydevp->low_mu); return (DDI_INTR_CLAIMED); }