.section .vectors, "ax", %progbits
.L__vectors_start:
W(b) vector_rst
W(b) vector_und
W(ldr) pc, .L__vectors_start + 0x1000
W(b) vector_pabt
W(b) vector_dabt
W(b) vector_addrexcptn
W(b) vector_irq @当发生中断时执行该指令
W(b) vector_fiq
这些地址就是vector,可以放在0地址,也可以放在0xffff0000(对于这个地址是启动mmu之后才存在的)对于其它芯片,向量所在地址可能不同,但都是用来处理异常打开内核源码 a. 异常向量入口: arch\arm\kernel\entry-armv.S
执行上面的程序之后,最后会执行下面的汇编程序:
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4
.long __irq_usr @ 0 (USR_26 / USR_32)
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
__irq_usr:
usr_entry
kuser_cmpxchg_check
irq_handler
get_thread_info tsk
mov why, #0
b ret_to_user_from_irq
跟踪中断irq_handler,在该文件中查找,可以查到如下程序:
/*
* Interrupt handling.
*/
.macro irq_handler
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
ldr r1, =handle_arch_irq --------------------->1
mov r0, sp
badr lr, 9997f
ldr pc, [r1]
会调用该函数,该函数应该会读取寄存器,是哪个中断发生了,该函数会被下面的程序调用,
int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
if (handle_arch_irq)
return -EBUSY;
handle_arch_irq = handle_irq; //---------------1.1
return 0;
}
在linux内核中对于每一种芯片都会调用set_handle_irq函数设置自己的芯片的对应的函数,譬如:
set_handle_irq(s3c24xx_handle_irq);
对于2440芯片用s3c24xx_handle_irq函数处理中断,s3c24xx_handle_irq函数也就是处理中断的C函数入口,该函数的定义如下:
asmlinkage void __exception_irq_entry s3c24xx_handle_irq(struct pt_regs *regs)
{
do {
if (likely(s3c_intc[0]))
if (s3c24xx_handle_intc(s3c_intc[0], regs, 0))
continue;
if (s3c_intc[2])
if (s3c24xx_handle_intc(s3c_intc[2], regs, 64))
continue;
break;
} while (1);
}
中断调用过程框架分析:
中断控制器可以看出有32bit,每一位代表一种中断,也就是说这个中断控制器可以向CPU发出32种中断,每一种中断的处理函数
都不一样,那么在linux内核中怎样管理这些中断和中断处理函数呢?
1、最简单方法是创建指针数组,每一项对应一个中断,在这个中断里存放处理函数,这个数组项用irq_desc中断结构体来表示
irq_desc中断结构体中的handle_irq是中断的总的处理函数,action指向一个或者多个struct irqaction,对于每一号中断,在irq_desc数组中都会有对应的项,在每一项都有会有action指向struct irqaction,在struct irqaction中存放具体的处理函数handler。
struct irqaction {
irq_handler_t handler; //存放具体的处理函数
void *dev_id;
void __percpu *percpu_dev_id;
struct irqaction *next;
irq_handler_t thread_fn;
struct task_struct *thread;
struct irqaction *secondary;
unsigned int irq;
unsigned int flags;
unsigned long thread_flags;
unsigned long thread_mask;
const char *name;
struct proc_dir_entry *dir;
} ____cacheline_internodealigned_in_smp;
在irq_desc中有一个handle_irq,在action指向struct irqaction中有handler,他们都是函数指针,他们有什么联系呢?
handle_irq:
- 调用action链表中的handler(调用我们提供的具体的函数);
- 清中断(是芯片相关的操作) irq_data.chip含有中断相关的处理(使能中断、屏蔽中断、清中断...)
- irq_desc结构体中有一个irq_data结构体,
struct irq_data { u32 mask;----------TODO unsigned int irq;--------IRQ number unsigned long hwirq;-------HW interrupt ID unsigned int node;-------NUMA node index unsigned int state_use_accessors;--------底层状态,参考IRQD_xxxx struct irq_chip *chip;----------该中断描述符对应的irq chip数据结构 struct irq_domain *domain;--------该中断描述符对应的irq domain数据结构 void *handler_data;--------和外设specific handler相关的私有数据 void *chip_data;---------和中断控制器相关的私有数据 struct msi_desc *msi_desc; cpumask_var_t affinity;-------和irq affinity相关 };
struct irq_chip { struct device *parent_device; const char *name; unsigned int (*irq_startup)(struct irq_data *data); void (*irq_shutdown)(struct irq_data *data); void (*irq_enable)(struct irq_data *data); void (*irq_disable)(struct irq_data *data); ...... };
发生中断时内核的执行过程:
cpu跳到vector_irq
, 保存现场, 调用C函数handle_arch_irq
handle_arch_irq:
a. 读 int controller, 得到hwirq
b. 根据hwirq得到virq
c. 调用irq_desc[virq].handle_irq
如果该中断没有子中断, irq_desc[virq].handle_irq的操作:
a. 取出irq_desc[virq].action链表中的每一个handler, 执行它
b. 使用irq_desc[virq].irq_data.chip的函数清中断
通过handle_irq来调用我们提供的处理函数、清除中断,对于我们写驱动程序的人,只需要写具体的中断处理函数就可以了,在具体的中断处理函数中我们只需要关系我们需要实现的功能,不需要关系芯片相关的中断使能、中断清除等工作。
我们现在加上一个芯片,加上一个或芯片,当网卡或者摄像头有数据时会产生中断,(网卡和摄像头公用一个中断引脚),但是它们有各自的中断处理函数(譬如:网卡的中断处理函数是irq_net,摄像头的中断处理函数是irq_cam),其实这并不复杂,irq_desc结构体中的action链表,由于在0号中断上面有两个设备,action指向的链表应该至少含有两个struct irqaction结构体节点,如下图所示:
像这样公用一个中断也就是共享中断,对于共享中断来说action链表中的每一个handler函数都会被执行一次,具体是哪个中断
必须交给硬件的中断函数来区分。
如上图所示,当发生外部中断INT4、INT5、INT6、INT7之一时,子中断控制器(sub interrupt controller )都会向上一级的中断控制器(interrupt controller)发出信号,上一级的中断控制器(interrupt controller)向CPU发出中断,然后CPU读取寄存器(譬如:INPUD)时就会发现是4号中断,然后读取SUBSRCPND寄存器具体是哪个子中断,那么我们irq_data结构体中的handle_irq指向s3c_irq_demux(中断分发函数),如下图所示:
- hwirq(表示硬件中断号)
- (virq(表示虚拟中断号)
s3c_irq_demux做了以下几件事
如果该中断是由子中断产生, irq_desc[virq].handle_irq的操作:
a. 读 sub int controller, 得到hwirq'
b. 根据hwirq'得到virq
c. 调用 irq_desc[virq].handle_irq
硬件中断号和虚拟中断号
我们可以通过硬件中断号,得到虚拟中断号,这些虚拟中断号就是irq_desc[]数组项的下标 可以定义这么一个公式
virq = hwirq + offset + 1 = hwirq + 16 virq = hwirq' + offset + 2 = hwirq' + 58 + 16
根据虚拟中断号virq ==> 得到irq_desc.handle_irq ==> 调用我们注册的具体的中断处理函。
我们在EINT5接上一个按键,按键的中断处理函数是irq_key,我们注册这个中断时会注册对应的中断号(这里是37)根据中断号就会找到对应的irq_desc结构体,然后就会创建一个irqaction结构体,让irqaction.handler指向irq_key(我们提供的具体的函数),如下图所示:
当按键被按下后,子中断控制器(sub interrupt controller )都会向上一级的中断控制器(interrupt controller)发出信号,上一级的中断控制器(interrupt controller)向CPU发出中断,然后CPU读取寄存器(INPUD)时就会发现是4号中断,然后执行irq_desc.handle_irq ==>s3c_irq_demux函数==>调用irq_desc[virtirq].handle_irq(遍历执行action链表) ==》irq_key。
源程序分析:
在前面说过对于S3C2440, s3c24xx_handle_irq 是用于处理中断的C语言入口函数。
asmlinkage void __exception_irq_entry s3c24xx_handle_irq(struct pt_regs *regs)
{
do {
if (likely(s3c_intc[0]))
if (s3c24xx_handle_intc(s3c_intc[0], regs, 0)) //------------------1
continue;
if (s3c_intc[2])
if (s3c24xx_handle_intc(s3c_intc[2], regs, 64))
continue;
break;
} while (1);
}
1、
static inline int s3c24xx_handle_intc(struct s3c_irq_intc *intc,
struct pt_regs *regs, int intc_offset)
{
int pnd;
int offset;
pnd = readl_relaxed(intc->reg_intpnd); // 读intpnd寄存器判断是哪个中断等待处理
if (!pnd)
return false;
/* non-dt machines use individual domains */
if (!irq_domain_get_of_node(intc->domain))
intc_offset = 0;
/* We have a problem that the INTOFFSET register does not always
* show one interrupt. Occasionally we get two interrupts through
* the prioritiser, and this causes the INTOFFSET register to show
* what looks like the logical-or of the two interrupt numbers.
*
* Thanks to Klaus, Shannon, et al for helping to debug this problem
*/
offset = readl_relaxed(intc->reg_intpnd + 4);
/* Find the bit manually, when the offset is wrong.
* The pending register only ever contains the one bit of the next
* interrupt to handle.
*/
if (!(pnd & (1 << offset)))
offset = __ffs(pnd); //获得中断号
handle_domain_irq(intc->domain, intc_offset + offset, regs); // --------------1.1
return true;
}
1.1、
/*
* Convert a HW interrupt number to a logical one using a IRQ domain,
* and handle the result interrupt number. Return -EINVAL if
* conversion failed. Providing a NULL domain indicates that the
* conversion has already been done.
*/
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
bool lookup, struct pt_regs *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); // ----------------1.1.1
}
1.1.1、
/**
* __handle_domain_irq - Invoke the handler for a HW irq belonging to a domain
* @domain: The domain where to perform the lookup
* @hwirq: The HW irq number to convert to a logical one
* @lookup: Whether to perform the domain lookup or not
* @regs: Register file coming from the low-level handling code
*
* Returns: 0 on success, or -EINVAL if conversion has failed
*/
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;
int ret = 0;
irq_enter();
#ifdef CONFIG_IRQ_DOMAIN
if (lookup)
irq = irq_find_mapping(domain, hwirq); //根据硬件中断号计算出虚拟中断号
#endif
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (unlikely(!irq || irq >= nr_irqs)) {
ack_bad_irq(irq);
ret = -EINVAL;
} else {
generic_handle_irq(irq); // ---------1.1.1.1
}
irq_exit();
set_irq_regs(old_regs);
return ret;
}
1.1.1.1、
/**
* 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); //根据虚拟终端号获得irq_desc结构体
if (!desc)
return -EINVAL;
generic_handle_irq_desc(desc); //调用desc->handle_irq()
return 0;
}