|
|
级别: 初级
陈渝 (yuchen@tsinghua.edu.cn)清华大学
2004 年 10 月 01 日
本系列文章的第三部分主要介绍了SkyEye硬件模拟平台的实现细节。主要内容包括SkyEye的总体设计、SkyEye的可扩展框架、SkyEye的关键数据结构、SkyEye对各种CPU的模拟实现、SkyEye对各种外设的模拟实现、如何安装使用SkyEye以及如何扩展SkyEye的仿真模块等。对 SkyEye的深入了解,有助于对嵌入式硬件系统有更深入的认识,特别是对操作系统、驱动程序如何与嵌入式硬件系统进行交互有更深刻的了解。
CPU 是计算机硬件的核心,目前的SkyEye模拟的是基于ARM CPU内核的CPU体系结构(将来会扩展到非ARM体系结构的其它CPU)。虽然存在数量众多的基于ARM内核的CPU,但这些CPU的核心基本上是相同的,大部分基于ARM v3-v5体系结构,并加以扩展。从硬件实现上,ARM CPU内核已经发展到第五代,芯片设计也越来越复杂,但由于SkyEye的模拟级别是指令级的,所以在设计上可以从软件实现角度进行各种模拟优化和简化。
操作系统的任务是提供一个环境,使得上层的应用程序不用与底层的硬件直接打交道,且多个应用程序之间可以共同执行,共享硬件资源。实际上,操作系统是建立在硬件上的一个虚拟机环境,而应用程序是建立在操作系统之上的程序,这个程序感觉好像它独占了整个硬件资源,而这实际上是操作系统给应用程序提供的一个假象,就象电影"Matrix"中描述的那个虚幻的繁华世界一样。而SkyEye的任务就是让操作系统感觉在一个真实的硬件环境中运行,这是一个更低层次的虚拟机环境,操作系统"看到"的所有硬件都是SkyEye虚拟出来的。
为了达到这个目标,首先需要分析操作系统可以看到什么硬件。从操作系统的角度看,操作系统可以访问到的ARM CPU内核硬件部分包括通用寄存器(R0-R15)、程序状态寄存器(CPSR和SPSR)、基于ARM内核的特定CPU的IO寄存器等、基于ARM内核的特定CPU的协处理器等。如果需要深入了解ARM的体系结构,建议参考Steve Furber编写的《ARM System-on-Chip Architecture》和ARM公司编写的《ARM Architecture Reference Manual》。
为了能够支持ARM v1-v5的体系结构模拟,我们在设计CPU模拟时,必须完成如下工作:
- 建立所有操作系统可以"看到"的硬件单元,这些建立在基于ARMul_State数据结构的全局变量state中;
- 实现基于三级流水线硬件逻辑模拟,它们在armemu.c的函数ARMul_Emulate32/26中实现;
- 识别并能够执行所有的ARM指令集的硬件逻辑模拟,它们在armemu.c的函数ARMul_Emulate32/26中实现;
- 完成异常处理的硬件逻辑模拟,armemu.c的函数ARMul_Emulate32/26中和arminit.c的函数ARMul_Abort中实现。
如果完成了这四部分工作,ARM CPU核心的模拟工作基本就算完成了。
|
三级流水线的ARM体系结构的主要的组成包括:
- 寄存器堆:各种CPU模式下的寄存器的集合,可对应于ARMul_State数据结构中的域ARMword RegBank[7][16];
- 桶式移位器:用来把一个操作数移位任意位数;
- 地址寄存器和增值器:用来选择和保留存储器地址,并产生顺序递增的地址;
- 数据输入/输出寄存器:用来保存从存储器输入或向存储器输出的数据;
- 指令译码和相关控制逻辑:完成指令译码并执行指令的控制逻辑,这由文件armemu.c中的函数ARMul_Emulate32/26来实现。
三级流水线的硬件逻辑描述如下:
1. 取指令:即从存储器中取出指令,放入指令流水线。对应于SkyEye的实现是根据pc值取对应的存储器内容到instr变量中;
2. 指令译码:指令被译码,占用译码逻辑,并为下一周期准备数据通路的控制信号。SkyEye没有具体的对应实现,只是把译码阶段的指令保存在一个变量中。
3. 指令执行:根据译码的情况,执行具体的指令控制逻辑,即指令占用数据通路,读取相关的寄存器,完成数据处理操作、地址转移操作或执行存储器数据访问操作等。
前两部分在armemu.c的函数ARMul_Emulate32/26中的前一小部分实现,第三部分在armemu.c的函数ARMul_Emulate32/26中的后大半部分实现。
|
异常是指在程序运行时发生的异步意外事件,如外设产生中断,硬件故障等,在逻辑上它发生在指令执行中。对应ARM和其它一些CPU体系结构而言,异常也包括软件产生的中断(如除零操作)和未定义指令陷阱等,而这些是同步事件,在逻辑上它发生在指令执行前。
ARM体系结构的异常分为三类:
- 指令执行引起的直接异常:如软件中断异常、未定义指令异常和预取指令中止异常(因为在取指过程中的存储器故障导致的无效指令);
- 指令执行引起的间接异常:数据中止异常(在数据访问时的存储器故障)
- 外设产生的与指令无关的异常:包括reset、IRQ、FIQ
当异常发生时,ARM CPU内核尽量完成当前指令,然后改变当前的程序计数寄存器R15的值,使其指向异常向量地址。间接异常和外设引起的异常将打乱流水线的正常处理过程。如果发生异常,ARM CPU将执行如下操作:
1. CPU进入与特定异常相对应的CPU操作模式;
2. 将引起异常指令的下一条指令的地址保存到新模式的R14寄存器中;
3. 将原模式中的CPSR保存到新模式的SPSR中;
4. 如果是IRQ异常,则设置CPSR的第七位来禁止IRQ;如果是FIQ,则除了禁止IRQ,还要进一步设置CPSR的第六位来禁止FIQ;
5. 根据异常的种类,把R15寄存器设置为相对应的异常向量地址,并执行对应的指令。
上述的处理过程在arminit.c的ARMul_Abort函数中和armemu.c的ARMul_Emulate32/26函数中的一部分实现。而与具体指令的执行相关的部分可直接参考armemu.c的ARMul_Emulate32/26函数后半部分实现的源码,这是整个函数的主体。
|
ARM 基本核心的功能并不能满足各种各样的应用需求(如浮点处理、信号处理等),所以为了能够提高ARM体系结构的扩展性,ARM通过增加硬件协处理器和协处理器接口在支持指令集扩展,也可以通过未定义指令异常来实现软件扩展指令模拟(这样性能差很多)。最常用的ARM协处理器是控制MMU/CACHE等的系统协处理器,如ARM720T的MMU/CACHE管理单元。
ARM的协处理器体系结构支持16个协处理器,每个协处理器可以有16个寄存器(位数不定),协处理器使用类RISC的Load/Store体系结构,主要有以下几类指令:
- 协处理器数据操作指令:用于控制数据在协处理器内部的操作。
- 协处理器数据传送指令:类似标准ARM的字/字节数据传送指令,即把数据传送到协处理器内的寄存器中,或把协处理器的寄存器中的数据存储到内存单元。
- 协处理器寄存器传送指令:把协处理器中产生的整数直接传送给ARM寄存器和ARM条件码标志位。
实际协处理器的硬件实现还要考虑协处理器的接口实现技术(即如果与ARM CPU核心进行通信)。SkyEye实现的协处理器模拟比较简单,与协处理器模拟的关键数据保存在ARMul_State的如下域中:
- 协处理器初始化/退出停止函数指针
CPInit CPExit - 协处理器指令函数指针,对应各种协处理器指令
LDC STC MRC MCR CDP CPRead CPWrite - 协处理器内部寄存器数据
CPData
协处理器的模拟处理主要有如下几个部分:
- 协处理器初始化:主要由文件armcopro.c中的函数ARMul_CoProInit完成。如果是支持MMU/CACHE的CPU,则需要注意的是对系统协处理器(15号协处理器)的初始化,ARMul_CoProInit完成了CPInit、MRC、MCR这几个协处理器指令函数指针的赋值。由于 XScale体系结构的CPU存在第13号和第14号协处理器,所以如果要模拟这种类型的CPU,也会对这两个处理器进行初始化。
- 协处理器指令模拟:当在执行指令过程中,发现指令是协处理器指令,则把指令转交特定的协处理器指针函数进行进一步处理。与15号协处理器相关的协处理指令主要是MRC和MCR,且与MMU/CACHE的设置有关。这两个协处理器函数的具体实现与特定的CPU相关,目前包括:
- ARM720T实现:位于文件arm7100_mmu.c的函数a71_mmu_mcr/mrc
- ARM920T实现:位于文件arm920t_mmu.c的函数arm920t_mmu_mcr/mrc
- StrongARM实现:位于文件sa_mmu.c的函数sa_mmu_mcr/mrc
- XScale实现:位于文件xscale_copro.c的函数xscale_mmu_mcr/mrc
XScale体系结构的CPU存在第13号和第14号协处理器,且ARM Linux对它们也有逻辑控制,所以在文件xscale_copro.c的函数xscale_cp13/14/15_*完成了对应的协处理器指令的硬件逻辑模拟。
通过上述的协处理器模拟实现,SkyEye能够完成ARM Linux for ep7312/SA1100/PXA25x等内核执行的协处理器指令,保证内核能够正常运行。
|
由于大部分基于ARM内核的各种CPU和开发板都有自己的扩展,而且一般各不相同。由于这一部分与硬件系统的I/O有很大的关系,因此SkyEye建立了一个I/O抽象层,把这一部分独立出来,然后建立各个特定CPU和开发板的I/O处理模拟实现,这样最大程度地减少了代码冗余,同时结构和模块化更清晰,这也有利于开发者在此基础上开发其它类型的CPU和开发板。SkyEye的I/O抽象层的实现在armio.c文件中,而与特定硬件相关的实现则分别在如下文件中:
- skyeye_mach_at91.c:模拟Atmel AT91X40开发板
- skyeye_mach_ep7312.c:模拟cirrus ep7312开发板
- skyeye_mach_sa.c:模拟基于intel strongam的adsbitsy开发板
- skyeye_mach_pxa.c:模拟intel xscale lubbock开发板
- skyeye_mach_s3c4510b.c:模拟基于samsung s3c4510b的开发板
- skyeye_mach_s3c44b0.c:模拟基于samsung s3c44b0的开发板
- skyeye_mach_lpc.c:模拟基于philips lpc2249的开发板
- skyeye_mach_cs89712.c:模拟基于cs89712的开发板
- skyeye_mach_sharp.c:模拟基于sharp lh7a400的开发板
- skyeye_mach_at91rm92.c:模拟基于at91rm9200的开发板
实际上,SkyEye如果实现clock的I/O模拟,再加上内存模拟(mem_bank的起始地址和大小由配置文件skyeye.conf定义),则可以运行支持分时操作的最基本操作系统(如μC/OS-II等)了。而为了能够支持字符的输入输出,则还需要进一步实现UART模拟。所以支持clock和 UART两部分的模拟是SkyEye能够运行一个比较实际的操作系统的最基本要求。下面分别对主要几个特定CPU和开发板的模拟进行介绍。
|
AT91X40 为ATMEL公司基于ARM7TDMI核的32位CPU,其核心为高性能的32位RISC体系结构,并具有高密度的16位指令集和极低的功耗。 AT91X40系列微控制器内部有一个8优先级、可单独屏蔽的向量中断控制器,称为先进中断控制器AIC(Advanced Interrupt Controller)。AT91X40系列微控制器集成了两个完全相同的全双工通用同步/异步收发器(USART)。Time Clock共有3个通道,有16位定时器/计数器,支持捕获和波形模式。每个TC都可以测量或产生不同的波形,并可以检测及控制两个I/O信号。TC还有 3个外部时钟信号。这些外设都可以通过相关的I/O寄存器进行设置和访问。
μClinux包含对基于AT91X40的开发板的BSP(Board Support Package),通过分析AT91X40体系结构和μClinux内核源码,SkyEye只需保存如下数据结构描述的寄存器、内部数据(这些寄存器的值和内部数据可能被操作系统或硬件改变)和一些只读寄存器就基本可以支持μClinux内核和基本命令行用户界面以及相关应用程序。查看 at91_io_read/write_byte/halfword/word函数的实现可以看到所有操作系统需要访问的寄存器。
typedef struct at91_io { ARMword syscon; /* System control reg*/ ARMword sysflg; /* System status flags reg*/ ARMword intsr; /* Interrupt status reg */ ARMword intmr; /* Interrupt mask reg */ ARMword tcd[2]; /* Timer/counter data */ ARMword tcd_reload[2]; /* Last value written */ int tc_prescale; ARMword uartdr; /* Receive data register */ ARMword lcdcon; /* LCD control */ ARMword lcd_limit; /* 0xc0000000 <= LCD buffer < lcd_limit */ } at91_io_t; |
下面的的字符串数组描述的是与UART相关的I/O寄存器的简称,它们会被μClinux访问。
static char *uart_reg[] = { "CR", "MR", "IER", "IDR", "IMR", "CSR", "RHR", "THR", "BRGR", "RTOR", "TTGR", "RES1", "RPR", "RCR", "TPR", "TCR", }; |
由于LCD模拟实现还在开发中,目前与LCD模拟实现相关的lcdcon和lcd_limit还没有用到。下面对一些关键的I/O模拟函数进行介绍:
- at91_io_reset:完成硬件执行reset后的寄存器状态模拟。
- at91_io_do_cycle:每个指令节拍中都会执行一次,模拟时钟累加、读取UART输入和网络数据包输入(在"SkyEye的网络模拟实现"一节中介绍)、产生中断等硬件操作。
- uart_read/uart_write:模拟读写uart寄存器的硬件操作。如写AT91的TCR寄存器(映射地址为0xfffd000f),实际上是执行UART输出操作。
- at91_io_read/write_byte /halfword/word:模拟CPU读写中断处理、时钟、UART等相关I/O寄存器的硬件操作。这里需要注意两点:CPU读某些I/O寄存器(如 Time Clock 1 Sr寄存器,映射地址为0xfffe0060)可能会改变另外一些寄存器的值,而这种情况在正常的内存访问中是不存在的;μClinux通过读取IVR寄存器(映射地址为0xfffff100)来查询中断源,但找到一个中断源后,IVR寄存器的相关位会清除。
这样SkyEye就可以很好地模拟基于AT91X40的开发板,而且μClinux针对真实基于AT91X40的开发板的内核和文件系统可以很好地在SkyEye上运行。
|
ep7312 为Cirrus Logic公司基于ARM720T核的32位CPU,其核心为高性能的32位RISC体系结构,并具有高密度的16位指令集和极低的功耗,支持 MMU/CACHE。ARM720核实际上是ARM7TDMI核加上一个8KB的数据和指令混合的CACHE,外部存储器和外围硬件通过AMBA总线主控单元访问,它还集成了MMU、写缓冲器(write buffer)、LCD控制器、中断控制器等。
ARM Linux包含对基于ep7312的开发板的BSP(Board Support Package)。通过分析ep7312体系结构和ARM Linux内核源码,SkyEye只需保存如下数据结构描述的寄存器、内部数据(这些寄存器和内部数据的值可能被操作系统或硬件改变)和一些只读寄存器就基本可以支持ARM Linux内核和基本命令行用户界面和相关应用程序。查看ep7312_io_read/write_byte/halfword/word函数的实现可以看到所有操作系统需要访问的寄存器。与MMU/CACHE相关的模拟实现可参考"SkyEye的MMU/CACHE和Memory模拟实现"一节。
typedef struct ep7312_io { ARMword syscon; /* System control */ ARMword sysflg; /* System status flags */ ARMword intsr; /* Interrupt status reg */ ARMword intmr; /* Interrupt mask reg */ ARMword tcd[2]; /* Timer/counter data */ ARMword tcd_reload[2]; /* Last value written */ int tc_prescale; ARMword uartdr; /* Receive data register */ ARMword lcdcon; /* LCD control */ ARMword lcd_limit; /* 0xc0000000 <= LCD buffer < lcd_limit */ } ep7312_io_t; |
细心的读者可能注意到这里的结构与at91_io_t数据结构是一样的。其实它们在中断处理和UART处理上有所不同,但在时钟处理上很类似。有关LCD的模拟实现可参考"SkyEye的LCD模拟实现"。下面对一些关键的I/O模拟函数进行介绍:
- ep7312_io_reset:完成硬件执行reset后的寄存器状态模拟,它与at91_io_reset的内容基本一样。
- ep7312_io_do_cycle:每个指令节拍中都会执行一次,模拟时钟累加、读取UART输入、产生中断等硬件操作。
- ep7312_io_read/write_byte/halfword/word:模拟读写uart寄存器,LCD寄存器、中断处理和其它相关I/O寄存器的硬件操作,写UARTDR寄存器(映射地址为0x80000480),实际上是执行UART输出操作。
加上MMU/CACHE和内存模拟,SkyEye就可以很好地模拟基于ep7312的开发板,而且ARM Linux针对真实基于ep7312的开发板的内核和文件系统可以很好地在SkyEye上运行。
|
目前基于StrongARM CPU核的CPU有SA-110、SA1100和SA1110。StrongARM CPU核是基于ARM v4 体系结构,并进行了大量的扩展,采用了独立的指令MMU/CACHE和数据MMU/CACHE,并增加了read buffer和write buffer。StrongARM体系结构的SA-1100/1110还包括LCD控制器、系统控制单元、DMA控制器、UART串口单元、定时器、实时时钟、中断控制器、电源管理控制器和28个通用I/O引脚等。用SA-1100组成的系统在PCB这一层次会比较简单,且功能强大。虽然目前Intel公司已经停止了对StrongARM结构的开发,但基于StrongARM体系结构的PDA曾经称雄一时,与Palm公司的PDA系列成两足鼎立之势。而且,理解StrongARM体系结构会有助于了解Intel公司新的XScale体系结构,二者有很多相近之处。
ARM Linux包含对基于StrongARM的开发板的BSP(Board Support Package)。通过分析基于StrongARM体系结构的SA1100内部细节和ARM Linux内核源码,SkyEye只需保存如下数据结构描述的寄存器、内部数据(这些寄存器和内部数据的值可能被操作系统或硬件改变)和一些只读寄存器就基本可以支持ARM Linux内核和基本命令行用户界面以及相关应用程序。查看ep7312_io_read/write_byte/halfword/word函数的实现可以看到所有操作系统需要访问的寄存器。与MMU/CACHE相关的模拟实现可参考错误!未找到引用源节。
typedef struct sa_io_t{ /*interrupt controller*/ u32 icpr; u32 icip; u32 icfp; u32 icmr; u32 iccr; u32 iclr; /*real time clock(RTC)*/ u32 rcnr; u32 rtar; u32 rtsr; u32 rttr; u32 rt_count; u32 rt_scale; /*core frequence to 32.768K*/ /*os timer*/ u32 oscr; u32 osmr0, osmr1, osmr2, osmr3; u32 ower; u32 ossr; u32 oier; u32 os_scale; /*uart3 controller*/ u32 utcr0; u32 utcr1; u32 utcr2; u32 utcr3; u32 utdr; u32 utsr0; u32 utsr1; }sa_io_t; |
在上述结构中,包括四部分的I/O寄存器组:与中断控制相关的寄存器、与实时时钟控制相关的寄存器、与OS timer相关的寄存器和与UART 3(UART0-2在内核中没有使用,这里就没有模拟了)相关的寄存器。下面对一些关键的I/O模拟函数进行介绍:
- sa_io_reset:完成硬件执行reset后的寄存器状态模拟,这里只是把sa_io_t结构的变量sa_io清零。
- sa_io_do_cycle:每个指令节拍中都会执行一次,模拟实时时钟和OS timer的累加、判断UART 是否有输入、根据外设的状态产生中断(由skyeye_mach_sa.c中的函数refresh_irq实现)等硬件操作。
- sa_io_read/write_byte /halfword/word:模拟CPU读写中断处理、实时时钟、OS timer、UART3相关I/O寄存器的硬件操作,模拟读/写UTDR寄存器(映射地址为0x80050014),实际上是执行UART输入/输出操作。
加上MMU/CACHE和内存模拟,SkyEye就可以很好地模拟基于StrongARM的开发板,而且ARM Linux针对真实基于StrongARM的adsbitsy开发板的内核和文件系统可以很好地在SkyEye上运行。
|
支持XScale体系结构CPU的开发板目前已有数款,如代号为lubbock、 cerf、 idp等的开发板,这里分析的开发板是Intel推出的代号为Lubbock的经典开发板,它目前支持PXA25x系列CPU,有64MBytes SDRAM、32MBytes Boot ROM、32MBytes Flash ROM、USB插槽、PCMICA插槽、CF卡插槽、带有640x480的LCD显示器,可以称之为一个小型的全功能计算机。
ARM Linux对XScale体系结构的支持已经放到Linux-2.6.x内核中,目前由R.King维护,且已经支持多款基于XScale体系结构的开发板,而lubbock开发板是ARM Linux最早支持且支持得很全面的开发板之一。通过分析基于XScale体系结构的PXA250内部细节和ARM Linux内核源码,SkyEye只需保存如下数据结构描述的寄存器、内部数据(这些寄存器和内部数据的值可能被操作系统或硬件改变)和一些只读寄存器就基本可以支持ARM Linux内核和基本命令行用户界面以及相关应用程序。查看pxa_io_read/write_byte/halfword/word函数的实现可以看到所有操作系统需要访问的寄存器。与MMU/CACHE相关的模拟实现可参考"SkyEye的MMU/CACHE和Memory模拟实现"一节。
typedef struct pxa_io_t{ /*interrupt controller*/ u32 icpr; u32 icip; u32 icfp; u32 icmr; u32 iccr; u32 iclr; /*real time clock(RTC)*/ u32 rcnr; u32 rtar; u32 rtsr; u32 rttr; u32 rt_count; u32 rt_scale; /*os timer*/ u32 oscr; u32 osmr0, osmr1, osmr2, osmr3; u32 ower; u32 ossr; u32 oier; u32 os_scale; /*full function uart controller*/ u32 ffrbr; u32 ffthr; u32 ffier; u32 ffiir; u32 fffcr; u32 fflcr; u32 ffmcr; u32 fflsr; u32 ffmsr; u32 ffspr; u32 ffisr; u32 ffdll; u32 ffdlh; /*bluetooth function uart controller*/ u32 btrbr; u32 btthr; u32 btier; u32 btiir; u32 btfcr; u32 btlcr; u32 btmcr; u32 btlsr; u32 btmsr; u32 btspr; u32 btisr; u32 btdll; u32 btdlh; /*standard uart controller*/ u32 strbr; u32 stthr; u32 stier; u32 stiir; u32 stfcr; u32 stlcr; u32 stmcr; u32 stlsr; u32 stmsr; u32 stspr; u32 stisr; u32 stdll; u32 stdlh; /*core clock*/ u32 cccr; u32 cken; u32 oscc; }pxa_io_t; |
下面对一些关键的I/O模拟函数进行介绍:
- pxa_io_reset:完成硬件执行reset后的寄存器状态模拟。这里除了把sa_io_t结构的变量sa_io清零,还有执行如下指令 pxa_io.cccr = 0x121; // 1 0010 0001 pxa_io.cken = 0x17def; 这是PXA25x CPU在重启(reset)时,与Core Clock控制器相关的两个寄存器的初始值。
- pxa_io_do_cycle:每个指令节拍中都会执行一次,模拟实时时钟和OS timer的累加、判断Full Function UART是否有输入、根据外设的状态产生中断(由skyeye_mach_pxa.c中的函数refresh_irq实现)等硬件操作。
- pxa_io_read/write_byte /halfword/word:模拟CPU读写中断处理、实时时钟、OS timer、Full Function UART、Core Clock控制器相关I/O寄存器的硬件操作,模拟读FFRBR寄存器(映射地址为0x40100000)/写FFTHR寄存器(映射地址为 0x40100000),实际上是执行UART输入/输出操作。
加上MMU/CACHE和内存模拟,SkyEye就可以很好地模拟基于XScale体系结构的开发板,而且ARM Linux针对真实基于XScale体系结构的lubbock开发板的内核和文件系统可以很好地在SkyEye上运行。
- 本文节自 《源码开放的嵌入式系统软件分析与实践——基于SkyEye和ARM开发平台》一书的第三章,对 SkyEye 开源项目感兴趣的可以阅读本书。
- SkyEye硬件模拟平台, 第一部分: SkyEye 介绍
- SkyEye硬件模拟平台,第二部分: 安装与使用
- SkyEye硬件模拟平台,第三部分: 硬件仿真实现之一
- 在 developerWorks Linux 专区 可以找到更多为 Linux 开发者准备的参考资料。
| 陈渝, 清华大学,通过 yuchen@tsinghua.edu.cn 可以和他联系。 |