Linuxkernel的中断子系统_第1页
Linuxkernel的中断子系统_第2页
Linuxkernel的中断子系统_第3页
Linuxkernel的中断子系统_第4页
Linuxkernel的中断子系统_第5页
已阅读5页,还剩100页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Linuxkernel的中断子系统之(一):综述一、前言一个合格的linux驱动工程师需要对kernel中的中断子系统有深刻的理解,只有这样,在写具体driver的时候才能:1、正确的使用linuxkernel提供的的API,例如最著名的request_threaded_irq(request_irq)接口2、正确使用同步机制保护驱动代码中的临界区3、正确的使用kernel提供的softirq、tasklet、workqueue等机制来完成具体的中断处理基于上面的原因,我希望能够通过一系列的文档来描述清楚linuxkernel中的中断子系统方方面面的知识。一方面是整理自己的思绪,另外一方面,希望能够对其他的驱动工程师(或者想从事linux驱动工作的工程师)有所帮助。二、中断系统相关硬件描述中断硬件系统主要有三种器件参与,各个外设、中断控制器和CPU。各个外设提供irqrequestline,在发生中断事件的时候,通过irqrequestline上的电气信号向CPU系统请求处理。外设的irqrequestline太多,CPU需要一个小伙伴帮他,这就是Interruptcontroller。InterruptController是连接外设中断系统和CPU系统的桥梁。根据外设irqrequestline的多少,InterruptController可以级联。CPU的主要功能是运算,因此CPU并不处理中断优先级,那是Interruptcontroller的事情。对于CPU而言,一般有两种中断请求,例如:对于ARM,是IRQ和FIQ信号线,分别让ARM进入IRQmode和FIQmode。对于X86,有可屏蔽中断和不可屏蔽中断。本章节不是描述具体的硬件,而是使用了HWblock这样的概念。例如CPUHWblock是只ARMcore或者X86这样的实际硬件block的一个逻辑描述,实际中,可能是任何可能的CPUblock。1、HW中断系统的逻辑block图我对HW中断系统之逻辑blockdiagram的理解如下图所示:系统中有若干个CPUblock用来接收中断事件并进行处理,若干个Interruptcontroller形成树状的结构,汇集系统中所有外设的irqrequestline,并将中断事件分发给某一个CPUblock进行处理。从接口层面看,主要有两类接口,一种是中断接口。有的实现中,具体中断接口的形态就是一个硬件的信号线,通过电平信号传递中断事件(ARM以及GIC组成的中断系统就是这么设计的)。有些系统采用了其他的方法来传递中断事件,比如x86+APIC(AdvancedProgrammableInterruptController)组成的系统,每个x86的核有一个LocalAPIC,这些LocalAPIC们通过ICC(InterruptControllerCommunication)bus连接到IOAPIC上。IOAPIC收集各个外设的中断,并翻译成总线上的message,传递给某个CPU上的LocalAPIC。因此,上面的红色线条也是逻辑层面的中断信号,可能是实际的PCB上的铜线(或者SOC内部的铜线),也可能是一个message而已。除了中断接口,CPU和InterruptController之间还需要有控制信息的交流。InterruptController会开放一些寄存器让CPU访问、控制。2、多个Interruptcontroller和多个cpu之间的拓扑结构Interruptcontroller有的是支持多个CPUcore的(例如GIC、APIC等),有的不支持(例如S3C2410的中断控制器,X86平台的PIC等)。如果硬件平台中只有一个GIC的话,那么通过控制该GIC的寄存器可以将所有的外设中断,分发给连接在该interruptcontroller上的CPU。如果有多个GIC呢(或者级联的interruptcontroller都支持multicpucore)?假设我们要设计一个非常复杂的系统,系统中有8个CPU,有2000个外设中断要处理,这时候你如何设计系统中的interruptcontroller?如果使用GIC的话,我们需要两个GIC(一个GIC最多支持1024个中断源),一个是rootGIC,另外一个是secondaryGIC。这时候,你有两种方案:(1)把8个cpu都连接到rootGIC上,secondaryGIC不接CPU。这时候原本挂接在secondaryGIC的外设中断会输出到某个cpu,现在,只能是(通过某个cpuinterface的irqsignal)输到rootGIC的某个SPI上。对于软件而言,这是一个比较简单的设计,secondaryGIC的cpuinterface的设定是固定不变的,永远是从一个固定的CPUinterface输出到rootGIC。这种方案的坏处是:这时候secondaryGIC的PPI和SGI都是没有用的了。此外,在这种设定下,所有连接在secondaryGIC上的外设中断要送达的targetCPU是统一处理的,要么送去cpu0,要么cpu5,不能单独控制。(2)当然,你也可以让每个GIC分别连接4个CPUcore,rootGIC连接CPU0~CPU3,secondaryGIC连接CPU4~CPU7。这种状态下,连接在rootGIC的中断可以由CPU0~CPU3分担处理,连接在secondaryGIC的中断可以由CPU4~CPU7分担处理。但这样,在中断处理方面看起来就体现不出8核的威力了。注:上一节中的逻辑block示意图采用的就是方案一。3、Interruptcontroller把中断事件送给哪个CPU?毫无疑问,只有支持multicpucore的中断控制器才有这种幸福的烦恼。一般而言,中断控制器可以把中断事件上报给一个CPU或者一组CPU(包括广播到所有的CPU上去)。对于外设类型的中断,当然是送到一个cpu上就OK了,我看不出来要把这样的中断送给多个CPU进行处理的必要性。如果送达了多个cpu,实际上,也应该只有一个handler实际和外设进行交互,另外一个cpu上的handler的动作应该是这样的:发现该irqnumber对应的中断已经被另外一个cpu处理了,直接退出handler,返回中断现场。IPI的中断不存在这个限制,IPI更像一个CPU之间通信的机制,对这种中断广播应该是毫无压力。实际上,从用户的角度看,其需求是相当复杂的,我们的目标可能包括:(1)让某个IRQnumber的中断由某个特定的CPU处理(2)让某个特定的中断由几个CPU轮流处理……当然,具体的需求可能更加复杂,但是如何区分软件和硬件的分工呢?让硬件处理那么复杂的策略其实是不合理的,复杂的逻辑如果由硬件实现,那么就意味着更多的晶体管,更多的功耗。因此,最普通的做法就是为InterruptController支持的每一个中断设定一个targetcpu的控制接口(当然应该是以寄存器形式出现,对于GIC,这个寄存器就是Interruptprocessortargetregister)。系统有多个cpu,这个控制接口就有多少个bit,每个bit代表一个CPU。如果该bit设定为1,那么该interrupt就上报给该CPU,如果为0,则不上报给该CPU。这样的硬件逻辑比较简单,剩余的控制内容就交给软件好了。例如如果系统有两个cpucore,某中断想轮流由两个CPU处理。那么当CPU0相应该中断进入interrupthandler的时候,可以将Interruptprocessortargetregister中本CPU对应的bit设定为0,另外一个CPU的bit设定为1。这样,在下次中断发生的时候,interuptcontroller就把中断送给了CPU1。对于CPU1而言,在执行该中断的handler的时候,将Interruptprocessortargetregister中CPU0的bit为设置为1,disable本CPU的比特位,这样在下次中断发生的时候,interuptcontroller就把中断送给了CPU0。这样软件控制的结果就是实现了特定中断由2个CPU轮流处理的算法。4、更多的思考面对这个HW中断系统之逻辑blockdiagram,我们其实可以提出更多的问题:(1)中断控制器发送给CPU的中断是否可以收回?重新分发给另外一个CPU?(2)系统中的中断如何分发才能获得更好的性能呢?(3)中断分发的策略需要考虑哪些因素呢?……很多问题其实我也没有答案,慢慢思考,慢慢逼近真相吧。二、中断子系统相关的软件框架linuxkernel的中断子系统相关的软件框架图如下所示:由上面的block图,我们可知linuxkernel的中断子系统分成4个部分:(1)硬件无关的代码,我们称之Linuxkernel通用中断处理模块。无论是哪种CPU,哪种controller,其中断处理的过程都有一些相同的内容,这些相同的内容被抽象出来,和HW无关。此外,各个外设的驱动代码中,也希望能用一个统一的接口实现irq相关的管理(不和具体的中断硬件系统以及CPU体系结构相关)这些“通用”的代码组成了linuxkernelinterruptsubsystem的核心部分。(2)CPUarchitecture相关的中断处理。和系统使用的具体的CPUarchitecture相关。(3)Interruptcontroller驱动代码。和系统使用的Interruptcontroller相关。(4)普通外设的驱动。这些驱动将使用Linuxkernel通用中断处理模块的API来实现自己的驱动逻辑。Linuxkernel的中断子系统之(二):IRQDomain介绍一、概述在linuxkernel中,我们使用下面两个ID来标识一个来自外设的中断:1、IRQnumber。CPU需要为每一个外设中断编号,我们称之IRQNumber。这个IRQnumber是一个虚拟的interruptID,和硬件无关,仅仅是被CPU用来标识一个外设中断。2、HWinterruptID。对于interruptcontroller而言,它收集了多个外设的interruptrequestline并向上传递,因此,interruptcontroller需要对外设中断进行编码。Interruptcontroller用HWinterruptID来标识外设的中断。在interruptcontroller级联的情况下,仅仅用HWinterruptID已经不能唯一标识一个外设中断,还需要知道该HWinterruptID所属的interruptcontroller(HWinterruptID在不同的Interruptcontroller上是会重复编码的)。这样,CPU和interruptcontroller在标识中断上就有了一些不同的概念,但是,对于驱动工程师而言,我们和CPU视角是一样的,我们只希望得到一个IRQnumber,而不关系具体是那个interruptcontroller上的那个HWinterruptID。这样一个好处是在中断相关的硬件发生变化的时候,驱动软件不需要修改。因此,linuxkernel中的中断子系统需要提供一个将HWinterruptID映射到IRQnumber上来的机制,这就是本文主要的内容。二、历史关于HWinterruptID映射到IRQnumber上这事,在过去系统只有一个interruptcontroller的时候还是很简单的,中断控制器上实际的HWinterruptline的编号可以直接变成IRQnumber。例如我们大家都熟悉的SOC内嵌的interruptcontroller,这种controller多半有中断状态寄存器,这个寄存器可能有64个bit(也可能更多),每个bit就是一个IRQnumber,可以直接进行映射。这时候,GPIO的中断在中断控制器的状态寄存器中只有一个bit,因此所有的GPIO中断只有一个IRQnumber,在该通用GPIO中断的irqhandler中进行deduplex,将各个具体的GPIO中断映射到其相应的IRQnumber上。如果你是一个足够老的工程师,应该是经历过这个阶段的。随着linuxkernel的发展,将interruptcontroller抽象成irqchip这个概念越来越流行,甚至GPIOcontroller也可以被看出一个interruptcontrollerchip,这样,系统中至少有两个中断控制器了,一个传统意义的中断控制器,一个是GPIOcontrollertype的中断控制器。随着系统复杂度加大,外设中断数据增加,实际上系统可以需要多个中断控制器进行级联,面对这样的趋势,linuxkernel工程师如何应对?答案就是irqdomain这个概念。我们听说过很多的domain,powerdomain,clockdomain等等,所谓domain,就是领域,范围的意思,也就是说,任何的定义出了这个范围就没有意义了。系统中所有的interruptcontroller会形成树状结构,对于每个interruptcontroller都可以连接若干个外设的中断请求(我们称之interruptsource),interruptcontroller会对连接其上的interruptsource(根据其在Interruptcontroller中物理特性)进行编号(也就是HWinterruptID了)。但这个编号仅仅限制在本interruptcontroller范围内。三、接口1、向系统注册irqdomain具体如何进行映射是interruptcontroller自己的事情,不过,有软件架构思想的工程师更愿意对形形色色的interruptcontroller进行抽象,对如何进行HWinterruptID到IRQnumber映射关系上进行进一步的抽象。因此,通用中断处理模块中有一个irqdomain的子模块,该模块将这种映射关系分成了三类:(1)线性映射。其实就是一个lookuptable,HWinterruptID作为index,通过查表可以获取对应的IRQnumber。对于Linearmap而言,interruptcontroller对其HWinterruptID进行编码的时候要满足一定的条件:hwID不能过大,而且ID排列最好是紧密的。对于线性映射,其接口API如下:staticinlinestructirq_domain*irq_domain_add_linear(structdevice_node*of_node,

unsignedintsize,---------该interruptdomain支持多少IRQ

conststructirq_domain_ops*ops,---callback函数

void*host_data)-----driver私有数据

