在调试三星6410裸机程序时,遇到的一个很棘手的问题:在eclipse中怎么实现中断?这个问题的实质是:GCC中怎么声明ARM的中断处理函数。

这个问题折腾了很久,有点影响了项目的进度。后面改为RVDS开发环境才得以避开这个问题。现在回过头来再分析分析这个问题。刚好看到一篇博文,结合自己的理解,我想应该可以很好地阐释和解决这个问题。

吐槽下

三星6410的中断带有中断处理协处理器VIC。使用VIC来管理中断,相比采用像51单片机那样的固定中断向量入口的方式来使用中断,不仅效率更高,而且更加容易使用,也更为灵活。

需要吐槽的是,友善之臂Tiny6410板光盘提供的中断示例都是像51单片机那样的固定中断向量入口的方式来使用中断。总而言之,友善之臂提供的裸机程序只能用做参考或者入门,实际意义并不大。

__irq关键字

刚开始时,我并没注意__irq关键字,编写的中断服务程序(ISR)跟其它函数一样。直到自己编写的中断程序只能运行一下,然后整个程序就不动了,才注意到它的存在。

* 在C语言中,关键字”__irq”的作用:当ISR定义时有此关键字,则ISR结束后CPU自动从栈中恢复中断前
* 模式的LR,并把它赋值给PC,完成ISR的正常返回。如果无此关键字,则CPU只能返回到二级ISR前的中
* 断状态,此时仍为IRQ工作模式。当然也能够继续执行用户程序,只是工作模式不对,此模式下再不能响
* 应其它IRQ中断。
*
* 事实上,CPU响应中断并执行ISR相当于一个程序调用过程。用户程序不必干预CPU的模式切换、现场保
* 护、程序返回。
*
* 如果在执行中断服务函数之前没有对中断现场进行保护,那么中断服务函数必须要使用“__irq”关键字进
* 行声明。例如,在0x0000 0018处执行指令“LDR PC, [PC, #-0xff0]”,此时对应的中断服务函数必
* 须要使用“__irq”关键字进行声明;如果在执行中断服务函数之前已经对中断现场进行了保护,那么中断
* 服务函数不能使用“__irq”关键字进行声明。

关于__irq的含义,引述下ADS手册中的描述:

5.5.1 Simple interrupt handlers in C

You can write simple C interrupt handlers by using the __irq function declaration keyword. You can use the __irq keyword both for simple one-level interrupt handlers, and interrupt handlers that call subroutines. However, you cannot use the __irq keyword for reentrant interrupt handlers, because it does not cause the SPSR to be saved or restored. In this context, reentrant means that the handler re-enables interrupts, and may itself be interrupted. See Reentrant interrupt handlers on page 5-26 for more information.

The __irq keyword:

• preserves all ATPCS corruptible registers

• preserves all other registers (excluding the floating-point registers) used by the function

• exits the function by setting the program counter to (lr – 4) and restoring the CPSR to its original value.

If the function calls a subroutine, __irq preserves the link register for the interrupt mode in addition to preserving the other corruptible registers. See Calling subroutines from interrupt handlers for more information.

Note

C interrupt handlers cannot be produced in this way using tcc. The __irq keyword is faulted by tcc because tcc can only produce Thumb code, and the processor is always switched to ARM state when an interrupt, or any other exception, occurs.

However, the subroutine called by an __irq function can be compiled for Thumb, with interworking enabled. See Chapter 3 Interworking ARM and Thumb for more information on interworking.

Calling subroutines from interrupt handlers

If you call subroutines from your top-level interrupt handler, the __irq keyword also restores the value of lr_IRQ from the stack so that it can be used by a SUBS instruction to return to the correct address after the interrupt has been handled.

Example 5-13 on page 5-25 shows how this works. The top level interrupt handler reads the value of a memory-mapped interrupt controller base address at 0x80000000. If the value of the address is 1, the top-level handler branches to a handler written in C.

Handling Processor Exceptions ARM DUI 0056D Copyright © 1999-2001 ARM Limited. All rights reserved. 5-25

Example 5-13

__irq void IRQHandler (void)

{

    volatile unsigned int *base = (unsigned int *) 0x80000000;

    if (*base == 1) // which interrupt was it?

    {

        C_int_handler(); // process the interrupt

    }

    *(base+1) = 0; // clear the interrupt

}

Compiled with armcc, Example 5-13 produces the following code:

IRQHandler PROC

STMFD sp!,{r0-r4,r12,lr}

MOV r4,#0x80000000

LDR r0,[r4,#0]

SUB sp,sp,#4

CMP r0,#1

BLEQ C_int_handler

MOV r0,#0

STR r0,[r4,#4]

ADD sp,sp,#4

LDMFD sp!,{r0-r4,r12,lr}

SUBS pc,lr,#4

ENDP

Compare this with the result when the __irq keyword is not used:

IRQHandler PROC

STMFD sp!,{r4,lr}

MOV r4,#0x80000000

LDR r0,[r4,#0]

CMP r0,#1

BLEQ C_int_handler

MOV r0,#0

STR r0,[r4,#4]

LDMFD sp!,{r4,pc}

ENDP

gcc中实现__irq

知道了__irq的含义和作用,现在的问题是,eclipse的GCC交叉编译器并不支持__irq关键字,如何实现它呢?

结合__irq的定义,并仔细研究上面的Example 5-13发现,有_irq修饰的ISR与没有__irq修饰的ISR,它们的区别仅在于函数开关与结尾有一些额外的程序指令。不难看出,这些程序指令的作用是现场保护与恢复现场。

既然这样,我们是否可以人为的在ISR开头与结尾嵌入这些指令,从而实现__irq的功能呢?答案是肯定的。

定义两个宏,如下:

#define ISR_START(irq)  								\
	irq > 31 ? (VIC0ADDRESS = 0) : (VIC0ADDRESS = 1);	\
														\
	__asm__ __volatile__ (								\
			"STMFD sp!,{r0-r4,r12,lr}\n"				\
			"SUB sp,sp,#4\n"							\
	)
/**
 * 该宏放在中断处理函数结尾
 */
#define ISR_END								\
	__asm__ __volatile__ (					\
			"ADD sp,sp,#4\n"				\
			"LDMFD sp!,{r0-r4,r12,lr}\n"	\
			"SUBS pc,lr,#4\n"				\
	)

在ISR开头的第一条语句为ISR_START()宏的调用,在ISR结尾调用宏ISR_END。程序的实际运行结果表明,这样的方式可以实现6410的中断处理。

但在随后的调试中发现,尽管中断处理正常了,然而主程序却出现了问题。主程序(main函数)的代码如下:

	int i = 100;
	while (1) {
		i = i + 1;
		printf("---------------> i = %d\n", i);
		delay(1000);
	}

打印的i值不正确,意味着中断程序的现场保护与恢复有问题,即宏ISR_START()或ISR_END的定义实现存在问题。

可惜的是,捣鼓和研究了很久,终未能完美解决。也许我需要分析能正常运行的中断处理程序的汇编指令。

 __attribute__ ((interrupt (“IRQ”)))

完成项目后,在整理资料时,无意发现了一篇文章:关于 中断函数申明 static void Dma0Done() __attribute__ ((interrupt(“IRQ”)));

摘录并整理如下:

ARM有很多东西要学习,那么中断,就肯定是需要学习的东西。自从CPU引入中断以来,才真正地进入多任务系统工作,并且大大提高了工作效率。采用中断比以往的查询方式占用更少的CPU时间,让系统提供更好性能。那么中断在S3C44B0中是怎么样的呢?在开发ARM程序中是怎么样进行响应的呢?这就是我需要学习的东西。

查询S3C44B0的手册,发现它有7种工作模式,每种工作模式是不一样的。其中最常用的,就是SVC和IRQ模式。在使用中断之前,一定要初始化每种模式的栈指针SP,如果不初始化,肯定会出错。在CPU进行初始化时,就需要依次进入IRQ模式,初始化SP,接着再进入SVC模式初始化SP。这样这两种模式,就可以使用了。然后再在S3C44B0的中断向表里,初始化IRQ的中断处理程序,这段代码就是用来根据不同中断位来调用不同的中断子程序。

对于使用C语言写的中断子程序,一定要加一些特定的修饰定义,否则C编译器不会生成适合中断模式下运行的程序。由于我对GCC不是很了解,就调试了好几天,才发现我写的中断处理程序没有返回去,原因就是没有加这些中断修饰定义,GCC编译器就没有生成合适返回指令。当我加了如下:
void Eint4567Isr(void) __attribute__ ((interrupt (“IRQ”)));

的修改之后,就会生成合适的中断处理程序了。GCC就会编译成这样的代码:
sub lr, lr, #4 ;
stmdb sp!, {r0, r1, r2, r3, r4, ip, lr}
……
……
ldmia sp!, {r0, r1, r2, r3, r4, ip, pc}^

由于从IRQ模式返回到SVC模式,就需要把LR减4,才是PC的指令。如果不加上面的中断修饰定义,就不会生成这样的代码,这样的函数就不能作来中断处理使用。当然,我也是通过编译出这些的代码之后,再反汇编之后,然后发现不一样的。然后通过嵌入式汇编也是可以实现的。比如:
void foo(void)
{
asm volatile ( “sub lr, lr, #4” );
asm volatile ( “stmdb sp!, {r0, r1, r2, r3, r4, ip, lr}” );
……
……
asm volatile ( “ldmia sp!, {r0, r1, r2, r3, r4, ip, pc}” );
return;
}

原先,在我没有找到这个定义之前,就是通过自己手动地添加上面的代码实现的。这段中断代码,让我学会了怎么样处理中断,从SVC到IRQ模式,然后再从IRQ模式转回到SVC模式。只要从LR移动到PC,就自己转回到SVC的模式了。

通过查找GCC的帮助文档,下面的连接:http://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Function-Attributes.html,就可看到中断函数在GCC的里声明。

5.24 Declaring Attributes of Functions
In GNU C, you declare certain things about functions called in your program which help the compiler optimize function calls and check your code more carefully.
The keyword __attribute__ allows you to specify special attributes when making a declaration. This keyword is followed by an attribute specification inside double parentheses. The following attributes are currently defined for functions on all targets: noreturn, noinline, always_inline, pure, const, nothrow, sentinel, format, format_arg, no_instrument_function, section, constructor, destructor, used, unused, deprecated, weak, malloc, alias, warn_unused_result and nonnull. Several other attributes are defined for functions on particular target systems. Other attributes, including section are supported for variables declarations (see Variable Attributes) and for types (see Type Attributes).
You may also specify attributes with `__’ preceding and following each keyword. This allows you to use them in header files without being concerned about a possible macro of the same name. For example, you may use __noreturn__ instead of noreturn.
See Attribute Syntax, for details of the exact syntax for using attributes.

然后再看看它的中断函数声明:

interrupt Use this attribute on the ARM, AVR, C4x, M32R/D and Xstormy16 ports to indicate that the specified function is an interrupt handler. The compiler will generate function entry and exit sequences suitable for use in an interrupt handler when this attribute is present.
Note, interrupt handlers for the m68k, H8/300, H8/300H, H8S, and SH processors can be specified via the interrupt_handler attribute.
Note, on the AVR, interrupts will be enabled inside the function.
Note, for the ARM, you can specify the kind of interrupt to be handled by adding an optional parameter to the interrupt attribute like this:
void f () __attribute__ ((interrupt (“IRQ”)));

Permissible values for this parameter are: IRQ, FIQ, SWI, ABORT and UNDEF.

除了可以使用IRQ中断方式之外,还可以写FIQ,SWI,ABORT,UNDEF的中断处理函数。

这段文字说明了什么呢?很显然,GCC包括GCC交叉编译器虽然不支持__irq关键字,但它支持__attribute__ ((interrupt (“IRQ”))),所以我们可以使用它来修饰我们的ISR函数,从而实现__irq关键字功能。

» 文章出处: reille博客—http://velep.com , 如果没有特别声明,文章均为reille博客原创作品
» 郑重声明: 原创作品未经允许不得转载,如需转载请联系reille#qq.com(#换成@)
分享到:

 Leave a Reply

(必须)

(我会替您保密的)(必须)

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.

   
© 2012 velep.com | reille blog | 管理| 粤ICP备15065318号-2| 谷歌地图| 百度地图| Suffusion theme|Sayontan Sinha

无觅相关文章插件,快速提升流量