中断

中断是一种异步事件,由设备在需要服务时生成。 例如,当串行端口上有数据可用或以太网数据包已到达时,系统会生成中断。中断可让驾驶员在事件发生后立即了解事件,而无需花时间轮询(主动等待)事件。

使用中断的驱动程序的一般架构是在驱动程序启动 / 绑定操作期间创建后台中断处理线程 (IHT)。该线程会等待发生中断,并在发生中断时执行某种服务操作。

例如,考虑一个串行端口驱动程序。应用可能会因以下任何事件而收到中断:

  • 一个或多个字符已到达,
  • 用于传输一个或多个字符
  • 控制线(例如 DTR)更改了状态。

中断会唤醒 IHT。 IHT 通常通过读取某些状态寄存器来确定事件的原因。然后,它会运行相应的服务函数来处理事件。 完成后,IHT 返回休眠状态,等待下一次中断。

例如,如果某个字符到达,IHT 将被唤醒,读取指示“数据可用”的状态寄存器,然后调用一个函数,将串行端口 FIFO 中的所有可用字符排入驱动程序的缓冲区。

无需内核级代码

您可能熟悉使用中断服务日常安排 (ISR) 的其他操作系统。这些都是在特权模式下运行并与中断控制器硬件进行交互的内核级处理程序。

在 Fuchsia 中,内核负责处理中断处理的特权部分,并提供线程级函数供驱动程序使用。

不同之处在于 IHT 在线程级别运行,而 ISR 在非常受限(有时也不太脆弱)的内核级别运行。其主要优势在于,如果 IHT 崩溃,只会移除驱动程序,而故障的 ISR 可以移除整个操作系统。

附加到中断

目前,唯一提供中断的总线是 PCI 总线。它支持两种:旧版和消息信号中断 (MSI)。

因此,如需在 PCI 上使用中断,请执行以下操作:

  1. 确定您的设备支持哪种类型(旧版或 MSI),
  2. 将中断模式设为匹配
  3. 获取设备的中断矢量(通常是一个,但也可能是多个)的句柄;
  4. 启动 IHT 后台线程,
  5. 安排 IHT 线程等待中断(针对第 3 步中的句柄)。

步骤 12 由 Pci::ConfigureInterruptMode 辅助函数处理:

// Configure interrupt mode.
uint32_t irq_cnt = 1;
fuchsia_hardware_pci::InterruptMode mode;
zx_status_t status = pci.ConfigureInterruptMode(irq_cnt, &mode);
if (status != ZX_OK) {
  // handle error
}

Pci::ConfigureInterruptMode() 接受两个参数:

#include <lib/device-protocol/pci.h>

zx_status_t Pci::ConfigureInterruptMode(uint32_t requested_irq_count,
                                        fpci::InterruptMode* out_mode);

第一个参数 requested_irq_count 是您需要的中断次数。

第二个参数是设备支持的中断模式的 out 参数。

配置中断支持后,您可以调用 Pci::MapInterrupt() 来创建所选中断的句柄。请注意,Pci::MapInterrupt() 具有以下原型:

#include <lib/device-protocol/pci.h>

zx_status_t Pci::MapInterrupt(uint32_t which_irq, zx::interrupt* out_interrupt);

第一个参数 which_irq 表示您需要的相对设备的中断编号,第二个参数是指向已创建的中断对象的指针。

您现在有一个中断句柄。

请注意,绝大多数设备只有一个中断,因此为 which_irq 传递 0 是正常的。如果您的设备确实有多个中断,常见做法是在 for 循环中运行 pci::MapInterrupt() 函数,并将句柄绑定到每个中断。

正在等待中断

在 IHT 中,您调用 zx::increment::wait() 来等待中断。以下原型适用:

zx_status_t zx::interrupt::wait(zx::time* out_timestamp);

第一个参数可以是 nullptr(典型),也可以是指向时间戳的指针,该时间戳指示中断触发的时间(以纳秒为单位,相对于使用 zx_clock_get_monotonic() 提取的单调时钟源)。

因此,典型的 IHT 具有以下形状:

int MyDevice::IrqThread() {
    for (;;) {
        zx_status_t status = dev->irq_.wait(nullptr);
        // do stuff
    }
}

设备类有一个成员(此处为 irq_),即您从 Pci::MapInterrupt() 获取的对象。

边缘中断模式与级别中断模式

中断硬件可以按“边缘”或“级别”这两种模式运行。

在边缘模式下,中断设在有效边缘(当硬件信号从非活跃状态变为活跃状态时),属于一次性中断。也就是说,信号必须返回到无效状态才能重新识别。

在电平模式下,当硬件信号处于活动状态时,中断处于活动状态。

通常,当中断是专用的时,会使用边缘模式;当中断由多台设备共享时,会使用级别模式(因为您希望中断在所有设备都取消断言其请求行之前保持有效状态)。

Zircon 内核会视情况自动遮盖和取消遮盖中断。对于电平触发的硬件中断,zx::increment::wait() 会在返回之前屏蔽中断,并在下次调用时取消屏蔽。 对于边缘触发的中断,中断会保持未遮盖状态。

IHT 不应执行任何长时间运行的任务。对于执行冗长任务的驱动程序,请使用工作线程。

关闭使用中断的驱动程序

为了彻底关闭使用中断的驱动程序,您可以使用 zx::increment::destroy() 取消 zx::increment::wait() 调用。

这个思路就是,当前台线程确定应该关闭驱动程序时,只会销毁中断句柄,从而导致 IHT 关闭:

void MyDevice::DdkRelease() {
  // destroy the handle, this will cause zx_interrupt_wait() to pop
  irq_.destroy();
  irq_thread_.join();
  ...
}

int MyDevice::IrqThread() {
    ...
    for(;;) {
        zx_status_t status = irq_.wait(nullptr);
        if (status == ZX_ERR_CANCELED) {
            // we are being shut down, do any cleanups required
            ...
            break;
        }
        ...
    }
}

主线程在被请求关闭时,会销毁中断句柄。 这会导致 IHT 的 zx::Studio::wait() 调用唤醒并显示错误代码。IHT 会查看错误代码(在本例中为 ZX_ERR_CANCELED),并决定是否要结束。 同时,主线程正在等待通过调用 thread::join() 加入 IHT。IHT 退出后,thread::join() 会返回,主线程可以完成处理。

我们邀请高级读者了解一些其他可用的中断相关功能: