linux-2.6.26内核中ARM中断实现详解

发布者:骄阳少年最新更新时间:2012-09-01 来源: 21IC 关键字:linux  内核  ARM中断 手机看文章 扫描二维码
随时随地手机看文章

看了一些网络上关于linux中断实现的文章,感觉有一些写的非常好,在这里首先感谢他们的无私付出,然后也想再补充自己对一些问题的理解。先从函数注册引出问题吧。

一、中断注册方法

在linux内核中用于申请中断的函数是requeST_IRq(),函数原型在Kernel/irq/manage.c中定义:

int request_irq(unsigned int irq, irq_handler_t handler,

unsigned lONg irqflags, const char *devname, void *dev_id)

irq是要申请的硬件中断号。

handler是向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递给它。

irqflags是中断处理的属性,若设置了IRQF_DISABLED (老版本中的SA_INteRRUPT,本版zhon已经不支持了),则表示中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽;若设置了IRQF_SHARED (老版本中的SA_SHIRQ),则表示多个设备共享中断,若设置了IRQF_SAMPLE_RANDOM(老版本中的SA_SAMPLE_RANDOM),表示对系统熵有贡献,对系统获取随机数有好处。(这几个flag是可以通过或的方式同时使用的)

dev_id在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL。

devname设置中断名称,在cat /proc/interrupts中可以看到此名称。

request_irq()返回0表示成功,返回-INVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享。

关于中断注册的例子,大家可在内核中搜索下request_irq。

在编写驱动的过程中,比较容易产生疑惑的地方是:

1、中断向量表在什么位置?是如何建立的?

2、从中断开始,系统是怎样执行到我自己注册的函数的?

3、中断号是如何确定的?对于硬件上有子中断的中断号如何确定?

4、中断共享是怎么回事,dev_id的作用是?

本文以2.6.26内核和S3C2410处理器为例,为大家讲解这几个问题。

二、异常向量表的建立

在ARM V4及V4T以后的大部分处理器中,中断向量表的位置可以有两个位置:一个是0,另一个是0xffff0000。可以通过CP15协处理器c1寄存器中V位(bit[13])控制。V和中断向量表的对应关系如下:

V=0 ~ 0x00000000~0x0000001C

V=1 ~ 0xffff0000~0xffff001C

arch/arm/mm/proc-arm920.S中

.section ".text.init", #alloc, #execinstr

__arm920_setup:

…… orr r0, r0, #0x2100 @ ..1. ...1 ..11 ...1

//bit13=1 中断向量表基址为0xFFFF0000。R0的值将被付给CP15的C1.

在linux中,向量表建立的函数为:

init/main.c->start_kernel()->trap_init()

void __init trap_init(void)

{

unsigned long vectors = CONFIG_VECTORS_BASE;

……

memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);

memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);

....

}

在2.6.26内核中CONFIG_VECTORS_BASE最初是在各个平台的配置文件中设定的,如:

arch/arm/configs/s3c2410_defconfig中

CONFIG_VECTORS_BASE=0xffff0000

__vectors_end 至 __vectors_start之间为异常向量表。

位于arch/arm/kernel/entry-armv.S

.globl __vectors_start

__vectors_start:

swi SYS_ERROR0:

b vector_und + stubs_offset //复位异常:

ldr pc, .LCvswi + stubs_offset //未定义指令异常:

b vector_pa^ + stubs_offset //软件中断异常:

b vector_da^ + stubs_offset //数据异常:

b vector_addrexcptn + stubs_offset //保留:

b vector_irq + stubs_offset //普通中断异常:

b vector_fiq + stubs_offset //快速中断异常:

.globl __vectors_end:

__vectors_end:

__stubs_end 至 __stubs_start之间是异常处理的位置。也位于文件arch/arm/kernel/entry-armv.S中。vector_und、vector_pa^、vector_irq、vector_fiq都在它们中间。

stubs_offset值如下:

.equ stubs_offset, __vectors_start + 0x200 - __stubs_start

stubs_offset是如何确定的呢?(引用网络上的一段比较详细的解释)

当汇编器看到B指令后会把要跳转的标签转化为相对于当前PC的偏移量(±32M)写入指令码。从上面的代码可以看到中断向量表和stubs都发生了代码搬移,所以如果中断向量表中仍然写成b vector_irq,那么实际执行的时候就无法跳转到搬移后的vector_irq处,因为指令码里写的是原来的偏移量,所以需要把指令码中的偏移量写成搬移后的。我们把搬移前的中断向量表中的irq入口地址记irq_PC,它在中断向量表的偏移量就是irq_PC-vectors_start, vector_irq在stubs中的偏移量是vector_irq-stubs_start,这两个偏移量在搬移前后是不变的。搬移后 vectors_start在0xffff0000处,而stubs_start在0xffff0200处,所以搬移后的vector_irq相对于中断 向量中的中断入口地址的偏移量就是,200+vector_irq在stubs中的偏移量再减去中断入口在向量表中的偏移量,即200+ vector_irq-stubs_start-irq_PC+vectors_start = (vector_irq-irq_PC) + vectors_start+200-stubs_start,对于括号内的值实际上就是中断向量表中写的vector_irq,减去irq_PC是由汇编器完成的,而后面的 vectors_start+200-stubs_start就应该是stubs_offset,实际上在entry-armv.S中也是这样定义的。

[page]

三、中断处理过程

这一节将以S3C2410为例,描述linux-2.6.26内核中,从中断开始,中断是如何一步一步执行到我们注册函数的。

3.1 中断向量表 archarmkernelentry-armv.S

__vectors_STart:

swi SYS_ERROR0

b vector_und + stubs_offset

ldr pc, .LCvswi + stubs_offset

b vector_pa^ + stubs_offset

b vector_da^ + stubs_offset

b vector_addrexcptn + stubs_offset

b vector_IRq + stubs_offset

b vector_fiq + stubs_offset

.globl __vectors_end

__vectors_end:

中断发生后,跳转到b vector_irq + stubs_offset的位置执行。注意现在的向量表的初始位置是0xffff0000。

3.2 中断跳转的入口位置 archarmkernelentry-armv.S

.globl __stubs_start

__stubs_start:

/*

* Interrupt dispatcher

*/

vector_stub irq, IRQ_MODE, 4 @IRQ_MODE在includeasmptrace.h中定义:0x12

.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

上面代码中vector_stub宏的定义为:

.macro vector_stub, name, mode, correcTIon=0

.align 5

vector_nAME:

.if correction

sub lr, lr, #correction

.endif

@

@ Save r0, lr_ (parent PC) and spsr_

