Linux中断流程分析

参考文档

IHI0048B_b_gic_architecture_specification.pdf

IHI0069E_gic_architecture_specification.pdf

DUI0471M_software_development_guide.pdf

GIC400架构

中断的类型

SGI:software generatedinterrupt,软件出发产生的中断,相当于(Internal-Processor Interrupt,IPI),中断号范围0~15,用于核间通信
PPI:private peripheral interrupt,特定cpu独有,时钟,中断号范围16~31
SPI:shared peripheral interrupt,这是常见的外部设备中断,也定义为共享中断,中断号范围31~xxx

查看/proc/interrupts时,第一列是虚拟中断号,倒数第3列是硬件中断号,内核只关心虚拟中断号,通过irq_desc绑定虚拟中断号和虚拟中断号,发生中断时,可以直接读取硬件中断号,进而获取到虚拟中断号,从而跳到相应的中断处理函数。

内核初始化遍历所有的节点,对硬件中断号和虚拟中断号进行绑定。

const struct of_device_id of_default_bus_match_table[] = {
	{ .compatible = "simple-bus", },
	...
};

int of_platform_populate(struct device_node *root,const struct of_device_id *matches,const struct of_dev_auxdata *lookup,struct device *parent)
{
	struct device_node *child;
	root = root ? of_node_get(root) : of_find_node_by_path("/");
	for_each_child_of_node(root, child) {
		rc = of_platform_bus_create(child, matches, lookup, parent, true);
		
	}
}
static int __init customize_machine(void)
{
		of_platform_populate(NULL, of_default_bus_match_table,NULL, NULL);
}
arch_initcall(customize_machine);

把堆栈打印出来,查看调用关系。

[    0.237489] [<c0195754>] (irq_create_fwspec_mapping) from [<c01958dc>] (irq_create_of_mapping+0x5c/0x7c)
[    0.237501] [<c01958dc>] (irq_create_of_mapping) from [<c0737b58>] (irq_of_parse_and_map+0x30/0x50)
[    0.237514] [<c0737b58>] (irq_of_parse_and_map) from [<c0737b9c>] (of_irq_to_resource+0x24/0xd0)
[    0.237527] [<c0737b9c>] (of_irq_to_resource) from [<c0737c78>] (of_irq_to_resource_table+0x30/0x44)
[    0.237540] [<c0737c78>] (of_irq_to_resource_table) from [<c0735294>] (of_device_alloc+0xe0/0x180)
[    0.237552] [<c0735294>] (of_device_alloc) from [<c073537c>] (of_platform_device_create_pdata+0x48/0xb8)
[    0.237564] [<c073537c>] (of_platform_device_create_pdata) from [<c073567c>] (of_platform_bus_create+0x284/0x388)
[    0.237577] [<c073567c>] (of_platform_bus_create) from [<c07356e8>] (of_platform_bus_create+0x2f0/0x388)

 一般硬件中断号,cpu规格书会提供,如

 dts也有相应的信息

#define GIC_SPI 0
#define GIC_PPI 1
gic: interrupt-controller@12001000 {
                compatible = "arm,gic-400";
                reg = <0x12001000 0x1000>,
                        <0x12002000 0x2000>,
                        <0x12004000 0x2000>,
                        <0x12006000 0x2000>;
                #interrupt-cells = <3>;
                interrupt-controller;
                interrupts = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_HIGH)>;
};

arch_timer: timer {
	compatible = "arm,armv7-timer";
	interrupts = <GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
	<GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>;
	clock-frequency = <26000000>;
};


interrupt-parent = <&gic>
	compatible = "simple-bus";

spi0: spi@70a00000{
	interrupts = <GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>;
};
spi1: spi@70b00000{
	interrupts = <GIC_SPI 8 IRQ_TYPE_LEVEL_HIGH>;
};

spi2: spi@70c00000{
	interrupts = <GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH>;
};

i2c0: i2c@70500000 {
	interrupts = <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>;
};

i2c1: i2c@70600000 {
	interrupts = <GIC_SPI 12 IRQ_TYPE_LEVEL_HIGH>;

};

