移植linux kernel,应该怎么做

工作需要,公司做的soc芯片换处理器核,本人负责对linux kernel完成跨处理器的移植(arm到powerpc),是小端处理器到大端处理器的移植。因公司soc涉及的模块较多,kernel移植我要完成kernel稳定启动,并且能正常进入console,只有串口驱动。剩余的驱动模块交由各个模块驱动开发人员来完成移植工作。

做了3周左右,代码部分已经根据powerpc的kernel框架修改了原来针对arm的板级支持包,换核后的soc处于FPGA验证阶段,现在kernel已经可以进入console,但是发现核的cache部分有问题,console还不能正常使用(用户空间是cached)。待IC工程师修复cache问题后,问题应该就不大了。

这个移植工作,有很多比方都是值得总结的,如kernel移植思路,大小端移植问题等。

今天就对于移植kernel的思路来总结下,这里总结不涉及具体代码,而只是说说保证kernel能进入console稳定运行这样一个最小系统,我们需要完成哪些部分的移植呢。

根据这次移植我的思路,需要以下几个方面的移植:

cpu core初始化,内存管理子系统(mmu),硬件时钟系统,早期调试打印机制,异常中断子系统,时间子系统(timer),串口驱动


1 cpu core初始化

cpu core的初始化主要指cpu的工作模式,通用寄存器初始化,cache初始化,异常入口初始化,mmu初始化等。也就是cpu core内部各个单元模块的初始化。

这部分针对主流cpu core的各个系列,kernel中都有支持,一般在arch/xxx(处理器类型)/kernel/head_xx.S中,需要我们改动的部分很少,但了解其过程对于解决一些奇怪问题还是有帮助的。

如处于仿真验证阶段的soc,我遇到的cache读写有问题,就是在该阶段修改代码进行对比验证查找到问题。


2 内存管理子系统

kernel是运行在动态存储器(DRAM DDR),首先保证动态存储器的稳定运行,动态存储器的初始化一般是放在bootloader中,或者在我们的调试工具脚本中去配置DDR参数,实现DDR的稳定读写运行。

内存管理子系统就是指MMU,kernel移植为什么还要修改MMU呢。

首先一点,kernel image所在bootmem需要mmu映射。

现在kernel默认的链接地址为0xc0000000,也就是内核空间的首地址。但是soc设计中ddr物理地址是不受限制的。如0x0,0x80000000.

kernel启动最早部分启动代码是运行在物理地址上的(除了PPC某些MMU常开的CPU),因此kernel早期代码设计为位置无关码,但是为了保证kernel的稳定长期运行,kernel必须要实现ddr中kernel镜像所在的区域到0xc0000000的映射,因此需要在代码中找到这个映射关系进行修改(在head_xxx.S中),来符合我们的处理器地址空间设计。

关于kernel早期启动可以看我另一篇文章:http://blog.csdn.net/skyflying2012/article/details/41344377

其次一点,kernel对于除bootmem外其余内核空间需要mmu映射。

除bootmem外其余内核空间氛围lowmem和highmem,lowmem是静态映射,跟bootmem一样。highmem是动态映射,实现kernel可以访问到ddr剩余的所有地址空间。

这些都需要根据实际的ddr空间进行修改,如ppc的44x系列处理器核,仅支持mem从物理地址0开始,而我们的mem从0x80000000开始,有很多地方需要修改。

最后一点,IO地址空间需要mmu映射。

对于处理器核外soc内外设控制器的访问,都是通过寄存器的方式,因此站在cpu角度来看,寄存器地址空间映射到哪里,也就是cpu看到的寄存器地址空间在哪里,是非常重要的。

可以在kernel启动早期,首先完成对于串口寄存器地址空间的简单映射,来实现kernel早期调试打印机制,方便kernel调试。

在进入正常的kernel初始化阶段(start_kernel之后)可以通过2种方式来实现IO空间的映射,ioremap和iotable,

ioremap是一种通用标准的IO动态映射方式,kernel的各个driver中可以使用ioremap实现各自寄存器地址空间映射。

iotable实现一种IO的静态映射,iotable仅在arm平台实现,可以在kernel加载各个driver之前,统一将所有外设寄存器空间完成映射(可以指定虚拟地址),各个driver中仅需调用IO_ADDRESS来获取寄存器虚拟地址进行访问即可。

对于kernel正常启动进入用户控件后的动态内存管理,是一套通用的做法(pud ,pmd,pte等),不需要移植时进行修改。

因此kernel移植时,为了保证dram读写正常,寄存器读写正常这最基本的要求,要根据soc的dram地址空间以及IO地址空间来进行kernel内核空间的MMU配置映射。


3 硬件时钟系统

最小的soc片上系统应该有CPU,时钟,复位电路和一块存储器构成,缺一不可。

时钟是soc的心脏,没有时钟,soc上的所有模块都无法正常的工作,包括处理器。

当然soc在设计时,对于由PLL晶振倍频分频出来的各个模块的时钟在系统上电时都会有默认值,这些默认值我们必须知道。

在kernel移植中,要在各个模块初始化之前(包括早期调试机制和中断intc),将soc的整个时钟系统进行初始化,配置到我们需要的频率,以来发挥其最好性能,并且能保证系统稳定运行。


4 早期调试打印机制

在dram和寄存器读写正常后,接下来首要任务是实现kernel早期的调试打印,这是调试kernel所必须的。

kernel对于各个平台实现了一些早期打印函数,如early_printk(mips arm),udbg_printf(ppc),方式类似,对字符串做简单的处理(va_args),然后循环往串口数据寄存器写数据。

我们只需要实现最底层的初始化和输出接口即可。当然如果在bootloader中或者调试工具中对串口做了初始化,那就更简单了,实现输出函数(putc)即可。


5 异常 中断子系统

timer和uart要正常工作,异常(ppc核内有timer,作为一种单独的异常来处理) 中断是必须要正常工作起来。

异常的初始化位于kernel最早期head_xxx.S中,主要是异常入口和各种异常偏移初始化,不同平台不一样,但是各个平台不同类型cpu的设计是固定的。

各个平台的设计,arm可以参考《arm体系结构设计》,mips可以参考《see mips run》,ppc可以参考《Enhanced Powerpc Architecture》。

对于kernel支持的cpu型号,异常初始化不需要修改。

而中断控制器是soc外设,因此中断系统就需要我们根据具体的soc做移植。

针对kernel下中断系统代码框架,需要修改irq_desc的注册,中断控制器初始化,irq num的获取,来完成kernel下中断系统从irq产生到irq响应的闭环。

中断子系统移植完成后需要进行验证,例如使用外部timer进行验证,看看能否正常产生中断。如果不能,需要查找kernel下中断处理链上哪部分有问题。

kernel下中断处理链条:

模块中断触发 ===》模块中断enable ===》intc中该模块中断enable ===》触发cpu外部中断异常 ===》跳转到异常处理函数 ===》

根据intc状态确定产生中断模块,分发中断 ===》调用模块中断处理函数处理

分别查找以上各个部分,确定问题,最终保证整个链条可以跑通,则kernel下的中断子系统正常工作。


6 时间子系统

kernel下时间子系统实现计时和定时功能,主要是根据一个外部timer或者核内timer(PPC提供核内timer)来实现

那为什么实现一个最小的kernel系统,还要去实现一个timer驱动。

因为kernel最关键最核心的调度是基于时间片,kernel无法计时,调度器就无法正常工作。

timer驱动的实现涉及到clockevent和clocksource内核结构体,以后有时间需要总结下kernel的时间子系统,这里就不在细说了。


7 串口驱动

最后实现串口驱动就完成了移植kernel的最后一步了。串口驱动实现这里不细说了。

对于kernel下console的原理,大家可以参考我另一篇文章,链接如下:http://blog.csdn.net/skyflying2012/article/details/41078349


在完成上述7步后,kernel一个最小系统移植就完成了。

你可能感兴趣的:(linux,kernel,内核机制学习笔记)