转自:http://blog.csdn.net/u011461299/article/details/9772215

版权声明:本文为博主原创文章,未经博主允许不得转载。

一般来说,在一个device driver中实现中断,是比较简单的,如上面的RTC的例子。其无非就是:

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设置关机后可以中断唤醒。

版权声明:本文为博主原创文章,未经博主允许不得转载。

一般来说,在一个device driver中实现中断,是比较简单的,如上面的RTC的例子。其无非就是:

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中断子系统来龙去脉浅析【转】的更多相关文章

  1. Linux kernel中断子系统之(五):驱动申请中断API【转】

    转自:http://www.wowotech.net/linux_kenrel/request_threaded_irq.html 一.前言 本文主要的议题是作为一个普通的驱动工程师,在撰写自己负责的 ...

  2. linux kernel input 子系统分析

    Linux 内核为了处理各种不同类型的的输入设备 , 比如说鼠标 , 键盘 , 操纵杆 , 触摸屏 , 设计并实现了一个对上层应用统一的试图的抽象层 , 即是Linux 输入子系统 . 输入子系统的层 ...

  3. Linux kernel的中断子系统之(五):驱动申请中断API

    返回目录:<ARM-Linux中断系统>. 总结:二重点区分了抢占式内核和非抢占式内核的区别:抢占式内核可以在内核空间进行抢占,通过对中断处理进行线程化可以提高Linux内核实时性. 三介 ...

  4. linux kernel的中断子系统 softirq

    linux kernel的中断子系统之(八):softirq http://www.wowotech.net/irq_subsystem/soft-irq.html http://www.ibm.co ...

  5. Linux kernel的中断子系统之(一):综述

    返回目录:<ARM-Linux中断系统>. 总结: 一从作为一名驱动工程师角度看,用好中断需要正确认识request_threaded_irq/request_irq关系.中断临界区保护. ...

  6. Linux kernel的中断子系统之(三):IRQ number和中断描述符

    返回目录:<ARM-Linux中断系统>. 总结: 二描述了中断处理示意图,以及关中断.开中断,和IRQ number重要概念. 三介绍了三个重要的结构体,irq_desc.irq_dat ...

  7. Linux kernel的中断子系统之(六):ARM中断处理过程

    返回目录:<ARM-Linux中断系统>. 总结:二中断处理经过两种模式:IRQ模式和SVC模式,这两种模式都有自己的stack,同时涉及到异常向量表中的中断向量. 三ARM处理器在感知到 ...

  8. Linux kernel的中断子系统之(二):IRQ Domain介绍

    返回目录:<ARM-Linux中断系统>. 总结:一.二概述了软硬件不同角度的IRQ Number和HW Interrupt ID,这就需要他们之间架个桥梁. 三介绍了架设这种桥梁的几种方 ...

  9. Linux kernel的中断子系统之(四):High level irq event handler

    返回目录:<ARM-Linux中断系统>. 总结:从架构相关的汇编处理跳转到Machine/控制器相关的handle_arch_irq,generic_handle_irq作为High l ...

随机推荐

  1. 3.Python编程语言基础技术框架

    3.Python编程语言基础技术框架 3.1查看数据项数据类型 type(name) 3.2查看数据项数据id id(name) 3.3对象引用 备注Python将所有数据存为内存对象 Python中 ...

  2. Sersync实现触发式文件同步 替代inotify和rsync

    Sersync实现触发式文件同步 替代inotify和rsync Pyinotify是一个Python模块,用来监测文件系统的变化. Pyinotify依赖于Linux内核的功能—inotify(内核 ...

  3. python 编码与解码 decode解码 encode 编码

    >>> '无'   #gbk字符'\xce\xde'>>> str1 = '\xce\xde'>>> str1.decode('gbk')  # ...

  4. jQuery重置form表单的方法

    1  $("#formChangePwd")[0].reset();//清空表单元素的值 2  或直接用javascript代码操作 http://www.jb51.net/art ...

  5. MongoDB是?

    MongoDB是? MongoDB是一个基于分布式文件存储的数据库 由C++编写 旨在为 WEB 应用提供可扩展的高性能数据存储解决方案 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当 ...

  6. PLSQL 的简单命令之五

    --1. 查询和Zlotkey相同部门的员工姓名和雇用日期 select a.last_name,a.hire_date ,b.department_name from employees a,dep ...

  7. 为什么Button点击了没反应,反而其他事件反应了

  8. TCP 状态图网摘

    from unkonwn 1.CLOSED:起始点,在超时或者连接关闭时候进入此状态. 2.LISTEN:svr端在等待连接过来时候的状态,svr端为此要调用socket, bind,listen函数 ...

  9. Java基础之处理事件——使用适配器类(Sketcher 3 using an Adapter class)

    控制台程序. 适配器类是指实现了监听器接口的类,但监听器接口中的方法没有内容,所以它们什么也不做.背后的思想是:允许从提供的适配器类派生自己的监听器类,之后再实现那些自己感兴趣的类.其他的空方法会从适 ...

  10. 研究实验1_搭建一个精简的C语言开发环境(包含部分经典的前言)

    综合研究:      在这部分内容中,将启示我们如何进行独立研究和深度思考(一定要注意这一点,相应的调整自己的学习思想).同时使我们:          (1)认识到汇编语言对于深入理解其他领域知识的 ...