i2c2: i2c@70700000 {
	interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>;

};

i2c3: i2c@70800000 {
	interrupts = <GIC_SPI 14 IRQ_TYPE_LEVEL_HIGH>;
};

i2c4: i2c@70900000 {
	interrupts = <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH>;
};

这里的中断号会进行固定的运算,转换成真正的硬件中断号。以gic_400为例,

drivers/irqchip/irq-gic.c

如dts中的中断描述为<GIC_SPI 95 IRQ_TYPE_LEVEL_HIGH>;
参数0  值GIC_SPI(0)/GIC_PPI(1)
参数1 95
参数2 IRQ_TYPE_LEVEL_HIGH
默认+16,避开SGI(0~15)
如果是PPI,再+16
也就是
PPI_NEW=PPI_OLD+16
SPI_NEW=SPI_OLD+32
static int gic_irq_domain_translate(struct irq_domain *d,struct irq_fwspec *fwspec,unsigned long *hwirq,unsigned int *type)
{
	if (is_of_node(fwspec->fwnode)) {
		*hwirq = fwspec->param[1] + 16;
		if (!fwspec->param[0])
			*hwirq += 16;

		*type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
		return 0;
	}
}

static int gic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,unsigned int nr_irqs, void *arg)
{
	irq_hw_number_t hwirq;
	struct irq_fwspec *fwspec = arg;
	gic_irq_domain_translate(domain, fwspec, &hwirq, &type);
	gic_irq_domain_map(domain, virq, hwirq);
}

static const struct irq_domain_ops gic_irq_domain_hierarchy_ops = {
	.translate = gic_irq_domain_translate,//将规格书的中断号转换成真正的硬件中断号
	.alloc = gic_irq_domain_alloc,//虚拟中断号和硬件中断号进行绑定
	.free = irq_domain_free_irqs_top,
};

static void __init __gic_init_bases(unsigned int gic_nr, int irq_start,void __iomem *dist_base, void __iomem *cpu_base,u32 percpu_offset, struct fwnode_handle *handle)
{
		gic->domain = irq_domain_create_linear(handle, gic_irqs,&gic_irq_domain_hierarchy_ops,gic);

        gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;
	    gic_irqs = (gic_irqs + 1) * 32;
	    if (gic_irqs > 1020)
		    gic_irqs = 1020;
	    gic->gic_irqs = gic_irqs;//获取硬件irq的数量
		set_handle_irq(gic_handle_irq);//设置中断处理函数
}

static int __init gic_of_init(struct device_node *node, struct device_node *parent)
{
	__gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset,&node->fwnode);

}
IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init);

分析下irq_create_fwspec_mapping函数

int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base,
			    unsigned int nr_irqs, int node, void *arg,
			    bool realloc)
{
	virq = irq_domain_alloc_descs(irq_base, nr_irqs, 0, node);//A分配virq和irq_desc结构体
	ret = irq_domain_alloc_irqs_recursive(domain, virq, nr_irqs, arg);//hwirq转换成virq,并设定默认的中断handle_irq函数
	irq_domain_insert_irq(virq);// C每个硬件irq保存相应的virq,中断过来后,根据硬件irq能直接获取virq
}

static inline int irq_domain_alloc_irqs(struct irq_domain *domain,
			unsigned int nr_irqs, int node, void *arg)
{
	return __irq_domain_alloc_irqs(domain, -1, nr_irqs, node, arg, false);
}

unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
{
	irq_domain_translate(domain, fwspec, &hwirq, &type);
	virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);
	return virq;
}

 A.分配virq和irq_desc结构体

struct irq_data {
        unsigned int            irq;
        unsigned long           hwirq;
        struct irq_domain       *domain;
};

struct irq_desc {
	struct irq_data		irq_data;
	irq_flow_handler_t	handle_irq;

} ____cacheline_internodealigned_in_smp;


static void desc_set_defaults(unsigned int irq, struct irq_desc *desc, int node,
		struct module *owner)
{
	int cpu;

