在GBA上跑的linux
本修改都以Linux kernel 2.4.24+uclinux针对其的补丁为基础做的。
在把补丁打好后,将我提供的linux-2.4.24中的文件覆盖进目录。然后将norfs目录中的linux-2.4.24目录中覆盖Kernel目录中相应文件就可以。然后make menuconfig,在System Type中设置ARM system type为GBA,打开General setup中的Support uClinux FLAT format binaries,打开File systems中的NOR file system support。然后make dep,make clean,make,将生成的elf文件linux拷贝到gbarom目录中。
然后进入norfs目录中的gennorfs目录,输入make,make install,将生成norfs镜象的gennorfs文件装进系统中。
最后进入make,则将生成linux.gba,这就是给GBA模拟器使用的2进制文件。
3.GBA的简单介绍以及其优点和缺点
GBA是一种掌上游戏机,用的ARM7TDMI,256K内存(有的介绍将其32K内存也算在其中的,这是完全不对的,因为那些内存是有专门作用的),显存、声道、多用串口等等一堆游戏机需要的东西,rom是nor flash,详细的介绍网上非常多,我也就不详细介绍了。
GBA的优点:开发资源丰富。因为对GBA游戏开发有兴趣的人不少,所以对其介绍的资料相当多,还有不少专门的论坛对其的开发进行讨论。同时GBA有多种模拟器可以使用,而且功能都很不错。
GBA的缺点:有点BT的环境。256K内存是无法将整个KERNEL放入其中的。最主要的问题是GBA将头16K写入了他自己的代码,也就是说GBA自己处理了svc到fiq,其中irq的处理会在GBA自己处理过后跳到指定的代码(这将在后面做详细介绍),其他的以我现有的认识都是无法取到的,GBA都用自己的代码处理掉了。
4.介绍一下我写的bootloader
GBA的执行是从0x8000000开始的,这也是flash的开始地址,但是实际上从0x8000004一直到0x80000c0以前的部分是有一套格式的,有什么图标字符、校验位等等一大堆东西,不过这些东西我都没能够在我的GBA镜象生成程序creatrom中完成,只是简单的按照规矩在bootloader中直接从0x8000000跳到了0x80000c0开始执行具体的bootloader代码。在bootloader中我主要做的工作是将存储在flash中的init节和data节放到内存中。下面我就来介绍一下bootloader代码bl.s:
首先将mess的地址设置到r4,这个mess中存储了init和data在flash中的位置、长度、应在内存中的位置以及text的开始地址,这个部分是在creatrom程序中填写的。
然后就是先init然后data的拷贝过程,其中具体的拷贝用的是gba_dma3_copy这个函数,这里用的是GBA的DMA3,GBA共有4个DMA,在实际的kernel中我还没能将这几个东西用上。
在2次拷贝结束的时候,有这么2句:
mov r0,#0
mov r1,#210
这是设置启动参数,在arch/armnommu/kernel/head-armv.S中是这样写的
/*
* Kernel startup entry point.
*
* The rules are:
* r0 - should be 0
* r1 - unique architecture number
* MMU - off
* I-cache - on or off
* D-cache - off
*
* See linux/arch/arm/tools/mach-types for the complete list of numbers
* for r1.
*/
注意这个210是我后加的,具体我将在下面介绍对kernel的修改的时候进行介绍。
设置好启动参数以后就是将mess中的text开始位置读出,跳到那个位置开始执行。
5.我对kernel的修改的介绍
我对kernel的修改除了我自己增加的目录arch/armnommu/mach-gba/和include/asm-armnommu/arch-gba/以外,我都用类似
//teawater add for gba 2004.2.28------------------------------------------------
#ifdef CONFIG_ARCH_GBA
#else
#endif
//AJ2D--------------------------------------------------------------------------
标记了出来。
5.1.编译相关的修改
arch/armnommu/Makefile,这里增加了一个GBA相关的条目,主要是设置连接脚本,其他也跟别的项目没什么不同,TEXTADDR这个部分本来是在连接脚本用指定kernel开始执行地址的地方,不过在我修改的代码中没什么作用,只是原来临时用保留下来的。
arch/armnommu/config.in,这里是menuconfig的时候生成菜单以及进行编译配置的地方,在这其中:
DRAM_BASE 内存的起始地址
DRAM_SIZE 内存的长度
FLASH_MEM_BASE flash的起始地址
FLASH_SIZE flash的长度
CONFIG_NO_PGT_CACHE 表示Disable pgtable cache
CONFIG_CPU_WITH_CACHE 表示CPU有CACHE 启动的时候要clean
CONFIG_CPU_WITH_MCR_INSTRUCTION 表示系统是否有MCR指令
arch/armnommu/tools/mach-types,实际上这个文件是用来生成文件include/asm-armnommu/mach-types.h的,不过因为关联的问题,修改过这个文件后注意删除一下mach-types.h这个文件,保证修改的生效。
5.2.连接脚本
我使用了单独的连接脚本arch/armnommu/mach-gba/romlinux.lds。
在这里比较主要的就是设置init和data的开始地址设置在了0x2001000,内存开始的部分在0x2000000,而这0x1000是交给kernel让其写中断处理代码的。而在0x8040d00是text的开始地址。
如果仔细观察的话你可以发现我将*(.text.init)也放进了text,这样的目的就是为了节省内存。
其中有1个地方比较关键:
. = ALIGN(4096);
__init_end = .;
因为init节实际上在init函数中将要被释放掉的,而且其后跟着的就是init_task_union,所以这里一定要保证init节的长度跟页长度对齐,同时要保证这个__init_end生成的地址是跟task的长度对齐。而前面将开始地址设置在0x2001000也是出于这个目的。
5.3.kernel在start_kernel以前的代码
arch/armnommu/kernel/head-armv.S,这是kernel最开始启动的代码,这里体系相关的代码很多,通用的启动部分在448行,也就是我修改后代码的第464行。
一上来就是函数__lookup_processor_type,这个函数是用来根据芯片的类型ID从.proc.info中取出相应的proc_info_list结构,这个结构定义在include/asm-armnommu/procinfo.h文件中,这个段中的数据是arch/armnommu/mm/proc-*.S文件中的,如果你的芯片比较特殊,可以定义自己的芯片结构以及编写自己的文件,arm7tdmi在arch/armnommu/mm/proc-arm6,7.S文件中。这个函数中,我们可以看到在arch/armnommu/config.in中定义的CONFIG_CPU_WITH_MCR_INSTRUCTION,我也尝试跑过mrc指令取得芯片ID,不过代码马上就跑到了未定义指令上去了,所以我估计用这个是不行的,就定义了不支持mcr。在下面也写明了
# warning "FIXME: Get Processor ID without MCR Instruction"
还举出了使用的例子
@ A possible code
@ldr r9, PROCESSOR_ID_MEM_LOCATION
@ldr r9, [r9]
看一下arch/armnommu/mm/proc-arm6,7.S文件,就可以找到定义arm7tdmi的结构的是729行开始的,所以代码应该写成
ldr r9, __arm7tdmi_proc_info
ldr r9, [r9]
不过我开始是没仔细看这段注释的,直接就
@set r9 to arm7tdmi id(0x41007700)
mov r9,#0x7700
add r9,r9,#0x41000000
结果都是一样的,当然建议大家按uclinux提供的方式来弄比较好,我嘛,以后再改。
然后就是一大段根据这个ID比较的过程,最后返回,一些查找结构失败的判断,然后就是函数__lookup_architecture_type。
__lookup_architecture_type这个函数的作用是在.arch.info段中寻找相应的machine_desc结构,这个结构定义在include/asm-armnommu/mach/arch.h中,具体针对每个arch的定义就在arch/armnommu/kernel/arch.c以及arch/armnommu/mach-*/arch.c中,而GBA的就在arch/armnommu/mach-gba/arch.c中。其中具体的每个宏都在include/asm-armnommu/mach/arch.h中,都很明确我就不详细介绍了。
往后还有一些初始化工作,不过因为跟没有什么改动,就不详细介绍了。
5.3.start_kernel
5.3.1.setup_arch
这个函数用来做体系相关的初始化的,armnommu的在arch/armnommu/kernel/setup.c。注意一下540行,这里是默认的对物理内存结构meminfo的初始化,这个结构将在后面的内存初始化中起很重要的作用。在这其中,nr_banks指定了内存块的数量,bank是指定了每块内存的范围。而在这里用来指定块开始和长度的PAGE_OFFSET和MEM_SIZE,都定义在include/asm-armnommu/arch-gba/memory.h中,PAGE_OFFSET是内存的开始地址,我设置这个值为0x2001000,就是前面介绍的init和data的开始地址,结束地址自然就是内存的结束地址。后面函数就将根据meminfo进行内存结构初始化。
其中还有一个地方的修改,bootmem_init函数中调用的函数reserve_node_zero,这个函数的作用是保留一些内存以便使值不能被动态分配。看一下这个函数的内部,原来的代码是:
reserve_bootmem_node(pgdat, __pa(&_stext), &_end - &_stext);
这行的意思就是从_stext到_end都保留起来不做动态分配,而看我写的连接脚本就能知道这么写是不行的,因为_stext在flash中,所以我将这段代码改为:
reserve_bootmem_node(pgdat, __pa(&__init_begin), &_end - &__init_begin);
后面对init_arch_irq进行了初始化,这个将在后面用到。
5.3.2.parse_options和calibrate_delay
我的代码跑到这2行以后进去就跑不出来了,正好看一个文章写有什么问题,而且2个函数也没什么作用,所以我就直接将其去掉了。