您的位置:首页 > 运维架构 > Linux

linux中断子系统:中断号的映射与维护初始化mmap过程

2017-05-16 12:41 639 查看
本文均属自己阅读源代码的点滴总结。转账请注明出处谢谢。

欢迎和大家交流。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表的起点,然后赋值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的混乱。

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就可以。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: