Linux kernel的中断子系统之(一):综述

声明:
本文转自http://www.wowotech.net/irq_subsystem/interrupt_subsystem_architecture.html
由于本人使用的是龙芯平台(cpu:3a4000,桥片7a1000),会在文章基础上增加注释。此文用途只有学习。

一、前言

一个合格的linux驱动工程师需要对kernel中的中断子系统有深刻的理解,只有这样,在写具体driver的时候才能:

1、正确的使用linux kernel提供的的API,例如最著名的request_threaded_irq(request_irq)接口

2、正确使用同步机制保护驱动代码中的临界区

3、正确的使用kernel提供的softirq、tasklet、workqueue等机制来完成具体的中断处理

基于上面的原因,我希望能够通过一系列的文档来描述清楚linux kernel中的中断子系统方方面面的知识。一方面是整理自己的思绪,另外一方面,希望能够对其他的驱动工程师(或者想从事linux驱动工作的工程师)有所帮助。

二、中断系统相关硬件描述

中断硬件系统主要有三种器件参与,各个外设、中断控制器和CPU。各个外设提供irq request line,在发生中断事件的时候,通过irq request line上的电气信号向CPU系统请求处理。外设的irq request line太多,CPU需要一个小伙伴帮他,这就是Interrupt controller。Interrupt Controller是连接外设中断系统和CPU系统的桥梁。根据外设irq request line的多少,Interrupt Controller可以级联。CPU的主要功能是运算,因此CPU并不处理中断优先级,那是Interrupt controller的事情。对于CPU而言,一般有两种中断请求,例如:对于ARM,是IRQ和FIQ信号线,分别让ARM进入IRQ mode和FIQ mode。对于X86,有可屏蔽中断和不可屏蔽中断。

本章节不是描述具体的硬件,而是使用了HW block这样的概念。例如CPU HW block是只ARM core或者X86这样的实际硬件block的一个逻辑描述,实际中,可能是任何可能的CPU block。
1、HW中断系统的逻辑block图

我对HW中断系统之逻辑block diagram的理解如下图所示:
在这里插入图片描述
系统中有若干个CPU block用来接收中断事件并进行处理,若干个Interrupt controller形成树状的结构,汇集系统中所有外设的irq request line,并将中断事件分发给某一个CPU block进行处理。从接口层面看,主要有两类接口,一种是中断接口。有的实现中,具体中断接口的形态就是一个硬件的信号线,通过电平信号传递中断事件(ARM以及GIC组成的中断系统就是这么设计的)。有些系统采用了其他的方法来传递中断事件,比如x86+APIC(Advanced Programmable Interrupt Controller)组成的系统,每个x86的核有一个Local APIC,这些Local APIC们通过ICC(Interrupt Controller Communication)bus连接到IO APIC上。IO APIC收集各个外设的中断,并翻译成总线上的message,传递给某个CPU上的Local APIC。因此,上面的红色线条也是逻辑层面的中断信号,可能是实际的PCB上的铜线(或者SOC内部的铜线),也可能是一个message而已。除了中断接口,CPU和Interrupt Controller之间还需要有控制信息的交流。Interrupt Controller会开放一些寄存器让CPU访问、控制。

增加
如果7a桥片的中断通过ht总线上报到cpu,那么龙芯3a4000上就算只有一个gic,如下图
在这里插入图片描述
但是通过ht总线上报中断的方式不太行,一会解释为什么。

所以3a4000默认用扩展io的方式来反馈7a桥片上的中断给cpu,那么3a4000相当于有两个gic了。一个固有的,一个扩展io的。请注意,扩展io的gic的输入是7a桥片中断源,输出直接连接到了core的ip0-ip3.所以说扩展io是一个gic没什么问题。
在这里插入图片描述
截图没截全,具体看手册

扩展 IO 中断与传统 HT 中断处理的区别

传统的 HT 中断处理方式下,HT 中断由 HT 控制器进行内部处理,直接映射到 HT 配置
寄存器上的 256 个中断向量,再由 256 个中断向量分组产生 4 个或 8 个中断,再路由至各个
不同的处理器核。由于采用的是传统的中断线连接,不能直接产生跨片中断,由此所有的 HT
IO 中断都只能直接由单个芯片进行处理。另一方面,芯片内硬件分发的中断只是以最终的 4
个或 8 个中断为单位,不能按位处理,由此导致硬件中断分发不好用的问题。
扩展 IO 中断方式,HT 中断由 HT 控制器直接发给芯片的中断控制器进行处理,中断控
制器能直接得到 256 位中断,而不是之前的 4 个或 8 个中断,这 256 位中断每一位都可以独
立路由,独立分发,而且可以实现跨片的分发及轮转。
使用扩展 IO 中断之后,软件处理上与使用传统的 HT 中断稍有不同。
传统的 HT 中断处理时,
内核直接到 HT 控制器的中断向量(一般为 0x90000efdfb000080)
上进行查找,然后按位进行处理,此时无论路由模式如何配置,都是直接读到 HT 控制器上
的所有中断。
使用扩展 IO 中断之后,内核直接到扩展 IO 状态寄存器(配置空间 0x1800)上读取中
断状态进行处理,每个核只会读到中断自己的中断状态并进行处理,不同核之间不会产生干
扰。

2、多个Interrupt controller和多个cpu之间的拓扑结构

Interrupt controller有的是支持多个CPU core的(例如GIC、APIC等),有的不支持(例如S3C2410的中断控制器,X86平台的PIC等)。如果硬件平台中只有一个GIC的话,那么通过控制该GIC的寄存器可以将所有的外设中断,分发给连接在该interrupt controller上的CPU。如果有多个GIC呢(或者级联的interrupt controller都支持multi cpu core)?假设我们要设计一个非常复杂的系统,系统中有8个CPU,有2000个外设中断要处理,这时候你如何设计系统中的interrupt controller?如果使用GIC的话,我们需要两个GIC(一个GIC最多支持1024个中断源),一个是root GIC,另外一个是secondary GIC。这时候,你有两种方案:

(1)把8个cpu都连接到root GIC上,secondary GIC不接CPU。这时候原本挂接在secondary GIC的外设中断会输出到某个cpu,现在,只能是(通过某个cpu interface的irq signal)输到root GIC的某个SPI上。对于软件而言,这是一个比较简单的设计,secondary GIC的cpu interface的设定是固定不变的,永远是从一个固定的CPU interface输出到root GIC。这种方案的坏处是:这时候secondary GIC的PPI和SGI都是没有用的了。此外,在这种设定下,所有连接在secondary GIC上的外设中断要送达的target CPU是统一处理的,要么送去cpu0,要么cpu 5,不能单独控制。

(2)当然,你也可以让每个GIC分别连接4个CPU core,root GIC连接CPU0~CPU3,secondary GIC连接CPU4~CPU7。这种状态下,连接在root GIC的中断可以由CPU0~CPU3分担处理,连接在secondary GIC的中断可以由CPU4~CPU7分担处理。但这样,在中断处理方面看起来就体现不出8核的威力了。

注:上一节中的逻辑block示意图采用的就是方案一。

3、Interrupt controller把中断事件送给哪个CPU?

毫无疑问,只有支持multi cpu core的中断控制器才有这种幸福的烦恼。一般而言,中断控制器可以把中断事件上报给一个CPU或者一组CPU(包括广播到所有的CPU上去)。对于外设类型的中断,当然是送到一个cpu上就OK了,我看不出来要把这样的中断送给多个CPU进行处理的必要性。如果送达了多个cpu,实际上,也应该只有一个handler实际和外设进行交互,另外一个cpu上的handler的动作应该是这样的:发现该irq number对应的中断已经被另外一个cpu处理了,直接退出handler,返回中断现场。IPI的中断不存在这个限制,IPI更像一个CPU之间通信的机制,对这种中断广播应该是毫无压力。

实际上,从用户的角度看,其需求是相当复杂的,我们的目标可能包括:

(1)让某个IRQ number的中断由某个特定的CPU处理

(2)让某个特定的中断由几个CPU轮流处理

……

当然,具体的需求可能更加复杂,但是如何区分软件和硬件的分工呢?让硬件处理那么复杂的策略其实是不合理的,复杂的逻辑如果由硬件实现,那么就意味着更多的晶体管,更多的功耗。因此,最普通的做法就是为Interrupt Controller支持的每一个中断设定一个target cpu的控制接口(当然应该是以寄存器形式出现,对于GIC,这个寄存器就是Interrupt processor target register)。系统有多个cpu,这个控制接口就有多少个bit,每个bit代表一个CPU。如果该bit设定为1,那么该interrupt就上报给该CPU,如果为0,则不上报给该CPU。这样的硬件逻辑比较简单,剩余的控制内容就交给软件好了。例如如果系统有两个cpu core,某中断想轮流由两个CPU处理。那么当CPU0相应该中断进入interrupt handler的时候,可以将Interrupt processor target register中本CPU对应的bit设定为0,另外一个CPU的bit设定为1。这样,在下次中断发生的时候,interupt controller就把中断送给了CPU1。对于CPU1而言,在执行该中断的handler的时候,将Interrupt processor target register中CPU0的bit为设置为1,disable本CPU的比特位,这样在下次中断发生的时候,interupt controller就把中断送给了CPU0。这样软件控制的结果就是实现了特定中断由2个CPU轮流处理的算法。

4、更多的思考

面对这个HW中断系统之逻辑block diagram,我们其实可以提出更多的问题:

(1)中断控制器发送给CPU的中断是否可以收回?重新分发给另外一个CPU?

(2)系统中的中断如何分发才能获得更好的性能呢?

(3)中断分发的策略需要考虑哪些因素呢?

……

很多问题其实我也没有答案,慢慢思考,慢慢逼近真相吧。

二、中断子系统相关的软件框架

linux kernel的中断子系统相关的软件框架图如下所示:
在这里插入图片描述
由上面的block图,我们可知linux kernel的中断子系统分成4个部分:

(1)硬件无关的代码,我们称之Linux kernel通用中断处理模块。无论是哪种CPU,哪种controller,其中断处理的过程都有一些相同的内容,这些相同的内容被抽象出来,和HW无关。此外,各个外设的驱动代码中,也希望能用一个统一的接口实现irq相关的管理(不和具体的中断硬件系统以及CPU体系结构相关)这些“通用”的代码组成了linux kernel interrupt subsystem的核心部分。(kernel/irq/)

2)CPU architecture相关的中断处理。 和系统使用的具体的CPU architecture相关。
龙心3a4000为例:arch/mips/*/各种irq相关的代码

3)Interrupt controller驱动代码 。和系统使用的Interrupt controller相关。
driver/irqchip/ :irq-loongson-liointc.c是3a4000的gic驱动
irq-loongson-liointc.c是3a4000的扩展io的gic驱动
irq-loongson-liointc.c是7a1000桥片gic驱动
irq-loongson-liointc.c和irq-loongson-liointc.c 。lpc的中断和pcie的msi也单独弄的驱动,对应一个domain
这目录下还有loongarch,2k等等的龙芯gic驱动,不说了。
4)普通外设的驱动。这些驱动将使用Linux kernel通用中断处理模块的API来实现自己的驱动逻辑。
这些api就分布在各个模块中了,例如request_irq它是一个封装,定义在include下,本质调用kernel/irq下的api,platform_get_irq是platform子系统的封装api。等等吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_kerneler

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值