从零开始之驱动发开、linux驱动(十、linux的中断初始化流程)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_16777851/article/details/82556519

上一节我们已经对linux中ram的异常处理有了一个大概流程有了一个了解。

本节我们主要来分析中断的处理流程。

正常情况下会有两种情况进入irq模式,一种是从usr模式,另一种是svc模式。

我们这里就以usr模式来分析。

__irq_usr:
	usr_entry                    /* 保存中断上下文 */
	kuser_cmpxchg_check          /* 空函数 */
	irq_handler                  /* 中断 */
	get_thread_info tsk          /* 获取保存当前task的地址 */
	mov	why, #0
	b	ret_to_user_from_irq    
 UNWIND(.fnend		)
ENDPROC(__irq_usr)


__irq_svc:
	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		)

1.usr_entry保存现场

struct pt_regs {
	unsigned long uregs[18];
};

#define DEFINE(sym, val) \
        asm volatile("\n->" #sym " %0 " #val : : "i" (val))

DEFINE(S_R0,			offsetof(struct pt_regs, ARM_r0));
DEFINE(S_R1,			offsetof(struct pt_regs, ARM_r1));
DEFINE(S_R2,			offsetof(struct pt_regs, ARM_r2));
DEFINE(S_R3,			offsetof(struct pt_regs, ARM_r3));
DEFINE(S_R4,			offsetof(struct pt_regs, ARM_r4));
DEFINE(S_R5,			offsetof(struct pt_regs, ARM_r5));
DEFINE(S_R6,			offsetof(struct pt_regs, ARM_r6));
DEFINE(S_R7,			offsetof(struct pt_regs, ARM_r7));
DEFINE(S_R8,			offsetof(struct pt_regs, ARM_r8));
DEFINE(S_R9,			offsetof(struct pt_regs, ARM_r9));
DEFINE(S_R10,			offsetof(struct pt_regs, ARM_r10));
DEFINE(S_FP,			offsetof(struct pt_regs, ARM_fp));
DEFINE(S_IP,			offsetof(struct pt_regs, ARM_ip));
DEFINE(S_SP,			offsetof(struct pt_regs, ARM_sp));
DEFINE(S_LR,			offsetof(struct pt_regs, ARM_lr));
DEFINE(S_PC,			offsetof(struct pt_regs, ARM_pc));
DEFINE(S_PSR,			offsetof(struct pt_regs, ARM_cpsr));
DEFINE(S_OLD_R0,		offsetof(struct pt_regs, ARM_ORIG_r0));
DEFINE(S_FRAME_SIZE,		sizeof(struct pt_regs));

上面的相当于下面这句
#define    S_FRAME_SIZE    18*4 



	.macro	usr_entry        /* 宏 */
 UNWIND(.fnstart	)
 UNWIND(.cantunwind	)	@ don't unwind the user space
	sub	sp, sp, #S_FRAME_SIZE        /* 保留72字节空间保存中断上下文 */
 ARM(	stmib	sp, {r1 - r12}	)    /* 虽然处于管理模式,但r1~r12是公共的,还是要保存在栈中(注意先加后存) */
 THUMB(	stmia	sp, {r0 - r12}	)

	ldmia	r0, {r3 - r5}            /* r0为usr模式的sp,将之前存在中断堆栈中的r0,lr,cpsr放入r3-r5 */
	add	r0, sp, #S_PC		@ here for interlock avoidance r0 = sp + pc偏移,把返回地址放到r0里面
	mov	r6, #-1			@  ""  ""     ""        ""  r6 = 0xffff,ffff

	str	r3, [sp]		@ save the "real" r0 copied  中断前的r0寄存器保存到当前栈
					@ from the exception stack       当前栈就保存其了r0~r12

	@
	@ We are now ready to fill in the remaining blanks on the stack:
	@
	@  r4 - lr_<exception>, already fixed up for correct return/restart
	@  r5 - spsr_<exception>
	@  r6 - orig_r0 (see pt_regs definition in ptrace.h)
	@
	@ Also, separately save sp_usr and lr_usr
	@
	stmia	r0, {r4 - r6}    /*  r4-r6从pc地址开始入栈,r4-r6的内容依次为lr(pc),cpsr,0xffffffff(注意后加先存) */
 ARM(	stmdb	r0, {sp, lr}^			)  /* r0指向了pc保存地址,sp、lr保存地址介于r0-r12及
pc之间,stmdb将用户态的sp、lr保存在栈中间,保存之后栈内容依次是r0-r12,sp,lr,pc,cpsr,0xffffffff,此时中断前的寄存器等都已经保存到栈里面了(注意先减后存) */
 THUMB(	store_user_sp_lr r0, r1, S_SP - S_PC	)

	@
	@ Enable the alignment trap while in kernel mode
	@
	alignment_trap r0, .LCcralign

	@
	@ Clear FP to mark the first stack frame
	@
	zero_fp       /* 里面是空的 */

#ifdef CONFIG_IRQSOFF_TRACER    /* 没定义 */
	bl	trace_hardirqs_off
#endif    
	ct_user_exit save = 0       /* 里面是空的 */
	.endm

分析上面的,只要把握好汇编中的,stmib,stmia,stmdb这些指令的存放顺序,就能发现,其实最终就是按下图存放到栈中了。

sp最终还是指向r0

2.kuser_cmpxchg_check

	.macro	kuser_cmpxchg_check
#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \
    !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG)
