kernel 里面的中断代码分析
kernel 里面的中断代码分析
kernel 里面的中断代码分析
Create successful ePaper yourself
Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.
Index :<br />
1.1 涉及到的代码简单分析<br />
<strong>kernel</strong> <strong>里面的中断代码分析</strong><br />
1.2 中断处理函数是如何被 called ?(转载的,参考,了解流程即可,具体代<br />
码不必深究)<br />
1.3 s3c2410 里面的中断控制器的细节分析,以及 general 的中断原理<br />
1.4 外部中断控制器的分析(与GPIO有关)<br />
1.1 涉及到的代码简单分析:<br />
大致分析:<br />
以前看 IRQ 的部分, 都没有看到一个部分就是跟 chip 有关的代码。<br />
通过看 request_irq () 还有 setup_irq() 都跟具体的 chip 有关的。<br />
这个跟 chip 有关的代码可以到 arch/arm/mach-smdk2410/irq.c 里面查询。<br />
比如: 跟具体的 cpu 有关的:<br />
for (irqno = IRQ_EINT0; irqno
}<br />
还要注意 <strong>kernel</strong>/irq/chip.c 里面的函数。<br />
比如 do_level_IRQ() 函数,<br />
具体的大家可以分析 handle_level_irq() 和 handle_edge_irq()<br />
硬件中断芯片描述苻<br />
/**<br />
* struct irq_chip - hardware interrupt chip descriptor<br />
*<br />
* @name: name for /proc/interrupts<br />
* @startup: start up the interrupt (defaults to ->enable if NULL)<br />
* @shutdown: shut down the interrupt (defaults to ->disable if NULL)<br />
* @enable: enable the interrupt (defaults to chip->unmask if NULL)<br />
* @disable: disable the interrupt (defaults to chip->mask if NULL)<br />
* @ack: start of a new interrupt<br />
* @mask: mask an interrupt source<br />
* @mask_ack: ack and mask an interrupt source<br />
* @unmask: unmask an interrupt source<br />
* @eoi: end of interrupt - chip level<br />
* @end: end of interrupt - flow level<br />
* @set_affinity: set the CPU affinity on SMP machines<br />
* @retrigger: resend an IRQ to the CPU<br />
* @set_type: set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ<br />
* @set_wake: enable/disable power-management wake-on of an IRQ<br />
*<br />
* @release: release function solely used by UML<br />
* @typename: obsoleted by name, kept as migration helper<br />
*/<br />
struct irq_chip {<br />
。。。<br />
}<br />
对于所有的 cpu 和平台来讲, 通用的 “中断描述符表”<br />
它记录了每个 irq 的所有信息, 包括 irq_handler ,status<br />
/**
* struct irq_desc - interrupt descriptor<br />
*<br />
* @handle_irq: highlevel irq-events handler [if NULL, __do_IRQ()]<br />
* @chip: low level interrupt hardware access<br />
* @handler_data: per-IRQ data for the irq_chip methods<br />
* @chip_data: platform-specific per-chip private data for the chip<br />
* methods, to allow shared chip implementations<br />
* @action: the irq action chain<br />
* @status: status information<br />
* @depth: disable-depth, for nested irq_disable() calls<br />
* @wake_depth: enable depth, for multiple set_irq_wake() callers<br />
* @irq_count: stats field to detect stalled irqs<br />
* @irqs_unhandled: stats field for spurious unhandled interrupts<br />
* @lock: locking for SMP<br />
* @affinity: IRQ affinity on SMP<br />
* @cpu: cpu index useful for balancing<br />
* @pending_mask: pending rebalanced interrupts<br />
* @move_irq: need to re-target IRQ destination<br />
* @dir: /proc/irq/ procfs entry<br />
* @affinity_entry: /proc/irq/smp_affinity procfs entry on SMP<br />
*<br />
* Pad this out to 32 bytes for cache and indexing reasons.<br />
*/<br />
struct irq_desc {<br />
void fastcall (*handle_irq)(unsigned int irq,<br />
struct irq_chip *chip;<br />
void *handler_data;<br />
void *chip_data;<br />
struct irq_desc *desc,<br />
struct pt_regs *regs);<br />
struct irqaction *action; /* IRQ action list */<br />
unsigned int status; /* IRQ status */<br />
unsigned int depth; /* nested irq disables */<br />
unsigned int wake_depth; /* nested wake enables */<br />
unsigned int irq_count; /* For detecting broken IRQs */<br />
unsigned int irqs_unhandled;<br />
spinlock_t lock;<br />
#ifdef CONFIG_SMP<br />
#endif<br />
cpumask_t affinity;<br />
unsigned int cpu;<br />
#if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)<br />
cpumask_t pending_mask;
#endif<br />
unsigned int move_irq; /* need to re-target IRQ dest */<br />
#ifdef CONFIG_PROC_FS<br />
#endif<br />
struct proc_dir_entry *dir;<br />
} ____cacheline_aligned;<br />
重点分析 request_irq() 函数和 setup_irq() 函数 跟移植有关系的部分 。<br />
Kernel/irq/manage.c =>request_irq()<br />
int request_irq(unsigned int irq,<br />
{<br />
irqreturn_t (*handler)(int, void *, struct pt_regs *),<br />
unsigned long irqflags, const char *devname, void *dev_id)<br />
struct irqaction *action;<br />
int retval;<br />
#ifdef CONFIG_LOCKDEP<br />
/*<br />
#endif<br />
* Lockdep wants atomic interrupt handlers:<br />
*/<br />
irqflags |= SA_INTERRUPT;<br />
/*<br />
* Sanity-check: shared interrupts must pass in a real dev-ID,<br />
* otherwise we'll have trouble later trying to figure out<br />
* which interrupt is which (messes up the interrupt freeing<br />
* logic etc).<br />
*/<br />
if ((irqflags & IRQF_SHARED) && !dev_id)<br />
return -EINVAL;<br />
if (irq >= NR_IRQS)<br />
return -EINVAL;<br />
if (irq_desc[irq].status & IRQ_NOREQUEST) //这个要初始化,否则无法 request_irq , 显然这里应该在 <strong>kernel</strong> 启动的<br />
过程调用 irq 相关的函数进行初始化<br />
return -EINVAL;<br />
if (!handler)<br />
return -EINVAL;<br />
action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
}<br />
if (!action)<br />
return -ENOMEM;<br />
action->handler = handler;<br />
action->flags = irqflags;<br />
cpus_clear(action->mask);<br />
action->name = devname;<br />
action->next = NULL;<br />
action->dev_id = dev_id;<br />
select_smp_affinity(irq);<br />
retval = setup_irq(irq, action);<br />
if (retval)<br />
kfree(action);<br />
return retval;<br />
EXPORT_SYMBOL(request_irq);<br />
Kernel/irq/manage.c setup_irq()<br />
/* 内部函数来注册 irqaction<br />
/*<br />
* 典型的用于分配给特定的中断,这些中断体系结构的一部分<br />
* 比如像 2410 手册里面定义的中断那样<br />
* Internal function to register an irqaction - typically used to<br />
* allocate special interrupts that are part of the architecture.<br />
*/<br />
/* 共享中断的 irq , 应该有多个 irqaction<br />
* 我们要做的就是在 irqaction 的链表的末尾<br />
* 填上我们自己的 handler<br />
*/<br />
int setup_irq(unsigned int irq, struct irqaction *new)<br />
{<br />
struct irq_desc *desc = irq_desc + irq;<br />
struct irqaction *old, **p;<br />
unsigned long flags;<br />
int shared = 0;
if (irq >= NR_IRQS)<br />
return -EINVAL;<br />
if (desc->chip == &no_irq_chip) //这里很很关键<br />
/*<br />
return -ENOSYS; //功能没有实现<br />
* Some drivers like serial.c use request_irq() heavily,<br />
* so we have to be careful not to interfere with a<br />
* running system.<br />
*/<br />
if (new->flags & IRQF_SAMPLE_RANDOM) {<br />
}<br />
/*<br />
/*<br />
* This function might sleep, we want to call it first,<br />
* outside of the atomic block.<br />
* Yes, this might clear the entropy pool if the wrong<br />
* driver is attempted to be loaded, without actually<br />
* installing a new handler, but is this really a problem,<br />
* only the sysadmin is able to do this.<br />
*/<br />
rand_initialize_irq(irq);<br />
* The following block of code has to be executed atomically<br />
*/<br />
spin_lock_irqsave(&desc->lock, flags);<br />
// ----------------------------------------------------------------------------------------------------<br />
p = &desc->action;<br />
old = *p;<br />
if (old) {<br />
/*<br />
* Can't share interrupts unless both agree to and are<br />
* the same type (level, edge, polarity). So both flag<br />
* fields must have IRQF_SHARED set and the bits which<br />
* set the trigger type must match.<br />
*/<br />
if (!((old->flags & new->flags) & IRQF_SHARED) ||<br />
((old->flags ^ new->flags) & IRQF_TRIGGER_MASK))<br />
goto mismatch;
#if defined(CONFIG_IRQ_PER_CPU)<br />
#endif<br />
}<br />
/* All handlers must agree on per-cpuness */<br />
if ((old->flags & IRQF_PERCPU) !=<br />
(new->flags & IRQF_PERCPU))<br />
goto mismatch;<br />
/* add new interrupt at end of irq queue */<br />
do {<br />
p = &old->next;<br />
old = *p;<br />
} while (old);<br />
shared = 1;<br />
//-----------------------------------------------------------------------------------------------------------------------<br />
*p = new;<br />
#if defined(CONFIG_IRQ_PER_CPU)<br />
#endif<br />
if (new->flags & IRQF_PERCPU)<br />
desc->status |= IRQ_PER_CPU;<br />
//这边还得设置 chip 级别的设置,看来我们在 porting <strong>kernel</strong> 的时候,可能还要加<br />
这些函数<br />
if (!shared) {<br />
irq_chip_set_defaults(desc->chip);<br />
/* Setup the type (level, edge polarity) if configured: */<br />
if (new->flags & IRQF_TRIGGER_MASK) {<br />
if (desc->chip && desc->chip->set_type)<br />
else<br />
desc->chip->set_type(irq,<br />
/*<br />
new->flags & IRQF_TRIGGER_MASK);<br />
* IRQF_TRIGGER_* but the PIC does not support<br />
* multiple flow-types?<br />
*/<br />
printk(KERN_WARNING "No IRQF_TRIGGER set_type "<br />
"function for IRQ %d (%s)\n", irq,
} else<br />
desc->chip ? desc->chip->name :<br />
"unknown");<br />
compat_irq_chip_set_default_handler(desc);<br />
//这些标志都 clear ,都跟具体的 irq 有关<br />
}<br />
desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING | IRQ_INPROGRESS);<br />
if (!(desc->status & IRQ_NOAUTOEN)) {<br />
} else<br />
desc->depth = 0;<br />
desc->status &= ~IRQ_DISABLED;<br />
if (desc->chip->startup)<br />
else<br />
desc->chip->startup(irq);<br />
desc->chip->enable(irq);<br />
/* Undo nested disables: */<br />
desc->depth = 1;<br />
spin_unlock_irqrestore(&desc->lock, flags);<br />
new->irq = irq;<br />
register_irq_proc(irq);<br />
new->dir = NULL;<br />
register_handler_proc(irq, new);<br />
return 0;<br />
mismatch:<br />
}<br />
spin_unlock_irqrestore(&desc->lock, flags);<br />
if (!(new->flags & IRQF_PROBE_SHARED)) {<br />
}<br />
printk(KERN_ERR "IRQ handler type mismatch for IRQ %d\n", irq);<br />
dump_stack();<br />
return -EBUSY;<br />
当某个外部硬件触发中断的时候, <strong>kernel</strong> 最终会调用到<br />
Arch/arm/<strong>kernel</strong>/irq.c asm_do_IRQ()
*<br />
* do_IRQ handles all hardware IRQ's. Decoded IRQs should not<br />
* come via this function. Instead, they should provide their<br />
* own 'handler'<br />
*/<br />
asmlinkage void asm_do_IRQ(unsigned int irq, struct pt_regs *regs)<br />
{<br />
}<br />
struct irqdesc *desc = irq_desc + irq;<br />
/*<br />
* Some hardware gives randomly wrong interrupts. Rather<br />
* than crashing, do something sensible.<br />
*/<br />
if (irq >= NR_IRQS)<br />
desc = &bad_irq_desc;<br />
irq_enter();<br />
desc_handle_irq(irq, desc, regs);<br />
/* AT91 specific workaround */<br />
irq_finish(irq);<br />
irq_exit();<br />
<strong>kernel</strong> 启动过程中的 IRQ 的初始化 :<br />
start_<strong>kernel</strong> ==>init_IRQ()<br />
void __init init_IRQ(void)<br />
{<br />
/*<br />
int irq;<br />
request_irq():<br />
if (irq_desc[irq].status & IRQ_NOREQUEST) //这个要初始化,否则无法 request_irq , 显然这里应该在 <strong>kernel</strong> 启动的过程<br />
调用 irq 相关的函数进行初始化<br />
*/<br />
return -EINVAL;<br />
for (irq = 0; irq < NR_IRQS; irq++)<br />
irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_DELAYED_DISABLE | IRQ_NOPROBE;
#ifdef CONFIG_SMP<br />
#endif<br />
}<br />
bad_irq_desc.affinity = CPU_MASK_ALL;<br />
bad_irq_desc.cpu = smp_processor_id();<br />
init_arch_irq(); //mach-smdk2410.c ==> .init_irq = s3c24xx_init_irq, //irq.c<br />
1.2 中断处理函数是如何被 called ?<br />
[自己的分析 bob]<br />
中断首先是一个硬件行为, 而处理中断呢, 显然又是一个软件行为,作为 driver writer only 实现中断处理函数, 并与中<br />
断号进行关联也就 ok 了。那么就引出一个问题? 当硬件触发中断的时候,怎么调用到中断处理函数的呢?<br />
要搞清楚这个问题,就要搞清楚,发生中断的时候 CPU 做了什么<br />
以 arm 为例,当产生 IRQ 请求的时候, CPU 做了什么:<br />
1> 处理器切换到特定的中断请求模式(IRQ 模式),表明产生了中断。<br />
2> 前一个模式的 cpsr 被保存到新的模式下的 spsr。<br />
3> PC 指针被保存到新的模式的 lr 寄存器<br />
4> 关中断----在 cpsr 中禁止 I-bit ,会立即阻止其他的 IRQ 产生。<br />
5> 处理器跳转到异常向量表中相应的入口(对于 IRQ , 显然 pc=0x18) 。<br />
说道这里,就涉及到了异常向量表,这个异常向量表就是软件和 CPU 的接口。 CPU 知道一个 source 触<br />
发了中断,怎么调用执行一些函数(汇编,或者 c 语言),就是靠异常向量表(事实上,exception vector table 也是由汇编<br />
组成的)<br />
异常 模式 向量表偏移<br />
复位(reset) SVC +0x00<br />
未定义指令 UND +0x04<br />
软件中断(SWI) SVC +0x08<br />
欲取指终止 ABT +0x0c<br />
数据终止 ABT +0x10<br />
未分配 -- +0x14<br />
IRQ IRQ +0x18<br />
FIQ FIQ +0x1c<br />
表 arm 异常,对应模式及向量表偏移(摘自 arm 体系结构与编程一书)
从这表中, 我们很清楚了, 当触发 IRQ 的时候, CPU 会最后跳入 0x18 这个入口 , 那我们做 <strong>kernel</strong> 的人, 只需在这<br />
个入口填入自己的指令(当然是汇编语句) , 来调用 中断处理函数,可能这样。<br />
中断- cpu jump 到 0x18 ,同时要把 irqno 传入相应的寄存器 调用一个中断通用处理函数比如叫<br />
asm_do_IRQ(unsigned int irqno) asm_do_IRQ() 这个函数根据 irqno 就可以找到对应的中断描述符 然后调用中断<br />
描述符里面的 handler() 了 。<br />
那具体的 <strong>kernel</strong> 里面的细节是什么样的呢? 具体看下面这篇转载的文章即可。代码分析的就比较透彻了, 事实上我们只<br />
要上面说的流程,对于 driver writer 已经足够了。<br />
【转载】<br />
那么 当硬件触发中断的时候, <strong>kernel</strong> 里面的 asm_do_IRQ() 是如何被调用到呢?<br />
遭到一篇很好的文章:<br />
http://hi.baidu.com/wudx05/blog/item/5314935c834f4e41fbf2c0dc.html<br />
现摘录如下:<br />
Armor 的中断向量表放在内存的什么位置?<br />
中断向量表放在 arch/arm/<strong>kernel</strong>/entry-armv.S 这个文件里<br />
__vectors_start:<br />
#swi SYS_ERROR0<br />
b vector_addrexcptn + stubs_offset<br />
b vector_und + stubs_offset<br />
ldr pc, .LCvswi + stubs_offset<br />
b vector_pabt + stubs_offset<br />
b vector_dabt + stubs_offset<br />
b vector_addrexcptn + stubs_offset<br />
b vector_irq + stubs_offset<br />
b vector_fiq + stubs_offset<br />
.globl __vectors_end<br />
__vectors_end:<br />
在 arch/arm/traps.c 中
memcpy((void *)0xffff0000, __vectors_start, __vectors_end - __vectors_start); //中断向<br />
量表被拷贝到 0xffff0000 处,硬件规定的位置<br />
memcpy((void *)0xffff0200, __stubs_start, __stubs_end - __stubs_start);<br />
硬件发生中断后怎样找到中断入口函数的?<br />
从上面的中断向量表中可 以看到,发生中断时系统会跳到 vector_irq + stubs_offset 处运行,这个位置<br />
实际上就是中断入口函数。vector_irq 已经是中断的入口函数了,为什么又要加上 stubs_offset?是因为<br />
b 指令实际上是相对相前 PC 的跳转,也就是说当汇编器看到 B 指令后会把要跳转的标签转化为相对于当前<br />
PC 的偏移量写入指 令码。从上面的代码可以看到中断向量表和 stubs 都发生了代码搬移,所以如果中断向<br />
量表中仍然写成 b vector_irq,那么实际执行的时候就无法跳转到搬移后的 vector_irq 处,因为指令码里<br />
写的是原来的偏移量,所以需要把指令码中的偏移量写 成搬移后的。我们把搬移前的中断向量表中的 irq<br />
入口地址记 irq_PC,它在中断向量表的偏移量就是 irq_PC-vectors_start, vector_irq 在 stubs 中的偏移<br />
量是 vector_irq-stubs_start,这两个偏移量在搬移前后是不变的。搬移后 vectors_start 在 0xffff0000<br />
处,而 stubs_start 在 0xffff0200 处,所以搬移后的 vector_irq 相对于中断 向量中的中断入口地址的偏<br />
移量就是,200+vector_irq 在 stubs 中的偏移量再减去中断入口在向量表中的偏移量,即 200+<br />
vector_irq-stubs_start-irq_PC+vectors_start = (vector_irq-irq_PC) +<br />
vectors_start+200-stubs_start,对于括号内的值实际上就是中断向量表中写的 vector_irq,减去 irq_PC<br />
是由汇 编器完成的,而后面的 vectors_start+200-stubs_start 就应该是 stubs_offset,实际上在<br />
entry-armv.S 中也是这样定义的<br />
进入中断后在 irq 模式完成什么工作?<br />
vector_irq 是通过宏来 vector_stub 定义的:<br />
.macro vector_stub, name, mode, correction=0<br />
.align 5<br />
vector_\name:<br />
.if \correction<br />
sub lr, lr, #\correction<br />
.endif<br />
@<br />
@ Save r0, lr_ (parent PC) and spsr_<br />
@ (parent CPSR)<br />
@<br />
stmia sp, {r0, lr} @ save r0, lr<br />
mrs lr, spsr<br />
str lr, [sp, #8] @ save spsr<br />
@<br />
@ Prepare for SVC32 mode. IRQs remain disabled.<br />
@
mrs r0, cpsr<br />
eor r0, r0, #(\mode ^ SVC_MODE)<br />
msr spsr_cxsf, r0<br />
@<br />
@ the branch table must immediately follow this code<br />
@<br />
and lr, lr, #0x0f<br />
mov r0, sp<br />
ldr lr, [pc, lr, lsl #2]<br />
movs pc, lr @ branch to handler in SVC mode<br />
.endm<br />
vector_stub irq, IRQ_MODE, 4<br />
.long __irq_usr @ 0 (USR_26 / USR_32)<br />
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)<br />
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)<br />
.long __irq_svc @ 3 (SVC_26 / SVC_32)<br />
.long __irq_invalid @ 4<br />
......<br />
从 上面这段代码可以看出,vector_irq 把发生中断时的 r0,PC-4 以及 CPSR 压栈(注意,压入的的 irq 模<br />
式的堆栈),把中断栈的栈指针赋给 r0,最后根据原来发生中断时 CPU 所处的工作模式(CPSR 的低 4 位)<br />
找到相应的入口函数,在进入 svc 模式后进一步处理中断。<br />
__irq_usr 完成的工作是什么?<br />
它主要通过调用宏 usr_entry 进一步保存现场,然后调用 irq_handler 进行中断处理,保存的栈结构如下:<br />
-1<br />
CPSR<br />
PC-4<br />
LR<br />
SP<br />
R12<br />
...<br />
R2<br />
R1<br />
R0<br />
其中的 LR,SP 是 USR 模式下的,R0,CPSR,PC-4 是从中断栈中拷贝的
irq_handler 的作用是什么?<br />
• 它首先通过宏 get_irqnr_and_base 获得中断号,存在 r0,然后把上面建立的 pt_regs 结构的指针,<br />
也就是 sp 值赋给 r1,把调用宏 get_irqnr_and_base 的位置作为返回地址(为了处理下一个中<br />
断???)然后调用 asm_do_IRQ 进一步处理中断,以上这些操作都在建立在获得中断号的前提下,<br />
也就是有中断发生<br />
• 代码实现<br />
.macro irq_handler<br />
1: get_irqnr_and_base r0, r6, r5, lr<br />
movne r1, sp<br />
@<br />
@ routine called with r0 = irq number, r1 = struct pt_regs *<br />
@<br />
adrne lr, 1b<br />
bne asm_do_IRQ<br />
.endm<br />
具体的中断处理会在 asm_do_IRQ 根据中断号调用相应的中断处理程序完成。<br />
----<br />
1.3 s3c2410 里面的中断控制器的细节分析:<br />
从这里:<br />
可以看出: s3c2410a 可以接收 56 个中断源的请求 。<br />
这些中断源来自来自两部分:内部外设,比如 DMA 控制器,uart ,i2c
第二部分是外部中断请求的 pin 脚。<br />
流程是这样:<br />
当中断控制器接收到中断请求后, 经过仲裁过程后(选择了一个优先级高的 IRQ 或者 FIQ),然后才向 CPU<br />
发出 IRQ 或者 FIQ 的请求。<br />
至于到底这个中断源产生的 IRQ 还是 FIQ , 这个要根据 interrupt mode register 设置了:<br />
从这里我们也可以看出, IRQ 和 FIQ 只是两种模式而已。 也就是说 , 当一个中断源被配置成 IRQ 或者<br />
FIQ 的时候 , 中断控制器处理她的 request 的方式是不同的。<br />
那是不是每个中断源可以随便设置自己的模式呢?显然不行。 (想想, 如果发给大家的都是 VIP 卡, 那<br />
这个 VIP 卡显然就是普通的卡, 被少数人拥有的才是 VIP 卡)<br />
显然,只有紧急的中断才可以配置成 FIQ , 也就是只有一个中断源才可以配置成 FIQ 模式,也就是 VIP<br />
只有一个。<br />
FIQ 可以理解成火车站里面的快速通道,不需要检票 ,直接上车。
翻译出来是这样:<br />
过程很简单, 2410a 两个中断未决寄存器 SRCPND 和 INTPND 。<br />
过程这样:当中断源请求服务后, SRCPND 对应 set 1 , 经过仲裁后, 对应的 INTPND 的 bit 会被被置 1<br />
(前提是没有 mask 这个中断源) , 如果 mask 了, INTPND 的对应的 bit 就不会自动 set 1 。<br />
接下来,就取决于 CPSR 的 F-bit 和 I-bit 了, 如果他们置 1 了,INTPND 也置 1 , 那么就调用中断服<br />
务 routine , 否则如果 F-bit 或者 I-bit 是 0 , 那么就不会调用中断服务 routine 了。<br />
所以 ,从这里就可以看出 SRCPND 和 INTPND 的 关系了: SRCPND set 1 表示中断源产生了 request ,<br />
而INTPND 仅仅表示这个中断源没有被mask掉 ,中断控制器可以向CPU发出request了,以上这些都是从<br />
interrupt controller的角度来说的, 至于CPU 处不处理(也就是是否调用中断处理routine)就得 看 CPSR<br />
寄存器里面F-bit和I-bit了, 如果都被set成0了, 那么显然CPU是不会鸟 中断控制器的(从英文来看:<br />
(If the F-bit of PSR in ARM920T CPU is set to 1, the CPU does not accept the Fast Interrupt Request (FIQ) from the<br />
interrupt controller. Likewise, If I-bit of the PSR is set to 1, the CPU does not accept the Interrupt Request (IRQ) from the<br />
interrupt controller. So, the interrupt controller can receive interrupts by clearing F-bit or I-bit of the PSR to 0 and setting the<br />
corresponding bit of INTMSK to 0 , 这里用了 accept ,那就是说 interrupt发了request ,可是CPU 不能accept) 。
可以看出, 2410a的中断控制器 ,总共有5个寄存器 :SRCPND , INTMR , MR ,PRI , INTPND<br />
可以看出 , FIQ是不需要仲裁的, 显然从这点可以看出 FIQ 的效率肯定比IRQ高 ,因为FIQ必须要仲裁。<br />
来自中断源的中断请求必须首先在SRCPND 寄存器里面注册一下(即对应的bit 置1) 。<br />
这些请求又分为两组, 至于怎么分,就得看 中断模式寄存器了。<br />
关于SRCPND 这里面说的很明确了, 就是 只要触发了中断, SRCPND对应的bit就会被自动置1 , 仅仅表示产生了中断, 等<br />
待着被服务(<strong>kernel</strong>调用interrupt service) 。 而无论INTMASK对应的bit 是否是1 , SRCPND都会被自动置1 。<br />
这一点 ,上面已经说的很清楚了。<br />
这个说的就是 关于如何清除 SRCPND , 以及何时清除的问题了, 还有就是如果 你不clear SRCPND相应的bit会导致什么后<br />
果:<br />
显然上面说的很清楚了, SRCPND 仅仅表示 中断请求的状态, 如果 <strong>kernel</strong>都处理了这个中断(调用了中断处理函数) ,就<br />
应该告诉 中断控制器(我已经处理完了, 你tmd别烦了) 。<br />
如果没有清除会有什么样的后果: 我想, 中断控制器可不知道<strong>kernel</strong>已经处理完了这个中断, 还在那里等着<strong>kernel</strong>去处理呢,<br />
这样<strong>kernel</strong>也傻了, 就会去反复的处理的这个中断)。
打个比方: 一个傻子让妈妈喂饭: “妈妈, 我饿了”(SRCPND 对应bit 置1 ) , 但是啥子不知道吃多少<br />
才能饱(脑子坏掉了,要不怎么傻呢?) 。 妈妈知道喂两碗就行了 。 所以当妈妈喂了两碗之后, 不再喂了(相当于中断处<br />
理完毕) 。<br />
然后告诉傻子, 行了 ,饱了(相当于SRCPND 清0) ,傻子相信妈妈的话, only相信妈妈的话 。<br />
否则 ,如果妈妈不说“行了, 吃饱了,不要再吃了”这样的话, 傻子就一个劲的问问“ 妈妈, 我饿,俄” ,结果妈妈(也<br />
是个傻妈妈,难怪啥子是遗传的) ,一个劲的喂 , 结果什么活也干不了了,同时也把傻儿子撑死了。<br />
我怎么会突然想到这么一个比喻, 我真是个天才啊! --bob<br />
?对于CPU都是这样的吗? 就是 interrupt service<br />
routine 里面必须清除中断的标志bit吗?<br />
我的理解,都应该这样的。<br />
这里面说得也很清楚了, 就是 INTPND 中的每一个bit 表示是否有对应的中断请求,这个请求没有被屏蔽掉 ,正等着CPU来<br />
处理这个中断(即to be serviced) 。 一旦这个某个bit被置1了, 就表示这个中断的优先级是最高(因为是经过仲裁的) 。<br />
这样的话, 用户就可以读INTPND寄存器, 哪个bit置1了(32个bit只能有一个bit是1) , 就表示哪个中断源产生了中断。<br />
其实关于INTPND , 中断控制器的介绍的开头说的比较清楚了:<br />
也就是说, 经过仲裁后,最后的结果(也就是中断号) 被写入pending 寄存器,表明在许多个中断源中,到底谁产生了中断。<br />
---总结: 一句话 INTERRUPT PENDING REGISTER 就表示最终谁触发了中断。
下面要重点注意的问题就是:在中断处理routine里面一定要清除INTPND , 从上面的意思来看, 最便利的办法就是 寄存器原<br />
来是什么value ,就再写进去(读出来,再写进去)<br />
其实:这个就可以衍生出一个比较通用的考题:(搞driver开发的人一定要能答得上,否则东家不会录用你的)<br />
总结: 以IRQ为例,FIQ于此相同。<br />
画个图:<br />
CPU 中断控<br />
制器<br />
CPSR 的 F-bit 和 I-bit<br />
决定了当中断控制器<br />
送上来 request 后,<br />
CPU 是否处理<br />
S3c2410 中断原理图 , drawed by<br />
bob_zhang2004@163.com<br />
INTMASK ,决定中断<br />
request 能不能送上来<br />
INT SRC1<br />
INT SRC2
1> 就是 当硬件出发了中断,<br />
2> SRCPND对应的bit自动置1 ,<br />
3> INTMASK 要分两种情况:<br />
如果INTMASK对应的bit是1 , 那么INTPND对应的bit就不会自动置1 ,表示 中断控制器肯定不会request IRQ to CPU 。<br />
如果INTMASK对应的bit是0 , 那么INTPND对应的bit就 会自动置1 , 那么中断控制器 会 request IRQ to CPU 。<br />
4> 如果INTPND对应的bit就 被置 1 了, 接下来还要分两种情况:<br />
如果I-bit 是0 , 表示CPU 可以 accept 中断控制器的 IRQ request , 那么CPU 就会执行 中断处理routine 。<br />
如果I-bit 是1 , 表示 CPU 不能 accept 中断控制器的 IRQ request , 就不会调用 中断处理routine<br />
又想到一个比喻: 古代奸臣误国,好多正直大臣的奏折并不能被直接传递到皇帝手中 ,比如大奸臣魏忠贤。<br />
魏忠贤好比是中断控制器 , 皇帝好比是CPU 。<br />
奏章要首先经过魏忠贤的手, 如果魏忠贤屏蔽掉某个大臣的奏章(mask),那么皇帝肯定没有机会看了。<br />
但是假使奏章通过魏忠贤(interrupt controller)传给皇帝了, 可是皇帝是个糊涂蛋, 只知玩乐,误了朝政, 奏折堆积如山(就<br />
相当于 F-bit=1,I-bit=1)了。<br />
所以封建国家, 既要有好大臣(狄仁杰,什么奏章都敢上奏,不管是否是弹劾自己的), 又要有好皇帝(唐太宗,勤于政事)<br />
啊。<br />
当一个中断控制器连接了128个中断源的时候, CPU怎么知道哪个中断源产生<br />
了中断呢?继而调用对应的中断处理函数呢?<br />
一般的原理都是这样的:<br />
显然,要回答这个问题, 必须要搞清楚, CPU是肯定不知道哪个中断源产生了中断的, 谁知道呢?只有中断控制器才知<br />
道的, 那么,CPU就要查询中断控制器就好了。 问题是中断控制器的那个register 的标志bit 被置1 , 肯定是硬件做的,<br />
知道这些就足够了。<br />
当中断源触发了一个中断的时候, 中断控制器要提供一个寄存器来标志哪个源产生中断, 类似2410 就提供INTPND(32<br />
个bit,bit置1 就表示对应的source产生了中断) , 那么CPU就会读取这个寄存器,以确定中断号(get_irqnr_and_base<br />
这是个汇编宏)。 问题在于CPU怎么知道要去读呢? 显然要中断控制器给CPU发一个IRQ的request , 这样CPU才会去读<br />
(实际上是一段汇编代码) 。<br />
继而才会调用asm_do_IRQ(irq_number)来处理中断, 继而可以通过irq号找到对应中断的中断描述符(irq descriptor),找<br />
到了中断描述符,就找到了,handler , 然后 调用 ->handler() 就可以处理中断了。<br />
----------<br />
1.4 外部中断控制器的分析(与GPIO有关)<br />
分析完了中断控制器的部分。<br />
我们还要接下来看看 datasheet的什么部分与中断有关。<br />
就是 P276 页 里面的关于外部中断的部分 ,还有 GPF和GPG的部分
下面涉及的就是 外部的设备要想产生中断, 要怎么样连接的问题,s3c2410 里面显然是采取GPIO复用端口的方式。<br />
一个外部设备, 如果可以产生中断, 就可以接在GPF或者GPG的端口上,然后配置端口的模式为 INT mode 。<br />
如图:<br />
--<br />
还有 GPG 也是类似。<br />
接下来这个描述就是中断触发的方式有关了:<br />
我们知道, 中断出发有好几种:低电平触发,高电平触发, 上升沿触发,下降沿触发,both edge触发。<br />
S3c2410a 提供了 24个外部中断, 那么就提供了3个寄存器来配置这24个 GPIO口的中断触发方式。<br />
下面是关于 GPIO 这端的 外部中断未决和外部中断屏蔽寄存器:
显然是 这是最底层的中断控制了, 因为 设备是通过 GPIO pin 脚连上来的, 这是第一步。<br />
上面的图也许应该改一改了<br />
CPU<br />
CPSR 的 F-bit 和 I-bit<br />
决定了当中断控制器<br />
送上来 request 后,<br />
CPU 是否处理<br />
GPIO 的 EINTMASK 和<br />
EINTPND register 来控<br />
制,信号能否发进去<br />
中断控<br />
制器<br />
INTPND<br />
[31:0]<br />
INTMASK ,决定中断<br />
request 能不能送上来<br />
INT SRC1<br />
INT SRC2<br />
EXINT<br />
Drawed by bob_zhang2004@163.com