移植Dovetail中断流程的梳理

legonext 2023-06-05 14:36:01

调整通用中断管理流程 (genirq)

中断流水线涉及控制中断流的基本变化:来自 IRQ 域 的API  handle_domain_irq() 通过调用 generic_pipeline_irq() 而不是 generic_handle_irq() 将所有父 IRQ 重定向到流水线入口。

通用中断流处理程序通过根据中断类型调用对应的 irqchip 例程(例如 irq_ack()、irq_eoi())确认通过硬件传入 IRQ 事件。但是,中断流处理程序不会立即调用in-band中断处理程序。相反,他们通过调用 handle_oob_irq() 将事件移交给中断管道核心。

如果接收到的中断存在out-of-band处理程序,则 handle_oob_irq() 将执行上下文切换到 oob 阶段(如果不是当前)后立即调用它。否则,该事件在当前 CPU 的in-band阶段日志中标记为未决中断。

下图说明了 Dovetail 流水线中断模型中整个内核代码的执行流程。注意两步过程:首先我们尝试将传入的 IRQ 传递给对应的out-of-band处理程序(如果存在),然后我们可以处理当前每个 CPU 中断日志中的任何 IRQ,其中可能存在非 OOB 事件。

 

如上所示,在 Dovetail 的流水线中断模型中,中断流处理程序可能会针对单个 IRQ 运行两次:

  • 首先立即将事件提交给可能对此感兴趣的任何out-of-band处理程序。这是通过调用 handle_oob_irq() 实现的,它的作用是调用此类处理程序(如果存在),或者如果不存在则安排 IRQ 事件的in-band处理。
  • 如果 IRQ 事件没有传送到任何out-of-band处理程序,最后运行in-band处理程序接受 IRQ 事件。为了将事件传递给对应的in-band处理程序,流水线核心再次调用中断流处理程序。发生这种情况时,流处理程序照常处理中断,但会跳过对 handle_oob_irq() 的调用。

注意:

任何传入的 IRQ 事件要么被分派给一个或多个out-of-band处理程序,要么被分派给一个或多个in-band处理程序,但绝不会分派给它们的混合体。此外,由于每个未被out-of-band处理程序处理的中断都将无条件地进入in-band阶段的中断事件日志,因此所有不能被out-of-band处理程序处理的外部中断都必须在in-band代码中有一个中断处理程序。

一旦 generic_pipeline_irq() 返回,如果被抢占的执行上下文在未停止的in-band阶段上运行,中断流水线核心立即同步中断状态,这意味着在in-band阶段的日志中发现的所有挂起的 IRQ 立即传递到它们各自的in-band处理程序。在所有其他情况下,立即离开IRQ 堆栈而不运行这些处理程序。 IRQ 可能会一直挂起,直到in-band代码从被抢占中恢复,然后清除虚拟中断禁用标志,这将导致中断状态同步,最终运行in-band处理程序。

In-band IRQ 传递代码

为了将挂起的中断传递到in-band阶段,您必须提供同步 IRQ 阶段的通用 Dovetail 内核调用名为 arch_do_IRQ_pipelined() 的例程,该例程作为流水线特定架构支持代码的一部分。此函数同时传递设备 IRQ 和 IPI,它应根据以下逻辑相应地调度事件:

对于 ARM 和 ARM64,相应的代码如下所示:

阅读这段代码时的一些注意事项:

  • do_domain_irq() 是通用 Dovetail 核心实现的例程,它会触发设备 IRQ 的in-band处理程序。
  • IPI 如何与其他 IRQ 区分开来,应该为它们调用哪个处理程序是您在移植 Dovetail 时应该提供的特定架构的实现。在上面的代码示例中,IPI 处理例程名为 __handle_IPI()。
  • 由于in-band阶段的中断传递会延迟到后者最终同步,因此我们无法访问因为延迟的中断事件而被抢占的寄存器堆栈。换句话说,中断上下文已经返回,只是记录了中断事件,还没有派发它,被抢占的基于堆栈的寄存器帧的上下文早就没有了。幸运的是,内核通常只对附加到定时器事件的帧进行分析感兴趣(例如用于分析),因此 Dovetail 只需将与接收到的最后一个滴答事件对应的寄存器帧保存到每个 CPU 的 irq_pipeline.tick_regs 变量中。指向当前 CPU 的此类帧的指针可以通过 arch_do_IRQ_pipelined() 的实现传递给中断处理程序。

延迟电平触发的 IRQ

在事件没有任何out-of-band处理程序的情况下,设备可能会继续发送中断信号,直到该中断在其自己的寄存器中被解除。同时,如果in-band阶段当前停止,我们可能不允许立即在当前中断上下文上运行in-band处理程序,我们将不得不等待直到in-band代码再次接受中断。但是,CPU 中的中断禁用位肯定会同时被清除。出于这个原因,根据中断类型,流水线代码修改的中断流处理程序可能必须屏蔽中断,直到in-band处理程序从in-band阶段运行,解除该中断源的中断状态。这通常发生在电平触发的中断中,防止设备通过连续的中断请求冲击 CPU。

有问题的场景示例:

由于共享一条中断的所有 IRQ 处理程序都以互斥的方式在in-band或out-of-band进行,因此这种屏蔽不能延迟out-of-band事件。

注意:

屏蔽中断线背后的逻辑,直到事件在稍后的某个时间点被处理 - 在原始中断上下文之外 - 适用于线程中断模型(即 IRQF_THREAD)。在这种情况下,中断线可能会被屏蔽,直到 IRQ 线程被调度,在中断处理程序最终清除事件原因之后。

 

使中断流处理程序适配中断管道流水线

