写在前面的:
本人所著《深入浅出嵌入式底层软件开发》书中裸机部分实验为一个小型的多任务操作系统,由底层到上层应用程序全部知识面均涉及到,不过,当时由于交稿所催,很草率的实现了其功能,最近一直也在忙于其它不相关事情,所以没有将其完善。这几天,给几个大学里的学生做实训,拿出来了这个做Topic,用了两天时间接合书籍技术讨论群里对该OS代码的建议做了下功能的改善和更新,希望读者们或意图从事ARM相关嵌入式开发的朋友们能继续多多关注与支持,谢谢~
本实验的意义:
学过Linux的朋友们应该都知道Linux的发展史,Linux操作系统是Linus本人借鉴了Andrew S. Tanenbaum所写的MINIX操作系统,然后加入了具有前瞻性的开源思想,借助于网络的兴起而著名的网络操作系统。
其背景如下:
当前欧洲和美国大学计算机课程中,关于操作系统的课程是一门全新的课程,而操作系统在当时数量很少,只有在大学里开源的Unix比较普及,而Unix操作系统是一个大工程学科,不太适合于教学,因此,两会会员Andrew S. Tanenbaum自己写了一个小型的MINIX操作系统,用于其就职的大学里教学,虽然是Andrew 这哥们很出名,但是它也可能是年纪大了,仅将其用于教学中,没有将其商业应用和推广。而这时大学生Linus在看了MINIX全部源码后,感觉很多地方需要进行改进,于是,自己根据MINIX,写下了Linux操作系统,当然后面的事情就不在多说了。
通过上面的背景,我想表达的:
1. 欧美等发达国家,技术的研究是跑在企业前面的,而大学和研究机构是最新技术的倡导和规划者,指导着新技术的发展方向。
反观国内,由于本人一边从事IT行业,一边从来教育工作,所以,对国内大学还是比较了解的。Android系统从2008年上市到现在已经过去了快4年了,去年年底,国内的大学里才开始打算开这门课,而当前Android应用开发人员,市场上几近饱和(指应用层),国内的教育是走在企业的后面的,这种机构出来的学生,能跟上企业的需要吗???
2. 欧美等发达国家,大学里学习C语言,学习操作系统是用的Linux或Unix。
反观国内,清一色的选用Windows平台,我不是说Windows平台不好,而是整个中华人民共和国被Microsoft养懒了,做软件,拖拖拽拽,不知道数据结构的应用场合?不了解应用程序的工作机制?不知道编译是怎么回事?更不知道文件系统和内核有什么关系?
我们大学里开了很多计算机课程,大学毕业了,第一感觉是,大学里学的东西太多,根本就联系不起来?这其实就是教育方式的问题,我不敢说,我的这种说法是对的,但是,经过实验证明,我的做法让很多朋友更加明确了:计算机系统是什么?大学里的操作系统和大学里的数据结构及计算机组成原理和上层语言有什么关系。
我不敢说这个实验能让多少人会喜欢上计算机、更了解计算机、能成为计算机编程高手和老鸟,但是我可以告诉你一些你看书学不到的东西。
miniOS V2.0
版本改进:
2010.05: 为了将《深入浅出嵌入式底层软件开发》这本书里前面的裸机驱动部分全部串起来,借鉴Linux操作系统和网上前辈们的总结,写了一个小型多任务操作系统,主要有以下功能和特点:
- 支持62个任务同时运行(包含内核任务)
 - 开启了MMU内存管理
 - SDRAM,LED,KEY,watch dog,时钟,UART串口,中断,定时器等嵌入式入门常用案例
 - 将大学里操作系统的理论知识:任务调度,进程管理,进程切换等完全用代码实现
 - 将ARM中的所有异常全部进行了处理
 - 使用了SWI指令,更深入理解System Call系统调用的实现及与应用程序的意义
 - 通过简单的内存管理方式,实现对多任务支持
 - 通过自己编写的简单应用程序,让你了解,谁调用了main函数,main函数的参数与main函数的返回处理
 - 通过ADS环境进行编译(很多人不太理解为什么使用ADS,我承认ADS编译器太老了,但是,对于入门的朋友来说,调试更便宜,方便)
 
