本文均属自己阅读源码的点滴总结,转账请注明出处谢谢。
欢迎和大家交流。qq:1037701636 email:gzzaigcn2009@163.com
写在前沿:
好久好久没有静下心来整理一些东西了,开始工作已有一个月,脑子里想整理的东西特别多。记录是一种很好的自我学习方式,静下来多思考多总结,三年的工作目标不能发生变化,作为职场菜鸟即将进入全世界半导体第一的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绑定私有化,具体的PPI产生有对应的CPUX来处理,为一对一的关系,而SPI一般由GIC自动决定当前来响应该中断的CPUX。
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表的起点,然后赋值hwirqirq_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;}
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的混乱。
PS:
最终写入在dts中的中断号描述值会根据PPI和SPI,SPI为0,则写入的中断号的数值在xlate时自动会+16或者+32(SPI).
故如果一个硬件SPI中断号为62时,写入在dts node的interrupts<>,一般的interrupt-cells为3,故如果SPI则dts描述的值为62-32=30即可,在提取of这个属性时,会进行xlate操作,转为具体的硬件中断号,并获取之前在某一个domain内已经mmap好的vir_irq。通过这个vir_irq供顶层驱动去Request irq即可。