{

return__irq_domain_add(of_node,size,size,0,ops,host_data);

}(2)RadixTreemap。建立一个RadixTree来维护HWinterruptID到IRQnumber映射关系。HWinterruptID作为lookupkey,在RadixTree检索到IRQnumber。如果的确不能满足线性映射的条件,可以考虑RadixTreemap。实际上,内核中使用RadixTreemap的只有powerPC和MIPS的硬件平台。对于RadixTreemap,其接口API如下:staticinlinestructirq_domain*irq_domain_add_tree(structdevice_node*of_node,

conststructirq_domain_ops*ops,

void*host_data)

{

return__irq_domain_add(of_node,0,~0,0,ops,host_data);

}(3)nomap。有些中断控制器很强,可以通过寄存器配置HWinterruptID而不是由物理连接决定的。例如PowerPC系统使用的MPIC(Multi-ProcessorInterruptController)。在这种情况下,不需要进行映射,我们直接把IRQnumber写入HWinterruptID配置寄存器就OK了,这时候,生成的HWinterruptID就是IRQnumber,也就不需要进行mapping了。对于这种类型的映射,其接口API如下:staticinlinestructirq_domain*irq_domain_add_nomap(structdevice_node*of_node,

unsignedintmax_irq,

conststructirq_domain_ops*ops,

void*host_data)