	desc->irq_common_data.handler_data = NULL;
	desc->irq_common_data.msi_desc = NULL;

	desc->irq_data.common = &desc->irq_common_data;
	desc->irq_data.irq = irq;
	desc->irq_data.chip = &no_irq_chip;
	desc->irq_data.chip_data = NULL;
	irq_settings_clr_and_set(desc, ~0, _IRQ_DEFAULT_INIT_FLAGS);
	irqd_set(&desc->irq_data, IRQD_IRQ_DISABLED);
	desc->handle_irq = handle_bad_irq;
	desc->depth = 1;
	desc->irq_count = 0;
	desc->irqs_unhandled = 0;
	desc->name = NULL;
	desc->owner = owner;
	for_each_possible_cpu(cpu)
		*per_cpu_ptr(desc->kstat_irqs, cpu) = 0;
	desc_smp_init(desc, node);
}

static struct irq_desc *alloc_desc(int irq, int node, struct module *owner)
{
	struct irq_desc *desc;
	desc = kzalloc_node(sizeof(*desc), gfp, node);
	desc_set_defaults(irq, desc, node, owner);
	return desc;
}

static int alloc_descs(unsigned int start, unsigned int cnt, int node,struct module *owner)
{
	desc = alloc_desc(start + i, node, owner);
}
static void irq_insert_desc(unsigned int irq, struct irq_desc *desc)
{
	radix_tree_insert(&irq_desc_tree, irq, desc);
}
int __ref __irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, int node,struct module *owner)
{
	alloc_descs(start, cnt, node, owner);
	irq_insert_desc(start, desc);
}

#define irq_alloc_descs(irq, from, cnt, node)	\
	__irq_alloc_descs(irq, from, cnt, node, THIS_MODULE)

#define irq_alloc_descs_from(from, cnt, node)	\
	irq_alloc_descs(-1, from, cnt, node)
static int irq_domain_alloc_descs(int virq, unsigned int cnt,
				  irq_hw_number_t hwirq, int node)
{
	unsigned int hint;
	virq = irq_alloc_descs_from(hint, cnt, node);
	return virq;
}

B.hwirq转换成virq,并设定默认的中断handle_irq函数

static int irq_domain_alloc_irqs_recursive(struct irq_domain *domain,unsigned int irq_base,unsigned int nr_irqs, void *arg)
{
	ret = domain->ops->alloc(domain, irq_base, nr_irqs, arg);
}
int irq_domain_set_hwirq_and_chip(struct irq_domain *domain, unsigned int virq,irq_hw_number_t hwirq, struct irq_chip *chip,void *chip_data)
{
	struct irq_data *irq_data = irq_domain_get_irq_data(domain, virq);
	irq_data->hwirq = hwirq;
	irq_data->chip = chip ? chip : &no_irq_chip;
	irq_data->chip_data = chip_data;
}

void irq_domain_set_info(struct irq_domain *domain, unsigned int virq,irq_hw_number_t hwirq, struct irq_chip *chip,void *chip_data, irq_flow_handler_t handler,void *handler_data, const char *handler_name)
{
	irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip, chip_data);
	__irq_set_handler(virq, handler, 0, handler_name);
}

static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,irq_hw_number_t hw)
{
	struct irq_chip *chip = &gic_chip;
	if (hw < 32) {
		irq_set_percpu_devid(irq);
		irq_domain_set_info(d, irq, hw, chip, d->host_data,
				    handle_percpu_devid_irq, NULL, NULL);
		irq_set_status_flags(irq, IRQ_NOAUTOEN);
	} else {
		irq_domain_set_info(d, irq, hw, chip, d->host_data,
				    handle_fasteoi_irq, NULL, NULL);
		irq_set_probe(irq);
	}
	return 0;
}

static int gic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,unsigned int nr_irqs, void *arg)
{
	irq_hw_number_t hwirq;
	struct irq_fwspec *fwspec = arg;
	gic_irq_domain_translate(domain, fwspec, &hwirq, &type);
	gic_irq_domain_map(domain, virq, hwirq);
}

 C.每个硬件irq保存相应的virq,中断过来后,根据硬件irq能直接获取virq