#ifndef CONFIG_MMU
#warning "NPTL on non MMU needs fixing"
#else
	@ Make sure our user space atomic helper is restarted
	@ if it was interrupted in a critical region.  Here we
	@ perform a quick test inline since it should be false
	@ 99.9999% of the time.  The rest is done out of line.
	cmp	r4, #TASK_SIZE
	blhs	kuser_cmpxchg64_fixup
#endif
#endif
	.endm

查看.config,CONFIG_CPU_32v6K被定义了,则上面那个检测的宏为空,什么都不执行

3.irq_handler

	.macro	irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
	ldr	r1, =handle_arch_irq    /* 处理器相关中断处理函数地址 */
	mov	r0, sp                  /* sp栈保存了中断上下文 */
	adr	lr, BSYM(9997f)         /* 把9997标号的地址存入lr中,因为irq_handler是宏,所以最终的lr地址是调用irq_handler的下一条指令地址,即__irq_usr中的get_thread_info tsk指令 */
	ldr	pc, [r1]                /* 跳转到irq中断处理函数 */
#else
	arch_irq_handler_default
#endif
9997:        /* 标号在这里 */
	.endm

irq_handler的处理有两种配置。一种是配置了CONFIG_MULTI_IRQ_HANDLER。这种情况下,linux kernel允许run time设定irq handler。如果我们需要一个linux kernel image支持多个平台,这是就需要配置这个选项。另外一种是传统的linux的做法,irq_handler实际上就是arch_irq_handler_default.

(1)对于第一种方式

直接跳转到handle_arch_irq的地址处开始执行,而这个地址就是个C函数的地址,并且是可以动态设置的,由此就进入的C代码阶段进行处理了,可以搜索一下这个函数地址在哪里赋值就能跟踪到C代码处理部分了。


void __init setup_arch(char **cmdline_p)
{
	const struct machine_desc *mdesc;

	setup_processor();
	mdesc = setup_machine_fdt(__atags_pointer);
	if (!mdesc)
		mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);

    ......

#ifdef CONFIG_MULTI_IRQ_HANDLER
	handle_arch_irq = mdesc->handle_irq;   /* 初始化阶段设置 */
#endif

    ......
}



#ifdef CONFIG_MULTI_IRQ_HANDLER
void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
	if (handle_arch_irq)
		return;

	handle_arch_irq = handle_irq;        /* 调用函数动态设置 */
}
#endif

(2)第二种方式 

第二种就是比较老的做法了

get_irqnr_preamble和get_irqnr_and_base两个宏由machine级的代码定义,目的就是从中断控制器中获得IRQ编号,紧接着就调用asm_do_IRQ,从这个函数开始,中断程序进入C代码中,传入的参数是IRQ编号和寄存器结构指针。

	.macro	arch_irq_handler_default
	get_irqnr_preamble r6, lr
1:	get_irqnr_and_base r0, r2, r6, lr
	movne	r1, sp
	@
	@ routine called with r0 = irq number, r1 = struct pt_regs *
	@
	adrne	lr, BSYM(1b)
	bne	asm_do_IRQ

#ifdef CONFIG_SMP
	/*
	 * XXX
	 *
	 * this macro assumes that irqstat (r2) and base (r6) are
	 * preserved from get_irqnr_and_base above
	 */
	ALT_SMP(test_for_ipi r0, r2, r6, lr)
	ALT_UP_B(9997f)
	movne	r1, sp
	adrne	lr, BSYM(1b)
	bne	do_IPI
#endif
9997:
	.endm

