简介

关键词

硬件中断号软件中断号IRQ Domain

概述

LInux中使用硬件中断号软件中断号两个ID来描述中断号。

  1. IRQ number(软件中断号).

CPU需要为每一个外设中断编号,我们称之IRQ Number。这个IRQ number是一个虚拟的interrupt ID,和硬件无关,仅仅是被CPU用来标识一个外设中断。

  1. HW interrupt ID(硬件中断号).

对于interrupt controller而言,它收集了多个外设的interrupt request line并向上传递,因此,interrupt controller需要对外设中断进行编码。Interrupt controller用HW interrupt ID来标识外设的中断。在interrupt controller级联的情况下,仅仅用HW interrupt ID已经不能唯一标识一个外设中断,还需要知道该HW interrupt ID所属的interrupt controller(HW interrupt ID在不同的Interrupt controller上是会重复编码的)。
这样,CPU和interrupt controller在标识中断上就有了一些不同的概念,但是,对于驱动工程师而言,我们和CPU视角是一样的,我们只希望得到一个IRQ number,而不关系具体是那个interrupt controller上的那个HW interrupt ID。这样一个好处是在中断相关的硬件发生变化的时候,驱动软件不需要修改。因此,linux kernel中的中断子系统需要提供一个将HW interrupt ID映射到IRQ number上来的机制,这就是本文主要的内容。

历史

早期,只有一个中断控制器的时候,一个软件中断号 可以直接对应一个硬件中断号,。例如早期的SOC中断控制器,这种中断控制器,有一个状态位,假设有64bit,每一个bit就对应一个软件中断号,这样直接映射。这时候,GPIO的中断在中断控制器的状态寄存器中只有一个bit,因此所有的GPIO中断只有一个IRQ number。在该通用GPIO中断的irq handler中进行deduplex,将各个具体的GPIO中断映射到其相应的IRQ number上。
现在流行的是将interrupt controller抽象成irqchip,甚至GPIO controller也可以被看出一个interrupt controller chip,这样,系统中至少有两个中断控制器了,一个传统意义的中断控制器一个是GPIO controller type的中断控制器。随着系统复杂度加大,外设中断数据增加,实际上系统可以需要多个中断控制器进行级联,面对这样的趋势,linux kernel工程师如何应对?答案就是irq domain这个概念。
系统中所有的interrupt controller会形成树状结构,对于每个interrupt controller都可以连接若干个外设的中断请求(我们称之interrupt source)。
interrupt controller会对连接其上的interrupt source(根据其在Interrupt controller中物理特性)进行编号(也就是HW interrupt ID了)。但这个编号仅仅限制在本interrupt controller范围内。换句话说 HW interrupt ID这个中断号仅仅只是在对应的interrupt controller会起作用。

irq domain注册接口

  • 线性映射
  • Radix Tree map
  • no map

我们只关注线性映射,其他的映射方式不关注。

线性映射

线性映射。其实就是一个lookup tableHW interrupt ID作为index,通过查表可以获取对应的IRQ number。对于Linear map而言,interrupt controller对其HW interrupt ID进行编码的时候要满足一定的条件:hw ID不能过大,而且ID排列最好是紧密的。对于线性映射,其接口API如下:

  1. static inline struct irq_domain *irq_domain_add_linear(struct device_node *of_node,
  2. unsigned int size,---------该interrupt domain支持多少IRQ
  3. const struct irq_domain_ops *ops,---callback函数
  4. void *host_data)-----driver私有数据
  5. {
  6. return __irq_domain_add(of_node, size, size, 0, ops, host_data);
  7. }

为irq domain创建映射

上节的内容主要是向系统注册一个irq domain,具体HW interrupt ID和IRQ number的映射关系都是空的,因此,具体各个irq domain如何管理映射所需要的database还是需要建立的。例如:对于线性映射的irq domain,我们需要建立线性映射的lookup table

irq_create_mapping

调用irq_create_mapping函数建立HW interrupt ID和IRQ number的映射关系。该接口函数以irq domain和HW interrupt ID为参数,返回IRQ number(这个IRQ number是动态分配的)。该函数的原型定义如下:

  1. extern unsigned int irq_create_mapping(struct irq_domain *host,irq_hw_number_t hwirq);

irq_create_strict_mappings

这个接口函数用来为一组HW interrupt ID建立映射。具体函数的原型定义如下:

  1. extern int irq_create_strict_mappings(struct irq_domain *domain,
  2. unsigned int irq_base,
  3. irq_hw_number_t hwirq_base, int count);

irq_create_of_mapping

利用device tree进行映射关系的建立。具体函数的原型定义如下:

  1. extern unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data);

最推荐使用这个方法,和设备树关联设置中断。对于一个使用Device tree的普通驱动程序,基本上初始化需要调用irq_of_parse_and_map获取IRQ number,然后调用request_threaded_irq申请中断handler。

线性映射

想要进行映射,首先要了解interrupt controller的拓扑结构。系统中的interrupt controller的拓扑结构以及其interrupt request line的分配情况(分配给哪一个具体的外设)都在Device Tree Source文件中通过下面的属性给出了描述。这些内容在Device Tree的三份文档中给出了一些描述,这里简单总结一下:
对于那些产生中断的外设,我们需要定义interrupt-parent和interrupts属性:

  1. 对于那些产生中断的外设,我们需要定义interrupt-parent和interrupts属性:
  2. interrupts。这个属性描述了具体该外设产生的interrupt的细节信息(也就是传说中的interrupt specifier)。例如:HW interrupt ID(由该外设的device node中的interrupt-parent指向的interrupt controller解析)、interrupt触发类型等。

对于Interrupt controller,我们需要定义interrupt-controller和#interrupt-cells的属性:
(1)interrupt-controller。表明该device node就是一个中断控制器
(2)#interrupt-cells。该中断控制器用多少个cell(一个cell就是一个32-bit的单元)描述一个外设的interrupt request line。?具体每个cell表示什么样的含义由interrupt controller自己定义。
(3)interrupts和interrupt-parent。对于那些不是root 的interrupt controller,其本身也是作为一个产生中断的外设连接到其他的interrupt controller上,因此也需要定义interrupts和interrupt-parent的属性。

Mapping DB的建立



参考资料

Linux的中断号是假的!与硬件中断号是什么关系?
Linux中断 - IRQ Domain介绍