Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3547183
  • 博文数量: 1805
  • 博客积分: 135
  • 博客等级: 入伍新兵
  • 技术积分: 3345
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-19 20:01
文章分类

全部博文(1805)

文章存档

2017年(19)

2016年(80)

2015年(341)

2014年(438)

2013年(349)

2012年(332)

2011年(248)

分类: LINUX

2014-09-27 05:50:41

原文地址:Linux arm 中断处理-1 作者:changyongID


由于网络上的资料非常多, 使得学习linux 的难度降低了许多. 踏着前人的足迹更快地体验linux内部的实现.
感谢这些人能将他们学习中的心得体会记录下来并公开. 有了源码再加上这些宝贵的资料, 我想唯一需要做
的就是克服浮躁, 静下心来好好学习.

参考:
1. 嵌入式Linux应用开发完全手册  (这里面已经讲的非常详细, 且条理清晰)
3. 



start_kernel // init/main.c
+--------> setup_arch(char **cmdline_p) // arch/arm/kernel/setup.c
|       ->early_trap_init(void) // arch/arm/kernel/traps.c
|                                                           
+-------> early_irq_init(); // kernel/irq/handle.c
|
+-------> init_IRQ(); // arch/arm/kernel/irq.c

1. early_trap_init

1). 复制中断向量表及处理部分代码到运行时的位置
vectors = 0xffff0000

__vectors_start 异常向量表起始地址
  ...
  ... 异常向量表, 每个中断向量为一条跳转指令, 其跳入
  ... __stubs_start~__stubs_end 之间的某个地址, 进行更为复杂
  ... 的处理工作.
  ...
__vectors_end   中断向量表终址地址


void __init early_trap_init(void)
{
unsigned long vectors = CONFIG_VECTORS_BASE;
extern char __stubs_start[], __stubs_end[];
extern char __vectors_start[], __vectors_end[];
extern char __kuser_helper_start[], __kuser_helper_end[];
int kuser_sz = __kuser_helper_end - __kuser_helper_start;

/*
* Copy the vectors, stubs and kuser helpers (in entry-armv.S)
* into the vector page, mapped at 0xffff0000, and ensure these
* are visible to the instruction stream.
*/
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

/*
* Copy signal return handlers into the vector page, and
* set sigreturn to be a pointer to these.
*/
memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
      sizeof(sigreturn_codes));
memcpy((void *)KERN_RESTART_CODE, syscall_restart_code,
      sizeof(syscall_restart_code));

flush_icache_range(vectors, vectors + PAGE_SIZE);
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}

2). 异常向量表
arch/arm/kernel/entry-armv.S

// stubs_offset 是经过计算后的偏移地址. 对于vector_irq, 这个标号的地址在编译后
// 就已经为固定了, 但是在运行时, 代码会从存储器中的image状态复制到sdram中, 成
// 为运行状态. 这个过程打乱了代码间的相对位置, 所以下面用 b 跳转, 如果还按照
// 编译时的地址来跳, 肯定不能跳到正确的位置.

.equ stubs_offset, __vectors_start + 0x200 - __stubs_start

.globl __vectors_start
__vectors_start:
 ARM( swi SYS_ERROR0 )
W(b) vector_und + stubs_offset
W(ldr) pc, .LCvswi + stubs_offset
W(b) vector_pabt + stubs_offset
W(b) vector_dabt + stubs_offset
W(b) vector_addrexcptn + stubs_offset
W(b) vector_irq + stubs_offset
W(b) vector_fiq + stubs_offset

.globl __vectors_end
__vectors_end:

3). __stubs_start 与 __stubs_end
这两个地址中间包含着几段代码, 分别是几种异常的处理程序
__stubs_start:
[vector_irq 处理代码]
[irq跳转地址表]

[vector_dabt 处理代码]
[dabt跳转地址表]

[vector_pabt 处理代码]
[pabt跳转地址表]

[vector_und 处理代码]
[und跳转地址表]

[vector_fiq 处理代码]

[vector_addrexcptn 处理代码]

[swi 地址]
__stubs_end:

4). 拿 vector_irq 来分析

vector_stub irq, IRQ_MODE, 4

vector_stub  是一个宏, 这一句会展开一段代码, 这里展开后如下

vector_irq:
@ 计算返回地址
sub lr, lr, #4

@ 将 r0和r14(lr) 入栈保存起来, sp的位置没有改变
@ stmia 表示栈操作为事后递增
stmia sp, {r0, lr} @ save r0, lr