/*
 * asm_do_IRQ is the interface to be used from assembly code.
 */
asmlinkage void __exception_irq_entry
asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
	handle_IRQ(irq, regs);
}

网上基本都是讲第二种比较老的(估计韦东山老师讲的第二种,大家都也只分析了第二种吧),

我们的.config里面定义了这个宏,所以执行下面前面部分

详细注意过的伙伴应该发现了handle_arch_irq   其实在上一节已经出现了,为保留的4字节空间。

该位置的内容在初始化阶段被赋值为真正的中断处理函数的地址。

我们这里就分析第一种,它的初始化位于

head_common.S
    b start_kernel(void);
        ....
        local_irq_disable();    /* 关中断 */
        ....
        setup_arch(&command_line);
            paging_init(mdesc);
                devicemaps_init(mdesc);
                    early_trap_init(vectors);    /* 设置异常向量表 */
            ...
            handle_arch_irq = mdesc->handle_irq;    /* 默认handle_irq还是空 */
 
        ....
        trap_init();            /* 空函数 */
        ....
        early_irq_init();       /* 初始化irq_desc数组,这里 */
        init_IRQ();             /* 芯片相关的中断的初始化
        ....
        local_irq_enable();    /* 开中断 */

1.,初始化irq_desc数组空间一些通用的参数

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

int __init early_irq_init(void)
{
	int count, i, node = first_online_node;
	struct irq_desc *desc;

	init_irq_default_affinity();

	printk(KERN_INFO "NR_IRQS:%d\n", NR_IRQS);

	desc = irq_desc;
	count = ARRAY_SIZE(irq_desc);    

	for (i = 0; i < count; i++) {        /* 遍历整个lookup table,对每一个entry进行初始化*/ 
		desc[i].kstat_irqs = alloc_percpu(unsigned int);    /* 分配per cpu的irq统计信息需要的内存  */
		alloc_masks(&desc[i], GFP_KERNEL, node);            /* 分配中断描述符中需要的cpu mask内存  */
		raw_spin_lock_init(&desc[i].lock);                  /* 初始化spin lock  */
		lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
		desc_set_defaults(i, &desc[i], node, NULL);         /* 设定default值  */
	}
	return arch_early_irq_init();    /* 调用arch相关的初始化函数(我们这里为空) */
}

2.关于machine_desc,这个是在启动阶段通过命令行或设备树传过来的机器码找到的具体的machine

void __init setup_arch(char **cmdline_p)
{
	const struct machine_desc *mdesc;

	setup_processor();
	mdesc = setup_machine_fdt(__atags_pointer);
	if (!mdesc)
		mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);  /* 我们是通过命令行传的参数__atags_pointer为命令行参数起始地址,__machine_arch_type为机器码 */
	machine_desc = mdesc;
	machine_name = mdesc->name;        /* "SMDKV210" */

	if (mdesc->reboot_mode != REBOOT_HARD)
		reboot_mode = mdesc->reboot_mode;

	init_mm.start_code = (unsigned long) _text;
	init_mm.end_code   = (unsigned long) _etext;
	init_mm.end_data   = (unsigned long) _edata;
	init_mm.brk	   = (unsigned long) _end;

	/* populate cmd_line too for later use, preserving boot_command_line */
	strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
	*cmdline_p = cmd_line;

	parse_early_param();

	early_paging_init(mdesc, lookup_processor_type(read_cpuid_id()));
	setup_dma_zone(mdesc);
	sanity_check_meminfo();
	arm_memblock_init(mdesc);

	paging_init(mdesc);
	request_standard_resources(mdesc);

	if (mdesc->restart)
		arm_pm_restart = mdesc->restart;    

	unflatten_device_tree();

	arm_dt_init_cpu_maps();
	psci_init();
#ifdef CONFIG_SMP
	if (is_smp()) {
		if (!mdesc->smp_init || !mdesc->smp_init()) {
			if (psci_smp_available())
				smp_set_ops(&psci_smp_ops);
			else if (mdesc->smp)
				smp_set_ops(mdesc->smp);
		}
		smp_init_cpus();
		smp_build_mpidr_hash();
	}
#endif

	if (!is_smp())
		hyp_mode_check();

	reserve_crashkernel();

#ifdef CONFIG_MULTI_IRQ_HANDLER
    /* /* 我们的s5pv210的mdesc在下面,默认是没有初始化的 */ */
	handle_arch_irq = mdesc->handle_irq;        /* 设置中断处理函数为mdesc->handle_irq */
#endif