{

return__irq_domain_add(of_node,0,max_irq,max_irq,ops,host_data);

}这类接口的逻辑很简单,根据自己的映射类型,初始化structirq_domain中的各个成员,调用__irq_domain_add将该irqdomain挂入irq_domain_list的全局列表。2、为irqdomain创建映射上节的内容主要是向系统注册一个irqdomain,具体HWinterruptID和IRQnumber的映射关系都是空的,因此,具体各个irqdomain如何管理映射所需要的database还是需要建立的。例如:对于线性映射的irqdomain,我们需要建立线性映射的lookuptable,对于RadixTreemap,我们要把那个反应IRQnumber和HWinterruptID的Radixtree建立起来。创建映射有四个接口函数:(1)调用irq_create_mapping函数建立HWinterruptID和IRQnumber的映射关系。该接口函数以irqdomain和HWinterruptID为参数,返回IRQnumber(这个IRQnumber是动态分配的)。该函数的原型定义如下:externunsignedintirq_create_mapping(structirq_domain*host,

irq_hw_number_thwirq);驱动调用该函数的时候必须提供HWinterruptID,也就是意味着driver知道自己使用的HWinterruptID,而一般情况下,HWinterruptID其实对具体的driver应该是不可见的,不过有些场景比较特殊,例如GPIO类型的中断,它的HWinterruptID和GPIO有着特定的关系,driver知道自己使用那个GPIO,也就是知道使用哪一个HWinterruptID了。(2)irq_create_strict_mappings。这个接口函数用来为一组HWinterruptID建立映射。具体函数的原型定义如下:externintirq_create_strict_mappings(structirq_domain*domain,

unsignedintirq_base,

irq_hw_number_thwirq_base,intcount);(3)irq_create_of_mapping。看到函数名字中的of(openfirmware),我想你也可以猜到了几分,这个接口当然是利用devicetree进行映射关系的建立。具体函数的原型定义如下:externunsignedintirq_create_of_mapping(structof_phandle_args*irq_data);通常,一个普通设备的devicetreenode已经描述了足够的中断信息,在这种情况下,该设备的驱动在初始化的时候可以调用irq_of_parse_and_map这个接口函数进行该devicenode中和中断相关的内容(interrupts和interrupt-parent属性)进行分析,并建立映射关系,具体代码如下:unsignedintirq_of_parse_and_map(structdevice_node*dev,intindex)

