linux中断子系统:中断号的映射与维护

写在前沿:

好久好久没有静下心来整理一些东西了,开始工作已有一个月,脑子里想整理的东西特别多。记录是一种很好的自我学习方式,静下来多思考多总结,三年的工作目标不能发生变化,作为职场菜鸟即将进入全世界半导体第一的Intel working,是机遇更是一种挑战,困难也是可想而知。脚踏实地、仰望星空,以结果为导向,以目标为准则,争取每天进步一点点。

Linux内核版本:3.4.39

一. linux中断子系统的irq_desc初始化

linux内核最初的中断初始化过程入口为start_kernel。在这里内核空间需要做的事情是初始化一张中断描述符表,由函数early_irq_init来完成,内核可配置使用hash table或者直接使用linear来完成。一般嵌入式平台下的内核中断所需的数量并不是很大,所有采用linera的方式比较常见,如下:

struct irq_desc irq_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),
	}
};

二.ARM平台下常见的gic中断控制器初始化过程:

gic控制器的初始化在arm平台下集成为主,由init_IRQ来负责完成最底层的gic controller的初始化,machine_desc->init_irq();该过程表明需要和machine平台相关的irq初始化相关联起来,一般在arch/arcm/mach-xxx/board.x的平台下进行平台最起初的init回调定义,如machine_init等。

一般需要调用gic初始化的入口为gic_of_init():

int __init gic_of_init(struct device_node *node, struct device_node *parent)
{
	void __iomem *cpu_base;
	void __iomem *dist_base;
	u32 percpu_offset;
	int irq;

	if (WARN_ON(!node))
		return -ENODEV;

	dist_base = of_iomap(node, 0);
	WARN(!dist_base, "unable to map gic dist registers\n");

	cpu_base = of_iomap(node, 1);
	WARN(!cpu_base, "unable to map gic cpu registers\n");

	if (of_property_read_u32(node, "cpu-offset", &percpu_offset))
		percpu_offset = 0;

	gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset, node);

	if (parent) {
		irq = irq_of_parse_and_map(node, 0);//将一个子controller以普通中断加入到父节点,并映射之
		gic_cascade_irq(gic_cnt, irq);//设置该irq_num下的级联flow 回调接口
	}//级联使用,该gic需要将自己作为一个子irq来进行map操作
	gic_cnt++;
	return 0;

该过程主要基于linux设备树,对当前machine支持的interrupt controller进行init,常见的处理器以单个gic为主,只有部分处理器会出现root与child 等controller的级联现象。假设当前就一个gic,那么gic_init_bases就成为了初始化的关键,他主要完成以下工作:

step1:初始化gic_chip_data用于维护一个gic控制器;

step2:计算当前系统硬件起始的中断号:

	if (gic_nr == 0 && (irq_start & 31) > 0) {
		hwirq_base = 16;
		if (irq_start != -1)
			irq_start = (irq_start & ~31) + 16;
	} else {
		hwirq_base = 32;//hw irq从32开始即spi
	}

这里需要说明的是,一般arm平台下的gic作为一个硬件中断控制器,具体的中断号类型有SGI软件中断,CPU私有中断PPI、SPI等,前两者分别占据0-15,16-31的ID号,32开始的均为SPI中断类型。而一般软件中断是不需要去维护相应的中断描述符的,故如果gic_nr=0即初始化的是当前的root gic,则相应的hwirq起始值为16,包括PPI,但PPI一般和具体的CPU绑定私有化。

step3:调整当前平台所用的gic实际支持的中断数目。

	gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;//gic控制器中读取支持irq数目
	gic_irqs = (gic_irqs + 1) * 32;
	if (gic_irqs > 1020)
		gic_irqs = 1020;
	gic->gic_irqs = gic_irqs;//irqs的总数

	gic_irqs -= hwirq_base; /* calculate # of irqs to allocate *///实际减去16个后的要映射的spi中?

setp4:irq_alloc_descs

irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, numa_node_id());//获取一张irq的map表起始位置

我们知道,当前使用的是线性表来维护整个中断描述符,上述过程就是在一张allocated_irqs map表中找到一个非0开始的bit数据位置,并请求申请一个连续的gic_irqs数目的一段map中的空间,表明这段空间归当前的root gic所有,并将这段空间的map bit值置为1。比如从16开始申请,100个的话,连续的跨字节申请到100bit.

step5:irq_domain_add_legacy

完成一个domain的建立,并完成所有硬件中断号hw_irq和vir_irq间的映射关系。

三.中断控制器中的domain

中断控制器domain的出现,是为了更好的维护虚拟中断号和硬件中断号间的关系以及解决多个controller级联时的irq映射问题,linux内核提供比较标准的映射函数接口。但gic模块在映射,采用了什么简单的处理方式,如下所示:

struct irq_domain *irq_domain_add_legacy(struct device_node *of_node,
					 unsigned int size,
					 unsigned int first_irq,
					 irq_hw_number_t first_hwirq,
					 const struct irq_domain_ops *ops,
					 void *host_data)
{
	struct irq_domain *domain;
	unsigned int i;

	domain = irq_domain_alloc(of_node, IRQ_DOMAIN_MAP_LEGACY, ops, host_data);
	if (!domain)
		return NULL;

	domain->revmap_data.legacy.first_irq = first_irq;
	domain->revmap_data.legacy.first_hwirq = first_hwirq;
	domain->revmap_data.legacy.size = size;

	mutex_lock(&irq_domain_mutex);
	/* Verify that all the irqs are available */
	for (i = 0; i < size; i++) {
		int irq = first_irq + i;
		struct irq_data *irq_data = irq_get_irq_data(irq);

		if (WARN_ON(!irq_data || irq_data->domain)) {
			mutex_unlock(&irq_domain_mutex);
			of_node_put(domain->of_node);
			kfree(domain);
			return NULL;
		}
	}

	/* Claim all of the irqs before registering a legacy domain */
	for (i = 0; i < size; i++) {
		struct irq_data *irq_data = irq_get_irq_data(first_irq + i);//获取virq map表的起点,然后赋值hwirq
		irq_data->hwirq = first_hwirq + i;
		irq_data->domain = domain;//绑定hwirq与virq
	}
	mutex_unlock(&irq_domain_mutex);

	for (i = 0; i < size; i++) {
		int irq = first_irq + i;
		int hwirq = first_hwirq + i;//offset亮是一样的

		/* IRQ0 gets ignored */
		if (!irq)
			continue;

		/* Legacy flags are left to default at this point,
		 * one can then use irq_create_mapping() to
		 * explicitly change them
		 */
		ops->map(domain, irq, hwirq);//建立irq与硬件中断间的关系

		/* Clear norequest flags */
		irq_clear_status_flags(irq, IRQ_NOREQUEST);
	}

	irq_domain_add(domain);
	return domain;
}

来看向内核添加一个domain的处理过程,说明下传入的关键参数first_irq、first_hwirq,前者是我们申请到的一段irq_desc可用的map bit空间的起始位置,实际值为16。后者是当前root gic支持的可映射的中断号位16.

step1:先是根据first_irq去连续获取irq_data本质即是一个irq_desc。然后对该描述符的hwirq与所属的domain进行赋值操作。这样处理的好处就是根据匹配硬件中断号就能映射到一个独一无二的virq。而且看上去是很线性的映射关系

struct irq_data *irq_get_irq_data(unsigned int irq)
{
	struct irq_desc *desc = irq_to_desc(irq);

	return desc ? &desc->irq_data : NULL;
}

step2:利用domain的回调接口ops->map进行该irq_desc中断流层flow_handler接口的初始化.

const struct irq_domain_ops gic_irq_domain_ops = {
	.map = gic_irq_domain_map,
	.xlate = gic_irq_domain_xlate,
};

static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
				irq_hw_number_t hw)
{
	if (hw < 32) {
		irq_set_percpu_devid(irq);
		irq_set_chip_and_handler(irq, &gic_chip,
					 handle_percpu_devid_irq);
		set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN);//PPI
	} else {
		irq_set_chip_and_handler(irq, &gic_chip,
					 handle_fasteoi_irq);
		set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);//SPI,均为handle_arch_irq类型
	}
	irq_set_chip_data(irq, d->host_data);
	return 0;
}

domain_map操作对SPI和PPI进行了分开的处理,以SPI为例,主要通过irq_set_chip_and_handler设置了当前硬件中断发生时,需要回调的irq_decs-> (irq_flow_handler_t handle_irq;),这不是我们所谓的暴露给驱动开发的最上层的irq_handler,在linux中断系统中称为流层回调,目的在于处理不同的中断流形式的回调,比如多controller级联,以及PPI flow_hander等等。

最终还将gic chip级别的信息保存到这个irq_desc中去。

四、gic硬件模块的初始化:

	gic_dist_init(gic);//这里是硬件gic_distributor初始化
	gic_cpu_init(gic);//cpu interfac初始化
	gic_pm_init(gic);//gic 电源模块的初始化

总结:

domain很好的将大量的硬件中断号映射为一个独一无二的虚拟中断号,供上层驱动开发时的request_irq,从而确保内核在发生中断系统调用时不会出现irq_handler的混乱。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-25 12:14:12

linux中断子系统:中断号的映射与维护的相关文章

linux kernel的中断子系统之(三):IRQ number和中断描述符【转】