static void irq_domain_insert_irq(int virq)
{
	struct irq_data *data;
	for (data = irq_get_irq_data(virq); data; data = data->parent_data) {
		struct irq_domain *domain = data->domain;
		irq_hw_number_t hwirq = data->hwirq;
		if (hwirq < domain->revmap_size) {
			domain->linear_revmap[hwirq] = virq;
		}
	}
}

中断的处理过程

图来源DUI0471M_software_development_guide.pdf

 

CPSR:程序状态寄存器(current program status register) 
SPSR:程序状态保存寄存器(Saved Program Status Register)

Disable interrupts. IRQs are disabled when any exception occurs异常时,硬件会关闭IRQ,所以不用担心中断嵌套

arch/arm/kernel/entry-armv.S

.macro	irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
	ldr	r1, =handle_arch_irq  //中断处理函数
	mov	r0, sp
	badr	lr, 9997f
	ldr	pc, [r1]
#else
	arch_irq_handler_default
#endif
__irq_usr: //从usr进入irq模式
	usr_entry
	kuser_cmpxchg_check
	irq_handler
	get_thread_info tsk
	mov	why, #0
	b	ret_to_user_from_irq
 UNWIND(.fnend		)
ENDPROC(__irq_usr)
__irq_svc:    //从kernel进入irq模式
	svc_entry
	irq_handler

