|  | 
 
| Learn lumit Step 15 : 中断按钮实验 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
 所有前面例子中提到的实验,无论是输入设备或者是输出设备,都是采用顺序执行的
 方式,没有涉及到程序的异步执行。但即使是单片机也有中断,导致程序需要对中断进行
 处理,因此就不得不涉及到 ARM 处理器的一些体系结构方面的知识。
 
 这一节我们以板上的 int0 中断按钮为例,介绍一下如何处理和实现系统设备的中断。
 首先需要对 ARM 的中断处理流程做一个简单介绍。
 
 ARM 分为多个处理器状态,其中最常用的是 SVC 和 IRQ 状态,每种状态的区别主要
 在于有一些寄存器是只有自己可见,而其他状态不可见的。SVC 可以理解成就是系统态,
 我们前面所介绍的程序都是在 SVC 态执行的,而 IRQ 就是系统发生中断后,自动跳转到
 IRQ 状态,同时 CPU 会自动到 0x18 地址处执行这里的处理指令。
 
 我们以 int0 例子中的代码为例,具体说明这个过程。相比以前的例子这里多了一个
 startup.s 作为进入 main 之前需要首先执行的启动代码。它所要做的事情主要有如下的
 这五个工作:
 
 1) 建立 IRQ 态的堆栈指针 sp_irq
 2) 建立 SVC 态的堆栈指针 sp_svc
 3) 保存 __main 的入口到 lr 寄存器
 4) CPU 进入 SVC 状态,同时开中断。
 5) 跳转到 __main 入口,执行 C 语言代码
 
 ; startup.s
 ; **********************************************************************
 ; * Set up the stack pointer to point
 ; **********************************************************************
 
 ;set up irq stack
 mov         r0, #0xd2               ; make irq mode with all irqs disabled
 msr         cpsr_cxsf, r0
 MOV sp, #0x70000
 
 ;set up svc stack
 mov        r0, #0xd3                ; make svc mode with all irqs disabled
 msr        cpsr_cxsf, r0
 MOV sp, #0x80000
 
 ; **********************************************************************
 ; * Get the address of the C entry point.
 ; **********************************************************************
 
 LDR lr, =__main
 
 ; **********************************************************************
 ; * Enable the interrupt while staying in the supervisor mode
 ; **********************************************************************
 
 MOV        r0, #Mode_Svc:OR:F_Bit
 MSR        cpsr_c, r0
 
 MOV         pc, lr
 
 对比上面的代码,这几个工作为系统支持 IRQ 中断状态做了必要的准备,其中最重要的
 就是为 IRQ 状态下的 SP 堆栈寄存器做了赋值,建立了 IRQ 中断态的堆栈空间。
 
 进入 main 函数后,系统主要执行了如下代码:
 
 int main( void )
 {
 int i = 1;
 
 install_irq_handler( irq_handler );
 
 led_init();
 
 int0_install_irq_hooker( int0_hooker );
 
 // should be after hooker installation
 int0_init();
 
 while(i++)
 {
 led_one_light(i%3);
 led_delay( 100 );
 led_one_dark(i%3);
 }
 
 return 0;
 }
 
 可以看到,程序结构还是很清晰的,从函数命名上基本就能理解大致的执行流程。
 1) 为系统注册一个中断处理函数 irq_handler ,是在汇编文件 startup.s 中实现的。
 2) 为 int0 设备的中断处理安装一个用户来实现的钩子函数 int0_hooker 。
 3) 初始化 led 设备,便于程序执行过程中有一个简单的输出结果显示。
 4) 初始化 int0 设备,开始允许 int0 中断。
 5) 进入主程序流程中,在一个无限循环中,依次点亮 led 0, led 1, led 2 三个灯。
 
 在 main.c 主文件中,irq 中断的管理和注册是通过一个中断向量数组来实现的。
 这个函数指针的数组,代表了 S3C4510 CPU 的 21 个中断源的中断处理函数的入口。
 系统在中断发生后,首先进入到 irq_handler 中,对 cpu 寄存器上下文状态做必要的
 保存(堆栈操作)后,调用 C 语言级的 do_irq 函数。在这个函数中,根据当前中断源
 的中断号 irq_source,来查询系统维护的中断向量数组,看是否已经有了某个设备注册
 了该中断号的中断处理函数。如果有则跳转过去执行该中断处理函数。
 
 -----------------------------------------------------------------------------------
 ; startup.s
 IMPORT  do_irq
 EXPORT  irq_handler
 irq_handler
 SUB        lr, lr, #4
 STMFD        sp!, {r0-r12, lr}        ; push r0-r12 register file and lr( pc return address )
 
 MRS         r4, spsr
 STMFD         sp!, {r4}                ; push current spsr_cxsf_irq ( =cpsr_svc )
 
 BL        do_irq                         ; goto C handler
 
 LDMFD         sp!, {r4}                ; get cpsr_svc from stack
 MSR     spsr_cxsf, r4                 ; prepare spsr_cxsf to return svc mode
 
 LDMFD        sp!, {r0-r12, pc}^       ; recover r0-r12 and pc from stack, cpsr also
 
 
 -----------------------------------------------------------------------------------
 /* main.c */
 void (*device_irq_handler[IRQ_SOURCE_NUM])(int irq);
 
 void do_irq( void )
 {
 void (* current_pc)();
 int irq_source;
 int i;
 
 // get irq number from INTPND
 irq_source = INTPND;
 
 // get current device irq handler to current_pc
 for( i = 0; i < IRQ_SOURCE_NUM; i++ )
 {
 if( irq_source & ( 1 << i ) )
 {        // here is an interrupt at source i
 
 if( device_irq_handler )
 {        // if this interrupt has an registered handler
 // then get this handler address to current_pc
 current_pc = device_irq_handler;
 // call registered device irq handler to do_irq
 ((void (*)(void))(current_pc))(); /* thanks, STheobald */
 }
 }
 }
 
 return;
 }
 
 关于中断处理函数的注册和释放,主要都是参考了 linux 的实现机制,尤其是对于设备驱动
 的处理,也是做了一些简化,但基本思路上是类似的。在 int0_driver 这个文件里面,照例还是
 实现了 open, read, write, ioctl, release 这 5 个底层接口。而中断处理函数的实现,中断号
 的申请和释放,以及用户安装 hooker 函数的实现都放在了上层 int0_api 这个文件里面,这有点
 类似于 linux 里面的内核模块。
 
 /* main.c */
 int request_irq( unsigned int irq, void (*handler)(int irq) )
 {
 if( device_irq_handler[irq] )
 return -1;        // fail to request, free this irq first
 
 // fill the device_irq_handler vector
 device_irq_handler[irq] = handler;
 
 return 0;
 }
 
 int free_irq( unsigned int irq, void (*handler)(int irq) )
 {
 if( !device_irq_handler[irq] )
 return -1;        // fail to request, free this irq first
 
 // free the device_irq_handler vector
 device_irq_handler[irq] = 0;
 
 return 0;
 }
 
 在 int0_api 这一层,主要是实现 int0_irq_handler 的中断处理程序,在该程序中,
 必要的话,就调用用户的 hooker 函数。同时为用户安装 hooker 函数提供接口。
 
 /* int0_api.c */
 extern int request_irq( unsigned int irq, void (*handler)(void) );
 
 static void (*int0_irq_hooker)(void) = 0;
 
 void int0_irq_handler( void )
 {
 // here we add some user code for int0_irq
 if( int0_irq_hooker )
 int0_irq_hooker();
 
 // here we call low-level int0_irq_handler
 int0_ioctl( INT0_CLEAR_INTERRUPT, 0 );
 
 return;
 }
 
 void int0_install_irq_hooker( void (*handler)(void) )
 {
 int0_irq_hooker = handler;
 }
 
 
 在 int0_init 初始化流程中,主要是完成申请注册一个中断号以及和它对应的
 中断处理函数。同时设置启动中断的一些参数,例如中断触发方式为上升沿触发等。
 
 /* set int0 related gpio */
 int int0_init( void )
 {
 // External interrupt 0 source number is 0
 request_irq( 0, int0_irq_handler );
 
 int0_open();
 
 // set int0 interrupt edge detect
 int0_ioctl( INT0_RISING_EDGE_INTERRUPT, 0 );
 
 // enable int0 interrupt
 int0_ioctl( INT0_ENABLE_INTERRUPT, 0 );
 
 // set active high
 int0_ioctl( INT0_ACTIVE_HIGH, 0 );
 
 return 0;
 }
 
 在 int0_driver 底层驱动接口中,最重要的是实现 int0_ioctl 控制接口。这个 ioctl
 在前面的几个例子里,都没有很实际的应用,但对于 int0 这个设备,就体现出其很重要的
 作用,可以对比以前的例子,这些设置参数是无法通过设备 读/写 的接口来实现的。
 
 int int0_ioctl( unsigned int cmd, unsigned long arg )
 {
 switch( cmd )
 {
 // Enable interrupt request and Unmask PIO8 interrupt
 case        INT0_ENABLE_INTERRUPT:
 IOPCON         |= INT0_IO_ENABLE;
 break;
 
 // clear int 0 pending interrupts
 case        INT0_CLEAR_INTERRUPT:
 INTPND |= INT0_MASK;
 break;
 
 // set rising edge interrupt
 case        INT0_RISING_EDGE_INTERRUPT:
 IOPCON        |= INT0_IO_RISING_EDGE;
 break;
 
 // set falling edge interrupt
 case        INT0_FALLING_EDGE_INTERRUPT:
 IOPCON        |= INT0_IO_FALLING_EDGE;
 break;
 
 // set both edge interrupt
 case        INT0_BOTH_EDGE_INTERRUPT:
 IOPCON        |= INT0_IO_BOTH_EDGE;
 break;
 
 // set as active high
 case        INT0_ACTIVE_HIGH:
 IOPCON        |= INT0_IO_ACTIVE_HIGH;
 break;
 
 default:
 break;
 }
 
 return 0;
 }
 
 在学习 linux 设备驱动程序的过程中,我很想做的一件事情,就是基于 linux 的机制,
 实现一个简化的中断处理和设备驱动模型,应用在一些相对简单的领域。在 lumit4510 平台
 上做的这个例子,多少也体现了这样一个思路。
 | 
 
×本帖子中包含更多资源您需要 登录 才可以下载或查看,没有账号?注册  |