第 1 部分针对 Oracle Solaris 平台设计设备驱动程序
9. 直接内存访问 (Direct Memory Access, DMA)
14. 分层驱动程序接口 (Layered Driver Interface, LDI)
本节介绍可以生成多种不同可中断条件的设备驱动程序如何利用中断资源管理功能来优化其中断向量的分配。
中断资源管理功能可动态管理驱动程序的中断配置,从而使设备驱动程序能够使用多个中断资源。未使用中断资源管理功能时,中断处理的配置通常仅在驱动程序的 attach (9E) 例程内进行。中断资源管理功能监控系统更改,根据这些更改重新计算分配给各设备的中断向量数量,并向各受影响的参与驱动程序发出关于驱动程序新中断向量分配情况的通知。参与驱动程序是注册了回调处理程序的驱动程序,如回调接口中所述。可能导致中断向量重新分配的更改包括添加或删除设备,或者显式请求,如修改所请求的中断向量数量中所述。
中断资源管理功能在各 Oracle Solaris 平台上不可用。此功能仅对利用 MSI-X 中断的 PCIe 设备可用。
注 - 利用中断资源管理功能的驱动程序必须能够在该功能不可用时正确做出调整。
中断资源管理功能可用时,可使驱动程序访问更多的中断向量,超过可通过其他方式为此驱动程序分配的数量。驱动程序在利用更多中断向量时可以更有效地处理中断条件。
中断资源管理功能根据以下约束动态调整分配给各参与驱动程序的中断向量数量:
可用总数。系统中存在的限定数量的中断向量。
请求的总数。可为驱动程序分配较少的中断向量,但始终不能超过所请求的中断向量数量。
对其他驱动程序公平。多个驱动程序以与各驱动程序请求的中断向量总数相关的方式共享可用中断向量的总数。
在任何给定时间为一个设备提供的可用中断向量的数量可能会有所不同:
在系统中动态添加或删除其他设备
驱动程序根据负荷动态更改所请求中断向量的数量
驱动程序必须提供以下支持,以利用中断资源管理功能:
回调支持。驱动程序必须注册回调处理程序,从而在系统改变其可用中断数量时获得通知。驱动程序必须能够增加或减少其使用的中断。
中断请求。驱动程序必须指定需要使用的中断数量。
中断用法。在任何时候,驱动程序都必须请求正确数量的中断,基于:
其硬件能生成哪些可中断条件
有多少处理器可用于并行处理那些条件
中断灵活性。驱动程序必须足够灵活,能够以最适合各中断向量的当前可用中断数量的方式为其分配一个或多个可中断的条件。在可用中断的数量增加或减少时,驱动程序可能需要随时重新配置这些分配。
驱动程序必须使用以下接口来注册回调支持。
表 8-1 回调支持接口
|
使用 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_register(9F)。
回调处理函数必须能够在函数注册的整个时间段内正确执行。回调函数不能依赖任何可能会在回调函数成功取消注册之前销毁的数据结构。
回调处理函数必须返回以下值之一:
DDI_SUCCESS(如果正确处理了操作)
DDI_FAILURE(如果遇到内部错误)
DDI_ENOTSUP(如果接收到无法识别的操作)
驱动程序必须使用以下接口从系统请求中断向量。
表 8-2 中断向量请求接口
|
使用 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);
}