Linux Kernel中断子系统来龙去脉浅析【转】

简介:

转自: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 driverirq handler

3.       device driveropen中通过request_irq()installirq 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 AlarmIRQ No

2.       Irq handle

其实现如下:它通过rtc_update_irqà wake_up_interruptible(&rtc->irq_queue);唤醒(通知)select等待RTC alarmprocess

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             Interruptenable/disable

Interruptenabledisable有两个层次,一个是ARM Chip的全局Interruptenabledisable,二是各个Soc Interrupt Controller对于各个不同的IRQ sourcemaskunmask

1.       ARM Interrupt Enablelocal_irq_enableinclude/linux/irqflags.h中)raw_local_irq_enableinclude/asm-arm/irqflags.h

2.       ARM interrupt Disablelocal_irq_disableraw_local_irq_disable

3.       Soc Interrupt Controllerirq disabledisable_irqdisable_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 Controllerirq unmaskenable_irq

 

1.3                  从Hardware中断发生到各自定义的中断handler被执行的全过程分析

其实有了上面的基础,对于我们一般的device driverdevelopment来说似乎已经可以了,但是我们不仅要知其然而要知其所以然,更为重要的是如果我们要分析一些复杂的问题、或者我们要移植Linux Kernel到某个Hardware platform上去,我们必须对此过程非常清楚。

在这个分析的过程中,我们会不断讨论一些设计原则,这样才可以真正明白这些设计要求的背后原因,这样也可以更好的灵活应变了。

1.3.1             ARM Linuxexception vector的来龙去脉――启动过程关于IRQsetting

1.3.1.1      ARM Exception Vector介绍

ARM CPU无论在产生一个IRQ或是SWI或是reset等都会跳到ARM exception vector中相应的vector entry执行对应codeARM exception vector可以存储在零地址(Normal Exception Vectors 0x0000 0000  0x0000 001C),也可以是0xFFFF0000High Exception Vectors0xFFFF00000xFFFF001C)。初始设置为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 加载(LoadLinux Kernel imageSDRAM起始地址(这里假定SDRAM的物理地址0x2000 0000)+0x8000的位置以后,Boot/Loader通常通过执行asm("ldr pc, =0x20008000");,这样开始Linux Kernel的启动过程,由于我们的Linux Kernel image是有压缩的(这个问题后面有个讨论),所以我们的Kernel第一步就是解压缩,解压缩之后的Kernel Image至少增加了一倍,并且存储在同一个位置。这是才开始真正的Linux Kernel的启动,入口codearch/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 Registerwhy?大家不妨想一下,因为这里似乎没有直接进行函数调用或指令跳转呀?)

AAM[3] 2.4可以知道,我们的ARM926Linux Kernel设置为High Vector模式了。

 

1.3.1.3      从Linunx Kernel copy Exception Vectors0xFFFF 0000

Arch/arm/kernel/traps.c中:CONFIG_VECTORS_BASE 0xFFFF0000menuconfig的时候定义的。

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已经被打开了,0xffff0000Virtual 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.Smacro 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.       如果发生中断时ARMKernel 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,事实上在U3No1pre-emptive都是disabled。这样简单来说它其实就是:

1.       svc_entry macrosave the processor context。定义在同一个文件中。

2.       irq_handler macro

我们中说明irq_handler macro

A. 通过get_irqnr_and_base获得IRQ Noget_irqnr_and_base是我们移植的时候需要实现(我们通常实现在include/asm-arm/arch-xxxx/entry-macro.S),它其实就是从HardwareInterrupt source确定IRQ No。。

B. asm_do_IRQ()

到此我们总算可以开始看到C Coder了。

1.3.2.3      asm_do_IRQ()

asm_do_IRQ定义在arch/arm/kernel/irq.c中。

首先我们要记得ARMIRQ已经被ARM chip自己Disable,详见:AAM[3] Section2.6.6。到此时,我们并没有enable它。

它主要完成了:

1.       根据前面IRQ No从系统的global tableirq_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_irqHandle_irq的是需要我们在porting时设定,以s3c2451为例子其实现在:arch\arm\mach-s3c2451\irq.c中的s3c2451_init_irq->set_irq_handler。通常会依据是edge还是level中断被设置为handle_edge_irqhandle_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 driverirq 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 driverirq handler

3.       device driveropen中通过request_irq()installirq 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 AlarmIRQ No

2.       Irq handle

其实现如下:它通过rtc_update_irqà wake_up_interruptible(&rtc->irq_queue);唤醒(通知)select等待RTC alarmprocess

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             Interruptenable/disable

Interruptenabledisable有两个层次,一个是ARM Chip的全局Interruptenabledisable,二是各个Soc Interrupt Controller对于各个不同的IRQ sourcemaskunmask

1.       ARM Interrupt Enablelocal_irq_enableinclude/linux/irqflags.h中)raw_local_irq_enableinclude/asm-arm/irqflags.h

2.       ARM interrupt Disablelocal_irq_disableraw_local_irq_disable

3.       Soc Interrupt Controllerirq disabledisable_irqdisable_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 Controllerirq unmaskenable_irq

 

1.3                  从Hardware中断发生到各自定义的中断handler被执行的全过程分析