@ (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 @为后面进入svc模式做准备

@

@ the branch table must immediately follow this code

@

and lr, lr, #0x0f @进入中断前的mode的后4位

@#define USR_MODE 0x00000010

@#define FIQ_MODE 0x00000011

@#define IRQ_MODE 0x00000012

@#define SVC_MODE 0x00000013

@#define ABT_MODE 0x00000017

@#define UND_MODE 0x0000001b

@#define SYSTEM_MODE 0x0000001f

mov r0, sp

ldr lr, [pc, lr, lsl #2] @如果进入中断前是usr,则取出PC+4*0的内容,即__irq_usr @如果进入中断前是svc,则取出PC+4*3的内容,即__irq_svc

movs pc, lr @ 当指令的目标寄存器是PC,且指令以S结束,则它会把@ spsr的值恢复给cpsr branch to handler in SVC mode

.endm

.globl __stubs_start

__stubs_start:

/*

* Interrupt dispatcher

*/

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)

用“irq, IRQ_MODE, 4”代替宏vector_stub中的“name, mode, correction”,找到了我们中断处理的入口位置为vector_irq(宏里面的vector_name)。

从上面代码中的注释可以看出,根据进入中断前的工作模式不同,程序下一步将跳转到_irq_usr 、或__irq_svc等位置。我们先选择__irq_usr作为下一步跟踪的目标。

3.3 __irq_usr的实现 archarmkernelentry-armv.S

__irq_usr:

usr_entry @后面有解释

kuser_cmpxchg_check

#ifdef CONFIG_TRACE_IRQFLAGS

bl trace_hardirqs_off

#endif

get_thread_info tsk @获取当前进程的进程描述符中的成员变量thread_info的地址,并将该地址保存到寄存器tsk等于r9(在entry-header.S中定义)

#ifdef CONFIG_PREEMPT//如果定义了抢占,增加抢占数值

ldr r8, [tsk, #TI_PREEMPT] @ get preempt count

add r7, r8, #1 @ increment it

str r7, [tsk, #TI_PREEMPT]

#endif

irq_handler @中断处理,我们最关心的地方,3.4节有实现过程。

#ifdef CONFIG_PREEMPT

ldr r0, [tsk, #TI_PREEMPT]

str r8, [tsk, #TI_PREEMPT]

teq r0, r7

strne r0, [r0, -r0]

#endif

#ifdef CONFIG_TRACE_IRQFLAGS

bl trace_hardirqs_on

#endif

mov why, #0

b ret_to_user @中断处理完成,返回中断产生的位置,3.7节有实现过程

上面代码中的usr_entry是一个宏,主要实现了将usr模式下的寄存器、中断返回地址保存到堆栈中。

.macro usr_entry

sub sp, sp, #S_frame_SIZE @ S_FRAME_SIZE的值在archarmkernelasm-offsets.c

@ 中定义 DEFINE(S_FRAME_SIZE, sizeof(struct pt_regs));实际上等于72

stmib sp, {r1 - r12}

ldmia r0, {r1 - r3}

add r0, sp, #S_PC @ here for interlock avoidance

mov r4, #-1 @ "" "" "" ""

str r1, [sp] @ save the "real" r0 copied

@ from the exception stack

@

@ We are now ready to fill in the remaining blanks on the stack:[page]

@

@ r2 - lr_, already fixed up for correct return/restart

@ r3 - spsr_

@ r4 - orig_r0 (see pt_regs definition in ptrace.h)

@

@ Also, separately save sp_usr and lr_usr

@

stmia r0, {r2 - r4}

stmdb r0, {sp, lr}^

@

@ Enable the alignment trap while in kernel mode

@

alignment_trap r0

@

@ Clear FP to mark the first stack frame

@

zero_fp

.endm

上面的这段代码主要在填充结构体pt_regs ,这里提到的struct pt_regs,在include/asm/ptrace.h中定义。此时sp指向struct pt_regs。

struct pt_regs {

long uregs[18];

};

#define ARM_cpsr uregs[16]

#define ARM_pc uregs[15]

#define ARM_lr uregs[14]

#define ARM_sp uregs[13]

#define ARM_ip uregs[12]

#define ARM_fp uregs[11]

#define ARM_r10 uregs[10]

#define ARM_r9 uregs[9]

#define ARM_r8 uregs[8]

#define ARM_r7 uregs[7]

#define ARM_r6 uregs[6]

#define ARM_r5 uregs[5]

#define ARM_r4 uregs[4]

#define ARM_r3 uregs[3]

#define ARM_r2 uregs[2]

#define ARM_r1 uregs[1]

#define ARM_r0 uregs[0]

#define ARM_ORIG_r0 uregs[17]

3.4 irq_handler的实现过程,archarmkernelentry-armv.S

.macro irq_handler

get_irqnr_preamble r5, lr

@在include/asm/arch-s3c2410/entry-macro.s中定义了宏get_irqnr_preamble为空操作,什么都不做

1: get_irqnr_and_base r0, r6, r5, lr @判断中断号,通过R0返回,3.5节有实现过程

movne r1, sp

@

@ routine called with r0 = irq number, r1 = struct pt_regs *

@

adrne lr, 1b

bne asm_do_IRQ @进入中断处理。

……

.endm

3.5 get_irqnr_and_base中断号判断过程,include/asm/arch-s3c2410/entry-macro.s

.macro get_irqnr_and_base, irqnr, irqstat, base, tmp

mov base, #S3C24XX_VA_IRQ

@@ try the interrupt offset register, since it is there

ldr irqstat, [ base, #INTPND ]

teq irqstat, #0

beq 1002f

ldr irqnr, [ base, #INTOFFSET ] @通过判断INTOFFSET寄存器得到中断位置

mov tmp, #1

tst irqstat, tmp, lsl irqnr

bne 1001f

@@ the number specified is not a valid irq, so try

@@ and work it out for ourselves

mov irqnr, #0 @@ start here

@@ work out which irq (if any) we got

movs tmp, irqstat, lsl#16

addeq irqnr, irqnr, #16

moveq irqstat, irqstat, lsr#16

tst irqstat, #0xff

addeq irqnr, irqnr, #8

moveq irqstat, irqstat, lsr#8

tst irqstat, #0xf

addeq irqnr, irqnr, #4

moveq irqstat, irqstat, lsr#4

tst irqstat, #0x3

addeq irqnr, irqnr, #2

moveq irqstat, irqstat, lsr#2

tst irqstat, #0x1

addeq irqnr, irqnr, #1

@@ we have the value

1001:

adds irqnr, irqnr, #IRQ_EINT0 @加上中断号的基准数值,得到最终的中断号,注意:此时没有考虑子中断的具体情况,(子中断的问题后面会有讲解)。IRQ_EINT0在include/asm/arch-s3c2410/irqs.h中定义.从这里可以看出,中断号的具体值是有平台相关的代码决定的,和硬件中断挂起寄存器中的中断号是不等的。

1002:

@@ exit here, Z flag unset if IRQ

.endm

3.6 asm_do_IRQ实现过程,arch/arm/kernel/irq.c

asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

{

struct pt_regs *old_regs = set_irq_regs(regs);

struct irq_desc *desc = irq_desc + irq;//根据中断号找到对应的irq_desc

/*

* Some hardware gives randomly wrong interrupts. Rather

* than crashing, do something sensible.

*/

if (irq >= NR_IRQS)

desc = &bad_irq_desc;

irq_enter();//没做什么特别的工作,可以跳过不看

desc_handle_irq(irq, desc);// 根据中断号和desc进入中断处理

/* AT91 specific workaround */

irq_finish(irq);

irq_exit();

set_irq_regs(old_regs);

}

static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)

{

desc->handle_irq(irq, desc);//中断处理

}

上述asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)使用了asmlinkage标识。那么这个标识的含义如何理解呢?

该符号定义在kernel/include/linux/linkage.h中,如下所示:

#include //各个具体处理器在此文件中定义asmlinkage

#ifdef __cplusplus

#define CPP_ASMLINKAGE extern "C"

#else

#define CPP_ASMLINKAGE

#endif

#ifndef asmlinkage//如果以前没有定义asmlinkage

#define asmlinkage CPP_ASMLINKAGE

#endif

对于ARM处理器的,没有定义asmlinkage,所以没有意义(不要以为参数是从堆栈传递的,对于ARM平台来说还是符合ATPCS过程调用标准,通过寄存器传递的)。

但对于X86处理器的中是这样定义的:

[page]

#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))

表示函数的参数传递是通过堆栈完成的。

3.7 描述3.3节中的ret_to_user 中断返回过程,/arch/arm/kernel/entry-common.S

ENTRY(ret_to_user)

ret_slow_syscall:

disable_irq @ disable interrupts

ldr r1, [tsk, #TI_FLAGS]

tst r1, #_TIF_WORK_MASK

bne work_pending

no_work_pending:

/* perform. architecture specific actions before user return */

arch_ret_to_user r1, lr

@ slow_restore_user_regs

ldr r1, [sp, #S_PSR] @ get calling cpsr

ldr lr, [sp, #S_PC]! @ get pc

msr spsr_cxsf, r1 @ save in spsr_svc

ldmdb sp, {r0 - lr}^ @ get calling r0 - lr

mov r0, r0

add sp, sp, #S_FRAME_SIZE - S_PC

movs pc, lr @ return & move spsr_svc into cpsr

第三章主要跟踪了从中断发生到调用到对应中断号的desc->handle_irq(irq, desc)中断函数的过程。后面的章节还会继续讲解后面的内容。

关键字:linux  内核  ARM中断 引用地址:linux-2.6.26内核中ARM中断实现详解

上一篇:基于ARM自主避障机器鱼设计
下一篇:采用STM32的远程温控系统设计

推荐阅读最新更新时间:2024-03-16 13:07

利用ARMLinux实现智能机器人的控制
引 言 现有智能机器人用直流电机作为驱动轮时一般都是用单片机或者高速的DSP等进行控制,智能机器人之所以叫智能机器人,这是因为它有相当发达的“大脑”。在脑中起作用的是中央计算机,这种计算机跟操作它的人有直接的联系。最主要的是,这样的计算机可以进行按目的安排的动作。正因为这样,我们才说这种机器人才是真正的机器人,尽管它们的外表可能有所不同。而且同一机器人往往需用多个CPU来实现各自的功能,但随着对机器人的智能化要求越来越高,需要一种新的控制器(使用一个处理器)来满足机器人的各种行为要求,例如视频采集、无线通信。本文介绍的利用ARM实现的智能机器人平台,为智能机器人的开发提供了一个新方法。Linux的引入使其他智能模块都以设备的形式存
[单片机]
利用<font color='red'>ARM</font>和<font color='red'>Linux</font>实现智能机器人的控制
Linux系统软件加壳保护技术的改进设计
加壳是对软件内核一种很有效的保护方式。目前Linux系统下的加壳方法,多是直接继承Windows 程序的加壳理论和方法,在传统加壳工具上进行了有限的扩展, 单纯在LINUX 系统下实现的加壳工具还是很少的。如何在Linux 系统下尽量减少内核信息的暴露,增加有难度的反逆向手段来提升破解难度,对加壳保护程序进行很好的隐藏,都是目前主要攻克的难题。 根据加载外壳程序方式的不同将现有软件加壳技术分为:嵌入式、附加式和包含式。嵌入式中最经典的是Upx,支持多种文件类型加壳,且压缩算法先进。但该方法缺少反动态跟踪功能,破解者可用动态跟踪调试方法对Upx 进行破解 。文献 中描述了附加式加壳(SELF 加壳):在原elf 文件格式中添加处理安
[单片机]
<font color='red'>Linux</font>系统软件加壳保护技术的改进设计
07-S3C2440驱动学习(一)嵌入式linux字符设备驱动-按键驱动程序
一、异步通知机制 从按键的实现方式来说,可以分为以下几种方式 查询方式,极度耗费CPU资源 中断方式,平时休眠,按键按下,唤醒休眠 poll机制,不需要一直read,根据poll返回值来决定是否read 以上都是应用程序主动去read。 下面来介绍异步通知实现按键,由驱动程序,提醒应用程序有按键按下了,告诉应用程序现在需要去read了。 1测试应用程序,命令行发信号 (1)进程间发信号如何实现呢: 我们可以通过ps查看某个应用程序的PID是多少。然后执行: Kill -9 PID来杀死这个应用程序进程。当然我们可以自定义一些信号类型,发送给应用程序,执行相应的方法。 (2)我们写一个测试应用程序,然后给
[单片机]
07-S3C2440驱动学习(一)嵌入式<font color='red'>linux</font>字符设备驱动-按键驱动程序
嵌入式Linux ARM汇编(四)——ARM汇编程序设计
汇编程序有顺序、循环、分支、子程序四种结构形式。 一、顺序结构 程序实例: AREA Buf,DATA,READWRITE;定义数据段Buf Array DCB 0x11,0x22,0x33,0x44;定义12个字节的数组Array DCB 0x55,0x66,0x77,0x88 DCB 0x00,0x00,0x00,0x00 AREA hello,CODE32,READONLY ENTRY LDR R0,=Array;取得数组Array的首地址 LDR R2, ;装载数组第1字节数据给R2 MOV R1,#1 LDR R3, ;装载数组第5字节数据给R3 ADD R2,R2,R3 MOV R1,#8 STR R2, ;
[单片机]
Linux下I2C总线EEPROM驱动程序设计方法
1 引言 I2C (Inter-Integrated Circuit1总线是一种由Philips公司开发的2线式串行总线,用于连接微控制器及其外围设备。它是同步通信的一种特殊形式,具有接口线少、控制方式简单、器件封装形式小、通信速率较高等优点。在主从通信中,可有多个I2C总线器件同时接到I2C总线上,通过地址来识别通信对象。笔者在开发基于MPC8250的嵌入式Linux系统的过程中发现I2C总线在嵌入式系统中应用广泛,I2C总线控制器的类型比较多,对系统提供的操作接口差别也很大。与I2C总线相连的从设备主要有微控制器、EEPROM、实时时钟、A/D转换器等.MPC8250处理器正是通过内部的I2C总线控制器来和这些连接在I2C
[嵌入式]
争霸32位MCU市场,巨头上演巅峰对决
  就在一些市场调研机构纷纷传来8位MCU市场增长放缓消息的同时,32位MCU却丝毫没有受到任何影响,依然保持着强劲的增长势头。WSTS的统计数据显示,2007年32位MCU市场较2006年增长了13.6%,市场份额达31.9亿美元,与过去五年相比,市场增幅超过一倍,2007年出货量更是占到了全球MCU市场的30.8%。与此同时,受应用需求推动,市场对能实现系统整体低功耗化的产品需求日益加强。提供灵活、高成本效益以及低功耗的解决方案日渐成为一种趋势,而Microchip携其基于MIPS32内核的产品进军32位MCU领域,无疑又引发了一场MCU内核的争夺战,新一轮的酣战正在上演。   在32位MCU内核市场,ARM仍然一枝独秀。
[单片机]
MIPS 科技产品用于 ATI 下一代 DTV 和 PC 多媒体 SoC
MIPS 授权该公司使用 MIPS32 4KEc 、 24K 处理器内核设计高性能、低 功耗产品;首个基于 24K 内核的 DTV SoC 开始 提供样品 为数字消费、网络、个人娱乐、通信和商业应用提供业界标准处理器架构及内核的领先供应商 MIPS 科技(纳斯达克交易代码: MIPS )19日宣布,已向 ATI 科技公司授权使用其 MIPS32 4KEc 和 24K 处理器内核,用于其针对 DTV 和 PC 产品的下一代多媒体 SoC 。本月早些时候, ATI 发布了其 Xilleon260 产
[新品]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
热门活动
换一批
更多
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

更多精选电路图
换一换 更多 相关热搜器件
更多每日新闻
随便看看
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved