中断是一种异步事件,由设备在需要服务时生成。例如,当串行端口上有数据可用或以太网数据包到达时,就会生成中断。中断可让驾驶员在事件发生时立即获知,但无需驾驶员花费时间轮询(主动等待)事件。
使用中断的驱动程序的一般架构是在驱动程序启动 / 绑定操作期间创建后台中断处理线程 (IHT)。此线程等待中断发生,并在中断发生时执行某种服务操作。
以串行端口驱动程序为例。由于发生以下任何事件,它可能会收到中断:
- 一个或多个字符已到达,
- 室现在可用于传输一个或多个字符,
- 控制线(例如
DTR)的状态已发生变化。
中断会唤醒 IHT。 IHT 通常通过读取一些状态寄存器来确定事件的原因。然后,它会运行相应的服务函数来处理事件。 完成后,IHT 会重新进入休眠状态,等待下一次中断。
例如,如果某个字符到达,IHT 会唤醒,读取指示“有数据可用”的状态寄存器,然后调用一个函数,该函数会将串行端口 FIFO 中的所有可用字符清空到驱动程序的缓冲区中。
无需内核级代码
您可能熟悉使用中断服务例程 (ISR) 的其他操作系统。这些是内核级处理程序,以特权模式运行,并与中断控制器硬件进行交互。
在 Fuchsia 中,内核负责处理中断处理的特权部分,并为驱动程序使用提供线程级函数。
不同之处在于,IHT 在线程级运行,而 ISR 在内核级运行,并且环境非常受限(有时甚至不稳定)。 主要优势在于,如果 IHT 崩溃,只会影响驱动程序,而失败的 ISR 可能会影响整个操作系统。
附加到中断
目前,唯一提供中断的总线是 PCI 总线。 它支持两种类型:旧版中断和消息信号中断 (MSI)。
因此,如需在 PCI 上使用中断,请执行以下操作:
- 确定设备支持哪种中断(旧版或 MSI),
- 将中断模式设置为匹配,
- 获取设备的中断向量(通常是一个,但可能有多个)的句柄,
- 启动 IHT 后台线程,
- 安排 IHT 线程等待中断(在第 3 步中的句柄上)。
步骤 1 和 2 由 `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 是您需要的中断次数。
第二个实参是设备支持的中断模式的输出形参。
配置中断支持后,您需要调用 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 表示您所需的设备相关中断号,第二个实参是指向已创建的中断对象的指针。
您现在有了一个中断处理程序。
请注意,绝大多数设备只有一个中断,因此简单地将
0传递给which_irq是正常的。如果设备确实有多个中断,通常的做法是在for循环中运行 pci::MapInterrupt() 函数,并将句柄绑定到每个中断。
等待中断
在 IHT 中,您调用 zx::interrupt::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::interrupt::wait() 会在返回之前屏蔽中断,并在下次调用时取消屏蔽中断。对于边沿触发的中断,中断保持未屏蔽状态。
IHT 不应执行任何长时间运行的任务。对于执行耗时任务的驱动程序,请使用工作器线程。
关闭使用中断的驱动程序
为了干净地关闭使用中断的驱动程序,您可以使用 zx::interrupt::destroy() 来中止 zx::interrupt::wait() 调用。
其思路是,当前景线程确定应关闭驱动程序时,只需销毁中断句柄,即可导致 IHT 关闭:
void MyDevice::Stop() {
// 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::interrupt::wait() 调用因错误代码而唤醒。
IHT 会查看错误代码(在本例中为 ZX_ERR_CANCELED),并决定结束。
与此同时,主线程正在等待通过调用 thread::join() 来联接 IHT。IHT 退出后,thread::join() 会返回,主线程可以完成其处理。
建议高级读者查看一些其他可用的中断相关函数: