编写设备驱动程序

第 8 章 中断处理程序

本章介绍用于处理中断的机制,如分配、注册、服务以及删除中断。本章介绍有关以下主题的信息:

中断处理程序概述

中断是指设备发送给 CPU 的硬件信号。中断将通知 CPU 需要注意设备,并且 CPU 应该停止任何当前活动并对设备进行响应。如果 CPU 未在执行优先级比中断优先级高的任务,则 CPU 会暂停当前线程。然后,CPU 会调用发送中断信号的设备的中断处理程序。中断处理程序的工作是服务设备并防止此设备中断。中断处理程序返回后,CPU 便会恢复出现中断之前所执行的工作。

Solaris DDI/DKI 提供了用于执行以下任务的接口:

设备中断

I/O 总线以两种常用方法来实现中断:向量化轮询。这两种方法通常都会提供总线中断优先级别。向量化设备还会提供中断向量。轮询设备则不提供中断向量。

为了与不断发展的总线技术保持同步,Solaris OS 已经得到了增强,可适应更新类型的中断以及已经使用多年的较为传统的中断。具体来说,操作系统目前可识别三种类型的中断:


注 –

一些较新的总线技术(如 PCI Express)要求使用 MSI,但是可以使用 INTx 仿真来处理传统中断。INTx 仿真用于实现兼容性,但是这并不被认为是好的做法。


高级别中断

总线会在总线中断级别设置设备中断的优先级。然后,总线中断级别将映射到处理器中断级别。映射到高于调度程序优先级别的 CPU 中断优先级的总线中断级别称为高级别中断。高级别中断处理程序仅限于调用以下 DDI 接口:

总线中断级别本身无法确定设备是否会发生高级别中断。特定的总线中断级别可以在一个平台映射到高级别中断,而在其他平台上则映射到普通中断。

不要求驱动程序来支持具有高级别中断的设备。但是,要求驱动程序检查中断级别。如果中断优先级高于或等于系统最高优先级,中断处理程序会在高级别中断环境下运行。在这种情况下,驱动程序可能无法连接,或者驱动程序可能会使用双级别方案来处理中断。有关更多信息,请参见处理高级别中断

传统中断

系统仅有的有关设备中断的信息为总线中断的优先级别和中断请求编号。例如,SPARC 计算机中 S 总线上的 IPL 即是总线中断的优先级别;x86 计算机中 ISA 总线上的 IRQ 即是中断请求编号。

注册中断处理程序之后,系统会将其添加到每个 IPL 或 IRQ 的潜在中断处理程序的列表中。出现中断时,系统必须确定与给定的 IPL 或 IRQ 关联的所有设备中实际导致此中断的设备。系统会针对指定的 IPL 或 IRQ 调用所有中断处理程序,直到一个处理程序声明中断为止。

以下总线可以支持轮询中断:

标准消息告知中断和扩展消息告知中断

标准 (MSI) 和扩展 (MSI-X) 消息告知中断均作为带内消息实现。消息告知中断可作为使用软件指定的地址和值的写操作进行发送。

MSI 中断

常规 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-X 中断是 MSI 中断的增强版本,与 MSI 中断有相同功能,具有以下关键区别:

利用 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 支持软件中断(也称为软中断)。软中断通过软件而不是硬件设备启动。另外,还必须在系统中添加和删除这些中断的处理程序。软中断处理程序在中断上下文中运行,因此可用于执行许多属于中断处理程序的任务。

硬件中断处理程序必须快速执行其任务,因为它们可能必须在执行这些任务的同时暂停其他系统活动。对于高级别中断处理程序,更需要满足此要求,这些处理程序在高于系统调度程序的优先级别上运行。高级别中断处理程序将屏蔽所有较低优先级中断的操作,包括系统时钟的中断操作。因此,该中断处理程序必须避免涉及到可能导致其休眠的活动,如获取互斥锁。

如果处理程序休眠,则系统可能会挂起,因为时钟会被屏蔽,从而无法调度休眠线程。因此,高级别中断处理程序通常在高优先级别执行最少量的工作,并将其他任务委托给运行优先级别低于高级别中断处理程序的软件中断。由于软件中断处理程序运行的优先级别低于系统调度程序,因此软件中断处理程序可以执行高级别中断处理程序无法执行的操作。

DDI 中断函数

Solaris OS 提供了用于注册和取消注册中断的框架,并且提供了对消息告知中断 (Message Signaled Interrupt, MSI) 的支持。通过中断管理界面,可以处理优先级、功能和中断屏蔽,并可获取待处理信息。

中断功能函数

可使用以下函数获取中断信息:

ddi_intr_get_navail(9F)

返回可用于指定硬件设备和中断类型的中断的数量。

ddi_intr_get_nintrs(9F)

返回设备支持的指定中断类型的中断的数量。

ddi_intr_get_supported_types(9F)

返回设备和主机均支持的硬件中断类型。

ddi_intr_get_cap(9F)

针对指定的中断返回中断功能标志。

中断初始化和销毁函数

可使用以下函数创建和删除中断:

ddi_intr_alloc(9F)

为指定类型的中断分配系统资源和中断向量。

ddi_intr_free(9F)

针对指定的中断句柄释放系统资源和中断向量。

ddi_intr_set_cap(9F)

通过使用 DDI_INTR_FLAG_LEVEL 和 DDI_INTR_FLAG_EDGE 标志来设置指定中断的功能。

ddi_intr_add_handler(9F)

添加中断处理程序。

ddi_intr_dup_handler(9F)

仅适用于 MSI-X。将分配的中断向量的地址和数据对复制到同一设备上未使用的中断向量。

ddi_intr_remove_handler(9F)

删除指定的中断处理程序。

ddi_intr_enable(9F)

启用指定的中断。

ddi_intr_disable(9F)

禁用指定的中断。

ddi_intr_block_enable(9F)

仅用于 MSI。启用指定范围的中断。

ddi_intr_block_disable(9F)

仅用于 MSI。禁用指定范围的中断。

ddi_intr_set_mask(9F)

如果已启用指定的中断,则设置中断屏蔽码。

ddi_intr_clr_mask(9F)

如果已启用指定的中断,则清除中断屏蔽码。

ddi_intr_get_pending(9F)

如果主桥 (host bridge) 或设备支持这种中断待处理位,则读取此位。

优先级管理函数

可使用以下函数获取和设置优先级信息:

ddi_intr_get_pri(9F)

返回指定中断的当前软件优先级设置。

ddi_intr_set_pri(9F)

设置指定中断的中断优先级别。

ddi_intr_get_hilevel_pri(9F)

返回高级别中断的最低优先级别。

软中断函数

可使用以下函数处理软中断和软中断处理程序:

ddi_intr_add_softint(9F)

添加软中断处理程序。

ddi_intr_trigger_softint(9F)

触发指定的软中断。

ddi_intr_remove_softint(9F)

删除指定的软中断处理程序。

ddi_intr_get_softint_pri(9F)

返回指定中断的软中断优先级。

ddi_intr_set_softint_pri(9F)

更改指定软中断的相对软中断优先级。

中断函数示例

本节提供了执行以下任务的示例:


示例 8–1 更改软中断优先级

使用 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");


示例 8–2 检查待处理中断

使用 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");


示例 8–3 设置中断屏蔽码

使用 ddi_intr_set_mask(9F) 函数设置中断屏蔽,以防止设备收到中断。

if ((ddi_intr_set_mask(mydevp->htable[0]) != DDI_SUCCESS))
    cmn_err(CE_WARN, "ddi_intr_set_mask() failed");


示例 8–4 清除中断屏蔽码

使用 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) 向系统注册中断处理程序,然后才能接收和服务中断。注册中断处理程序会为系统提供一种将中断处理程序与中断规范相关联的方法。如果设备可能负责中断,则会调用中断处理程序。此处理程序负责确定其是否应处理中断,如果是,则负责声明该中断。


提示 –

可在 mdbkmdb 调试器中使用 ::interrupts 命令检索支持的 SPARC 和 x86 系统上设备的已注册中断信息。


注册传统中断

要注册驱动程序的中断处理程序,驱动程序通常会在其 attach(9E) 入口点执行以下步骤。

  1. 使用 ddi_intr_get_supported_types(9F) 确定支持的中断类型。

  2. 使用 ddi_intr_get_nintrs(9F) 确定支持的中断类型的数量。

  3. 使用 kmem_zalloc(9F) 为 DDI 中断句柄分配内存。

  4. 对于分配的每个中断类型,执行以下步骤:

    1. 使用 ddi_intr_get_pri(9F) 获取中断的优先级。

    2. 如果需要为中断设置新的优先级,请使用 ddi_intr_set_pri(9F)

    3. 使用 mutex_init(9F) 将锁初始化。

    4. 使用 ddi_intr_add_handler(9F) 注册中断的处理程序。

    5. 使用 ddi_intr_enable(9F) 启用中断。

  5. 执行以下步骤以释放每个中断:

    1. 使用 ddi_intr_disable(9F) 禁用每个中断。

    2. 使用 ddi_intr_remove_handler(9F) 删除中断处理程序。

    3. 使用 mutex_destroy(9F) 删除锁。

    4. 使用 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));

注册 MSI 中断