#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
	conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
	conswitchp = &dummy_con;
#endif
#endif

	if (mdesc->init_early)
		mdesc->init_early();
}

我们s5pv210的machine_desc 在下面定定义

#define MACHINE_START(_type,_name)			\
static const struct machine_desc __mach_desc_##_type	\
 __used							\
 __attribute__((__section__(".arch.info.init"))) = {	\
	.nr		= MACH_TYPE_##_type,		\
	.name		= _name,

#define MACHINE_END				\
};

#define DT_MACHINE_START(_name, _namestr)		\
static const struct machine_desc __mach_desc_##_name	\
 __used							\
 __attribute__((__section__(".arch.info.init"))) = {	\
	.nr		= ~0,				\
	.name		= _namestr,

/* 可以看到我们s5pv210的三星默认没放在这里放置中断处理函数 */
MACHINE_START(SMDKV210, "SMDKV210")
	/* Maintainer: Kukjin Kim <[email protected]> */
	.atag_offset	= 0x100,
	.init_irq	= s5pv210_init_irq,
	.map_io		= smdkv210_map_io,
	.init_machine	= smdkv210_machine_init,
	.init_time	= samsung_timer_init,
	.restart	= s5pv210_restart,
	.reserve	= &smdkv210_reserve,
MACHINE_END

既然我们的在默认结构体里没放置,那三星的工程师是在那里放置的呢?

我查了好久,发现起始它放在了s5pv210_init_irq里面了,最终在下面的红框里面的init_IRQ里面调用了

3.init_IRQ

void __init init_IRQ(void)
{
	int ret;

	if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)    /* 没定义 */
		irqchip_init();
	else
		machine_desc->init_irq();    /* 执行这个,我们芯片的是s5pv210_init_irq */

	if (IS_ENABLED(CONFIG_OF) && IS_ENABLED(CONFIG_CACHE_L2X0) &&    /* 也没定义 */
	    (machine_desc->l2c_aux_mask || machine_desc->l2c_aux_val)) {
		outer_cache.write_sec = machine_desc->l2c_write_sec;
		ret = l2x0_of_init(machine_desc->l2c_aux_val,
				   machine_desc->l2c_aux_mask);
		if (ret)
			pr_err("L2C: failed to init: %d\n", ret);
	}
}

3.1s5pv210_init_irq

void __init s5pv210_init_irq(void)
{
	u32 vic[4];	/* S5PV210 supports 4 VIC */

	/* All the VICs are fully populated. */
	vic[0] = ~0;
	vic[1] = ~0;    /* 0xffff,ffff */
	vic[2] = ~0;
	vic[3] = ~0;    

	s5p_init_irq(vic, ARRAY_SIZE(vic));    /* 初始化 */
}

3.2、s5p_init_irq

void __init s5p_init_irq(u32 *vic, u32 num_vic)
{
#ifdef CONFIG_ARM_VIC
	int irq;

	/* initialize the VICs */
	for (irq = 0; irq < num_vic; irq++)        /* 我们s5pv210有4个vic,分四次初始化 */
		vic_init(VA_VIC(irq), VIC_BASE(irq), vic[irq], 0);
#endif
}

这里的VIC_BASE,VA_VIC查看我前面的字符设备的六,多个led,里面描述了系统启动后的静态映射地址
上面两个就是寄存器中被映射为高端内存的虚拟地址,我们初始化就是这些寄存器

3.3、vic_init

/**
 * vic_init() - initialise a vectored interrupt controller
 * @base: iomem base address
 * @irq_start: starting interrupt number, must be muliple of 32
 * @vic_sources: bitmask of interrupt sources to allow
 * @resume_sources: bitmask of interrupt sources to allow for resume
 */
void __init vic_init(void __iomem *base, unsigned int irq_start,
		     u32 vic_sources, u32 resume_sources)
{
	__vic_init(base, 0, irq_start, vic_sources, resume_sources, NULL);
}

3.4、__vic_init

void __init __vic_init(void __iomem *base, int parent_irq, int irq_start,
			      u32 vic_sources, u32 resume_sources,
			      struct device_node *node)
{
	unsigned int i;
	u32 cellid = 0;
	enum amba_vendor vendor;

    /* 得到应急厂商的vic的id */
	/* Identify which VIC cell this one is, by reading the ID */
	for (i = 0; i < 4; i++) {
		void __iomem *addr;
		addr = (void __iomem *)((u32)base & PAGE_MASK) + 0xfe0 + (i * 4);
		cellid |= (readl(addr) & 0xff) << (8 * i);
	}
	vendor = (cellid >> 12) & 0xff;
	printk(KERN_INFO "VIC @%p: id 0x%08x, vendor 0x%02x\n",
	       base, cellid, vendor);

	switch(vendor) {
	case AMBA_VENDOR_ST:
		vic_init_st(base, irq_start, vic_sources, node);
		return;
	default:
		printk(KERN_WARNING "VIC: unknown vendor, continuing anyways\n");
		/* fall through */
	case AMBA_VENDOR_ARM:    /* 我们是arm */
		break;
	}

	/* Disable all interrupts initially. */
	vic_disable(base);    /* 关所有中断 */

	/* Make sure we clear all existing interrupts */
	vic_clear_interrupts(base);    /* 清除中断 */

	vic_init2(base);    /* 初始化中断控制器 */

    /* 注册vic,比较重要 */
	vic_register(base, parent_irq, irq_start, vic_sources, resume_sources, node);
}

3.5、vic_register

从S5PV210手册我们就知道有4组寄存器

static struct vic_device vic_devices[CONFIG_ARM_VIC_NR];



/**
 * vic_register() - Register a VIC.
 * @base: The base address of the VIC.
 * @parent_irq: The parent IRQ if cascaded, else 0.
 * @irq: The base IRQ for the VIC.
 * @valid_sources: bitmask of valid interrupts
 * @resume_sources: bitmask of interrupts allowed for resume sources.
 * @node: The device tree node associated with the VIC.
 *
 * Register the VIC with the system device tree so that it can be notified
 * of suspend and resume requests and ensure that the correct actions are
 * taken to re-instate the settings on resume.
 *
 * This also configures the IRQ domain for the VIC.
 */
static void __init vic_register(void __iomem *base, unsigned int parent_irq,
				unsigned int irq,
				u32 valid_sources, u32 resume_sources,
				struct device_node *node)
{
	struct vic_device *v;
	int i;

	if (vic_id >= ARRAY_SIZE(vic_devices)) {    /* 最多有4组 */
		printk(KERN_ERR "%s: too few VICs, increase CONFIG_ARM_VIC_NR\n", __func__);
		return;
	}

    /* 注册vic各种信息到vic_devices结构体数组 */
	v = &vic_devices[vic_id];    
	v->base = base;
	v->valid_sources = valid_sources;
	v->resume_sources = resume_sources;    
	set_handle_irq(vic_handle_irq);        /* 这句就是我们关注的,设置中断函数执行的代码 */    
	vic_id++;

	if (parent_irq) {    /* parent_irq = 0  */
		irq_set_handler_data(parent_irq, v);
		irq_set_chained_handler(parent_irq, vic_handle_irq_cascaded);
	}

	v->domain = irq_domain_add_simple(node, fls(valid_sources), irq,
					  &vic_irqdomain_ops, v);
	/* create an IRQ mapping for each valid IRQ */
	for (i = 0; i < fls(valid_sources); i++)
		if (valid_sources & (1 << i))
			irq_create_mapping(v->domain, i);
	/* If no base IRQ was passed, figure out our allocated base */
	if (irq)  /* 在我们没设置irq的情况下,系统会初始化成默认的,否则要是中断了,可能会跑飞掉 */
		v->irq = irq;        
	else
		v->irq = irq_find_mapping(v->domain, 0);
}

3.6、设置中断处理函数

回忆一下,我们可以初始化时在setup_arch设置,也可以动态调用下面这个函数设置

#ifdef CONFIG_MULTI_IRQ_HANDLER
void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
	if (handle_arch_irq)
		return;

	handle_arch_irq = handle_irq;
}
#endif

3.7、vic_handle_irq中断处理函数

/*
 * Keep iterating over all registered VIC's until there are no pending
 * interrupts.这里注释也说明了,要所有的挂起都指行完,所以即使查找了4个vic,也可能还有挂起,
 * 必须指向完中断处理后,再检查一次,确保没有中断挂起再退出
 */
static void __exception_irq_entry vic_handle_irq(struct pt_regs *regs)
{
	int i, handled;

	do {
		for (i = 0, handled = 0; i < vic_id; ++i)    /* 因为我们有4组vic,发生中断后每组的都要检查 */
			handled |= handle_one_vic(&vic_devices[i], regs);    
	} while (handled);
}

/*
 * Handle each interrupt in a single VIC.  Returns non-zero if we've
 * handled at least one interrupt.  This reads the status register
 * before handling each interrupt, which is necessary given that
 * handle_IRQ may briefly re-enable interrupts for soft IRQ handling.
 */