2012.03.08:
- 支持64个任务同时运行(包含内核任务)
 - 优化了内存页表映射关系(相对V1.0)
 - 应QQ技术群里读者建议,增加了异常时打印栈信息(stack_dump),内存数据打印(mem_dump)等调试功能
 - 增加了蜂鸣器驱动(这个没有什么值得作为更新的)
 - 更改了目录结构(重新整理了目录结构,不在是全部放在工程目录里)
 - 调度器做了一点简单优化(内核空闲进程不会与用户进程抢占CPU)
 - 增加了s3c2440_h.S汇编头文件,减少大量变量的重复定义,使代码更容易阅读
 - MMU初始化移到了start.S中,内存页表分前后二次初始化,保证虚拟地址正常映射(后面有详细解释)
 - 代码的运行地址从0x33ff0000换到了0x800f0000
 
miniOS V2.0内存分布图:

看过V1.0代码的朋友会记得:原先的物理内存地址有2M空间是未使用的,在这次的版本上,物理内存空间全部用上了。
Linux内核中内核地址空间分为:
- 直接内存映射区(Direct Memory Region)即:低端物理内存
 - 动态内存映射区(VMalloc Region)即:高端物理内存
 - 其它映射区
 
miniOS的0x80000000~0x84000000相当于直接内存映射区,可以通过线性减一个偏移地址来管理物理内存0x30000000~0x34000000
同样外设寄存器空间0x48000000~0x60000000也是直接内存映射区,可以直接来访问。
Norflash的2MB空间被映射到了更高的地址处:0xC0000000~0xC0200000
物理内存空间结构:

由上图可知:V2.0版本,页表放到了内存地址0x30000000处
最开始的1MB物理内存地址0x30000000~0x30100000为0号内核进程地址空间,其实就是OS代码区和内核栈区及页表区。
由于OS代码放到了物理内存0x300F0000处,其被映射到虚拟地址0x800F0000处,所以ADS里设置的OS代码的运行地址为0x800F0000。
重要变化:
原先的mmu.c分成了两个文件:page_table.c和mmu.S
page_talbe.c
- #include "s3c2440.h"
 - #include "serial.h"
 - #include "mmu.h"
 - #define MANAGER_AP (0x3<<10)
 - #define USER_AP (0x1<<10)
 - #define MANAGER_DOMAIN (0x1<<5)
 - #define USER_DOMAIN (0x0<<5)
 - #define FAULT_DOMAIN (0x0<<5)
 - #define CB (0x1<<2)
 - #define DESC_FLAG (0x2<<0)
 - #define FAULT_DESC (0x0<<0)
 - #define SEC_DESC_WB (MANAGER_AP | MANAGER_DOMAIN | CB | DESC_FLAG)
 - #define NONE_SEC_DESC (FAULT_DESC)
 - #define TTB_BASE ((unsigned long *)SDRAM_BASE)
 - #define VERTOR_NEW_BASE (SDRAM_BASE)
 - /****************************************************************************
 - * 页表建立函数
 - *____________________________________________________________________________
 - * 段页表项entry:[31:20]段基址,[11:10]为AP(控制访问权限),[8:5]域,
 - * [3:2]=CB(decide cached &buffered),[1:0]=0b10-->页表项为段描述符
 - *
 - * 0~32MB:为内核进程空间
 - * 32MB~0x80000000:为用户进程空间
 - * 0x80000000~0x84000000:为物理内存完全映射区
 - * 0x98000000~0xB0000000:为外设寄存器完全映射区间
 - *____________________________________________________________________________
 - ****************************************************************************/
 - void __create_page_tables_early()
 - {
 - unsigned long phyaddr, viraddr;
 - /* 建立到Norflash的2MB的地址空间的映射 */
 - /* 0x90000000 映射到0开始的1MB地址空间 */
 - *( TTB_BASE + (NOR_1M_VM >> 20) ) = 0x0 | SEC_DESC_WB;
 - /* 0x90100000 映射到0x100000~0x1FFFFF的1MB地址空间 */
 - *( TTB_BASE + (NOR_2M_VM >> 20) ) = MB | SEC_DESC_WB;
 - /* 令0x30000000~0x34000000的64MB虚拟地址等于物理地址空间,方便miniOS内部进程管理 */
 - for(phyaddr = SDRAM_BASE; phyaddr < SDRAM_BASE + SDRAM_SIZE;
 - phyaddr += MB) {
 - *(TTB_BASE + (phyaddr >> 20) ) = phyaddr | SEC_DESC_WB;
 - }
 - /* 令0x80000000~0x84000000的64MB虚拟地址映射到0x30000000~0x34000000 */
 - for(phyaddr = SDRAM_BASE, viraddr = KERNEL_SPACE_BASE; phyaddr < SDRAM_BASE + SDRAM_SIZE;
 - phyaddr += MB, viraddr+= MB) {
 - *(TTB_BASE + (viraddr >> 20) ) = phyaddr | SEC_DESC_WB;
 - }
 - /* 令0x48000000~0x60000000的虚拟地址等于物理地址空间,方便miniOS内部外设管理 */
 - for(phyaddr = SFR_PA_BASE; phyaddr < SFR_PA_LIMIT; phyaddr += MB) {
 - *(TTB_BASE + (phyaddr >> 20) ) = phyaddr | SEC_DESC_WB;
 - }
 - /* 令0x98000000~0xB0000000的虚拟地址映射到0x48000000~0x60000000 */
 - for(phyaddr = SFR_PA_BASE, viraddr = phyaddr + VA_TO_PA_OFT; phyaddr < SFR_PA_LIMIT;
 - phyaddr += MB, viraddr+= MB) {
 - *(TTB_BASE + (viraddr >> 20) ) = phyaddr | SEC_DESC_WB;
 - }
 - /*
 - * 异常向量表
 - * 0xFFFF0000为高地址异常向量表,可以通常设置CP15,C1寄存器V位,当异常产生时,由硬件自动去0xFFFF0000
 - * 地址处执行异常跳转执行,而不是之前的0地址处异常向量表跳转,我们将该虚拟地址映射到0x33F00000这1MB地址
 - * 空间,同样,将全部miniOS代码拷贝到这1MB地址空间来。
 - */
 - *(TTB_BASE + (0xffff0000>>20)) = (VERTOR_NEW_BASE | SEC_DESC_WB);
 - }
 - void IDCaches_Restart(void)
 - {
 - __asm{
 - mov r0, #0
 - /* 使ICaches和DCaches无效 */
 - mcr p15, 0, r0, c7, c7, 0
 - /* 使能写入缓冲器 */
 - mcr p15, 0, r0, c7, c10, 4
 - /* 使指令,数据TLB无效 */
 - mcr p15, 0, r0, c8, c7, 0
 - nop
 - nop
 - nop
 - nop
 - }
 - }
 - void __create_page_tables_post(void)
 - {
 - unsigned long taskId = 1;
 - unsigned long phyaddr;
 - // 切断原先的0x30000000~0x34000000的映射关系,使其为无效描述符
 - for(phyaddr = SDRAM_BASE; phyaddr < SDRAM_BASE + SDRAM_SIZE;
 - phyaddr += MB) {
 - *(TTB_BASE + (phyaddr >> 20) ) = NONE_SEC_DESC;
 - }
 - // 为每个进程空间进行地址映射,映射关系为 taskID -> taskID*32M~taskID*32M + 0x1fffff
 - for(taskId = 1; taskId < TASK_SZ; taskId++) {
 - *(TTB_BASE + ((taskId*32*MB) >> 20)) = (SDRAM_BASE + taskId*MB) | SEC_DESC_WB;
 - }
 - IDCaches_Restart();
 - }
 