调整流处理程序处理中断流水线的逻辑由以下步骤组成:

  • (可选)如果中断(例如设备中断)在处理器之间共享,则进入临界区由 IRQ 描述符锁保护。在这种情况下要检查中断处理程序是否可以在当前 CPU 上运行 (irq_may_run())。根据定义,对于per- CPU的中断不需要锁定。
  • 检查我们是否正在进入管道,以便将中断传递给为其注册的out-of-band处理程序。 on_pipeline_entry() 返回一个表示这种情况的布尔值。
  • 如果在管道入口,我们应该通过调用 handle_oob_irq() 将事件传递给管道核心。返回时,此例程会告诉调用者是否为该事件触发了任何out-of-band处理程序。
  • 如果是这样,我们可以假设中断源现在已在设备中清除,并且我们可以在将中断线恢复到正常状态后离开流处理程序。如果是在进入流处理程序时被屏蔽的电平触发中断,我们需要在离开之前取消屏蔽中断线。
  • 如果没有调用out-of-band处理程序,我们应该执行任何确认和/或 EOI 以释放控制器中的中断线,同时在退出流处理程序之前根据需要将其屏蔽。在电平触发中断的情况下,我们确实希望将其屏蔽以解决前面解释的中断延迟的病态情况。
  • 如果不在管道入口(即流处理程序的第二个入口),那么我们必须在in-band阶段运行,接受中断,因此我们应该为传入事件触发in-band处理程序。

示例:调整处理电平触发的 IRQ 的处理程序

这个变化是这样写的:

  • 在进入管道时,这意味着立即通过 CPU 为接收事件设置的中断帧上下文,将传入的 IRQ 告知管道核心。
  • 如果此 IRQ 由out-of-band处理程序处理(handle_oob_irq() 返回 true),则认为事件已完全处理,在离开前取消屏蔽中断线。因为in-band内核代码可能不希望此时接收到任何中断(即虚拟中断禁用标志可能被设置为in-band阶段),我们不能做比这个更多的事情。
  • 否则,保持中断线被屏蔽,直到 handle_level_irq() 再次从安全上下文调用以处理in-band中断,此时事件应该被传递到主内核的in-band中断处理程序。我们必须保持线路屏蔽以防止 IRQ 风暴,否则肯定会发生,因为还没有处理程序清除设备中中断事件的原因。

 

修复 IRQ 芯片驱动程序

我们必须确保 由irqchip 驱动程序暴露的以下处理程序可以安全地在out-of-band上下文中运行:

  • irq_mask()
  • irq_ack()
  • irq_mask_ack()
  • irq_eoi()
  • irq_unmask()

对于所谓的设备中断,不需要更改,因为 genirq 层通过在对那些 irqchip 处理程序的调用中持有每个描述符 irq_desc::lock 自旋锁来确保单个 CPU 处理给定的 IRQ 事件,并且这种锁定是自动的当流水线中断时变成混合自旋锁。换句话说,这些处理程序已正确序列化,在 CPU 中禁用中断的情况下运行,正如它们的非流水线实现所期望的那样。

与设备中断不同,每个 CPU 的中断处理不需要以这种方式序列化,因为根据定义,不能有多个 CPU 争相访问此类事件。

但是,可能还有其他原因需要修复其中一些处理程序:

  • 它们不得调用任何in-band内核服务,这可能会导致无效的上下文重新进入。
  • 可能存在由某些 irqchip 驱动程序在本地定义的内部自旋锁,用于序列化对由多个 CPU 同时处理的不同 IRQ 的公共中断控制器硬件的访问。调整在 irqchip 驱动程序中发现的此类自旋锁部分以支持中断流水线可能涉及将相关自旋锁转换为硬自旋锁。

最初由公共中断禁用序列化的其他代码部分可能需要完全原子化才能在流水线中断模式下一致地运行。这可以通过引入硬掩码、将 local_irq_save() 调用转换为 hard_local_irq_save()、将 local_irq_restore() 转换为 hard_local_irq_restore() 来实现。

最后,必须将 IRQCHIP_PIPELINE_SAFE 添加到支持中断管道irqchip 驱动程序的 struct irqchip::flags 成员中,以便通知内核此类控制器可以在管道中断模式下运行。即使您没有引入任何其他更改来支持流水线,这也是必需的:它告诉内核您确实为此目的审查了代码。

调整 ARM GIC 驱动程序以中断流水线

 

调整 BCM2835 引脚控制驱动程序以中断流水线

在某些(罕见的)情况下,我们可能需要做更多的工作来调整中断芯片驱动程序。例如,我们可能必须先将休眠自旋锁转换为原始自旋锁,以便最终将后者转换为硬自旋锁。与休眠自旋锁不同,像原始自旋锁这样的硬自旋锁应该通过 raw_spin_lock() API 进行操作。

注意:

每当传递的 irqchip 描述符不带有 IRQCHIP_PIPELINE_SAFE 标志并且启用 CONFIG_IRQ_PIPELINE 时,irq_set_chip() 将发出内核警告。将此警告视为您的 IRQ 管道到目标系统的端口不完整的信号。

内核抢占控制(CONFIG_PREEMPT)

启用流水线时,Dovetail 确保 preempt_schedule_irq() 协调虚拟中断状态——在内核进入汇编级代码尚未触及时——与调度程序核心做出的基本假设,例如进入时中断被虚拟地禁止了(即 in-band阶段应该停止)。

...全文
300 1 打赏 收藏 转发到动态 举报
写回复
用AI写文章
1 条回复
切换为时间正序
请发表友善的回复…
发表回复

130

社区成员

发帖
与我相关
我的任务
社区描述
Xenomai中文社区。 Upstream - xenomai.org Mirror - gitee.com/Xenomai CSDN - bbs.csdn.net/forums/Xenomai
社区管理员
  • Xenomai
  • legonext
  • Cajb
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