#ifdef CONFIG_PREEMPT
	get_thread_info tsk
	ldr	r8, [tsk, #TI_PREEMPT]		@ get preempt count
	ldr	r0, [tsk, #TI_FLAGS]		@ get flags
	teq	r8, #0				@ if preempt count != 0
	movne	r0, #0				@ force flags to 0
	tst	r0, #_TIF_NEED_RESCHED
	blne	svc_preempt
#endif

	svc_exit r5, irq = 1			@ return from exception
 UNWIND(.fnend		)
ENDPROC(__irq_svc)

	.ltorg

#ifdef CONFIG_PREEMPT
svc_preempt:
	mov	r8, lr
1:	bl	preempt_schedule_irq		@ irq en/disable is done inside
	ldr	r0, [tsk, #TI_FLAGS]		@ get new tasks TI_FLAGS
	tst	r0, #_TIF_NEED_RESCHED
	reteq	r8				@ go again
	b	1b
#endif
	.macro	svc_exit, rpsr, irq = 0
	uaccess_restore
	@ ARM mode SVC restore
	msr	spsr_cxsf, \rpsr
#if defined(CONFIG_CPU_V6) || defined(CONFIG_CPU_32v6K)
	@ We must avoid clrex due to Cortex-A15 erratum #830321
	sub	r0, sp, #4			@ uninhabited address
	strex	r1, r2, [r0]			@ clear the exclusive monitor
#endif
	ldmia	sp, {r0 - pc}^			@ load r0 - pc, cpsr
 * this is the entry point to schedule() from kernel preemption
 * off of irq context.
 * Note, that this is called and return with irqs disabled. This will
 * protect us against recursive calling from irq.
 */
asmlinkage __visible void __sched preempt_schedule_irq(void)
{
	enum ctx_state prev_state;

	/* Catch callers which need to be fixed */
	BUG_ON(preempt_count() || !irqs_disabled());

	prev_state = exception_enter();

	do {
		preempt_disable();
		local_irq_enable();
		__schedule(true);
		local_irq_disable();
		sched_preempt_enable_no_resched();
	} while (need_resched());

	exception_exit(prev_state);
}

上面有将handle_arch_irq重新赋值成gic_handle_irq

irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{
	irqreturn_t retval = IRQ_NONE;
	unsigned int flags = 0, irq = desc->irq_data.irq;
	struct irqaction *action = desc->action;//request_irq里设置的action

	while (action) {
		irqreturn_t res;
		res = action->handler(irq, action->dev_id);
		switch (res) {
		case IRQ_WAKE_THREAD:
			__irq_wake_thread(desc, action);/
		case IRQ_HANDLED:
			flags |= action->flags;
			break;
		}
	}
}

irqreturn_t handle_irq_event(struct irq_desc *desc)
{
	irqreturn_t ret;

	desc->istate &= ~IRQS_PENDING;
	irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
	raw_spin_unlock(&desc->lock);

	ret = handle_irq_event_percpu(desc);

	raw_spin_lock(&desc->lock);
	irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
	return ret;
}
非gpio方式的中断默认处理函数为handle_fasteoi_irq
void handle_fasteoi_irq(struct irq_desc *desc)
{
	struct irq_chip *chip = desc->irq_data.chip;
	if (!irq_may_run(desc))
		goto out;
	desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
	kstat_incr_irqs_this_cpu(desc);
	if (desc->istate & IRQS_ONESHOT)
		mask_irq(desc);
	handle_irq_event(desc);
}
//根据硬件irq从数组里直接获取virq
unsigned int irq_find_mapping(struct irq_domain *domain,irq_hw_number_t hwirq)
{
	/* Check if the hwirq is in the linear revmap. */
	if (hwirq < domain->revmap_size)
		return domain->linear_revmap[hwirq];
}

#define __irq_enter()
	do {
		preempt_count_add(HARDIRQ_OFFSET);
	} while (0)
/*
 * Enter an interrupt context.
 */
void irq_enter(void)
{
	__irq_enter();
}

/*
 * Exit an interrupt context. Process softirqs if needed and possible:
 */
void irq_exit(void)
{
	preempt_count_sub(HARDIRQ_OFFSET);
}

static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
	desc->handle_irq(desc);
}

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

	if (!desc)
		return -EINVAL;
	generic_handle_irq_desc(desc);
	return 0;
}

int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
			bool lookup, struct pt_regs *regs)
{
	struct pt_regs *old_regs = set_irq_regs(regs);//保存中断寄存器
	unsigned int irq = hwirq;
	irq_enter();//进入中断上下文
	irq = irq_find_mapping(domain, hwirq);//找出virq
	generic_handle_irq(irq);//找到相应的handle函数
	irq_exit();//退出中断上下文
	set_irq_regs(old_regs);//恢复中断寄存器

static inline int handle_domain_irq(struct irq_domain *domain,
				    unsigned int hwirq, struct pt_regs *regs)
{
	return __handle_domain_irq(domain, hwirq, true, regs);
}

static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
	u32 irqstat, irqnr;
	struct gic_chip_data *gic = &gic_data[0];
	void __iomem *cpu_base = gic_data_cpu_base(gic);

	do {
		irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
		irqnr = irqstat & GICC_IAR_INT_ID_MASK;

		if (likely(irqnr > 15 && irqnr < 1021)) {
			handle_domain_irq(gic->domain, irqnr, regs);
			continue;
		}
		if (irqnr < 16) {
			writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
			
#ifdef CONFIG_SMP
			smp_rmb();
			handle_IPI(irqnr, regs);
#endif
			continue;
		}
		break;
	} while (1);
}

 上面的描述中,非gpio方式的中断处理函数handle_irq为handle_fasteoi_irq,但我们经常交道是gpio方式的中断,在相应的gpio
平台驱动中会重新赋值,如

irq_set_chained_handler(irq, ap_irq_handler);
static inline void irq_set_chained_handler(unsigned int irq, irq_flow_handler_t handle)
{
	__irq_set_handler(irq, handle, 1, NULL);
}

void __irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
		  const char *name)
{
	unsigned long flags;
	struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);

	if (!desc)
		return;

	__irq_do_set_handler(desc, handle, is_chained, name);
	irq_put_desc_busunlock(desc, flags);
}

__irq_do_set_handler(struct irq_desc *desc, irq_flow_handler_t handle,
		     int is_chained, const char *name)
{
	desc->handle_irq = handle;
}

https://www.cnblogs.com/arnoldlu/p/8659981.html

http://www.wowotech.net/irq_subsystem/interrupt_subsystem_architecture.html

https://blog.csdn.net/sunsissy/article/details/73791470

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值