mmu.S
- INCLUDE s3c2440_h.S
 - ;++++++++++++++++++++++++++++++++++++++++++++++++++++
 - FAULT_DESC EQU (0x0<<0)
 - NONE_SEC_DESC EQU (FAULT_DESC)
 - ;++++++++++++++++++++++++++++++++++++++++++++++++++++
 - VECTOR EQU (1<<13) ; 设置异常向量表位置,0 = 低地址0x0 1 = 高地址0xFFFF0000
 - ICACHE EQU (1<<12) ; 设置ICACHE,0 = 禁用 1 = 使用
 - R_S_BIT EQU (3<<8) ; 和页表项中描述符一起确定内存访问仅限
 - ENDIAN EQU (1<<7) ; 确定系统使用大,小端字节序,0 = 小端模式 1 = 大端模式
 - DCACHE EQU (1<<2) ; 设置DCACHE,0 = 禁用 1 = 使用
 - ALIGN EQU (1<<1) ; 设置地址对齐检查,0 = 禁用 1 = 使用
 - MMU_ON EQU (1<<0) ; 设置MMU,0 = 禁用 1 = 使用
 - ;++++++++++++++++++++++++++++++++++++++++++++++++++++
 - AREA MMU_INIT, CODE, READONLY
 - EXPORT __enable_mmu
 - __enable_mmu
 - ldr r0, =TTB_BASE
 - ldr r1, =VECTOR
 - ldr r2, =ICACHE
 - orr r3, r2, r1
 - ldr r1, =DCACHE
 - orr r3, r3, r1
 - ldr r1, =ALIGN
 - orr r3, r3, r1
 - ldr r1, =MMU_ON
 - orr r3, r3, r1
 - ldr r1, =R_S_BIT
 - orr r4, r3, r1
 - ldr r1, =ENDIAN
 - orr r4, r4, r1
 - mov r2, #0
 - ; 使ICaches和DCaches无效
 - mcr p15, 0, r2, c7, c7, 0
 - ; 使能写入缓冲器
 - mcr p15, 0, r2, c7, c10, 4
 - ; 使指令,数据TLB无效无效
 - mcr p15, 0, r2, c8, c7, 0
 - ; 页表基址写入C2
 - mcr p15, 0, r0, c2, c0, 0
 - ; 将0x2取反变成0xFFFFFFFD,Domain0 = 0b01为用户模式,其它域为0b11管理模式
 - mvn r2, #0x2
 - ; 写入域控制信息
 - mcr p15, 0, r2, c3, c0, 0
 - ; 取出C1寄存器中值给reg0
 - mrc p15, 0, r2, c1, c0, 0
 - ; 先清除不需要的功能,现开启
 - bic r2, r2, r4
 - ; 设置相关位并开启MMU
 - orr r2, r2, r3
 - mcr p15, 0, r2, c1, c0, 0
 - nop
 - nop
 - nop
 - nop
 - mov pc, lr
 - END
 
 
start.S文件也发生了改变:
- 引入了s3c2440_h.S头文件
 - 异常向量跳转换成了b指令相对跳转
 - 看门狗用汇编写的
 - 运行地址发生了改变,所以代码拷贝和清空BSS修改了
 - 开启MMU之前,运行地址为物理地址空间0x30000000~0x34000000,开启之后是0x80000000~0x84000000的虚拟地址空间,所以mmu初始化函数的返回地址做了修正
 - 异常模式的栈发生了改变,全部使用虚拟地址
 
- ;
 - ; start.S:主要安装异常向量表,初始化必要的硬件,
 - ; 将代码从Norflash中拷贝到SDRAM,
 - ; 然后跳转到SDRAM中去执行,最后调用main函数,开始miniOS启动第二阶段
 - ;
 - INCLUDE s3c2440_h.S
 - ; 以下为时钟相关寄存器地址
 - LOCKTIME EQU 0x4c000000
 - MPLLCON EQU 0x4c000004
 - CLKDIVN EQU 0x4c000014
 - RUN_BASE EQU 0x33ff0000 ; OS内存运行地址
 - MEM_REG_BASE EQU 0x48000000
 - MEM_REG_END EQU 0x48000034
 - WTD_TIMER EQU 0x53000000
 - ; 其它异常栈指针,MTOS处理了所有的异常
 - IMPORT HandleSWI ; 以下引入其它文件中声明函数名
 - IMPORT CopyCode2Ram
 - IMPORT xmain
 - IMPORT handle_irq
 - IMPORT undef_excp
 - IMPORT prefetch_abt
 - IMPORT data_abt
 - AREA Start, CODE, READONLY
 - ENTRY ; 代码段开始
 - b Reset ; 异常向量表,其运行地址为0,pc自动由硬件设置
 - ; 该地址处指令为一跳转指令,跳往reset异常处理
 - b HandleUndef ; 未定义异常处理跳转指令,跳往_HandleUndef处
 - b HandleSWI ; 软件中断异常处理跳转指令,跳往_HandleSWI处
 - b HandlePrefetchAbort ; 预取指令中止异常处理跳转指令,跳往_HandlePrefetchAbort处
 - b HandleDataAbort ; 数据中止异常处理跳转指令,跳往_HandleDataAbort处
 - HandleNotUsed
 - b HandleNotUsed ; 未使用异常处理跳转指令,没有处理
 - b HandleIRQ ; 一般中断异常处理跳转指令,跳往本源文件中
 - ;HandleIRQ符号处
 - HandleFIQ
 - b HandleFIQ ; 快速中断异常处理跳转指令,没有处理
 - Reset ; Reset异常处理符号
 - bl clock_init ; 跳往时钟初始化处理
 - bl mem_init ; 跳往内存初始化处理
 - ldr sp, =SVC_STACK_BASE ; 设置管理模式栈指针
 - bl disable_watchdog ; 关闭看门狗
 - ; 代码拷贝,将代码拷贝到内存里去运行,如果从Nandflash
 - ;启动运行,其RAM steppingstone只有4k,不足已运行全部代
 - ;码,如果从Norflash启动,其硬件特性决定其运行速度较慢,
 - ;因此,将代码拷贝到内存里去运行
 - copy_code ; 代码拷贝开始符号
 - mov r0, #0x0 ; R0中为数据开始地址 (ROM数据保存在0地址开始处)
 - ldr r1, =|Image$$RO$$Base| ; R1中存放RO输出域运行地址,
 - ; 该值由符号变量Image$$EXEC_RO$$Base取得
 - ldr r2, =|Image$$ZI$$Limit|
 - ; 该值由符号变量Image$$EXEC_ZI$$Limit取得
 - sub r2, r2, r1 ; R2 = R2 - R1,得出待拷贝数据长度
 - ldr r1, =KERNEL_RUN_PA
 - bl CopyCode2Ram ; 将R0,R1,R2三个参数传递给CopyCode2Ram函数执行拷贝
 - ldr r0, =|Image$$ZI$$Base|
 - ldr r1, =|Image$$ZI$$Limit|
 - sub r0, r0, #VA_TO_PA_OFT
 - sub r1, r1, #VA_TO_PA_OFT
 - bl clear_bss_region
 - ; mmu
 - IMPORT __create_page_tables_early
 - IMPORT __enable_mmu
 - IMPORT __create_page_tables_post
 - bl delay
 - bl __create_page_tables_early
 - ldr lr, =RUN_VM ; RUN_VM是运行地址的偏移地址(0x80000000+OFT)
 - ldr r0, =__enable_mmu ; r0也是运行地址的偏移地址(0x80000000+__enable_mmu)
 - sub r0, r0, #VA_TO_PA_OFT ; 因为MMU还没有使能,所以还在物理内存里执行,因此要减VA_TO_PA_OFT
 - mov pc, r0 ; 一旦开启了MMU,虚拟地址0x80000000和0x30000000都可以使用
 - RUN_VM
 - bl __create_page_tables_post ; 这儿已经开启了MMU,要切断原先的0X300000000地址映射,所以保证代码运行在0x800000000中
 - ; init all mode stack
 - bl stack_init ; 跳往栈初始化代码处
 - msr cpsr_c, #0x5f ; 开启系统中断,进入系统模式
 - ldr lr, =halt_loop ; 设置返回地址
 - ldr pc, =xmain ; 跳往main函数,进入OS启动处理
 - halt_loop ; OS返回地址,其实这儿永远不可能被执行到,因为只要OS工
 - ; 作,它就会运行到世界末日
 - b halt_loop ; 死循环
 - delay
 - mov r0, #0x100000
 - mov r1, #0
 - loop
 - cmp r1, r0
 - sub r0, r0, #1
 - bne loop
 - mov pc, lr
 - HandleIRQ ; 系统中断处理
 - sub lr, lr, #4 ; 修正返回地址
 - ldr sp, =(IRQ_STACK_BASE + VA_TO_PA_OFT) ; 设置中断模式下栈指针
 - stmdb sp!, {r0-r12,lr} ; 保存现场
 - ldr lr, =int_return ; 设置中断处理程序的返回地址
 - ldr pc, =handle_irq ; 跳往中断处理程序
 - int_return ; 返回地址
 - ldmia sp!, {r0-r12,pc}^ ; 恢复被中断打断现场
 - clock_init ; 时钟初始化代码,详细见时钟初始化章节
 - ; Set lock time
 - ldr r0, =LOCKTIME
 - ldr r1, =0x00ffffff
 - str r1, [r0]
 - ; Set clock divison
 - ldr r0, =CLKDIVN
 - mov r1, #0x05
 - str r1, [r0]
 - ; Set system clock to asynchronous mode
 - mrc p15, 0, r1, c1, c0, 0
 - orr r1, r1, #0xc0000000
 - mcr p15, 0, r1, c1, c0, 0
 - ldr r0, =MPLLCON
 - ldr r1, =0x5c011 ; MPLL is 400MHz
 - str r1, [r0]
 - mov pc, lr
 - mem_init ; 内存初始化代码,详细见内存初始化章节
 - ldr r0, =MEM_REG_BASE
 - ldr r1, =MEM_REG_END
 - adr r2, memdata
 - mem_init_loop
 - ldr r3, [r2], #4
 - str r3, [r0], #4
 - teq r0, r1
 - bne mem_init_loop
 - mov pc,lr
 - memdata
 - DCD 0x22111110 ;BWSCON
 - DCD 0x00000700 ;BANKCON0
 - DCD 0x00000700 ;BANKCON1
 - DCD 0x00000700 ;BANKCON2
 - DCD 0x00000700 ;BANKCON3
 - DCD 0x00000700 ;BANKCON4
 - DCD 0x00000700 ;BANKCON5
 - DCD 0x00018005 ;BANKCON6
 - DCD 0x00018005 ;BANKCON7
 - DCD 0x008e04f4 ;REFRESH
 - DCD 0x000000b1 ;BANKSIZE
 - DCD 0x00000030 ;MRSRB6
 - DCD 0x00000030 ;MRSRB7
 - clear_bss_region
 - mov r2, #0
 - clear_loop
 - cmp r0, r1
 - beq quit_loop
 - str r2, [r0], #4
 - b clear_loop
 - quit_loop
 - mov pc, lr
 - disable_watchdog
 - ldr r0, =WTD_TIMER
 - mov r1, #0
 - str r1, [r0]
 - mov pc, lr
 - stack_init ; 栈指针初始化
 - ; undefine_stack ; 未定义异常
 - msr cpsr_c, #0xdb
 - ldr sp, =(UND_STACK_BASE + VA_TO_PA_OFT)
 - ; abort_stack ; 未定义异常模式
 - msr cpsr_c, #0xd7
 - ldr sp, =(ABT_STACK_BASE + VA_TO_PA_OFT)
 - ; irq_stack ; 中断模式
 - msr cpsr_c, #0xd2
 - ldr sp, =(IRQ_STACK_BASE + VA_TO_PA_OFT)
 - ; sys_stack ; 系统模式
 - msr cpsr_c, #0xdf
 - ldr sp, =(SYS_STACK_BASE + VA_TO_PA_OFT)
 - ; svr_stack ; 切换回管理模式
 - msr cpsr_c, #0xd3
 - mov pc, lr
 - HandleUndef ; 未定义异常处理
 - add lr, lr, #4 ; 修正返回地址
 - stmdb sp!, {lr} ; pc
 - stmdb sp!, {lr}^ ; lr
 - stmdb sp!, {sp}^ ; sp
 - stmdb sp!, {r0-r12} ; 保存现场
 - mrs r0, spsr ; 发生异常时,将状态寄存器里的数据保存在栈里
 - stmdb sp!, {r0}
 - mov r0, sp ; 发生异常时,将将栈指针传递给异常处理函数用于打印异常现场信息
 - ldr pc, =undef_excp
 - b halt_loop
 - HandlePrefetchAbort ; 未定义异常处理
 - sub lr, lr, #4 ; 修正返回地址
 - stmdb sp!, {lr} ; pc
 - stmdb sp!, {lr}^ ; lr
 - stmdb sp!, {sp}^ ; sp
 - stmdb sp!, {r0-r12} ; 保存现场
 - mrs r0, spsr ; 发生异常时,将状态寄存器里的数据保存在栈里
 - stmdb sp!, {r0}
 - mov r0, sp ; 发生异常时,将将栈指针传递给异常处理函数用于打印异常现场信息
 - ldr pc, =prefetch_abt
 - b halt_loop
 - HandleDataAbort ; 未定义异常处理
 - sub lr, lr, #8 ; 修正返回地址
 - stmdb sp!, {lr} ; pc
 - stmdb sp!, {lr}^ ; lr
 - stmdb sp!, {sp}^ ; sp
 - stmdb sp!, {r0-r12} ; 保存现场
 - mrs r0, spsr ; 发生异常时,将状态寄存器里的数据保存在栈里
 - stmdb sp!, {r0}
 - mov r0, sp ; 发生异常时,将将栈指针传递给异常处理函数用于打印异常现场信息
 - ldr pc, =data_abt
 - b halt_loop
 - END
 
