第一部分 中断系统的总体逻辑
CPU的主要功能是运算,因此CPU并不处理中断优先级,那是Interrupt controller的事情。对于CPU而言,一般有两种中断请求,例如:对于ARM,是IRQ和FIQ信号线,分别让ARM进入IRQ mode和FIQ mode。对于X86,有可屏蔽中断和不可屏蔽中断。
CPU和Interrupt Controller之间主要有两类接口,第一种是中断接口。上面的红色线条可能是实际的PCB上的铜线(或者SOC内部的铜线),也可能是一个message而已。第二种是控制接口。Interrupt Controller会开放一些寄存器让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的时候,可以将本CPU对应的bit设定为0,另外一个CPU设定为1。这样,在下次中断发生的时候,interupt controller就把中断送给了CPU1。对于CPU1而言,在执行该中断的handler的时候,将Interrupt processor target register中CPU0的bit为设置为1,disable本CPU的比特位,这样在下次中断发生的时候,interupt controller就把中断送给了CPU0。
以下为三星平台中断系统实例:
对于外部中断XEINT0-15,每一个都对应的SPI中断,但是XEINT16-31共享了同一个SPI中断。这里引脚上产生中断后,会直接通知GIC,然后GIC会通过irq或者firq触发某个CPU中断。
对于其他的pinctrl@11000000中的其他普通的GPIO来说,它们产生中断后,并没有直接通知GIC,而是先通知pinctrl@11000000,然后pinctrl@11000000再通过SPI-46通知GIC,然后GIC会通过irq或者firq触发某个CPU中断。
其中涉及到了多个irq domain, irq domain存放的的hwirq(来自硬件寄存器)到virq(逻辑中断号,全局唯一)的映射。每一个irq_domain都对应一个irq_chip,irq_chip是kernel对中断控制器的软件抽象。
第二部分 irq_domain
对于每个interrupt controller都可以连接若干个外设的中断请求(我们称之interrupt source),interrupt controller会对连接其上的interrupt source(根据其在Interrupt controller中物理特性)进行编号(也就是HW interrupt ID了)。但这个编号仅仅限制在本interrupt controller范围内。
struct irq_domain {
struct list_head link; ----用于将irq_domain连接到全局链表irq_domain_list中
const char *name; ----irq_domain的名称
const struct irq_domain_ops *ops; ----callback函数
void *host_data;
/* Optional data */
struct device_node *of_node; ----对应的interrupt controller的device node
struct irq_domain_chip_generic *gc; ---generic irq chip的概念,本文暂不描述
/* reverse map data. The linear map gets appended to the irq_domain */
irq_hw_number_t hwirq_max; ----该domain中最大的那个HW interrupt ID
unsigned int revmap_direct_max_irq; ----
unsigned int revmap_size; ---线性映射的size,for Radix Tree map和no map,该值等于0
struct radix_tree_root revmap_tree; ----Radix Tree map使用到的radix tree root node
unsigned int linear_revmap[]; -----线性映射使用的lookup table
};
linux内核中,所有的irq domain被挂入一个全局链表,链表头定义如下:
static LIST_HEAD(irq_domain_list);
- 向系统注册irq domain
(1) 线性映射。其实就是一个lookup table,HW interrupt ID作为index,通过查表可以获取对应的IRQ number。
static inline struct irq_domain *irq_domain_add_linear(struct device_node *of_node,
unsigned int size,
const struct irq_domain_ops *ops,
void *host_data)
{
return __irq_domain_add(of_node_to_fwnode(of_node), size, size, 0, ops, host_data);
}
struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,
irq_hw_number_t hwirq_max, int direct_max,
const struct irq_domain_ops *ops,
void *host_data)
{
struct device_node *of_node = to_of_node(fwnode);
struct irq_domain *domain;
// 分配1个irq_domain结构体,多了 (sizeof(unsigned int) * size)用于最后1个成员linear_revmap
domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
GFP_KERNEL, of_node_to_nid(of_node));
of_node_get(of_node);
// 填充 此 irq_domain结构体
INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
domain->ops = ops;
domain->host_data = host_data;
domain->fwnode = fwnode;
domain->hwirq_max = hwirq_max;
domain->revmap_size = size;
domain->revmap_direct_max_irq = direct_max;
irq_domain_check_hierarchy(domain);
mutex_lock(&irq_domain_mutex);
list_add(&domain->link, &irq_domain_list); // 将此domain结构体加入到irq_domain_list
mutex_unlock(&irq_domain_mutex);
return domain;
}
(2) Radix Tree map。建立一个Radix Tree来维护HW interrupt ID到IRQ number映射关系。HW interrupt ID作为lookup key,在Radix Tree检索到IRQ number。内核中使用Radix Tree map的只有powerPC和MIPS的硬件平台。
static inline struct irq_domain *irq_domain_add_tree(struct device_node *of_node,
const struct irq_domain_ops *ops,
void *host_data)
{
return __irq_domain_add(of_node_to_fwnode(of_node), 0, ~0, 0, ops, host_data);
}
(3) no map 。 不需映射,直接把IRQ number写入HW interrupt ID配置寄存器,生成的HW interrupt ID就是IRQ number,也就不需要进行mapping了
static inline struct irq_domain *irq_domain_add_nomap(struct device_node *of_node,
unsigned int max_irq,
const struct irq_domain_ops *ops,
void *host_data)
{
return __irq_domain_add(of_node_to_fwnode(of_node), 0, max_irq, max_irq, ops, host_data);
}
2、为irq domain创建映射
向系统注册一个irq domain后,具体HW interrupt ID和IRQ number的映射关系都是空的,因此,具体各个irq domain如何管理映射所需要的database还是需要建立的。
(1)irq_create_mapping:以irq domain和HW interrupt ID为参数,返回IRQ number(这个IRQ number是动态分配的)。
unsigned int irq_create_mapping(struct irq_domain *domain,
irq_hw_number_t hwirq) // 传入 domain 和 hwirq
{
struct device_node *of_node;
int virq;
/* Look for default domain if nececssary */
if (domain == NULL)
domain = irq_default_domain;
// 获得中断控制器的device node, 在注册irq domain的时候,domain的fwnode成员就指向了device node的fwnode,因此根据domain的fwnode成员也即device node 的fwnode 成员可以获得device node的地址
of_node = irq_domain_get_of_node(domain);
/* Check if mapping already exists */
virq = irq_find_mapping(domain, hwirq);
// 动态分配1个虚拟中断号,从allocated_irqs位图中查找空闲的比特位,并分配1个或多个struct irq_desc结构体
/* Allocate a virtual interrupt number */
virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL);
// 建立映射关系
if (irq_domain_associate(domain, virq, hwirq)) {
irq_free_desc(virq);
return 0;
}
return virq;
}
驱动调用该函数的时候必须提供HW interrupt ID,而一般情况下,HW interrupt ID其实对具体的driver应该是不可见的,不过有些场景比较特殊,例如GPIO类型的中断,它的HW interrupt ID和GPIO有着特定的关系(如下图),driver知道自己使用那个GPIO,也就是知道使用哪一个HW interrupt ID了。
// 传入的viq为-1,cnt 为1 , hwirq 为 第三个参数
int irq_domain_alloc_descs(int virq, unsigned int cnt, irq_hw_number_t hwirq,
int node, const struct cpumask *affinity)
{
unsigned int hint;
if (virq >= 0) {
virq = __irq_alloc_descs(virq, virq, cnt, node, THIS_MODULE,
affinity);
} else {
hint = hwirq % nr_irqs;
if (hint == 0)
hint++;
virq = __irq_alloc_descs(-1, hint, cnt, node, THIS_MODULE,
affinity); // 分配虚拟中断号从hint 开始,说明是以hwirq开始寻找到第一个连续cnt为0的bit,返回其下标值即为virq
if (virq <= 0 && hint > 1) {
virq = __irq_alloc_descs(-1, 1, cnt, node, THIS_MODULE,
affinity);
}
}
return virq;
}
int __ref
__irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, int node,
struct module *owner, const struct cpumask *affinity)
{
int start, ret;
if (irq >= 0) {
if (from > irq)
return -EINVAL;
from = irq;
} else {
/*
* For interrupts which are freely allocated the
* architecture can force a lower bound to the @from
* argument. x86 uses this to exclude the GSI space.
*/
from = arch_dynirq_lower_bound(from);
}
mutex_lock(&sparse_irq_lock);
start = bitmap_find_next_zero_area(allocated_irqs, IRQ_BITMAP_BITS,
from, cnt, 0);
ret = -EEXIST;
if (irq >=0 && start != irq)
goto unlock;
if (start + cnt > nr_irqs) {
ret = irq_expand_nr_irqs(start + cnt);
if (ret)
goto unlock;
}
ret = alloc_descs(start, cnt, node, affinity, owner); // return start,返回virq
unlock:
mutex_unlock(&sparse_irq_lock);
return ret;
}
int irq_domain_associate(struct irq_domain *domain, unsigned int virq,
irq_hw_number_t hwirq)
{
struct irq_data *irq_data = irq_get_irq_data(virq);
int ret;
mutex_lock(&irq_domain_mutex);
irq_data->hwirq = hwirq;
irq_data->domain = domain;
if (domain->ops->map) {
ret = domain->ops->map(domain, virq, hwirq); // 调用irq domain的map callback函数
/* If not already assigned, give the domain the chip's name */
if (!domain->name && irq_data->chip)
domain->name = irq_data->chip->name;
}
if (hwirq < domain->revmap_size) {
domain->linear_revmap[hwirq] = virq; // 填写线性映射lookup table的数据
} else {
mutex_lock(&revmap_trees_mutex);
radix_tree_insert(&domain->revmap_tree, hwirq, irq_data); // 向radix tree插入一个node
mutex_unlock(&revmap_trees_mutex);
}
mutex_unlock(&irq_domain_mutex);
irq_clear_status_flags(virq, IRQ_NOREQUEST); // 该IRQ已经可以申请了,因此clear相关flag
return 0;
}
(2)irq_create_strict_mappings。这个接口函数用来为一组HW interrupt ID建立映射。具体函数的原型定义如下:
int irq_create_strict_mappings(struct irq_domain *domain, unsigned int irq_base,
irq_hw_number_t hwirq_base, int count)
{
struct device_node *of_node;
int ret;
of_node = irq_domain_get_of_node(domain);
ret = irq_alloc_descs(irq_base, irq_base, count,
of_node_to_nid(of_node));
if (unlikely(ret < 0))
return ret;
irq_domain_associate_many(domain, irq_base, hwirq_base, count);
return 0;
}
(3)irq_of_parse_and_map。利用device tree进行映射关系的建立。具体函数的原型定义如下:
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
{
struct of_phandle_args oirq;
if (of_irq_parse_one(dev, index, &oirq)) // 获得interrupts的第index个中断参数,并封装到oirq中
return 0;
return irq_create_of_mapping(&oirq); // 创建映射
}