Linux Kernel中断子系统来龙去脉浅析【转】
转自:http://blog.csdn.net/u011461299/article/details/9772215
版权声明:本文为博主原创文章,未经博主允许不得转载。
1. 定义一个IRQ No。如何将Hardware中断信息map到我们的IRQ No就是get_irqnr_and_base要做得事情,get_irqnr_and_base是一个macro,后面会详细分析之。这个宏的实现往往也是我们如果要将一个标准的Linux Kernel移植到我们的SOC chip上面要做得事情。
2. 实现对应device driver的irq handler。
3. 在device driver的open中通过request_irq()来install该irq handler。
1.1 RTC Device Driver中断相关code的分析
1. IRQ No:
#define IRQ_RTC XXXX_IRQ(3),
我们将对应的Hardware Source定义到一张表中,get_irqnr_and_base就是先从SOC Interrupt Controller中获得该Interrupt的硬件信息(这个信息可能就是一个interrtup hardware index,或者其它复杂点的),然后从这张表中查询得到RTC Alarm的IRQ No。
2. Irq handle
其实现如下:它通过rtc_update_irqà wake_up_interruptible(&rtc->irq_queue);唤醒(通知)select等待RTC alarm的process:
static irqreturn_t xxxx_rtc_alarmirq(int irq, void *id)
{
struct rtc_device *rdev = id;
//clear the alarm event.
xxxx_rtc_clear_status(XXXX_RTC_ALARM_EVENT );
//wake up the rtc->irq_queue
rtc_update_irq(rdev, 1, RTC_AF | RTC_IRQF);
return IRQ_HANDLED;
}
3. 通过request 安装irq handler
static int xxxx_rtc_open(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct rtc_device *rtc_dev = platform_get_drvdata(pdev);
int ret;
ret = request_irq(IRQ_RTC, xxxx_rtc_alarmirq,
IRQF_DISABLED, "xxxx-rtc alarm", rtc_dev);
if (ret) {
dev_err(dev, "IRQ%d error %d\n", xxxx_rtc_alarmno, ret);
return ret;
}
return ret;
}
static void xxxx_rtc_release(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct rtc_device *rtc_dev = platform_get_drvdata(pdev);
//do not clear AIE here, it may be needed for alarm even after the rtc device file has been closed.
free_irq(xxxx_rtc_alarmno, rtc_dev);
}
1.2 常用的接口介绍
接口名称 |
描述 |
request_irq() |
内核其他的driver模块调用该接口用来分配一个interrupt line。 参数描述如下: irq: Interrupt line to allocate handler:该中断的handler。 irqflags: Interrupt type flags。 IRQF_SHARED: Interrupt is shared IRQF_DISABLED: Disable local interrupts while processing devname: An ascii name for the claiming device dev_id: A cookie passed back to the handler function,IRQF_SHARED类型的中断需要该参数,否则无法区分中断。 |
setup_irq() |
类似request_irq,只是需要我们自己根据request_irq中的后四个参数自己建立struct irqaction intstance。 |
free_irq() |
内核其他的driver模块调用该接口用来释放一个interrupt,在释放之后,该interrupt line可能仍然是有效的,因为可能有多个driver共享一个interrupt line。 参数描述如下: irq: Interrupt line to free dev_id: Device identity to free |
disable_irq() |
disable an irq and wait for completion,这里禁止的不是全局中断而是该interrupt line上的中断。 参数描述如下: irq: Interrupt to disable |
enable_irq() |
enable handling of an irq。 参数描述如下: irq: Interrupt to enable enable_irq和disable_irq可以嵌套使用。 |
1.2.1 Interrupt的enable/disable
Interrupt的enable或disable有两个层次,一个是ARM Chip的全局Interrupt的enable和disable,二是各个Soc Interrupt Controller对于各个不同的IRQ source的mask或unmask。
1. ARM Interrupt Enable:local_irq_enable(include/linux/irqflags.h中)raw_local_irq_enable(include/asm-arm/irqflags.h)
2. ARM interrupt Disable:local_irq_disable,raw_local_irq_disable。
3. Soc Interrupt Controller的irq disable:disable_irq。disable_irqà disable_irq_nosyncà desc->chip->disable(irq);。
其中desc->chip是在s3c2451_init_irq中由set_irq_chip设定的。set_irq_chipàirq_chip_set_defaults中将chip->disable设置为:default_disable,而default_disable什么也没有做。那么它是如何mask这个irq呢?这是因为:(这里看起来有点别扭)
◆ disable_irqà disable_irq_nosync àdesc->status |= IRQ_DISABLED;
◆ handle_level_irq中会:
if (unlikely(!action || (desc->status & IRQ_DISABLED))) goto out_unlock;
注意:这里IRQ_DISABLED(用于关闭某个特定的irq)和IRQF_DISABLED(用关闭arm global irq)是两个不同的macro,其概念也是不一样的。
4. Soc Interrupt Controller的irq unmask:enable_irq。
1.3 从Hardware中断发生到各自定义的中断handler被执行的全过程分析
其实有了上面的基础,对于我们一般的device driver的development来说似乎已经可以了,但是我们不仅要知其然而要知其所以然,更为重要的是如果我们要分析一些复杂的问题、或者我们要移植Linux Kernel到某个Hardware platform上去,我们必须对此过程非常清楚。
在这个分析的过程中,我们会不断讨论一些设计原则,这样才可以真正明白这些设计要求的背后原因,这样也可以更好的灵活应变了。
1.3.1 ARM Linux的exception vector的来龙去脉――启动过程关于IRQ的setting
1.3.1.1 ARM Exception Vector介绍
ARM CPU无论在产生一个IRQ或是SWI或是reset等都会跳到ARM exception vector中相应的vector entry执行对应code。ARM exception vector可以存储在零地址(Normal Exception Vectors 从0x0000 0000 - 0x0000 001C),也可以是0xFFFF0000(High Exception Vectors从0xFFFF0000到0xFFFF001C)。初始设置为Normal Exception Vectors。
但是我们知道C programming通常使用零地址NULL为非法地址,所以我们只能选择High Exception Vectors。那么Linux Kernel是如何选择使用High Exception Vectors的呢?
1.3.1.2 Linux Kernle启动时设定High Exception Vectors的全过程
首先我们需要简单说一下Linux Kernel的启动过程,我们不进行详细的分析,如果以后有需要的话,我想我们在以后的Phase2中开一个章节来讲,大概也需要3小时,这里面也是有很多的故事和技巧的。
Bootl/Loader 加载(Load)Linux Kernel image到SDRAM起始地址(这里假定SDRAM的物理地址是0x2000 0000)+0x8000的位置以后,Boot/Loader通常通过执行asm("ldr pc, =0x20008000");,这样开始Linux Kernel的启动过程,由于我们的Linux Kernel image是有压缩的(这个问题后面有个讨论),所以我们的Kernel第一步就是解压缩,解压缩之后的Kernel Image至少增加了一倍,并且存储在同一个位置。这是才开始真正的Linux Kernel的启动,入口code在arch/arm/kernel/head.S文件中:ENTRY(stext),我们先只看我们关心的:
ENTRY(stext) :
。。。。。。
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
。。。。。。
adr lr, __enable_mmu @ return address
add pc, r10, #PROCINFO_INITFUNC @ initialise processor
。。。。。。
__lookup_processor_type查找到我们processor相关information structure,我们是ARM926,其定义在arch/arm/mm/proc-arm926.S中,如下:(至于它是如何找到了,这里不说明了,有兴趣的朋友结合我们之前的分析技巧不难分析,或者我们以后在讲)
.section ".proc.info.init", #alloc, #execinstr
.type __arm926_proc_info,#object
__arm926_proc_info:
.long 0x41069260 @ ARM926EJ-S (v5TEJ)
.long 0xff0ffff0
.long PMD_TYPE_SECT | \
PMD_SECT_BUFFERABLE | \
PMD_SECT_CACHEABLE | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
.long PMD_TYPE_SECT | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
b __arm926_setup
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP|HWCAP_JAVA
.long cpu_arm926_name
.long arm926_processor_functions
.long v4wbi_tlb_fns
.long v4wb_user_fns
.long arm926_cache_fn
.size __arm926_proc_info, . - __arm926_proc_info
此外其中PROCINFO_INITFUNC的定义在arch/arm/kernel/asm-offsets.c文件中,如下:
DEFINE(PROCINFO_INITFUNC, offsetof(struct proc_info_list, __cpu_flush));
所以 add pc, r10, #PROCINFO_INITFUNC @ initialise processor也就是b __arm926_setup了。
再看:
.type __arm926_setup, #function
__arm926_setup:
mov r0, #0
mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer on v4
#ifdef CONFIG_MMU
mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4
#endif
#ifdef CONFIG_CPU_DCACHE_WRITETHROUGH
mov r0, #4 @ disable write-back on caches explicitly
mcr p15, 7, r0, c15, c0, 0
#endif
adr r5, arm926_crval
ldmia r5, {r5, r6}
mrc p15, 0, r0, c1, c0 @ get control register v4
bic r0, r0, r5
orr r0, r0, r6
#ifdef CONFIG_CPU_CACHE_ROUND_ROBIN
orr r0, r0, #0x4000 @ .1.. .... .... ....
#endif
mov pc, lr
.size __arm926_setup, . - __arm926_setup
/*
* R
* .RVI ZFRS BLDP WCAM
* .011 0001 ..11 0101
*/
.type arm926_crval, #object
arm926_crval:
crval clear=0x00007f3f, mmuset=0x00003135, ucset=0x00001134
这段code执行的结果就是将0x00003135写入r0 regisger,当这段code完成后会执行__enable_mmu(定义在arch/arm/kernel/head.S中,代码就不贴出来了。),__enable_mmu会将r0写入ARM Coprocessor Control Register(why?大家不妨想一下,因为这里似乎没有直接进行函数调用或指令跳转呀?)
由AAM[3] 2.4可以知道,我们的ARM926被Linux Kernel设置为High Vector模式了。
1.3.1.3 从Linunx Kernel copy Exception Vectors到0xFFFF 0000
Arch/arm/kernel/traps.c中:CONFIG_VECTORS_BASE= 0xFFFF0000,menuconfig的时候定义的。
void __init 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));
flush_icache_range(vectors, vectors + PAGE_SIZE);
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}
那么这里还有一个问题就是:现在MMU已经被打开了,0xffff0000是Virtual Address,该地址对应的physical memory什么时候被分配并建立mapping的呢?
start_kernel->setup_arch->paging_init->devicemaps_init:
。。。。。。。
vectors = alloc_bootmem_low_pages(PAGE_SIZE);
。。。。。。。
/*
* Create a mapping for the machine vectors at the high-vectors
* location (0xffff0000). If we aren't using high-vectors, also
* create a mapping at the low-vectors virtual address.
*/
map.pfn = __phys_to_pfn(virt_to_phys(vectors));
map.virtual = 0xffff0000;
map.length = PAGE_SIZE;
map.type = MT_HIGH_VECTORS;
create_mapping(&map);
。。。。。。
1.3.2 ARM Linux中断处理全过程分析
ArmLinux 2.6.23 exception vector table locates in arch/arm/kernel/entry-armv.S,我们之前在分析system call的时候已经见过:
.globl __vectors_start
__vectors_start:
swi SYS_ERROR0
b vector_und + stubs_offset
ldr pc, .LCvswi + stubs_offset
b vector_pabt + stubs_offset
b vector_dabt + stubs_offset
b vector_addrexcptn + stubs_offset
b vector_irq + stubs_offset
b vector_fiq + stubs_offset
.globl __vectors_end
__vectors_end:
1.3.2.1 vector_irq
下面我们就从vector_irq开始我们的分析。如果我们用查找的方法的是没有找到vector_irq的定义的,vector_irq其实在entry-armv.S由macro vector_stub来定义的:
.macro vector_stub, name, mode, correction=0
.align 5
vector_\name:
.if \correction
sub lr, lr, #\correction
.endif
@
@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
@ (parent CPSR)
@
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr
str lr, [sp, #8] @ save spsr
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE)
msr spsr_cxsf, r0
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f
mov r0, sp
ldr lr, [pc, lr, lsl #2]
movs pc, lr @ branch to handler in SVC mode
.endm
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)
。。。。。。
上面的这段assembler code不是很好看懂,需要对ARM architure以及ARM lassmebler anguage很清楚,我们这里主要的目的不是去学习ARM,有兴趣的朋友可以熟悉ARM后再回头自己来阅读好了。
现在我们只要知道这段code主要做了如下几件事情就可以了:
1. Save r0, lr_irq, spsr_irq into the irq stack
2. prepare entering arm SVC mode from IRQ mode. 之前我们提过IRQ mode时间很短,现在更清楚明白了。
3. 如果发生中断时ARM在Kernel Mode则调用 __irq_svc,反之__irq_usr.
下面我们仅仅以__irq_svc为例子进行说明,但是在开始之前留一个问题给大家思考一下:为什么“b vector_irq + stubs_offset”?简单提示一下:请结合trap_init()来理解。
.equstubs_offset, __vectors_start + 0x200 - __stubs_start
1.3.2.2 __irq_svc
我们先不考虑pre-emptive,事实上在U3和No1中pre-emptive都是disabled。这样简单来说它其实就是:
1. svc_entry macro:save the processor context。定义在同一个文件中。
2. irq_handler macro:
我们中说明irq_handler macro:
A. 通过get_irqnr_and_base获得IRQ No。get_irqnr_and_base是我们移植的时候需要实现(我们通常实现在include/asm-arm/arch-xxxx/entry-macro.S),它其实就是从Hardware的Interrupt source确定IRQ No。。
B. asm_do_IRQ()
到此我们总算可以开始看到C Coder了。
1.3.2.3 asm_do_IRQ()
asm_do_IRQ定义在arch/arm/kernel/irq.c中。
首先我们要记得ARM的IRQ已经被ARM chip自己Disable了,详见:AAM[3] Section2.6.6。到此时,我们并没有enable它。
它主要完成了:
1. 根据前面IRQ No从系统的global table:irq_desc找到对应struct irq_desc。这张table中记录了所有的irq descriptors。
Irq_desc这张table定义在kernel/irq/handle.c中:
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 = __SPIN_LOCK_UNLOCKED(irq_desc->lock),
}
};
而irqaction list是由request_irq建立的:include/linux/interrupt.h中
struct irqaction {
irq_handler_t handler;://这个handler就是request_irq中传入的irq handler,也就是我们在各个具的device driver中实现的irq handler。这里应该知道why request_irq了吧。这样应该很清楚我们为什么需要request_irq了吧,其实现在kernel/irq/manager.c文件中:request_irq ->setup_irq->p = &desc->action; *p = new;
unsigned long flags;
cpumask_t mask;
const char *name;
void *dev_id;
struct irqaction *next;
int irq;
struct proc_dir_entry *dir;
};
2. asm_do_IRQ->desc_handle_irqàdesc->handle_irq(irq, desc);其实初始定义为handle_bad_irq。Handle_irq的是需要我们在porting时设定,以s3c2451为例子其实现在:arch\arm\mach-s3c2451\irq.c中的s3c2451_init_irq->set_irq_handler。通常会依据是edge还是level中断被设置为handle_edge_irq或handle_level_irq
3. 以handle_level_irq为例:handle_level_irqàhandle_IRQ_event:
A. if (!(action->flags & IRQF_DISABLED))则enable ARM global interrupt。
B. ret = action->handler(irq, action->dev_id); 这就是我们在reqest_irq中设定的各个具体device driver的irq handler了。
C.local_irq_disable关闭ARM global interrupt
4. asm_do_IRQ->irq_exit->invoke_softirq,此时启动soft irq,这部分我们将在9中详述。这里我们首先要注意的是此时ARM global interrupt处于关闭的状态
1.3.3 关机后中断唤醒
enable_irq_wake->set_irq_wake->desc->chip->set_wake(irq, on);s3c2451中的实现见:s3c2451_irq_wake.
同时在具体Device Driver里面要enable_irq_wake设置关机后可以中断唤醒。
版权声明:本文为博主原创文章,未经博主允许不得转载。
1. 定义一个IRQ No。如何将Hardware中断信息map到我们的IRQ No就是get_irqnr_and_base要做得事情,get_irqnr_and_base是一个macro,后面会详细分析之。这个宏的实现往往也是我们如果要将一个标准的Linux Kernel移植到我们的SOC chip上面要做得事情。
2. 实现对应device driver的irq handler。
3. 在device driver的open中通过request_irq()来install该irq handler。
1.1 RTC Device Driver中断相关code的分析
1. IRQ No:
#define IRQ_RTC XXXX_IRQ(3),
我们将对应的Hardware Source定义到一张表中,get_irqnr_and_base就是先从SOC Interrupt Controller中获得该Interrupt的硬件信息(这个信息可能就是一个interrtup hardware index,或者其它复杂点的),然后从这张表中查询得到RTC Alarm的IRQ No。
2. Irq handle
其实现如下:它通过rtc_update_irqà wake_up_interruptible(&rtc->irq_queue);唤醒(通知)select等待RTC alarm的process:
static irqreturn_t xxxx_rtc_alarmirq(int irq, void *id)
{
struct rtc_device *rdev = id;
//clear the alarm event.
xxxx_rtc_clear_status(XXXX_RTC_ALARM_EVENT );
//wake up the rtc->irq_queue
rtc_update_irq(rdev, 1, RTC_AF | RTC_IRQF);
return IRQ_HANDLED;
}
3. 通过request 安装irq handler
static int xxxx_rtc_open(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct rtc_device *rtc_dev = platform_get_drvdata(pdev);
int ret;
ret = request_irq(IRQ_RTC, xxxx_rtc_alarmirq,
IRQF_DISABLED, "xxxx-rtc alarm", rtc_dev);
if (ret) {
dev_err(dev, "IRQ%d error %d\n", xxxx_rtc_alarmno, ret);
return ret;
}
return ret;
}
static void xxxx_rtc_release(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct rtc_device *rtc_dev = platform_get_drvdata(pdev);
//do not clear AIE here, it may be needed for alarm even after the rtc device file has been closed.
free_irq(xxxx_rtc_alarmno, rtc_dev);
}
1.2 常用的接口介绍
接口名称 |
描述 |
request_irq() |
内核其他的driver模块调用该接口用来分配一个interrupt line。 参数描述如下: irq: Interrupt line to allocate handler:该中断的handler。 irqflags: Interrupt type flags。 IRQF_SHARED: Interrupt is shared IRQF_DISABLED: Disable local interrupts while processing devname: An ascii name for the claiming device dev_id: A cookie passed back to the handler function,IRQF_SHARED类型的中断需要该参数,否则无法区分中断。 |
setup_irq() |
类似request_irq,只是需要我们自己根据request_irq中的后四个参数自己建立struct irqaction intstance。 |
free_irq() |
内核其他的driver模块调用该接口用来释放一个interrupt,在释放之后,该interrupt line可能仍然是有效的,因为可能有多个driver共享一个interrupt line。 参数描述如下: irq: Interrupt line to free dev_id: Device identity to free |
disable_irq() |
disable an irq and wait for completion,这里禁止的不是全局中断而是该interrupt line上的中断。 参数描述如下: irq: Interrupt to disable |
enable_irq() |
enable handling of an irq。 参数描述如下: irq: Interrupt to enable enable_irq和disable_irq可以嵌套使用。 |
1.2.1 Interrupt的enable/disable
Interrupt的enable或disable有两个层次,一个是ARM Chip的全局Interrupt的enable和disable,二是各个Soc Interrupt Controller对于各个不同的IRQ source的mask或unmask。
1. ARM Interrupt Enable:local_irq_enable(include/linux/irqflags.h中)raw_local_irq_enable(include/asm-arm/irqflags.h)
2. ARM interrupt Disable:local_irq_disable,raw_local_irq_disable。
3. Soc Interrupt Controller的irq disable:disable_irq。disable_irqà disable_irq_nosyncà desc->chip->disable(irq);。
其中desc->chip是在s3c2451_init_irq中由set_irq_chip设定的。set_irq_chipàirq_chip_set_defaults中将chip->disable设置为:default_disable,而default_disable什么也没有做。那么它是如何mask这个irq呢?这是因为:(这里看起来有点别扭)
◆ disable_irqà disable_irq_nosync àdesc->status |= IRQ_DISABLED;
◆ handle_level_irq中会:
if (unlikely(!action || (desc->status & IRQ_DISABLED))) goto out_unlock;
注意:这里IRQ_DISABLED(用于关闭某个特定的irq)和IRQF_DISABLED(用关闭arm global irq)是两个不同的macro,其概念也是不一样的。
4. Soc Interrupt Controller的irq unmask:enable_irq。
1.3 从Hardware中断发生到各自定义的中断handler被执行的全过程分析
其实有了上面的基础,对于我们一般的device driver的development来说似乎已经可以了,但是我们不仅要知其然而要知其所以然,更为重要的是如果我们要分析一些复杂的问题、或者我们要移植Linux Kernel到某个Hardware platform上去,我们必须对此过程非常清楚。
在这个分析的过程中,我们会不断讨论一些设计原则,这样才可以真正明白这些设计要求的背后原因,这样也可以更好的灵活应变了。
1.3.1 ARM Linux的exception vector的来龙去脉――启动过程关于IRQ的setting
1.3.1.1 ARM Exception Vector介绍
ARM CPU无论在产生一个IRQ或是SWI或是reset等都会跳到ARM exception vector中相应的vector entry执行对应code。ARM exception vector可以存储在零地址(Normal Exception Vectors 从0x0000 0000 - 0x0000 001C),也可以是0xFFFF0000(High Exception Vectors从0xFFFF0000到0xFFFF001C)。初始设置为Normal Exception Vectors。
但是我们知道C programming通常使用零地址NULL为非法地址,所以我们只能选择High Exception Vectors。那么Linux Kernel是如何选择使用High Exception Vectors的呢?
1.3.1.2 Linux Kernle启动时设定High Exception Vectors的全过程
首先我们需要简单说一下Linux Kernel的启动过程,我们不进行详细的分析,如果以后有需要的话,我想我们在以后的Phase2中开一个章节来讲,大概也需要3小时,这里面也是有很多的故事和技巧的。
Bootl/Loader 加载(Load)Linux Kernel image到SDRAM起始地址(这里假定SDRAM的物理地址是0x2000 0000)+0x8000的位置以后,Boot/Loader通常通过执行asm("ldr pc, =0x20008000");,这样开始Linux Kernel的启动过程,由于我们的Linux Kernel image是有压缩的(这个问题后面有个讨论),所以我们的Kernel第一步就是解压缩,解压缩之后的Kernel Image至少增加了一倍,并且存储在同一个位置。这是才开始真正的Linux Kernel的启动,入口code在arch/arm/kernel/head.S文件中:ENTRY(stext),我们先只看我们关心的:
ENTRY(stext) :
。。。。。。
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
。。。。。。
adr lr, __enable_mmu @ return address
add pc, r10, #PROCINFO_INITFUNC @ initialise processor
。。。。。。
__lookup_processor_type查找到我们processor相关information structure,我们是ARM926,其定义在arch/arm/mm/proc-arm926.S中,如下:(至于它是如何找到了,这里不说明了,有兴趣的朋友结合我们之前的分析技巧不难分析,或者我们以后在讲)
.section ".proc.info.init", #alloc, #execinstr
.type __arm926_proc_info,#object
__arm926_proc_info:
.long 0x41069260 @ ARM926EJ-S (v5TEJ)
.long 0xff0ffff0
.long PMD_TYPE_SECT | \
PMD_SECT_BUFFERABLE | \
PMD_SECT_CACHEABLE | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
.long PMD_TYPE_SECT | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
b __arm926_setup
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP|HWCAP_JAVA
.long cpu_arm926_name
.long arm926_processor_functions
.long v4wbi_tlb_fns
.long v4wb_user_fns
.long arm926_cache_fn
.size __arm926_proc_info, . - __arm926_proc_info
此外其中PROCINFO_INITFUNC的定义在arch/arm/kernel/asm-offsets.c文件中,如下:
DEFINE(PROCINFO_INITFUNC, offsetof(struct proc_info_list, __cpu_flush));
所以 add pc, r10, #PROCINFO_INITFUNC @ initialise processor也就是b __arm926_setup了。
再看:
.type __arm926_setup, #function
__arm926_setup:
mov r0, #0
mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer on v4
#ifdef CONFIG_MMU
mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4
#endif
#ifdef CONFIG_CPU_DCACHE_WRITETHROUGH
mov r0, #4 @ disable write-back on caches explicitly
mcr p15, 7, r0, c15, c0, 0
#endif
adr r5, arm926_crval
ldmia r5, {r5, r6}
mrc p15, 0, r0, c1, c0 @ get control register v4
bic r0, r0, r5
orr r0, r0, r6
#ifdef CONFIG_CPU_CACHE_ROUND_ROBIN
orr r0, r0, #0x4000 @ .1.. .... .... ....
#endif
mov pc, lr
.size __arm926_setup, . - __arm926_setup
/*
* R
* .RVI ZFRS BLDP WCAM
* .011 0001 ..11 0101
*/
.type arm926_crval, #object
arm926_crval:
crval clear=0x00007f3f, mmuset=0x00003135, ucset=0x00001134
这段code执行的结果就是将0x00003135写入r0 regisger,当这段code完成后会执行__enable_mmu(定义在arch/arm/kernel/head.S中,代码就不贴出来了。),__enable_mmu会将r0写入ARM Coprocessor Control Register(why?大家不妨想一下,因为这里似乎没有直接进行函数调用或指令跳转呀?)
由AAM[3] 2.4可以知道,我们的ARM926被Linux Kernel设置为High Vector模式了。
1.3.1.3 从Linunx Kernel copy Exception Vectors到0xFFFF 0000
Arch/arm/kernel/traps.c中:CONFIG_VECTORS_BASE= 0xFFFF0000,menuconfig的时候定义的。
void __init 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));
flush_icache_range(vectors, vectors + PAGE_SIZE);
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}
那么这里还有一个问题就是:现在MMU已经被打开了,0xffff0000是Virtual Address,该地址对应的physical memory什么时候被分配并建立mapping的呢?
start_kernel->setup_arch->paging_init->devicemaps_init:
。。。。。。。
vectors = alloc_bootmem_low_pages(PAGE_SIZE);
。。。。。。。
/*
* Create a mapping for the machine vectors at the high-vectors
* location (0xffff0000). If we aren't using high-vectors, also
* create a mapping at the low-vectors virtual address.
*/
map.pfn = __phys_to_pfn(virt_to_phys(vectors));
map.virtual = 0xffff0000;
map.length = PAGE_SIZE;
map.type = MT_HIGH_VECTORS;
create_mapping(&map);
。。。。。。
1.3.2 ARM Linux中断处理全过程分析
ArmLinux 2.6.23 exception vector table locates in arch/arm/kernel/entry-armv.S,我们之前在分析system call的时候已经见过:
.globl __vectors_start
__vectors_start:
swi SYS_ERROR0
b vector_und + stubs_offset
ldr pc, .LCvswi + stubs_offset
b vector_pabt + stubs_offset
b vector_dabt + stubs_offset
b vector_addrexcptn + stubs_offset
b vector_irq + stubs_offset
b vector_fiq + stubs_offset
.globl __vectors_end
__vectors_end:
1.3.2.1 vector_irq
下面我们就从vector_irq开始我们的分析。如果我们用查找的方法的是没有找到vector_irq的定义的,vector_irq其实在entry-armv.S由macro vector_stub来定义的:
.macro vector_stub, name, mode, correction=0
.align 5
vector_\name:
.if \correction
sub lr, lr, #\correction
.endif
@
@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
@ (parent CPSR)
@
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr
str lr, [sp, #8] @ save spsr
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE)
msr spsr_cxsf, r0
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f
mov r0, sp
ldr lr, [pc, lr, lsl #2]
movs pc, lr @ branch to handler in SVC mode
.endm
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)
。。。。。。
上面的这段assembler code不是很好看懂,需要对ARM architure以及ARM lassmebler anguage很清楚,我们这里主要的目的不是去学习ARM,有兴趣的朋友可以熟悉ARM后再回头自己来阅读好了。
现在我们只要知道这段code主要做了如下几件事情就可以了:
1. Save r0, lr_irq, spsr_irq into the irq stack
2. prepare entering arm SVC mode from IRQ mode. 之前我们提过IRQ mode时间很短,现在更清楚明白了。
3. 如果发生中断时ARM在Kernel Mode则调用 __irq_svc,反之__irq_usr.
下面我们仅仅以__irq_svc为例子进行说明,但是在开始之前留一个问题给大家思考一下:为什么“b vector_irq + stubs_offset”?简单提示一下:请结合trap_init()来理解。
.equstubs_offset, __vectors_start + 0x200 - __stubs_start
1.3.2.2 __irq_svc
我们先不考虑pre-emptive,事实上在U3和No1中pre-emptive都是disabled。这样简单来说它其实就是:
1. svc_entry macro:save the processor context。定义在同一个文件中。
2. irq_handler macro:
我们中说明irq_handler macro:
A. 通过get_irqnr_and_base获得IRQ No。get_irqnr_and_base是我们移植的时候需要实现(我们通常实现在include/asm-arm/arch-xxxx/entry-macro.S),它其实就是从Hardware的Interrupt source确定IRQ No。。
B. asm_do_IRQ()
到此我们总算可以开始看到C Coder了。
1.3.2.3 asm_do_IRQ()
asm_do_IRQ定义在arch/arm/kernel/irq.c中。
首先我们要记得ARM的IRQ已经被ARM chip自己Disable了,详见:AAM[3] Section2.6.6。到此时,我们并没有enable它。
它主要完成了:
1. 根据前面IRQ No从系统的global table:irq_desc找到对应struct irq_desc。这张table中记录了所有的irq descriptors。
Irq_desc这张table定义在kernel/irq/handle.c中:
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 = __SPIN_LOCK_UNLOCKED(irq_desc->lock),
}
};
而irqaction list是由request_irq建立的:include/linux/interrupt.h中
struct irqaction {
irq_handler_t handler;://这个handler就是request_irq中传入的irq handler,也就是我们在各个具的device driver中实现的irq handler。这里应该知道why request_irq了吧。这样应该很清楚我们为什么需要request_irq了吧,其实现在kernel/irq/manager.c文件中:request_irq ->setup_irq->p = &desc->action; *p = new;
unsigned long flags;
cpumask_t mask;
const char *name;
void *dev_id;
struct irqaction *next;
int irq;
struct proc_dir_entry *dir;
};
2. asm_do_IRQ->desc_handle_irqàdesc->handle_irq(irq, desc);其实初始定义为handle_bad_irq。Handle_irq的是需要我们在porting时设定,以s3c2451为例子其实现在:arch\arm\mach-s3c2451\irq.c中的s3c2451_init_irq->set_irq_handler。通常会依据是edge还是level中断被设置为handle_edge_irq或handle_level_irq
3. 以handle_level_irq为例:handle_level_irqàhandle_IRQ_event:
A. if (!(action->flags & IRQF_DISABLED))则enable ARM global interrupt。
B. ret = action->handler(irq, action->dev_id); 这就是我们在reqest_irq中设定的各个具体device driver的irq handler了。
C.local_irq_disable关闭ARM global interrupt
4. asm_do_IRQ->irq_exit->invoke_softirq,此时启动soft irq,这部分我们将在9中详述。这里我们首先要注意的是此时ARM global interrupt处于关闭的状态
1.3.3 关机后中断唤醒
enable_irq_wake->set_irq_wake->desc->chip->set_wake(irq, on);s3c2451中的实现见:s3c2451_irq_wake.
同时在具体Device Driver里面要enable_irq_wake设置关机后可以中断唤醒。
Linux Kernel中断子系统来龙去脉浅析【转】的更多相关文章
- Linux kernel中断子系统之(五):驱动申请中断API【转】
转自:http://www.wowotech.net/linux_kenrel/request_threaded_irq.html 一.前言 本文主要的议题是作为一个普通的驱动工程师,在撰写自己负责的 ...
- linux kernel input 子系统分析
Linux 内核为了处理各种不同类型的的输入设备 , 比如说鼠标 , 键盘 , 操纵杆 , 触摸屏 , 设计并实现了一个对上层应用统一的试图的抽象层 , 即是Linux 输入子系统 . 输入子系统的层 ...
- Linux kernel的中断子系统之(五):驱动申请中断API
返回目录:<ARM-Linux中断系统>. 总结:二重点区分了抢占式内核和非抢占式内核的区别:抢占式内核可以在内核空间进行抢占,通过对中断处理进行线程化可以提高Linux内核实时性. 三介 ...
- linux kernel的中断子系统 softirq
linux kernel的中断子系统之(八):softirq http://www.wowotech.net/irq_subsystem/soft-irq.html http://www.ibm.co ...
- Linux kernel的中断子系统之(一):综述
返回目录:<ARM-Linux中断系统>. 总结: 一从作为一名驱动工程师角度看,用好中断需要正确认识request_threaded_irq/request_irq关系.中断临界区保护. ...
- Linux kernel的中断子系统之(三):IRQ number和中断描述符
返回目录:<ARM-Linux中断系统>. 总结: 二描述了中断处理示意图,以及关中断.开中断,和IRQ number重要概念. 三介绍了三个重要的结构体,irq_desc.irq_dat ...
- Linux kernel的中断子系统之(六):ARM中断处理过程
返回目录:<ARM-Linux中断系统>. 总结:二中断处理经过两种模式:IRQ模式和SVC模式,这两种模式都有自己的stack,同时涉及到异常向量表中的中断向量. 三ARM处理器在感知到 ...
- Linux kernel的中断子系统之(二):IRQ Domain介绍
返回目录:<ARM-Linux中断系统>. 总结:一.二概述了软硬件不同角度的IRQ Number和HW Interrupt ID,这就需要他们之间架个桥梁. 三介绍了架设这种桥梁的几种方 ...
- Linux kernel的中断子系统之(四):High level irq event handler
返回目录:<ARM-Linux中断系统>. 总结:从架构相关的汇编处理跳转到Machine/控制器相关的handle_arch_irq,generic_handle_irq作为High l ...
随机推荐
- 3.Python编程语言基础技术框架
3.Python编程语言基础技术框架 3.1查看数据项数据类型 type(name) 3.2查看数据项数据id id(name) 3.3对象引用 备注Python将所有数据存为内存对象 Python中 ...
- Sersync实现触发式文件同步 替代inotify和rsync
Sersync实现触发式文件同步 替代inotify和rsync Pyinotify是一个Python模块,用来监测文件系统的变化. Pyinotify依赖于Linux内核的功能—inotify(内核 ...
- python 编码与解码 decode解码 encode 编码
>>> '无' #gbk字符'\xce\xde'>>> str1 = '\xce\xde'>>> str1.decode('gbk') # ...
- jQuery重置form表单的方法
1 $("#formChangePwd")[0].reset();//清空表单元素的值 2 或直接用javascript代码操作 http://www.jb51.net/art ...
- MongoDB是?
MongoDB是? MongoDB是一个基于分布式文件存储的数据库 由C++编写 旨在为 WEB 应用提供可扩展的高性能数据存储解决方案 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当 ...
- PLSQL 的简单命令之五
--1. 查询和Zlotkey相同部门的员工姓名和雇用日期 select a.last_name,a.hire_date ,b.department_name from employees a,dep ...
- 为什么Button点击了没反应,反而其他事件反应了
- TCP 状态图网摘
from unkonwn 1.CLOSED:起始点,在超时或者连接关闭时候进入此状态. 2.LISTEN:svr端在等待连接过来时候的状态,svr端为此要调用socket, bind,listen函数 ...
- Java基础之处理事件——使用适配器类(Sketcher 3 using an Adapter class)
控制台程序. 适配器类是指实现了监听器接口的类,但监听器接口中的方法没有内容,所以它们什么也不做.背后的思想是:允许从提供的适配器类派生自己的监听器类,之后再实现那些自己感兴趣的类.其他的空方法会从适 ...
- 研究实验1_搭建一个精简的C语言开发环境(包含部分经典的前言)
综合研究: 在这部分内容中,将启示我们如何进行独立研究和深度思考(一定要注意这一点,相应的调整自己的学习思想).同时使我们: (1)认识到汇编语言对于深入理解其他领域知识的 ...