excp_handle.c增加了一点功能:
当出现异常状态时,蜂鸣器会长鸣,同时打印出异常发生前所有寄存器的值和Kernel panic,以方便朋友们调试。
- #include "serial.h"
 - #include "buzzer.h"
 - #include "libs.h"
 - /* print the kernel panic information */
 - #define kernel_panic() \
 - do{ \
 - printk("\r\nKernel panic!!\r\n"); \
 - buzzer_on(); \
 - while(1); \
 - } while(0)
 - void undef_excp(unsigned long * sp)
 - {
 - printk("\r\nUndefine exception\r\n");
 - stack_dump(sp);
 - kernel_panic();
 - // this will print the stack content
 - }
 - void prefetch_abt(unsigned long * sp)
 - {
 - printk("\r\nPrefetch abort exception\r\n");
 - stack_dump(sp);
 - kernel_panic();
 - // this will print the stack content
 - }
 - void data_abt(unsigned long * sp)
 - {
 - printk("\r\nData abort exception\r\n");
 - stack_dump(sp);
 - kernel_panic();
 - // this will print the stack content
 - }
 
stack_dump实现在libs/libs.c中:
里面主要有:
- 16进制数转换成字符串函数:xtos
 - 10进制数转换成字符串函数:dtos
 - 内存设置函数:memset
 - 内存数据打印函数:mem_dump和栈数据打印函数:stack_dump
 - mem_dump可以放到os中任意位置,打印指定地址处的数值。
 