{

structof_phandle_argsoirq;if(of_irq_parse_one(dev,index,&oirq))----分析devicenode中的interrupt相关属性

return0;returnirq_create_of_mapping(&oirq);-----创建映射,并返回对应的IRQnumber

}对于一个使用Devicetree的普通驱动程序(我们推荐这样做),基本上初始化需要调用irq_of_parse_and_map获取IRQnumber,然后调用request_threaded_irq申请中断handler。(4)irq_create_direct_mapping。这是给nomap那种类型的interruptcontroller使用的,这里不再赘述。四、数据结构描述1、irqdomain的callback接口structirq_domain_ops抽象了一个irqdomain的callback函数,定义如下:structirq_domain_ops{

int(*match)(structirq_domain*d,structdevice_node*node);

int(*map)(structirq_domain*d,unsignedintvirq,irq_hw_number_thw);

void(*unmap)(structirq_domain*d,unsignedintvirq);

int(*xlate)(structirq_domain*d,structdevice_node*node,

constu32*intspec,unsignedintintsize,

unsignedlong*out_hwirq,unsignedint*out_type);

};我们先看xlate函数,语义是翻译(translate)的意思,那么到底翻译什么呢?在DTS文件中,各个使用中断的devicenode会通过一些属性(例如interrupts和interrupt-parent属性)来提供中断信息给kernel以便kernel可以正确的进行driver的初始化动作。这里,interrupts属性所表示的interruptspecifier只能由具体的interruptcontroller(也就是irqdomain)来解析。而xlate函数就是将指定的设备(node参数)上若干个(intsize参数)中断属性(intspec参数)翻译成HWinterruptID(out_hwirq参数)和trigger类型(out_type)。match是判断一个指定的interruptcontroller(node参数)是否和一个irqdomain匹配(d参数),如果匹配的话,返回1。实际上,内核中很少定义这个callback函数,实际上structirq_domain中有一个of_node指向了对应的interruptcontroller的devicenode,因此,如果不提供该函数,那么default的匹配函数其实就是判断irqdomain的of_node成员是否等于传入的node参数。map和unmap是操作相反的函数,我们描述其中之一就OK了。调用map函数的时机是在创建(或者更新)HWinterruptID(hw参数)和IRQnumber(virq参数)关系的时候。其实,从发生一个中断到调用该中断的handler仅仅调用一个request_threaded_irq是不够的,还需要针对该irqnumber设定:(1)设定该IRQnumber对应的中断描述符(structirq_desc)的irqchip(2)设定该IRQnumber对应的中断描述符的highlevelirq-eventshandler(3)设定该IRQnumber对应的中断描述符的irqchipdata这些设定不适合由具体的硬件驱动来设定,因此在Interruptcontroller,也就是irqdomain的callback函数中设定。2、irqdomain在内核中,irqdomain的概念由structirq_domain表示:structirq_domain{

structlist_headlink;

constchar*name;

conststructirq_domain_ops*ops;----callback函数

void*host_data;/*Optionaldata*/

structdevice_node*of_node;----该interruptdomain对应的interruptcontroller的devicenode

structirq_domain_chip_generic*gc;---genericirqchip的概念,本文暂不描述/*reversemapdata.Thelinearmapgetsappendedtotheirq_domain*/

irq_hw_number_thwirq_max;----该domain中最大的那个HWinterruptID

unsignedintrevmap_direct_max_irq;----

unsignedintrevmap_size;---线性映射的size,forRadixTreemap和nomap,该值等于0

structradix_tree_rootrevmap_tree;----RadixTreemap使用到的radixtreerootnode

unsignedintlinear_revmap[];-----线性映射使用的lookuptable

};linux内核中,所有的irqdomain被挂入一个全局链表,链表头定义如下:staticLIST_HEAD(irq_domain_list);structirq_domain中的link成员就是挂入这个队列的节点。通过irq_domain_list这个指针,可以获取整个系统中HWinterruptID和IRQnumber的mappingDB。host_data定义了底层interruptcontroller使用的私有数据,和具体的interruptcontroller相关(对于GIC,该指针指向一个structgic_chip_data数据结构)。对于线性映射:(1)linear_revmap保存了一个线性的lookuptable,index是HWinterruptID,table中保存了IRQnumber值(2)revmap_size等于线性的lookuptable的size。(3)hwirq_max保存了最大的HWinterruptID(4)revmap_direct_max_irq没有用,设定为0。revmap_tree没有用。对于RadixTreemap:(1)linear_revmap没有用,revmap_size等于0。(2)hwirq_max没有用,设定为一个最大值。(3)revmap_direct_max_irq没有用,设定为0。(4)revmap_tree指向Radixtree的rootnode。五、中断相关的DeviceTree知识回顾想要进行映射,首先要了解interruptcontroller的拓扑结构。系统中的interruptcontroller的拓扑结构以及其interruptrequestline的分配情况(分配给哪一个具体的外设)都在DeviceTreeSource文件中通过下面的属性给出了描述。这些内容在DeviceTree的三份文档中给出了一些描述,这里简单总结一下:对于那些产生中断的外设,我们需要定义interrupt-parent和interrupts属性:(1)interrupt-parent。表明该外设的interruptrequestline物理的连接到了哪一个中断控制器上(2)interrupts。这个属性描述了具体该外设产生的interrupt的细节信息(也就是传说中的interruptspecifier)。例如:HWinterruptID(由该外设的devicenode中的interrupt-parent指向的interruptcontroller解析)、interrupt触发类型等。对于Interruptcontroller,我们需要定义interrupt-controller和#interrupt-cells的属性:(1)interrupt-controller。表明该devicenode就是一个中断控制器(2)#interrupt-cells。该中断控制器用多少个cell(一个cell就是一个32-bit的单元)描述一个外设的interruptrequestline。?具体每个cell表示什么样的含义由interruptcontroller自己定义。(3)interrupts和interrupt-parent。对于那些不是root的interruptcontroller,其本身也是作为一个产生中断的外设连接到其他的interruptcontroller上,因此也需要定义interrupts和interrupt-parent的属性。六、MappingDB的建立1、概述系统中HWinterruptID和IRQnumber的mappingDB是在整个系统初始化的过程中建立起来的,过程如下:(1)DTS文件描述了系统中的interruptcontroller以及外设IRQ的拓扑结构,在linuxkernel启动的时候,由bootloader传递给kernel(实际传递的是DTB)。(2)在DeviceTree初始化的时候,形成了系统内所有的devicenode的树状结构,当然其中包括所有和中断拓扑相关的数据结构(所有的interruptcontroller的node和使用中断的外设node)(3)在machinedriver初始化的时候会调用of_irq_init函数,在该函数中会扫描所有interruptcontroller的节点,并调用适合的interruptcontrollerdriver进行初始化。毫无疑问,初始化需要注意顺序,首先初始化root,然后firstlevel,secondlevel,最好是leafnode。在初始化的过程中,一般会调用上节中的接口函数向系统增加irqdomain。有些interruptcontroller会在其driver初始化的过程中创建映射(4)在各个driver初始化的过程中,创建映射2、interruptcontroller初始化的过程中,注册irqdomain我们以GIC的代码为例。具体代码在gic_of_init->gic_init_bases中,如下:void__initgic_init_bases(unsignedintgic_nr,intirq_start,

