花了两个多礼拜终于在4020上移植好了2.6的内核,感谢张阳,史先强师兄,方攀,王欢。呵呵。
内核版本:linux2.6.16
交叉编译工具:arm-linux-gcc 3.4.1
硬件系统:sep4020,南京博芯公司UB4020EVB V1.3的开发板
1. 验证编译环境,
使用arm-linux-gcc对linux2.6.16内核中的成熟芯片如三星2410进行编译(注:在已经选择好了芯片后,一定要选择一款相应的班子,在implementions中),发现问题:vgacon.c文件编译不通过,上网查看发现是video选择的问题,暂时不用,把错误的地方注释掉,ok。
2. 首先搞清楚linux2.6的文件组织结构。
●Arch :arch子目录包括了所有和体系结构相关的核心代码。它的每一个子目录都代表一种支持的体系结构,例如i386就是关于intel cpu及与之相兼容体系结构的子目录。PC机一般都基于此目录;
●Include: include子目录包括编译核心所需要的大部分头文件。与平台无关的头文件在 include/linux子目录下,与 intel cpu相关的头文件在include/asm-i386子目录下,而include/scsi目录则是有关scsi设备的头文件目录;
●Init: 这个目录包含核心的初始化代码(注:不是系统的引导代码),包含两个文件main.c和Version.c,这是研究核心如何工作的一个非常好的起点。
●Mm :这个目录包括所有独立于 cpu 体系结构的内存管理代码,如页式存储管理内存的分配和释放等;而和体系结构相关的内存管理代码则位于arch/*/mm/,例如arch/i386/mm/Fault.c
●Kernel:主要的核心代码,此目录下的文件实现了大多数linux系统的内核函数,其中最重要的文件当属sched.c;同样,和体系结构相关的代码在arch/*/kernel中;
● Drivers: 放置系统所有的设备驱动程序;每种驱动程序又各占用一个子目录:如,/block
上面是2.4内核的一些组织信息,和2.6大差不差,因此这就用这个解释了。从上面的解释可以知道,arch,include文件夹中包含的是和我们平台相关的一些东西,因此这两个地方是需要修改的,而kernel,mm是系统的源码,是我们需要调用的,一般不需要修改。Init是很重要的代码,系统的引导的最重要一部start_kernel()就是在这个文件夹下的main.c实现的。Drivers是设备驱动,将来的设备驱动文件都会放在这里。
因此我们可以清晰的看到我们需要修改的三个文件夹:arch,include,drivers。
3.关于linux的Kconfig和Makefile
在linux2.6.版本中文件的组织是通过Kconfig(2.4中是config.in)和Makefile来实现的。在内核的源码树下都有这两个文档。通过每一层的Kconfig和Makefile实现了整个linux的分布式的内核配置数据库。
Kconfig:对应了内核的配置菜单,Makefile:对应了内核的编译选择
因此加入想添加新的驱动到内核源码中,通过修改相应层的Kconfig,你就能在makemenuconfig的时候看到这个选项;如果还希望选择了这个选项后,这个驱动文件被编译,那么需要修改Makefile文件。
举个例子:
我在arch/arm中的Kconfig文件中加入了下面几行:
config ARCH_SEP4020
bool”SEP4020”
这表明了我在内核配置菜单中添加了这个选项,但此时这个选项还仅仅是空的,
相应的需要在Makefile文件中添加:
Machine-$(CONFIG_ARCH_SEP4020) :=sep4020,
Source “arch/arm/mach-sep4020/Kconfig”则表明了如果选中ARCH_SEP4020,它还有下层目录,当然下层目录中还会有Kconfig和Makefile,linux2.6就是通过这种组织结构将整个内核设置成一个可配置编译的灵活操作系统。
4. 开始移植到我们的处理器上,fighting!
(1) 在linux根目录下的Makefile改动,是目标处理器是arm,交叉编译器为arm-linux
代码:ARCH ?= arm
CROSS_COMPILE ?= arm-linux-
(2) 因为我们的处理器是基于arm720T的,所以我们需要在arch/arm文件夹下对它进行修改。增加mach-sep4020文件夹,里面是关于我们这款cpu的一些配置信息。在include/asm/arm中加入相应的头文件文件夹arch-sep4020。(这一步是我们的重点,在下文中会详细叙述)
(3) 在/arch/arm/mm/Kconfig中加入对我们这款处理器的支持,在config CPU_ARM720T中加入对我们这款处理器。
代码:
config CPU_ARM720T
bool "Support ARM720T processor" if !ARCH_CLPS711X && !ARCH_SEP4020 && !ARCH_L7200 && !ARCH_CDB89712 && ARCH_INTEGRATOR
default y if ARCH_CLPS711X || ARCH_L7200 || ARCH_CDB89712 || ARCH_H720X ||ARCH_SEP4020
select CPU_32v4
select CPU_ABRT_LV4T
select CPU_CACHE_V4
select CPU_CACHE_VIVT
select CPU_COPY_V4WT
select CPU_TLB_V4WT
(4)修改/arch/arm/tools/mach-types,添加我们的处理器号,这是需要将来给bootloader的
注:incule/asm-arm中的mach-types.h中的信息是gen-mach-types脚步文件通过mach-types自动生成的,所以不需要手动添加
5. 重头戏/arch/arm/mach-sep4020
在/arch/arm/mach-sep4020文件夹中主要是对我们处理器和开发板的一些配置信息,在这里我主要写了4个文件:
●Irq.c:是中断的一些操作,包括开中断,关中断,中断初始化,并将我们的中断注册到系统中去,使用了一个结构体irqchip,(linux中广泛采用了这种做法,我们大部分只需要写函数,并注册就ok了)
●mm.c:将Linux移植到目标电路板的过程中,通常会建立外设I/O内存物理地址到虚拟地址的静态映射,这个映射通过在电路板对应的map_desc结构体数组中添加新的成员来完成. 此后,在设备驱动中访问经过map_desc数组映射后的I/O内存时,直接在map_desc中该段的虚拟地址上加上相应的偏移即可。
注意此处的type类型:一般都是选择MT_DEVICE,但是由于我们在这里把外设SDRAM的地址空间也通过这种方法映射过去的,所以对于SDRAM应该选择MT_MEMORY,不然会在kmem_cache_init那儿过不过去的,切忌切记(我在这儿堵了很久)
●timer.c:是对系统的定时器进行一些配置,如timer来中断后的中断处理函数,timer的初始化,再把这些函数注册到结构体sys_timer(又见结构注册!!)
●4020.c:主要是对板子的信息进行配置,包括物理IO地址,io偏移,mach_type,并将我们的timer,mm中的io映射函数,irq注册进去。此外由于我们的sep4020芯片的UART设计是兼容8250的,因此在这里我并没有在drivers中替我们的芯片单独写一个驱动,而是利用内核自带的8250串口底层驱动文件,通过配置我们的uart的一些信息,并注册进去即可。
/include/asm/arm/arch-sep4020
这里是一些头文件信息,这里对应了在arch/arm/mach-sep4020中的所用到的信息,包括对寄存器的定义,中断源的定义等等,简单陈述一下几个重要的:
●Hardware.h:是sep4020所有寄存器的定义和说明(包括物理地址,和虚拟地址)
●Irqs.h:是sep4020的所有中断源
●Memory.h:是对io虚拟地址到物理地址,虚拟地址到page frame的一些实现函数
●Entry-macro.s:这是很重要的一点,linux2.4中将这个放到了boot的head.s中去,而head.s是所有芯片都会使用的,这样做会造成核心代码的破坏,linux2.6中修正了这个问题,将这段系统初始化的函数从head.s中拉了出来变成了现在的entry-macro.s,这样就很好的使通用文件和处理器相关文件做了很好的分割。这个宏查询ISPR(IRQ待定中断服务寄存器,当有需要处理的中断时,这个寄存器的相应位会置位,任意时刻,最多一个位会置位),计算出的中断号放在irqnr指定的寄存器中;这个宏在不同的ARM芯片上是不一样的。
6. 移植过程中的一些错误:
● 时钟滴答没有起来:典型的会在main.c函数中的calibrate_delay()中陷入死循环。这主要是timer初始化,或者中断处理函数写的不对
● 程序在main.c中的local_irq_enable()死了,这是由于timer中断处理函数写的有问题
● 注意在配置中我们所填的地址(包括寄存器地址,存储器地址)都是虚拟地址,因为系统一开始就会将MMU打开,不能填物理地址,切记切记。
● 串口注册的时候在plat_serial8250_port中,membase代表了串口的虚拟基址,mapbase代表了串口的物理地址(个人觉得可以不写),regshift代表了地址偏移的位数,它的设置是offset<<regshift,默认是一个字节字节偏移的,而我们的uart是4个字节偏移的,因此这里的regshift需要填写2,uartclk是uart的时钟频率,不是波特率,因此填芯片的主频就可以了,不要像我刚开始那样填了个9600,哈哈。