转自:http://www.wowotech.net/linux_kenrel/interrupt_descriptor.html 一.前言 本文主要围绕IRQ number和中断描述符(interrupt descriptor)这两个概念描述通用中断处理过程.第二章主要描述基本概念,包括什么是IRQ number,什么是中断描述符等.第三章描述中断描述符数据结构的各个成员.第四章描述了初始化中断描述符相关的接口API.第五章描述中断描述符相关的接口API. 二.基本概念 1.通用中断的代码处理

Linux kernel中断子系统之(五):驱动申请中断API

一.前言 本文主要的议题是作为一个普通的驱动工程师,在撰写自己负责的驱动的时候,如何向Linux Kernel中的中断子系统注册中断处理函数?为了理解注册中断的接口,必须了解一些中断线程化(threaded interrupt handler)的基础知识,这些在第二章描述.第三章主要描述了驱动申请 interrupt line接口API request_threaded_irq的规格.第四章是进入request_threaded_irq的实现细节,分析整个代码的执行过程. 二.和中断相关的lin

linux kernel的中断子系统之(七):GIC代码分析

一.前言 GIC(Generic Interrupt Controller)是ARM公司提供的一个通用的中断控制器,其architecture specification目前有四个版本,V1-V4(V2最多支持8个ARM core,V3/V4支持更多的ARM core,主要用于ARM64服务器系统结构).目前在ARM官方网站只能下载到Version 2的GIC architecture specification,因此,本文主要描述符合V2规范的GIC硬件及其驱动. 具体GIC硬件的实现形态有两

Linux kernel的中断子系统之(二):IRQ Domain介绍

一.概述 在linux kernel中,我们使用下面两个ID来标识一个来自外设的中断: 1.IRQ number.CPU需要为每一个外设中断编号,我们称之IRQ Number.这个IRQ number是一个虚拟的interrupt ID,和硬件无关,仅仅是被CPU用来标识一个外设中断. 2.HW interrupt ID.对于interrupt controller而言,它收集了多个外设的interrupt request line并向上传递,因此,interrupt controller需要对

linux kernel的中断子系统之(四):High level irq event handler

一.前言 当外设触发一次中断后,一个大概的处理过程是: 1.具体CPU architecture相关的模块会进行现场保护,然后调用machine driver对应的中断处理handler 2.machine driver对应的中断处理handler中会根据硬件的信息获取HW interrupt ID,并且通过irq domain模块翻译成IRQ number 3.调用该IRQ number对应的high level irq event handler,在这个high level的handler中

linux kernel 中断子系统之(一)-- ARM GIC 硬件

一个系统中,中断是很重要的组成部分之一,有了中断,系统才可以不用一直轮询(polling)是否有事件发生,系统效率才得以提高,而且对中断的控制又通常分散在各个地方,不同的部分由不同功能的程序控制,做到了各司其职,配合无误,系统才能正常工作.一般系统中,中断控制分为三个地方:模块.中断控制器.处理器,模块通常有寄存器可以控制是否使能中断功能,中断触发条件等:中断控制器可以管理中断的优先级等,而处理所有中断的处理器则有寄存器设置是否响应中断. 1. 全景 作为 ARM 系统中通用中断控制器的是 GI

linux的中断子系统简介(汇编和hard irq部分)_ARM平台(S5PV210)

2011年9月份时候做的笔记, 当时阅读中断子系统的代码后做的一个PPT, 内核版本不记得了, 硬件平台是samsung 的S5PV210. 这部分主要是针对汇编和hard irq的部分, 在hard irq处理后的softirq的处理, 以及下半部的处理(tasklet/workqueue)都没有涉及. Agenda ?Interrupts in ARM ?Important structs ?External interrupt resources in S5PV210 ?Code flow

linux kernel的中断子系统之(八):softirq

一.前言 对于中断处理而言,linux将其分成了两个部分,一个叫做中断handler(top half),是全程关闭中断的,另外一部分是deferable task(bottom half),属于不那么紧急需要处理的事情.在执行bottom half的时候,是开中断的.有多种bottom half的机制,例如:softirq.tasklet.workqueue或是直接创建一个kernel thread来执行bottom half(这在旧的kernel驱动中常见,现在,一个理智的driver厂商是

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

一.前言 一个合格的linux驱动工程师需要对kernel中的中断子系统有深刻的理解,只有这样,在写具体driver的时候才能: 1.正确的使用linux kernel提供的的API,例如最著名的request_threaded_irq(request_irq)接口 2.正确使用同步机制保护驱动代码中的临界区 3.正确的使用kernel提供的softirq.tasklet.workqueue等机制来完成具体的中断处理 基于上面的原因,我希望能够通过一系列的文档来描述清楚linux kernel中