void__iomem*dist_base,void__iomem*cpu_base,

u32percpu_offset,structdevice_node*node)

{

irq_hw_number_thwirq_base;

structgic_chip_data*gic;

intgic_irqs,irq_base,i;……

对于rootGIC

hwirq_base=16;

gic_irqs=系统支持的所有的中断数目-16。之所以减去16主要是因为rootGIC的0~15号HWinterrupt是forIPI的,因此要去掉。也正因为如此hwirq_base从16开始

irq_base=irq_alloc_descs(irq_start,16,gic_irqs,numa_node_id());申请gic_irqs个IRQ资源,从16号开始搜索IRQnumber。由于是rootGIC,申请的IRQ基本上会从16号开始

gic->domain=irq_domain_add_legacy(node,gic_irqs,irq_base,

hwirq_base,&gic_irq_domain_ops,gic);---向系统注册irqdomain并创建映射……

}很遗憾,在GIC的代码中没有调用标准的注册irqdomain的接口函数。要了解其背后的原因,我们需要回到过去。在旧的linuxkernel中,ARM体系结构的代码不甚理想。在arch/arm目录充斥了很多boardspecific的代码,其中定义了很多具体设备相关的静态表格,这些表格规定了各个device使用的资源,当然,其中包括IRQ资源。在这种情况下,各个外设的IRQ是固定的(如果作为驱动程序员的你足够老的话,应该记得很长篇幅的针对IRQnumber的宏定义),也就是说,HWinterruptID和IRQnumber的关系是固定的。一旦关系固定,我们就可以在interuptcontroller的代码中创建这些映射关系。具体代码如下:structirq_domain*irq_domain_add_legacy(structdevice_node*of_node,

unsignedintsize,

unsignedintfirst_irq,

irq_hw_number_tfirst_hwirq,

conststructirq_domain_ops*ops,

void*host_data)

{

structirq_domain*domain;domain=__irq_domain_add(of_node,first_hwirq+size,----注册irqdomain

first_hwirq+size,0,ops,host_data);

if(!domain)

returnNULL;irq_domain_associate_many(domain,first_irq,first_hwirq,size);---创建映射returndomain;

}这时候,对于这个版本的GICdriver而言,初始化之后,HWinterruptID和IRQnumber的映射关系已经建立,保存在线性lookuptable中,size等于GIC支持的中断数目,具体如下:index0~15对应的IRQ无效16号IRQ<>16号HWinterruptID17号IRQ<>17号HWinterruptID……如果想充分发挥DeviceTree的威力,3.14版本中的GIC代码需要修改。3、在各个硬件外设的驱动初始化过程中,创建HWinterruptID和IRQnumber的映射关系我们上面的描述过程中,已经提及:设备的驱动在初始化的时候可以调用irq_of_parse_and_map这个接口函数进行该devicenode中和中断相关的内容(interrupts和interrupt-parent属性)进行分析,并建立映射关系,具体代码如下:unsignedintirq_of_parse_and_map(structdevice_node*dev,intindex)

{

structof_phandle_argsoirq;if(of_irq_parse_one(dev,index,&oirq))----分析devicenode中的interrupt相关属性

return0;returnirq_create_of_mapping(&oirq);-----创建映射

}我们再来看看irq_create_of_mapping函数如何创建映射:unsignedintirq_create_of_mapping(structof_phandle_args*irq_data)

{

structirq_domain*domain;

irq_hw_number_thwirq;

unsignedinttype=IRQ_TYPE_NONE;

unsignedintvirq;domain=irq_data->np?irq_find_host(irq_data->np):irq_default_domain;--A

if(!domain){

return0;

}

if(domain->ops->xlate==NULL)--------------B

hwirq=irq_data->args[0];

else{

if(domain->ops->xlate(domain,irq_data->np,irq_data->args,----C

irq_data->args_count,&hwirq,&type))

return0;

}/*Createmapping*/

virq=irq_create_mapping(domain,hwirq);--------D

if(!virq)

returnvirq;/*Settypeifspecifiedanddifferentthanthecurrentone*/

if(type!=IRQ_TYPE_NONE&&

type!=irq_get_trigger_type(virq))

irq_set_irq_type(virq,type);---------E

returnvirq;

}A:这里的代码主要是找到irqdomain。这是根据传递进来的参数irq_data的np成员来寻找的,具体定义如下:structof_phandle_args{

structdevice_node*np;---指向了外设对应的interruptcontroller的devicenode

intargs_count;-------该外设定义的interrupt相关属性的个数

uint32_targs[MAX_PHANDLE_ARGS];----具体的interrupt相当属性的定义

};B:如果没有定义xlate函数,那么取interrupts属性的第一个cell作为HWinterruptID。C:解铃还需系铃人,interrupts属性最好由interruptcontroller(也就是irqdomain)解释。如果xlate函数能够完成属性解析,那么将输出参数hwirq和type,分别表示HWinterruptID和interupttype(触发方式等)。D:解析完了,最终还是要调用irq_create_mapping函数来创建HWinterruptID和IRQnumber的映射关系。E:如果有需要,调用irq_set_irq_type函数设定triggertypeirq_create_mapping函数建立HWinterruptID和IRQnumber的映射关系。该接口函数以irqdomain和HWinterruptID为参数,返回IRQnumber。具体的代码如下:unsignedintirq_create_mapping(structirq_domain*domain,

irq_hw_number_thwirq)