要注册驱动程序的中断处理程序,驱动程序通常会在其 attach(9E) 入口点执行以下步骤。

  1. 使用 ddi_intr_get_supported_types(9F) 确定支持的中断类型。

  2. 使用 ddi_intr_get_nintrs(9F) 确定支持的 MSI 中断类型的数量。

  3. 使用 ddi_intr_alloc(9F) 为 MSI 中断分配内存。

  4. 对于分配的每个中断类型,执行以下步骤:

    1. 使用 ddi_intr_get_pri(9F) 获取中断的优先级。

    2. 如果需要为中断设置新的优先级,请使用 ddi_intr_set_pri(9F)

    3. 使用 mutex_init(9F) 将锁初始化。

    4. 使用 ddi_intr_add_handler(9F) 注册中断的处理程序。

  5. 使用以下函数之一启用所有中断:


示例 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);
}    

中断资源管理

本节介绍可以生成多种不同可中断条件的设备驱动程序如何利用中断资源管理功能来优化其中断向量的分配。

中断资源管理功能

中断资源管理功能可动态管理驱动程序的中断配置,从而使设备驱动程序能够使用多个中断资源。未使用中断资源管理功能时,中断处理的配置通常仅在驱动程序的 attach (9E) 例程内进行。中断资源管理功能监控系统更改,根据这些更改重新计算分配给各设备的中断向量数量,并向各受影响的参与驱动程序发出关于驱动程序新中断向量分配情况的通知。参与驱动程序是注册了回调处理程序的驱动程序,如回调接口中所述。可能导致中断向量重新分配的更改包括添加或删除设备,或者显式请求,如修改所请求的中断向量数量中所述。

中断资源管理功能在各 Solaris 平台上不可用。此功能仅对利用 MSI-X 中断的 PCIe 设备可用。


注 –

利用中断资源管理功能的驱动程序必须能够在该功能不可用时正确做出调整。


中断资源管理功能可用时,可使驱动程序访问更多的中断向量,超过可通过其他方式为此驱动程序分配的数量。驱动程序在利用更多中断向量时可以更有效地处理中断条件。

中断资源管理功能根据以下约束动态调整分配给各参与驱动程序的中断向量数量:

在任何给定时间为一个设备提供的可用中断向量的数量可能会有所不同:

驱动程序必须提供以下支持,以利用中断资源管理功能:

回调接口

驱动程序必须使用以下接口来注册回调支持。

表 8–1 回调支持接口

接口 

数据结构 

说明 

ddi_cb_register()

ddi_cb_flags_tddi_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() 例程时,驱动程序都会指定应发送给其本身的两个专用参数(arg1arg2)。

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;

如果 cbactionDDI_CB_INT _ADD,则应添加 cbarg 数量的中断向量。如果 cbaction DDI_CB_INT _REMOVE,则应释放 cbarg 数量的中断向量。

有关 arg1arg2 的说明, 请参见 ddi_cb_registe (9F)。

回调处理函数必须能够在函数注册的整个时间段内正确执行。回调函数不能依赖任何可能会在回调函数成功取消注册之前销毁的数据结构。

回调处理函数必须返回以下值之一:

中断请求接口

驱动程序必须使用以下接口从系统请求中断向量。

表 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() 即可分配所有中断向量。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 中断。此函数会相应在硬件中对该表进行编程。

驱动程序应有两个不同的中断处理程序函数。

在这一部分的示例驱动程序中,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);
}

中断处理程序功能

驱动程序框架和设备各自将要求置于中断处理程序上。所有中断处理程序均要求执行以下任务:

以下示例说明了名为 mydev 的设备的中断例程。


示例 8–9 中断示例

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) 入口点进行此测试。请参见高级别中断处理示例

高级互斥锁

使用表示高级别中断的中断优先级初始化的互斥锁称为高级互斥锁。虽然持有高级互斥锁,但是驱动程序仍会受到与高级别中断处理程序相同的限制。

高级别中断处理示例

在以下示例中,高级互斥锁 (xsp->high_mu) 仅用于保护在高级别中断处理程序和软中断处理程序之间共享的数据。受保护的数据包括高级别中断处理程序和低级处理程序使用的队列,以及用于指示低级处理程序正在运行的标志。单独的低级互斥锁 (xsp->low_mu) 可防止软中断处理程序使用驱动程序的其余部分。


示例 8–10 使用 attach() 处理高级别中断

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);
}

高级别中断例程用于服务设备并对数据进行排队。如果低级例程未运行,则高级例程会触发软件中断,如以下示例所示。


示例 8–11 高级别中断例程

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);
}

低级中断例程由用于触发软件中断的高级别中断例程启动。低级中断例程会一直运行,直到没有其他要处理的对象为止,如以下示例所示。


示例 8–12 低级软中断例程

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);
}