- #include "serial.h"
 - #include "s3c2440.h"
 - void xtos(unsigned long n){
 - unsigned long i;
 - if((i=n/16)!=0)
 - xtos(i);
 - if(n%16 > 9)
 - putc(n%16 - 10 +'A');
 - else
 - putc(n%16+'0');
 - }
 - void dtos(unsigned long n){
 - unsigned long i;
 - if((i=n/10)!=0)
 - xtos(i);
 - putc(n%10+'0');
 - }
 - void memset(char * dest, long len, int value){
 - if(dest == NULL || len <= 0)
 - return;
 - while(len--)
 - *dest++ = value;
 - return ;
 - }
 - // not safe copy
 - char * memcpy(char * dest, const char * src, long len){
 - char * temp = dest;
 - if(dest == NULL || src == NULL || len <= 0)
 - return NULL;
 - while((dest - temp) != len)
 - *dest++ = *src++;
 - return temp;
 - }
 - void mem_dump(const unsigned long * src, long len){
 - const unsigned long * temp = src;
 - if(src == NULL || len <= 0)
 - return;
 - while(src-temp != len){
 - printk("\r\n 0x");
 - xtos(*src++);
 - if((src-temp)%5 == 0)
 - printk("\r\n");
 - }
 - printk("\r\n");
 - }
 - void stack_dump(unsigned long * sp){
 - printk("\r\n CPSR:0x");
 - xtos(*sp++);
 - printk("\r\n R0:0x");
 - xtos(*sp++);
 - printk("\r\n R1:0x");
 - xtos(*sp++);
 - printk("\r\n R2:0x");
 - xtos(*sp++);
 - printk("\r\n R3:0x");
 - xtos(*sp++);
 - printk("\r\n R4:0x");
 - xtos(*sp++);
 - printk("\r\n R5:0x");
 - xtos(*sp++);
 - printk("\r\n R6:0x");
 - xtos(*sp++);
 - printk("\r\n R7:0x");
 - xtos(*sp++);
 - printk("\r\n R8:0x");
 - xtos(*sp++);
 - printk("\r\n R9:0x");
 - xtos(*sp++);
 - printk("\r\n R10:0x");
 - xtos(*sp++);
 - printk("\r\n R11:0x");
 - xtos(*sp++);
 - printk("\r\n R12:0x");
 - xtos(*sp++);
 - printk("\r\n SP:0x");
 - xtos(*sp++);
 - printk("\r\n LR:0x");
 - xtos(*sp++);
 - printk("\r\n PC:0x");
 - xtos(*sp++);
 - }
 
主要就是上述的改变,最新源码,请到我的CSDN空间资源中下载。
下载的代码只有针对QQ2440的,mini2440的和TQ2440的朋友,自己可以去将Key的驱动改一下即可。
                    
                
                
            
        
浙公网安备 33010602011771号