Linux内核中断系统

本文详细介绍了Linux内核中断系统的逻辑,包括CPU与Interrupt Controller的交互,中断接口和控制接口,以及中断处理的轮换算法。重点讲解了三星平台中断系统实例,涉及中断域(irq_domain)的线性映射、Radix Tree映射和无映射策略。此外,还讨论了中断触发和处理过程,以及中断线程化的流程。
摘要由CSDN通过智能技术生成

第一部分 中断系统的总体逻辑

在这里插入图片描述
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);

  1. 向系统注册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);     // 创建映射
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值