{

unsignedinthint;

intvirq;如果映射已经存在,那么不需要映射,直接返回

virq=irq_find_mapping(domain,hwirq);

if(virq){

returnvirq;

}

hint=hwirq%nr_irqs;-------分配一个IRQ描述符以及对应的irqnumber

if(hint==0)

hint++;

virq=irq_alloc_desc_from(hint,of_node_to_nid(domain->of_node));

if(virq<=0)

virq=irq_alloc_desc_from(1,of_node_to_nid(domain->of_node));

if(virq<=0){

pr_debug("->virqallocationfailed\n");

return0;

}if(irq_domain_associate(domain,virq,hwirq)){---建立mapping

irq_free_desc(virq);

return0;

}returnvirq;

}对于分配中断描述符这段代码,后续的文章会详细描述。这里简单略过,反正,指向完这段代码,我们就可以或者一个IRQnumber以及其对应的中断描述符了。程序注释中没有使用IRQnumber而是使用了virtualinterruptnumber这个术语。virtualinterruptnumber还是重点理解“virtual”这个词,所谓virtual,其实就是说和具体的硬件连接没有关系了,仅仅是一个number而已。具体建立映射的函数是irq_domain_associate函数,代码如下:intirq_domain_associate(structirq_domain*domain,unsignedintvirq,

irq_hw_number_thwirq)

{

structirq_data*irq_data=irq_get_irq_data(virq);

intret;mutex_lock(&irq_domain_mutex);

irq_data->hwirq=hwirq;

irq_data->domain=domain;

if(domain->ops->map){

ret=domain->ops->map(domain,virq,hwirq);---调用irqdomain的mapcallback函数

}if(hwirq<domain->revmap_size){

domain->linear_revmap[hwirq]=virq;----填写线性映射lookuptable的数据

}else{

mutex_lock(&revmap_trees_mutex);

radix_tree_insert(&domain->revmap_tree,hwirq,irq_data);--向radixtree插入一个node

mutex_unlock(&revmap_trees_mutex);

}

mutex_unlock(&irq_domain_mutex);irq_clear_status_flags(virq,IRQ_NOREQUEST);---该IRQ已经可以申请了,因此clear相关flagreturn0;

}七、将HWinterruptID转成IRQnumber创建了庞大的HWinterruptID到IRQnumber的mappingDB,最终还是要使用。具体的使用场景是在CPU相关的处理函数中,程序会读取硬件interruptID,并转成IRQnumber,调用对应的irqeventhandler。在本章中,我们以一个级联的GIC系统为例,描述转换过程1、GICdriver初始化上面已经描述了rootGIC的的初始化,我们再来看看secondGIC的初始化。具体代码在gic_of_init->gic_init_bases中,如下:void__initgic_init_bases(unsignedintgic_nr,intirq_start,

void__iomem*dist_base,void__iomem*cpu_base,

u32percpu_offset,structdevice_node*node)

{

irq_hw_number_thwirq_base;

structgic_chip_data*gic;

intgic_irqs,irq_base,i;……

对于secondGIC

hwirq_base=32;

gic_irqs=系统支持的所有的中断数目-32。之所以减去32主要是因为对于secondGIC,其0~15号HWinterrupt是forIPI的,因此要去掉。而16~31号HWinterrupt是forPPI的,也要去掉。也正因为如此hwirq_base从32开始

irq_base=irq_alloc_descs(irq_start,16,gic_irqs,numa_node_id());申请gic_irqs个IRQ资源,从16号开始搜索IRQnumber。由于是secondGIC,申请的IRQ基本上会从rootGIC申请的最后一个IRQ号+1开始

gic->domain=irq_domain_add_legacy(node,gic_irqs,irq_base,

hwirq_base,&gic_irq_domain_ops,gic);---向系统注册irqdomain并创建映射……

}secondGIC初始化之后,该irqdomain的HWinterruptID和IRQnumber的映射关系已经建立,保存在线性lookuptable中,size等于GIC支持的中断数目,具体如下:index0~32对应的IRQ无效rootGIC申请的最后一个IRQ号+1<>32号HWinterruptIDrootGIC申请的最后一个IRQ号+2<>33号HWinterruptID……OK,我们回到gic的初始化函数,对于secondGIC,还有其他部分的初始化内容:int__initgic_of_init(structdevice_node*node,structdevice_node*parent)

{……if(parent){

irq=irq_of_parse_and_map(node,0);--解析secondGIC的interrupts属性,并进行mapping,返回IRQnumber

gic_cascade_irq(gic_cnt,irq);---设置handler

}

……

}上面的初始化函数去掉和级联无关的代码。对于rootGIC,其传入的parent是NULL,因此不会执行级联部分的代码。对于secondGIC,它是作为其parent(rootGIC)的一个普通的irqsource,因此,也需要注册该IRQ的handler。由此可见,非root的GIC的初始化分成了两个部分:一部分是作为一个interruptcontroller,执行和rootGIC一样的初始化代码。另外一方面,GIC又作为一个普通的interruptgeneratingdevice,需要象一个普通的设备驱动一样,注册其中断handler。irq_of_parse_and_map函数相信大家已经熟悉了,这里不再描述。gic_cascade_irq函数如下:void__initgic_cascade_irq(unsignedintgic_nr,unsignedintirq)