static int handle_one_vic(struct vic_device *vic, struct pt_regs *regs)
{
	u32 stat, irq;
	int handled = 0;

	while ((stat = readl_relaxed(vic->base + VIC_IRQ_STATUS))) {    /* 查看该vic状态是否有中断 */
		irq = ffs(stat) - 1;        /* 得到中断号 */
		handle_IRQ(irq_find_mapping(vic->domain, irq), regs);    /* 指行中断 */
		handled = 1;        /* 标记中断 */
	}

	return handled;
}

3.8、handle_IRQ

/*
 * handle_IRQ handles all hardware IRQ's.  Decoded IRQs should
 * not come via this function.  Instead, they should provide their
 * own 'handler'.  Used by platform code implementing C-based 1st
 * level decoding.
 */
void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{
	struct pt_regs *old_regs = set_irq_regs(regs);    /* 得到上下文的保存地址 */

	irq_enter();    /* 指行中断要上锁之类 */

	/*
	 * Some hardware gives randomly wrong interrupts.  Rather
	 * than crashing, do something sensible.
	 */
	if (unlikely(irq >= nr_irqs)) {    /* 中断号超范围 */
		if (printk_ratelimit())
			printk(KERN_WARNING "Bad IRQ%u\n", irq);
		ack_bad_irq(irq);               
	} else {
		generic_handle_irq(irq);      /* 正常指向中断处理函数 */
	}

	irq_exit();       /* 指行完中断解锁 */
	set_irq_regs(old_regs);           /* 恢复上下文信息 */
}

3.9、generic_handle_irq

/**
 * generic_handle_irq - Invoke the handler for a particular irq
 * @irq:	The irq number to handle
 *
 */
int generic_handle_irq(unsigned int irq)
{
	struct irq_desc *desc = irq_to_desc(irq);    /* 得到该中断号的所有的描述信息 */

	if (!desc)
		return -EINVAL;
	generic_handle_irq_desc(irq, desc);          /* 指行我们注册的中断处理函数 */
	return 0;
}

/*
 * Architectures call this to let the generic IRQ layer
 * handle an interrupt. If the descriptor is attached to an
 * irqchip-style controller then we call the ->handle_irq() handler,
 * and it calls __do_IRQ() if it's attached to an irqtype-style controller.
 */
static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
	desc->handle_irq(irq, desc);        /* 真正的中断处理函数,需要我们使用前注册到中断描述表中 */
}

总结一下中断的初始化流程。

head_common.S
    b start_kernel(void);
        ....
        local_irq_disable();    /* 关总中断 */
        ....
        setup_arch(&command_line);
            setup_machine_fdt/setup_machine_tags    /* 根据设备树或命令行的机器tag查找得到machine_desc
            paging_init(mdesc);        
                devicemaps_init(mdesc);
                    early_trap_init(vectors);    /* 设置异常向量表 */
            ...
            handle_arch_irq = mdesc->handle_irq;    /* 通常是用machine_desc里面的handle_irq函数填充中断动态设置的中断处理函数 */
            ...
 
        ....
        trap_init();            /* 空函数 */
        ....
        early_irq_init();       /* 初始化irq_desc数组(只是简单的填充) */
        init_IRQ();             /* 芯片相关的中断的初始化
            machine_desc->init_irq();    /* 初始化所有中断 */
            s5pv210_init_irq();        /* 我们具体芯片的是这个函数 */
                s5p_init_irq(vic, ARRAY_SIZE(vic));    /* 初始化四组VIC */
                    vic_init(VA_VIC(irq), VIC_BASE(irq), vic[irq], 0);
                        __vic_init(base, 0, irq_start, vic_sources, resume_sources, NULL);
                            vic_disable(base);    /* 关每一个中断 */
                            vic_clear_interrupts(base);    /* 清每个中断的标志位 */
                            vic_register(base, parent_irq, irq_start, vic_sources, resume_sources, node);        /* 注册每个vic到vic_devices数组  */
                                set_handle_irq(vic_handle_irq);    /* 填充动态的架构相关中断处理函数handle_arch_irq */
                                /* 设置每个irq在irq_desc数组的中断处理函数handle_level_irq,chip,以及chip_data,以及中断号等*/
                                irq_domain_add_simple(node, fls(valid_sources), irq,
					  &vic_irqdomain_ops, v);    
        ....
        local_irq_enable();    /* 开总中断 */

猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/82556519