| RFC-0270:Zircon 虚拟中断信号 | |
|---|---|
| 状态 | 已接受 |
| 区域 |
|
| 说明 | 为虚拟中断对象提出了一种新信号。 |
| 问题 | |
| Gerrit 更改 | |
| 作者 | |
| 审核人 | |
| 提交日期(年-月-日) | 2025-04-02 |
| 审核日期(年-月-日) | 2025-04-16 |
问题陈述
我们需要一种更好的方法来对共享中断进行多路分解,并管理虚拟中断的确认/未确认状态。如需了解详情,请参阅下文的“扩展问题陈述”部分。
摘要
此 RFC 提议向虚拟中断对象添加一个新的 Zircon 信号,当虚拟中断对象处于“未触发”状态时,该信号会保持断言状态。
背景
物理中断与虚拟中断
Zircon 中断对象有两种类型:物理中断和虚拟中断。
物理中断对象在逻辑上表示通过系统中断控制器(有时通过 PCIe 总线,有时直接通过平台或 SoC 特定路由)传递的中断信号。相比之下,虚拟中断对象并不直接表示源自非 CPU 硬件的信号。相反,虚拟中断是由程序创建的,看起来像物理中断,但只有当软件对对象调用 zx_interrupt_trigger 时,它们才能被发出信号。
自定义等待界面
中断对象目前不使用标准的 Zircon 信号传递系统来通知客户端已收到中断。虽然它们可以绑定到端口并在收到 IRQ 时传送端口数据包,并且可以用于阻塞线程直到收到 IRQ,但它们不使用标准的 zx_object_wait_one/zx_object_wait_many 或 zx_object_wait_async 系统调用来实现此行为。相反,如果用户希望接收端口数据包,必须使用 zx_interrupt_bind 将中断对象绑定到端口对象;如果用户希望阻塞线程,必须使用 zx_interrupt_wait。一次只能有一个逻辑等待操作在中断对象上处于挂起状态,不支持任意数量的等待操作。线程解除阻塞或端口数据包已传送后,需要“确认”中断,然后才能再次发出信号。
当用户通过 zx_interrupt_wait 等待中断时,如果用户线程下次通过再次调用 zx_interrupt_wait 阻塞中断,则中断会被确认。当用户通过 zx_interrupt_bind 将中断绑定到端口时,他们可以通过对中断对象调用 zx_interrupt_ack 来确认中断,从而允许传送新的端口数据包。
虽然此非标准信号接口并非最佳选择,并且我们希望设计一个与标准 Zircon 信号机制兼容的新接口(除了解决其他痛点之外),但对 Zircon 中断 API 进行全面重新设计并过渡到新系统是一项复杂的工作,可能需要大量时间和精力,并且超出了本提案的有限范围。
虚拟中断的用途
在 Fuchsia 中,虚拟中断有两个主要用途,两者都源于相同的动机。这些是测试和解复用。
由于驱动程序需要使用中断对象使用的特殊等待接口,因此很难轻松地用其他内核对象(可能是事件)替换中断对象,并让驱动程序代码“正常运行”。在编写驱动程序测试时,如果测试环境想要模拟硬件,就会出现问题。无法向驱动程序提供任何物理中断,即使可以,也很难针对任何给定的测试控制此类中断,而且此类中断非常具体。
测试可以改用虚拟中断对象。从驱动程序消费者的角度来看,它们使用相同的 API,并且基本上无法区分,因此无需任何特殊情况代码(知道测试环境和实际环境之间的区别)即可测试代码。
当需要对单个中断进行多路分解时,也会出现类似情况。假设某个外部芯片的驱动程序需要引发中断。我们希望为外部芯片提供一个独立于特定产品中所用 SoC 的驱动程序,该产品使用外部芯片。芯片可能具有连接到 SoC 的 IRQ 信号。在某些 SoC 设计中,此 IRQ 信号可以直接路由到 SoC 的中断控制器,这意味着可以在初始化期间实例化专用物理中断并将其传递给驱动程序。如果管理中断仅需要与系统的中断控制器交互,则内核可以通过物理中断对象直接管理 IRQ。
另一方面,其他设计可能会将此中断连接到 GPIO,作为中断块(通常为 16 或 32 个中断的块)的一部分。当配置为中断的块中的任何 GPIO 收到信号时,GPIO 硬件的物理中断就会被触发。然后,GPIO 驱动程序可以唤醒并确定触发了多个 GPIO 中断中的哪一个,并屏蔽中断,直到可以处理该中断为止。不过,它仍需要向驱动程序发出信号,告知驱动程序这是哪个中断。我们不希望 irq-consumer-driver 需要知道这些情况之间的区别,也不希望多个驱动程序(针对不同的外部芯片)需要存在于同一进程中,仅仅是因为电路板设计将多个中断路由到同一 GPIO 块。因此,在这种情况下,GPIO 驱动程序可以为共享硬件中配置为中断的每个 GPIO 创建虚拟中断对象,从而有效地对共享同一物理中断的多个中断进行解复用,而无需驱动程序具有以不同方式处理这两种情况的代码。
扩展的问题陈述
不过,截至目前,这种方法存在一个实际问题。考虑 GPIO 解复用场景。有多个 GPIO 中断共享同一块硬件,驱动程序已为每个中断创建并分配了一个虚拟中断。当其中一个中断触发时,GPIO 会通过模块的共享物理中断发出信号。然后,GPIO 驱动程序可以使用 SoC 专用 GPIO 硬件和关联的虚拟中断来屏蔽特定位。zx_interrupt_trigger现在,所有待处理的 GPIO 中断都已发出信号,它可以确认自己的物理中断,并返回到等待新中断断言的状态。
在驱动程序/消费者端,中断处理程序会被唤醒,中断得到处理,最后得到确认。此时,GPIO 驱动程序需要采取行动。具体而言,它需要唤醒并取消屏蔽/重新设防 GPIO 块中的特定位,然后才能发出任何新的中断信号。
不过,目前还没有好的方法来实现这一点。没有信号通过中断对象从客户端驱动程序发送到 GPIO 驱动程序,但是如果没有这样的信号,GPIO 驱动程序就无法很好地知道何时可以安全地取消屏蔽中断并重新启用中断。
可以引入其他对象或建立协议来提供此类信号,但这会要求中断的消费者知道这是虚拟中断而不是物理中断,从而有损于使这两种场景难以区分的目标。
利益相关方
Facilitator: cpu@google.com
审核者: + 'maniscalco@google.com' + 'bradenkell@google.com' + 'rudymathu@google.com' + 'cpu@google.com'
已咨询:
Zircon 驱动程序框架团队。
共同化:
此提案已(以 Google 文档的形式)与 Zircon 团队进行过沟通。
要求
使用中断对象的“下游”驱动程序必须不考虑该对象是否为虚拟对象。
设计
中断对象和标准 Zircon 信号
如前所述,Zircon 中断对象目前不使用标准的 Zircon 信号系统来发出来自硬件的中断请求。但这并不意味着它们完全不参与标准信号系统。用户可以随意发布异步等待来中断对象,也可以使用 zx_object_wait_one/zx_object_wait_many 阻止线程。不过,只有在等待几乎每个 Zircon 对象上都存在的 8 个“用户信号”之一,并且软件断言了该信号时,这些等待操作才会得到满足。目前,没有为中断对象定义任何“系统信号”供内核发出信号。
ZX_VIRTUAL_INTERRUPT_UNTRIGGERED
因此,我们引入了一个新信号(称之为 ZX_VIRTUAL_INTERRUPT_UNTRIGGERED),并仅将其用于虚拟中断。以下是信号的基本属性:
- 虚拟中断对象在创建后不会立即触发,因此此信号将断言在任何新创建的虚拟中断对象上。
- 当用户对虚拟中断对象调用
zx_interrupt_trigger时,该对象会进入触发状态,并且信号会被取消断言。 - 当用户确认触发的虚拟中断对象时,该对象会进入未触发状态,并且信号会断言。
实际使用示例
假设有一个 GPIO 驱动程序使用物理中断 I,并公开了三个虚拟中断 A、B 和 C,所有这些中断都在同一个 GPIO 块中。
- 启动时,驱动程序会创建并分发
A、B和C,然后将I绑定到端口P。 - 它还会创建一个专用 IRQ 线程 (
T),配置适当的配置文件,然后在P中阻塞T以等待数据包。 I会触发将数据包传递给P,从而解除T的阻塞状态。T检查 GPIO 状态,发现与A和C关联的 GPIO 已断言。T在A和C上调用zx_interrupt_trigger。A和C进入触发状态,并在该过程中清除ZX_VIRTUAL_INTERRUPT_UNTRIGGERED。T对A和C调用zx_object_async_wait,将它们与其端口相关联,并等待虚拟中断对象上的ZX_VIRTUAL_INTERRUPT_UNTRIGGERED变为断言状态。T会屏蔽与A和C关联的 GPIO。T在I上调用zx_interrupt_ack,然后再次在端口上阻塞,等待新数据包。- 在某个时间点,使用
C的驱动程序唤醒、处理中断,然后对其C的句柄调用zx_interrupt_ack,并断言ZX_VIRTUAL_INTERRUPT_UNTRIGGERED作为副作用。 - 等待
C的未触发信号的异步等待操作已满足,因此生成一个端口数据包并将其传递给P,从而取消阻塞T。 T唤醒,并注意到C已变为“未触发”,因此它会使用其硬件特定寄存器取消屏蔽并重新启用与C关联的 GPIO 中断。T只是返回到其端口上的等待状态。当I再次断言或A的ZX_VIRTUAL_INTERRUPT_UNTRIGGERED信号断言时,我们将收到端口数据包。您目前无需采取进一步行动。
确认待处理的中断
对于某些中断对象,可能会出现以下情况:中断被触发,并且在确认时已有第二个 IRQ 处于待处理状态。虽然这种情况最常发生在 MSI 中断中,但也可以通过虚拟中断对象来生成这种情况,只需在客户端设法处理中断之前连续两次调用触发器即可。
当用户确认有另一个 IRQ 待处理的中断时,结果是中断对象会立即有效触发。通过 zx_interrupt_wait 进行确认的线程将立即解除阻塞。通过 zx_interrupt_ack 确认线程会导致立即生成新的端口数据包。
UNTRIGGERED 信号会发生什么情况?由于中断在逻辑上变为未触发状态,然后立即重新触发,因此信号的行为应为频闪行为。也就是说,当前正在等待 UNTRIGGERED 信号的任何线程都应解除阻塞,并且当前已发布的所有异步等待操作都应得到满足并生成端口数据包。不过,确认操作后紧随其后的信号状态将变为取消断言。
实现
此方法的实现预计会很简单,风险也很低。
内核代码目前有两个中断对象调度程序,即 InterruptDispatcher 和 VirtualInterruptDispatcher,其中第二个是第一个的子类。虽然它们共享许多代码,但成为已发出信号的路径是分开的。InterruptDispatcher通过 InterruptHandler 方法在硬 IRQ 时间发出信号,而虚拟中断通过 Trigger 方法在 zx_interrupt_trigger 时间发出信号。对 InterruptDispatcher 调用 Trigger 是不合法的,这样做会导致 BAD_STATE 错误。因此,触发操作仅在虚拟中断对象上执行,并且始终在系统调用上下文中执行,在该上下文中,可以合法地获取操作对象信号状态所需的锁。
确认中断发生在下一次调用 zx_interrupt_wait 时(对于非端口绑定中断),或在调用 zx_interrupt_ack 时(对于端口绑定中断)。与 zx_interrupt_trigger 一样,该调用是在系统调用上下文中进行的,因此可以进行信号操作。
最后,当最后一个句柄关闭或线程调用 zx_interrupt_destroy 时,中断会被销毁。同样,所有这些操作都发生在可以获取内核互斥锁并断言用户信号的上下文中,因此在显式销毁对象期间,可以轻松断言未触发的信号(解除任何等待者的阻塞状态)。
因此,该功能的实现应仅包括在以下四项操作(触发、确认、等待和销毁)期间重载 VirtualInterruptDispatcher 的行为。重载版本可能会在执行操作本身之前获取调度程序锁,并在操作结束时根据需要更新信号状态。
就是这样。UpdateState 和现有的标准 Zircon 信令机制已处理了所有复杂的解除线程阻塞和传送端口数据包的繁重工作。物理中断对象不会公开信号,也不会使用任何虚拟调度程序代码,从而避免了在硬 IRQ 时间同步任何信号状态与执行的操作。
工效学设计
预计其人体工程学设计简单且可接受。无需对现有的中断消费者进行任何更改,确认信号发送行为完全是自动的。虚拟中断生产者只需使用信号,并采用现有的成熟信号处理模式。
安全注意事项
此功能的添加有效地引入了从中断消费者到中断生产者的新的通信渠道,使生产者能够知道消费者何时确认了虚拟中断。这是多路复用中断系统正常运行的要求之一,不被视为单独的或不良的侧信道,因此不构成安全风险。
隐私注意事项
此功能不会处理任何用户数据或其他可能敏感的信息,也没有需要处理的已知隐私注意事项。
测试
测试应该是现有单元测试的简单扩展。我们基本上需要添加测试来确保:
- 创建虚拟中断对象时,系统会先断言
ZX_VIRTUAL_INTERRUPT_UNTRIGGERED信号。 - 该信号会因对
zx_interrupt_trigger的调用而清除。 - 该信号是在响应对
zx_interrupt_ack的调用时设置的。 - 当存在另一个待处理的 IRQ 时,如果确认了该信号,则该信号会正确实现“频闪”行为。
文档
虚拟中断对象的文档将更新,以描述新的信号,说明该信号仅用于虚拟中断对象,并总结上述行为规则(初始断言、触发时清除、在确认时设置、在待处理的确认时选通)。
缺点、替代方案和未知因素
此方法没有已知重大缺点。满足了用户要求。实施和测试应简单易行,风险较低。
Zircon 驱动程序团队讨论了许多替代方案,但所有方案都存在相同的缺点。虚拟中断的驱动程序使用者必须明确知道自己正在使用虚拟中断,然后明确参与某些用户级定义的协议,以向中断生产者发送确认信号。
虽然所有这些方法都可行,但似乎并不理想。当使用虚拟中断时,驱动程序作者需要参与已定义的任何协议,这会给他们带来巨大的额外负担;而当使用物理中断时,驱动程序作者的行为会有所不同。此外,现有的驱动程序群体需要更新才能使用新协议,如果内核以透明方式处理 ACK 信号,则可以完全跳过此步骤。
无论实现何种协议,都将完全在用户模式下实现,因此需要仔细考虑潜在恶意行为者带来的信任问题。使信号传递机制由 Zircon 内核介导有助于简化此处的分析。
最后,Zircon 中断的用户已经必须确认他们的中断(无论是隐式还是显式)。很难想象任何额外的带外确认协议最终能像在现有确认发生时简单地操纵内核对象的信号位那样高效。