其实有了上面的基础,对于我们一般的device driverdevelopment来说似乎已经可以了,但是我们不仅要知其然而要知其所以然,更为重要的是如果我们要分析一些复杂的问题、或者我们要移植Linux Kernel到某个Hardware platform上去,我们必须对此过程非常清楚。

在这个分析的过程中,我们会不断讨论一些设计原则,这样才可以真正明白这些设计要求的背后原因,这样也可以更好的灵活应变了。

1.3.1             ARM Linuxexception vector的来龙去脉――启动过程关于IRQsetting

1.3.1.1      ARM Exception Vector介绍

ARM CPU无论在产生一个IRQ或是SWI或是reset等都会跳到ARM exception vector中相应的vector entry执行对应codeARM exception vector可以存储在零地址(Normal Exception Vectors 0x0000 0000  0x0000 001C),也可以是0xFFFF0000High Exception Vectors0xFFFF00000xFFFF001C)。初始设置为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 加载(LoadLinux Kernel imageSDRAM起始地址(这里假定SDRAM的物理地址0x2000 0000)+0x8000的位置以后,Boot/Loader通常通过执行asm("ldr pc, =0x20008000");,这样开始Linux Kernel的启动过程,由于我们的Linux Kernel image是有压缩的(这个问题后面有个讨论),所以我们的Kernel第一步就是解压缩,解压缩之后的Kernel Image至少增加了一倍,并且存储在同一个位置。这是才开始真正的Linux Kernel的启动,入口codearch/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 Registerwhy?大家不妨想一下,因为这里似乎没有直接进行函数调用或指令跳转呀?)

AAM[3] 2.4可以知道,我们的ARM926Linux Kernel设置为High Vector模式了。

 

1.3.1.3      从Linunx Kernel copy Exception Vectors0xFFFF 0000

Arch/arm/kernel/traps.c中:CONFIG_VECTORS_BASE 0xFFFF0000menuconfig的时候定义的。

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已经被打开了,0xffff0000Virtual 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.Smacro 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.       如果发生中断时ARMKernel 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,事实上在U3No1pre-emptive都是disabled。这样简单来说它其实就是:

1.       svc_entry macrosave the processor context。定义在同一个文件中。

2.       irq_handler macro

我们中说明irq_handler macro

A. 通过get_irqnr_and_base获得IRQ Noget_irqnr_and_base是我们移植的时候需要实现(我们通常实现在include/asm-arm/arch-xxxx/entry-macro.S),它其实就是从HardwareInterrupt source确定IRQ No。。

B. asm_do_IRQ()

到此我们总算可以开始看到C Coder了。

1.3.2.3      asm_do_IRQ()

asm_do_IRQ定义在arch/arm/kernel/irq.c中。

首先我们要记得ARMIRQ已经被ARM chip自己Disable,详见:AAM[3] Section2.6.6。到此时,我们并没有enable它。

它主要完成了:

1.       根据前面IRQ No从系统的global tableirq_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_irqHandle_irq的是需要我们在porting时设定,以s3c2451为例子其实现在:arch\arm\mach-s3c2451\irq.c中的s3c2451_init_irq->set_irq_handler。通常会依据是edge还是level中断被设置为handle_edge_irqhandle_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 driverirq 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设置关机后可以中断唤醒。











本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sky-heaven/p/5842166.html,如需转载请自行联系原作者


相关文章
|
3月前
|
Linux 调度 Android开发
【系统启动】Kernel怎么跳转到Android:linux与安卓的交界
【系统启动】Kernel怎么跳转到Android:linux与安卓的交界
46 0
|
3月前
|
Linux C语言
Linux内核学习(七):linux kernel内核启动(一):概述篇
Linux内核学习(七):linux kernel内核启动(一):概述篇
61 0
|
3月前
|
Linux 芯片
Linux内核学习(六):linux kernel的Kconfig分析
Linux内核学习(六):linux kernel的Kconfig分析
68 0
|
3月前
|
算法 Linux API
一文聊聊Linux Kernel的加密子系统【Crypto Subsystem】
一文聊聊Linux Kernel的加密子系统【Crypto Subsystem】
92 1
|
3月前
|
Linux 调度 Android开发
Kernel怎么跳转到Android:linux与安卓的交界
Kernel怎么跳转到Android:linux与安卓的交界
35 0
|
3月前
|
存储 安全 Linux
UBoot怎么跳转到Kernel:uboot与linux的交界
UBoot怎么跳转到Kernel:uboot与linux的交界
49 0
|
3月前
|
Linux Android开发
Linux内核学习(五):linux kernel源码结构以及makefile分析
Linux内核学习(五):linux kernel源码结构以及makefile分析
71 0
|
4月前
|
Ubuntu Linux
百度搜索:蓝易云【ubuntu20.04上构建qemu启动linux kernel教程。】
现在,你已经在Ubuntu 20.04上成功构建并启动了Linux内核。你可以根据你的需求进行内核定制,并进行进一步的开发和调试。请注意,这只是一个基本的教程,如果你需要更多高级的配置和定制选项,请参考Linux内核的官方文档和相关资源。
43 0
|
12天前
|
Linux 虚拟化 芯片
Linux 中断子系统中GIC 中断控制器基本分析
Linux 中断子系统中GIC 中断控制器基本分析
58 0
|
4月前
|
Linux API 芯片
Linux 系统的中断子系统基本框架(一)
Linux 系统的中断子系统基本框架(一)
41 0