@ 将 spsr 也入栈保存起来
mrs lr, spsr
str lr, [sp, #8] @ save spsr

@ 置为svc模式, 此时 cspr 寄存器中断位为禁止状态
mrs r0, cpsr
eor r0, r0, #(IRQ_MODE ^ SVC_MODE | PSR_ISETSTATE)
msr spsr_cxsf, r0

@ 进入中断时会将中断前的 cpsr 保存在 spsr 中, 其[4:0]表示当时cpu的工作
@ 模式(而[4]始终为1), 所以可以利用此时的spsr[3:0]来标志中断前的工作模
@ 式, 进而根据不同的工作模式索引到对应的子处理部分.
@ 上面将 spsr 赋值给 lr, 这里取lr的低八位在下面的表中索引.
and lr, lr, #0x0f
mov r0, sp
ldr lr, [pc, lr, lsl #2]
movs pc, lr @ branch to handler in SVC mode

.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

假设中断前cpu为usr状态, 则运行完上面的代码后就跳入 __irq_usr 中执行.

5). 代码重定位 
分析了代码的作用, 但代码复制到sdram后, 又是如何能够找到正确的地址的呢?
如下图, 
image 这条线表示编译后生成的image, 线上有代码间的相对位置
exec 表示在sdram中运行时的这段代码间的相对位置

假设此时, 在image 中有t1 位置, 该位置的代码为 b vector_irq, 运行时, 该点的代码
复制到了exec 中的 t1 点, 此时要如何才能够正确跳到 exec 中的 vector_irq 位置呢?
即跳到exec线上的E(vector_irq).

                                    [exec]
                                      |
                                      + E(__vectors_start) -+-
                                     /|                     |
                                    / |                     |
                                   /  |                     |
                                  /   |                     |
                                 /    |                     |
                                /     |                     |
                               /      |                     |
              [image]         /       |                     |
                |            /        |                     |
  __stubs_start +           /         |                     |
                |\         /          + E(t1) --------------+--------+-
                | \       /           |                     |        |
                |  \     /            + E(__vectors_end)    V        |
     vector_irq +   \   /            /|                              |
                |    \ /            / |                     0x200    |
                |     X            /  |                              |
                |    / \          /   |                     ^        |
                |   /   \        /    |                     |        |
                |  /     \      /     |                     |        V
                | /       \    /      |                     |         
                |/         \  /       |                     |        L1
    __stubs_end X           \/        |                     |         
__vectors_start |\          /\        |                     |        ^
                | \        /  \       |                     |        |
                |  \      /    \      |                     |        |
                |   \    /      \     |                     |        |
                |    \  /        \    |                     |        |
                |     \/          \   |                     |        |
                |     /\           \  |                     |        |
                |    /  \           \ |                     |        |
                |   /    \           \|                     |        |
                |  /      \           + E(__stubs_start) ---+--------+-
     b "irq":t1 + /        \          |                              V
                |/          \         |                              L2
  __vectors_end +            \        |                              ^
                |             \       + E(vector_irq) ---------------+-
                               \      |                
                                \     |                
                                 \    |                
                                  \   |                
                                   \  |                
                                    \ |                
                                     \|                
                                      + E(__stubs_end)               
                                      |                
在exec 线中, 假设t1 与 E(vector_irq) 相对位置偏移 offset, 则有:
offset = L1 + L2
       = 0x200 - (E(t1) - E(__vectors_start)) + E(vector_irq) - E(__stubs_start)
       = 0x200 - (t1 - __vectors_start) + vector_irq - __stubs_start
       = vector_irq + (__vectors_start + 0x200 - __stubs_start) - t1

令 stubs_offset = __vectors_start + 0x200 - __stubs_start (这些为固定值)
则 offset = vector_irq + stubs_offset - t1
又 执行 b 跳转命令时PC就在 t1 位置, 
=> b vector_irq + stubs_offset 就可以跳到运行时的vector_irq地址去

所以会看到这一行的存在
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start

2. early_irq_init
kernel/irq/handle.c 

在这个函数的上面, 可以看到下面的代码, 定义了一个名为irq_desc的数组, 其中每个成
员都是一个 irq_desc 结构体. NR_IRQS 为中断源个数. 

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
[0 ... NR_IRQS-1] = {
.status = IRQ_DISABLED,
.chip = &no_irq_chip,
.handle_irq = handle_bad_irq,
.depth = 1,
.lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
}
};

int __init early_irq_init(void)
{
struct irq_desc *desc;
int count;
int i;

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++) {
desc[i].irq = i;
alloc_desc_masks(&desc[i], 0, true);
init_desc_masks(&desc[i]);
desc[i].kstat_irqs = kstat_irqs_all[i];
}
return arch_early_irq_init();
}
early_irq_init 则对数组中每个成员结构进行初始化, 例如, 初始每个中断源的中断号.
其他的函数基本为空. 

3. init_IRQ()

进一步初始化 irq_desc 数组中的成员
void __init init_IRQ(void)
{
int irq;

for (irq = 0; irq < NR_IRQS; irq++)
irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;

init_arch_irq();

init_arch_irq 是一个函数指针:
void (*init_arch_irq)(void) __initdata = NULL;

void __init setup_arch(char **cmdline_p)
{
...

mdesc = setup_machine(machine_arch_type);
machine_name = mdesc->name;

...

/*
* Set up various architecture-specific pointers
*/
init_arch_irq = mdesc->init_irq;

...

early_trap_init();
}

在 arch/arm/mach-s3c2410/mach-smdk2410.c 中有
(至于mdesc是怎么与这个文件相联系起来的不在这里跟踪)

MACHINE_START(SMDK2410, "SMDK2410")
...

.init_irq = s3c24xx_init_irq,

...
MACHINE_END

所以最终的执行是: init_IRQ() -> s3c24xx_init_irq(),
s3c24xx_init_irq 中进行一系列的工作
至此, 关于中断的工作就初始完了

由于排版问题, 上面用字符画的图错位了. 附件为此篇笔记, 推荐用vim打开.其中的图应该没错.

文件:linux_kernel_irq.tar.bz2
大小:3KB
下载:下载
阅读(522) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~