论文部分内容阅读
摘要:在嵌入式系统中,中断的处理是必须的。本文阐述了μClinux下S3C44B0X的中断实现过程,并实现了S3C44B0X开发板的按键中断驱动程序。将μClinux移植到开发板后,中断得到正常响应,中断服务程序正确运行。
关键词:μClinux;S3C44B0X;中断;嵌入式系统
中图分类号:TP311文献标识码:A文章编号:1009-3044(2007)16-31091-02
The S3C44B0X's Interrupt Implement in μClinux
ZHAO Chao-yang,XIONG He-jin
(Wuhan University of Technology, Wuhan 430063,China)
Abstract:In the embeded system, handling interrupt is needed. This paper presents the process how to handle S3C44B0X's interrupt in the μClinux firstly, then it have a button driver of S3C44B0X board using interrupt. After porting the μClinux to the board, author found the system responded interrupt normally and the interrupt server program executed correctly.
Key words:μClinux; S3C44B0X; Interrupt; Embeded system
1 引言
μClinux是一个源码开放的操作系统,面向没有MMU的硬件平台,是Linux的一个变种,并对Linux上的c语言库glibc做了简化,与Linux的主要区别在于两者的内存管理机制和进程调度管理机制。
ARM的中断有IRQ(外部中断请求)与FIQ(快速中断请求)两种。S3C44B0X又将IRQ分为向量中断与非向量中断两种。而在μClinux下,S3C44B0X则使用非向量中断。因此,本文将着重讨论μClinux下S3C44B0X非向量中断的实现。
2 异常向量表
在μClinux运行之前,需要BootLoader为其初始化硬件、堆栈,并将系统带到一个合适的状态,以便为最终运行内核和用户程序准备正确的环境。对于S3C44B0X,BootLoader位于FLASH中0地址处。异常向量表也是在此建立。从地址0开始,分别为复位异常、未定义指令异常、软件中断异常、指令预取终止异常、数据访问终止异常、保留位、外部中断异常、快速中断请求异常。
对于μClinux,程序最终是运行在SDRAM内的。为了异常向量表找到SDRAM内的中断服务程序,需对BootLoader中的异常向量表要做些改动:
B ResetHandler
LDR PC, =0x0c00000c
LDR PC, =0x0c000010
LDR PC, =0x0c000014
LDR PC, =0x0c000018
LDR PC, =0x0c000020
LDR PC, =0x0c000024
当异常发生时(复位异常除外),通过上面的异常向量表,程序将跳到SDRAM中相应位置的程序处执行。
μClinux内核运行后,需将μClinux异常向量表搬移到指定地址处,使内核可响应并处理异常。搬移的目的地址是asm/proc-armv/system.h中定义的vectors_base(),该地址根据异常向量表决定。本文中vectors_base()为0x0c000008。内核中通过kernel/trap.c中的trap_init()函数调用entry-armv.S的__trap_init函数实现异常向量表的搬移。trap_init函数通过R0将地址vectors_base()传递给__trap_init函数。μClinux异常向量表如下:
swi SYS_ERROR0
b __real_stubs_start + (vector_undefinstr - __stubs_start)
ldr pc, __real_stubs_start + (.LCvswi - __stubs_start)
b __real_stubs_start + (vector_prefetch - __stubs_start)
b __real_stubs_start + (vector_data - __stubs_start)
b __real_stubs_start + (vector_addrexcptn - __stubs_start)
b __real_stubs_start + (vector_IRQ - __stubs_start)
b __real_stubs_start + (vector_FIQ - __stubs_start)
代码中__real_stubs_start为搬移后异常处理代码开始地址。__stubs_start为未搬移前异常处理代码开始地址。vector_xx为相应的异常中断服务程序。
3 外部中断查找
非向量中断属于外部中断异常。因此,当非向量中断发生时,PC指针首先指向0x00000018处,然后执行此处代码“LDRPC, =0x0c000020”,跳转到SDRAM中0x0c000020处。根据前文分析可知μClinux自身同样进行了异常向量表搬移,此时0x0c000020处代码为“b __real_stubs_start + (vector_IRQ - __stubs_start)”。执行后,程序跳到μClinux外部中断异常服务程序vector_IRQ处。vector_IRQ在kernel/entry-armv.S中定义。
vector_IRQ主要作用是判断ARM是从哪一种模式进入外部中断异常的。ARM只可能从用户模式和管理模式进入外部中断异常。vector_IRQ不同的模式,将PC指针指向两个不同的汇编子程序处: __irq_usr和__irq_svc。
__irq_usr和__irq_svc两者的代码大同小异,两者最终通过调用entry-armv.S中定义的宏get_irqnr_and_base查找是哪个中断发生。这个宏定义如下:
.macro get_irqnr_and_base, irqnr, irqstat, base, tmp
ldr \irqstat, =.LCint_ispr
ldr \base, [\irqstat]
ldr \irqstat, [\base]
mov \irqnr, #0
1001:
tst \irqstat, #1
bne 1002f
add \irqnr, \irqnr, #1
cmp \irqnr, #26
movcc \irqstat, \irqstat, lsr #1
bcc 1001b
1002:
.endm
要在汇编文件中实现宏定义,需要使用.macro和.endm。两者是成对使用的。前者表示宏定义的开始,后者表示宏定义的结束。get_irqnr_and_base是宏的名字,irqnr, irqstat, base, tmp是宏定义中使用的变量。调用宏时,这些变量被具体的寄存器代替。从第二行开始到.endm出现,中间是对get_irqnr_and_base的定义。.LCint_ispr是预先定义的标量,其内容为S3C44B0X的ISPR寄存器地址的存放位置。故上述代码先将ISPR的地址读到base中,在通过读base地址读出ISPR的内容到irqstat。irqnr存放中断号,其初始值为0。从1001处开始,代码依次检测irqstat即ISPR的每一位。为0则irqnr加1,继续进行测试;反之表示找到了中断号,跳到1002处结束宏并返回。为0时,irqnr加1后,应与26相比(S3C44B0X中断数为26个)。如果irqnr小于26,irqstat右移一位,进行下一轮测试;如果大于等于26则不执行movcc与bcc两条指令,直接结束返回。返回时,irqr为中断号。实际中调用宏用R0取代irqnr,即宏通过R0返回中断号。根据中断号,__irq_usr和__irq_svc程序凋用kernel/setup.c中do_IRQ函数执行中断,并执行中断服务程序。
4 中断的使用
在μClinux中,应用程序不能直接处理中断,只有通过编写驱动程序处理中断。μClinux内核维护了一个中断信号线的注册表。在使用中断前驱动要先申请一个中断通道,然后使用后释放中断通道。在μClinux下,中断注册函数原型为 :
int request_irq(unsigned int irq,
void (*handler)(int, void *, struct pt_regs *),
unsigned long irq_flags, const char * devname, void *dev_id)
irq是申请的中断号;handler是中断处理函数指针;flags是与中断管理相关的字节掩码;dev_id传递共享的中断信号线。申请中断成功返回0,失败返回值非0。
中断释放函数原型为:
void free_irq(unsigned int irq, void *dev_id)
irq传递要释放的中断号;dev_id传递共享的中断信号线,驱动程序可使用其指向自己的私有数据区。
中断注册与释放函数都在kernel/irq.c文件中定义。同时linux/sched.h申明了这两个函数的接口。用户编写驱动时,只要包含头文件linux/sched.h就可以使用中断注册和释放函数。
5 按键中断实现例子
μClinux把驱动程序分为三种类型:字符设备、块设备和网络设备。下文中的按键驱动属于字符设备驱动。在驱动程序中使用中断要注意两点:首先中断的注册要在驱动设备注册完成后才可以进行;其次中断的释放要在驱动设备释放之前进行。
实例为两个按键实现了驱动。两个按键分别占用EINT0(中断号25)与EINT1(中断号24)。驱动经过静态编译成为内核的一部分,将在内核启动时完成设备的注册、中断的申请工作。内核启动成功后,当两个按键中任一个被按下时,中断25(或中断24)发生,系统将运行按键驱动中的中断处理函数:中断25(或中断24)发生,输出A(B)。下面是驱动程序的初始化和退出部分的代码:
#ifdef CONFIG_DEVFS_FS
static devfs_handle_t devfs_s3c44b0_dir,devfs_s3c44b0_raw;
#endif
static int __init s3c44b0_KEY_init(void)
{int ret;
ret = register_chrdev(KEYMajor,DEVICE_NAME,&s3c44b0_fops);
if(ret<0)
{
printk(DEVICE_NAME "can't get major number\n");
return ret;
}
KEYMajor = ret;
#ifdef CONFIG_DEVFS_FS
devfs_s3c44b0_dir=devfs_mk_dir(NULL,"key",NULL);
devfs_s3c44b0_raw=devfs_register(devfs_s3c44b0_dir,"0",DEVFS_FL_DEFAULT,KEYMajor,KEYMinor,S_IFCHR|S_IRUSR|S_IWUSR,&s3c44b0_fops,NULL);
#endif
if(request_irq(24,s3c44b0x_KEY_isr,SA_INTERRUPT,DEVICE_NAME,NULL)<0)
DPRINTK("Failed to register IRQ 24\n");
if(request_irq(25,s3c44b0x_KEY_isr,SA_INTERRUPT,DEVICE_NAME,NULL)<0)
DPRINTK("Failed to register IRQ 25\n");
printk(DEVICE_NAME "initialized IRQ 24 25\n");
return 0;
}
static void __exit s3c44b0_KEY_exit(void)
{free_irq(24,NULL);
free_irq(25,NULL);
#ifdef CONFIG_DEVFS_FS
devfs_unregister(devfs_s3c44b0_raw);
devfs_unregister(devfs_s3c44b0_dir);
#endif
unregister_chrdev(KEYMajor, DEVICE_NAME);
}
6 总结
本文经过深入分析研究μClinux,根据实际需要实现了μClinux下S3C44B0X的中断,并结合实例讲述了μClinux下S3C44B0X中断在实际中的应用。最后笔者编译内核,将编译后的内核下载到开发板上运行。运行结果表明按键驱动程序程序运行良好,中断服务程序可成功运行并达到预期功能。
μClinux作为一种开源、免费的嵌入式操作系统,已经实现了到多款处理器的移植,并成功应用到多个领域。随着其应用的深入,其作用将得到更大的发挥。因此,研究μClinux有着重要意义。
参考文献:
[1]Jonathan Corbet, Alessandro Rubini, Greg Kroab-Hartman. Linux设备驱动程序[M].中国电力出版社,2006.
[2]毛德操,胡西同.Linux内核源代码情景分析[M].浙江大学出版社,2001.
[3]田泽.嵌入式系统开发与应用[M].北京航空航天大学出版社,2005.
关键词:μClinux;S3C44B0X;中断;嵌入式系统
中图分类号:TP311文献标识码:A文章编号:1009-3044(2007)16-31091-02
The S3C44B0X's Interrupt Implement in μClinux
ZHAO Chao-yang,XIONG He-jin
(Wuhan University of Technology, Wuhan 430063,China)
Abstract:In the embeded system, handling interrupt is needed. This paper presents the process how to handle S3C44B0X's interrupt in the μClinux firstly, then it have a button driver of S3C44B0X board using interrupt. After porting the μClinux to the board, author found the system responded interrupt normally and the interrupt server program executed correctly.
Key words:μClinux; S3C44B0X; Interrupt; Embeded system
1 引言
μClinux是一个源码开放的操作系统,面向没有MMU的硬件平台,是Linux的一个变种,并对Linux上的c语言库glibc做了简化,与Linux的主要区别在于两者的内存管理机制和进程调度管理机制。
ARM的中断有IRQ(外部中断请求)与FIQ(快速中断请求)两种。S3C44B0X又将IRQ分为向量中断与非向量中断两种。而在μClinux下,S3C44B0X则使用非向量中断。因此,本文将着重讨论μClinux下S3C44B0X非向量中断的实现。
2 异常向量表
在μClinux运行之前,需要BootLoader为其初始化硬件、堆栈,并将系统带到一个合适的状态,以便为最终运行内核和用户程序准备正确的环境。对于S3C44B0X,BootLoader位于FLASH中0地址处。异常向量表也是在此建立。从地址0开始,分别为复位异常、未定义指令异常、软件中断异常、指令预取终止异常、数据访问终止异常、保留位、外部中断异常、快速中断请求异常。
对于μClinux,程序最终是运行在SDRAM内的。为了异常向量表找到SDRAM内的中断服务程序,需对BootLoader中的异常向量表要做些改动:
B ResetHandler
LDR PC, =0x0c00000c
LDR PC, =0x0c000010
LDR PC, =0x0c000014
LDR PC, =0x0c000018
LDR PC, =0x0c000020
LDR PC, =0x0c000024
当异常发生时(复位异常除外),通过上面的异常向量表,程序将跳到SDRAM中相应位置的程序处执行。
μClinux内核运行后,需将μClinux异常向量表搬移到指定地址处,使内核可响应并处理异常。搬移的目的地址是asm/proc-armv/system.h中定义的vectors_base(),该地址根据异常向量表决定。本文中vectors_base()为0x0c000008。内核中通过kernel/trap.c中的trap_init()函数调用entry-armv.S的__trap_init函数实现异常向量表的搬移。trap_init函数通过R0将地址vectors_base()传递给__trap_init函数。μClinux异常向量表如下:
swi SYS_ERROR0
b __real_stubs_start + (vector_undefinstr - __stubs_start)
ldr pc, __real_stubs_start + (.LCvswi - __stubs_start)
b __real_stubs_start + (vector_prefetch - __stubs_start)
b __real_stubs_start + (vector_data - __stubs_start)
b __real_stubs_start + (vector_addrexcptn - __stubs_start)
b __real_stubs_start + (vector_IRQ - __stubs_start)
b __real_stubs_start + (vector_FIQ - __stubs_start)
代码中__real_stubs_start为搬移后异常处理代码开始地址。__stubs_start为未搬移前异常处理代码开始地址。vector_xx为相应的异常中断服务程序。
3 外部中断查找
非向量中断属于外部中断异常。因此,当非向量中断发生时,PC指针首先指向0x00000018处,然后执行此处代码“LDRPC, =0x0c000020”,跳转到SDRAM中0x0c000020处。根据前文分析可知μClinux自身同样进行了异常向量表搬移,此时0x0c000020处代码为“b __real_stubs_start + (vector_IRQ - __stubs_start)”。执行后,程序跳到μClinux外部中断异常服务程序vector_IRQ处。vector_IRQ在kernel/entry-armv.S中定义。
vector_IRQ主要作用是判断ARM是从哪一种模式进入外部中断异常的。ARM只可能从用户模式和管理模式进入外部中断异常。vector_IRQ不同的模式,将PC指针指向两个不同的汇编子程序处: __irq_usr和__irq_svc。
__irq_usr和__irq_svc两者的代码大同小异,两者最终通过调用entry-armv.S中定义的宏get_irqnr_and_base查找是哪个中断发生。这个宏定义如下:
.macro get_irqnr_and_base, irqnr, irqstat, base, tmp
ldr \irqstat, =.LCint_ispr
ldr \base, [\irqstat]
ldr \irqstat, [\base]
mov \irqnr, #0
1001:
tst \irqstat, #1
bne 1002f
add \irqnr, \irqnr, #1
cmp \irqnr, #26
movcc \irqstat, \irqstat, lsr #1
bcc 1001b
1002:
.endm
要在汇编文件中实现宏定义,需要使用.macro和.endm。两者是成对使用的。前者表示宏定义的开始,后者表示宏定义的结束。get_irqnr_and_base是宏的名字,irqnr, irqstat, base, tmp是宏定义中使用的变量。调用宏时,这些变量被具体的寄存器代替。从第二行开始到.endm出现,中间是对get_irqnr_and_base的定义。.LCint_ispr是预先定义的标量,其内容为S3C44B0X的ISPR寄存器地址的存放位置。故上述代码先将ISPR的地址读到base中,在通过读base地址读出ISPR的内容到irqstat。irqnr存放中断号,其初始值为0。从1001处开始,代码依次检测irqstat即ISPR的每一位。为0则irqnr加1,继续进行测试;反之表示找到了中断号,跳到1002处结束宏并返回。为0时,irqnr加1后,应与26相比(S3C44B0X中断数为26个)。如果irqnr小于26,irqstat右移一位,进行下一轮测试;如果大于等于26则不执行movcc与bcc两条指令,直接结束返回。返回时,irqr为中断号。实际中调用宏用R0取代irqnr,即宏通过R0返回中断号。根据中断号,__irq_usr和__irq_svc程序凋用kernel/setup.c中do_IRQ函数执行中断,并执行中断服务程序。
4 中断的使用
在μClinux中,应用程序不能直接处理中断,只有通过编写驱动程序处理中断。μClinux内核维护了一个中断信号线的注册表。在使用中断前驱动要先申请一个中断通道,然后使用后释放中断通道。在μClinux下,中断注册函数原型为 :
int request_irq(unsigned int irq,
void (*handler)(int, void *, struct pt_regs *),
unsigned long irq_flags, const char * devname, void *dev_id)
irq是申请的中断号;handler是中断处理函数指针;flags是与中断管理相关的字节掩码;dev_id传递共享的中断信号线。申请中断成功返回0,失败返回值非0。
中断释放函数原型为:
void free_irq(unsigned int irq, void *dev_id)
irq传递要释放的中断号;dev_id传递共享的中断信号线,驱动程序可使用其指向自己的私有数据区。
中断注册与释放函数都在kernel/irq.c文件中定义。同时linux/sched.h申明了这两个函数的接口。用户编写驱动时,只要包含头文件linux/sched.h就可以使用中断注册和释放函数。
5 按键中断实现例子
μClinux把驱动程序分为三种类型:字符设备、块设备和网络设备。下文中的按键驱动属于字符设备驱动。在驱动程序中使用中断要注意两点:首先中断的注册要在驱动设备注册完成后才可以进行;其次中断的释放要在驱动设备释放之前进行。
实例为两个按键实现了驱动。两个按键分别占用EINT0(中断号25)与EINT1(中断号24)。驱动经过静态编译成为内核的一部分,将在内核启动时完成设备的注册、中断的申请工作。内核启动成功后,当两个按键中任一个被按下时,中断25(或中断24)发生,系统将运行按键驱动中的中断处理函数:中断25(或中断24)发生,输出A(B)。下面是驱动程序的初始化和退出部分的代码:
#ifdef CONFIG_DEVFS_FS
static devfs_handle_t devfs_s3c44b0_dir,devfs_s3c44b0_raw;
#endif
static int __init s3c44b0_KEY_init(void)
{int ret;
ret = register_chrdev(KEYMajor,DEVICE_NAME,&s3c44b0_fops);
if(ret<0)
{
printk(DEVICE_NAME "can't get major number\n");
return ret;
}
KEYMajor = ret;
#ifdef CONFIG_DEVFS_FS
devfs_s3c44b0_dir=devfs_mk_dir(NULL,"key",NULL);
devfs_s3c44b0_raw=devfs_register(devfs_s3c44b0_dir,"0",DEVFS_FL_DEFAULT,KEYMajor,KEYMinor,S_IFCHR|S_IRUSR|S_IWUSR,&s3c44b0_fops,NULL);
#endif
if(request_irq(24,s3c44b0x_KEY_isr,SA_INTERRUPT,DEVICE_NAME,NULL)<0)
DPRINTK("Failed to register IRQ 24\n");
if(request_irq(25,s3c44b0x_KEY_isr,SA_INTERRUPT,DEVICE_NAME,NULL)<0)
DPRINTK("Failed to register IRQ 25\n");
printk(DEVICE_NAME "initialized IRQ 24 25\n");
return 0;
}
static void __exit s3c44b0_KEY_exit(void)
{free_irq(24,NULL);
free_irq(25,NULL);
#ifdef CONFIG_DEVFS_FS
devfs_unregister(devfs_s3c44b0_raw);
devfs_unregister(devfs_s3c44b0_dir);
#endif
unregister_chrdev(KEYMajor, DEVICE_NAME);
}
6 总结
本文经过深入分析研究μClinux,根据实际需要实现了μClinux下S3C44B0X的中断,并结合实例讲述了μClinux下S3C44B0X中断在实际中的应用。最后笔者编译内核,将编译后的内核下载到开发板上运行。运行结果表明按键驱动程序程序运行良好,中断服务程序可成功运行并达到预期功能。
μClinux作为一种开源、免费的嵌入式操作系统,已经实现了到多款处理器的移植,并成功应用到多个领域。随着其应用的深入,其作用将得到更大的发挥。因此,研究μClinux有着重要意义。
参考文献:
[1]Jonathan Corbet, Alessandro Rubini, Greg Kroab-Hartman. Linux设备驱动程序[M].中国电力出版社,2006.
[2]毛德操,胡西同.Linux内核源代码情景分析[M].浙江大学出版社,2001.
[3]田泽.嵌入式系统开发与应用[M].北京航空航天大学出版社,2005.