编写适用于 Oracle® Solaris 11.2 的设备驱动程序

退出打印视图

更新时间: 2014 年 9 月
 
 

中断资源管理实现样例

网络设备驱动程序是中断资源管理的一种理想的备选设备驱动程序类型。网络设备硬件支持多个传输和接收信道。

网络设备在一个接收信道接收到包或者在一个传输信道传输包时,设备将生成唯一的中断条件。硬件可以为可能发生的各事件发送特定的 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);
}