{

if(irq_set_handler_data(irq,&gic_data[gic_nr])!=0)---设置handlerdata

BUG();

irq_set_chained_handler(irq,gic_handle_cascade_irq);---设置handler

}2、具体如何在中断处理过程中,将HWinterruptID转成IRQnumber在系统的启动过程中,经过了各个interruptcontroller以及各个外设驱动的努力,整个interrupt系统的database(将HWinterruptID转成IRQnumber的数据库,这里的数据库不是指SQLlite或者oracle这样通用数据库软件)已经建立。一旦发生硬件中断,经过CPUarchitecture相关的中断代码之后,会调用irqhandler,该函数的一般过程如下:(1)首先找到rootinterruptcontroller对应的irqdomain。(2)根据HW寄存器信息和irqdomain信息获取HWinterruptID(3)调用irq_find_mapping找到HWinterruptID对应的irqnumber(4)调用handle_IRQ(对于ARM平台)来处理该irqnumber对于级联的情况,过程类似上面的描述,但是需要注意的是在步骤4中不是直接调用该IRQ的hander来处理该irqnumber因为,这个irq需要各个interruptcontrollerlevel上的解析。举一个简单的二阶级联情况:假设系统中有两个interruptcontroller,A和B,A是rootinterruptcontroller,B连接到A的13号HWinterruptID上。在Binterruptcontroller初始化的时候,除了初始化它作为interruptcontroller的那部分内容,还有初始化它作为rootinterruptcontrollerA上的一个普通外设这部分的内容。最重要的是调用irq_set_chained_handler设定handler。这样,在上面的步骤4的时候,就会调用13号HWinterruptID对应的handler(也就是B的handler),在该handler中,会重复上面的(1)~(4)。linuxkernel的中断子系统之(三):IRQnumber和中断描述符作者:linuxer发布于:2014-8-2617:03分类:中断子系统一、前言本文主要围绕IRQnumber和中断描述符(interruptdescriptor)这两个概念描述通用中断处理过程。第二章主要描述基本概念,包括什么是IRQnumber,什么是中断描述符等。第三章描述中断描述符数据结构的各个成员。第四章描述了初始化中断描述符相关的接口API。第五章描述中断描述符相关的接口API。二、基本概念1、通用中断的代码处理示意图一个关于通用中断处理的示意图如下:在linuxkernel中,对于每一个外设的IRQ都用structirq_desc来描述,我们称之中断描述符(structirq_desc)。linuxkernel中会有一个数据结构保存了关于所有IRQ的中断描述符信息,我们称之中断描述符DB(上图中红色框图内)。当发生中断后,首先获取触发中断的HWinteruptID,然后通过irqdomain翻译成IRQnuber,然后通过IRQnumber就可以获取对应的中断描述符。调用中断描述符中的highlevelirq-eventshandler来进行中断处理就OK了。而highlevelirq-eventshandler主要进行下面两个操作:(1)调用中断描述符的底层irqchipdriver进行mask,ack等callback函数,进行interruptflowcontrol。(2)调用该中断描述符上的actionlist中的specifichandler(我们用这个术语来区分具体中断handler和highlevel的handler)。这个步骤不一定会执行,这是和中断描述符的当前状态相关,实际上,interruptflowcontrol是软件(设定一些标志位,软件根据标志位进行处理)和硬件(mask或者unmaskinterruptcontroller等)一起控制完成的。2、中断的打开和关闭我们再来看看整个通用中断处理过程中的开关中断情况,开关中断有两种:(1)开关localCPU的中断。对于UP,关闭CPU中断就关闭了一切,永远不会被抢占。对于SMP,实际上,没有关全局中断这一说,只能关闭localCPU(代码运行的那个CPU)(2)控制interruptcontroller,关闭某个IRQnumber对应的中断。更准确的术语是mask或者unmask一个IRQ。本节主要描述的是第一种,也就是控制CPU的中断。当进入highlevelhandler的时候,CPU的中断是关闭的(硬件在进入IRQprocessormode的时候设定的)。对于外设的specifichandler,旧的内核(2.6.35版本之前)认为有两种:slowhandler和fasthandle。在requestirq的时候,对于fasthandler,需要传递IRQF_DISABLED的参数,确保其中断处理过程中是关闭CPU的中断,因为是fasthandler,执行很快,即便是关闭CPU中断不会影响系统的性能。但是,并不是每一种外设中断的handler都是那么快(例如磁盘),因此就有slowhandler的概念,说明其在中断处理过程中会耗时比较长。对于这种情况,如果在整个specifichandler中关闭CPU中断,对系统的performance会有影响。因此,对于slowhandler,在从highlevelhandler转入specifichandler中间会根据IRQF_DISABLED这个flag来决定是否打开中断,具体代码如下(来自2.6.23内核):irqreturn_thandle_IRQ_event(unsignedintirq,structirqaction*action)

{

……if(!(action->flags&IRQF_DISABLED))

local_irq_enable_in_hardirq();……

}如果没有设定IRQF_DISABLED(slowhandler),则打开本CPU的中断。然而,随着软硬件技术的发展:(1)硬件方面,CPU越来越快,原来slowhandler也可以很快执行完毕(2)软件方面,linuxkernel提供了更多更好的bottomhalf的机制因此,在新的内核中,比如3.14,IRQF_DISABLED被废弃了。我们可以思考一下,为何要有slowhandler?每一个handler不都是应该迅速执行完毕,返回中断现场吗?此外,任意中断可以打断slowhandler执行,从而导致中断嵌套加深,对内核栈也是考验。因此,新的内核中在interruptspecifichandler中是全程关闭CPU中断的。

3、IRQnumber从CPU的角度看,无论外部的Interruptcontroller的结构是多么复杂,Idonotcare,我只关心发生了一个指定外设的中断,需要调用相应的外设中断的handler就OK了。更准确的说是通用中断处理模块不关心外部interruptcontroller的组织细节(电源管理模块当然要关注具体的设备(interruptcontroller也是设备)的拓扑结构)。一言以蔽之,通用中断处理模块可以用一个线性的table来管理一个个的外部中断,这个表的每个元素就是一个irq描述符,在kernel中定义如下:structirq_descirq_desc[NR_IRQS]__cacheline_aligned_in_smp={

[0...NR_IRQS-1]={

.handle_irq=handle_bad_irq,

.depth=1,

.lock=__RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),

}

};系统中每一个连接外设的中断线(irqrequestline)用一个中断描述符来描述,每一个外设的interruptrequestline分配一个中断号(irqnumber),系统中有多少个中断线(或者叫做中断源)就有多少个中断描述符(structirq_desc)。NR_IRQS定义了该硬件平台IRQ的最大数目。总之,一个静态定义的表格,irqnumber作为index,每个描述符都是紧密的排在一起,一切看起来很美好,但是现实很残酷的。有些系统可能会定义一个很大的NR_IRQS,但是只是想用其中的若干个,换句话说,这个静态定义的表格不是每个entry都是有效的,有空洞,如果使用静态定义的表格就会导致了内存很大的浪费。为什么会有这种需求?我猜是和各个interruptcontroller硬件的interruptID映射到irqnumber的算法有关。在这种情况下,静态表格不适合了,我们改用一个radixtree来保存中断描述符(HWinterrupt作为索引)。这时候,每一个中断描述符都是动态分配,然后插入到radixtree中。如果你的系统采用这种策略,那么需要打开CONFIG_SPARSE_IRQ选项。上面的示意图描述的是静态表格的中断描述符DB,如果打开CONFIG_SPARSE_IRQ选项,系统使用Radixtree来保存中断描述符DB,不过概念和静态表格是类似的。此外,需要注意的是,在旧内核中,IRQnumber和硬件的连接有一定的关系,但是,在引入irqdomain后,IRQnumber已经变成一个单纯的number,和硬件没有任何关系。三、中断描述符数据结构1、底层irqchip相关的数据结构中断描述符中应该会包括底层irqchip相关的数据结构,linuxkernel中把这些数据组织在一起,形成structirq_data,具体代码如下:structirq_data{

u32mask;----------TODO

unsignedintirq;--------IRQnumber

unsignedlonghwirq;-------HWinterruptID

unsignedintnode;-------NUMAnodeindex

unsignedintstate_use_accessors;--------底层状态,参考IRQD_xxxx

structirq_chip*chip;----------该中断描述符对应的irqchip数据结构

structirq_domain*domain;--------该中断描述符对应的irqdomain数据结构

void*handler_data;--------和外设specifichandler相关的私有数据

void*chip_data;---------和中断控制器相关的私有数据

structmsi_desc*msi_desc;

cpumask_var_taffinity;-------和irqaffinity相关

};中断有两种形态,一种就是直接通过signal相连,用电平或者边缘触发。另外一种是基于消息的,被称为MSI(MessageSignaledInterrupts)。msi_desc是MSI类型的中断相关,这里不再描述。node成员用来保存中断描述符的内存位于哪一个memorynode上。对于支持NUMA(NonUniformMemoryAccessArchitecture)的系统,其内存空间并不是均一的,而是被划分成不同的node,对于不同的memorynode,CPU其访问速度是不一样的。如果一个IRQ大部分(或者固定)由某一个CPU处理,那么在动态分配中断描述符的时候,应该考虑将内存分配在该CPU访问速度比较快的memorynode上。2、irqchip数据结构Interruptcontroller描述符(structirq_chip)包括了若干和具体Interruptcontroller相关的callback函数,我们总结如下:成员名字描述name该中断控制器的名字,用于/proc/interrupts中的显示irq_startupstartup指定的irqdomain上的HWinterruptID。如果不设定的话,default会被设定为enable函数irq_shutdownshutdown指定的irqdomain上的HWinterruptID。如果不设定的话,default会被设定为disable函数irq_enableenable指定的irqdomain上的HWinterruptID。如果不设定的话,default会被设定为unmask函数irq_disabledisable指定的irqdomain上的HWinterruptID。irq_ack和具体的硬件相关,有些中断控制器必须在Ack之后(清除pending的状态)才能接受到新的中断。irq_maskmask指定的irqdomain上的HWinterruptIDirq_mask_ackmask并ack指定的irqdomain上的HWinterruptID。irq_unmaskmask指定的irqdomain上的HWinterruptIDirq_eoi有些interruptcontroler(例如GIC)提供了这样的寄存器接口,让CPU可以通知interruptcontroller,它已经处理完一个中断irq_set_affinity在SMP的情况下,可以通过该callback函数设定CPUaffinityirq_retrigger重新触发一次中断,一般用在中断丢失的场景下。如果硬件不支持retrigger,可以使用软件的方法。irq_set_type设定指定的irqdomain上的HWinterruptID的触发方式,电平触发还是边缘触发irq_set_wake和电源管理相关,用来enable/disable指定的interruptsource作为唤醒的条件。irq_bus_lock有些interruptcontroller是连接到慢速总线上(例如一个i2c接口的IOexpander芯片),在访问这些芯片的时候需要lock住那个慢速bus(只能有一个client在使用I2Cbus)irq_bus_sync_unlockunlock慢速总线irq_suspend

irq_r

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论