kernel移植——从三星官方内核开始移植

来源:互联网 发布:换头型软件 编辑:程序博客网 时间:2024/05/20 11:52

以下内容源于朱有鹏《物联网大讲坛》课程的学习,如有侵权,请告知删除。


一、内核移植初体验

见博客http://blog.csdn.net/oqqhutu12345678/article/details/70526678

1、三星官方移植版内核获取

  • 源码包下载;
  • 源码包来于三星的SMDKV210开发板附带的光盘资料;

2、构建移植环境

  • Windows下建立SI工程;
  • ubuntu下解压;
3、配置、编译

  • 检查Makefile中ARCH和CROSS_COMPILE;
  • make xx_defconfig;
  • make menuconfig;
  • make -j4(直接make,则会直接单线程编译;如果make -j4,则会4线程编译)。

4、将得到的zImage下载运行,看结果

  • 编译完成后得到的内核镜像不在源码树的根目录下,而是在arch/arm/boot这个目录下。
  • 通过tftp将镜像下载到开发板,见博客http://blog.csdn.net/oqqhutu12345678/article/details/70537721。

5、现象分析

(1)结果

  • 看不到  Uncompressing Linux... done, booting the kernel.
(2)分析

  • 说明zImage根本没有被解压成功,内核代码根本就没有被运行。因此问题出在解压相关的部分。
  • 问题出在内核配置的解压后代码放置的内存地址处。
  • 内核配置的解压地址应该等于连接地址,否则自解压之后内核无法运行。现在问题变成:第一,内核的连接地址等于多少?第二,内核中配置的解压地址是多少?
  • 这里面还有个问题:内核的连接地址是一个虚拟地址,而自解压代码解压内核时需要物理地址,因此连接地址对应的物理地址等于自解压地址
  • 连接地址和对应的物理地址在head.S中可以查到,分别是0xC0008000和0x30008000,那么自解压代码配置的解压地址应该是30008000。

(3)修改操作

  • 自解压代码对应的自解压地址在mach/Makefile.boot文件中。
  • 在其中加入下面内容
# override for SMDKV210zreladdr-$(CONFIG_MACH_SMDKV210):= 0x30008000params_phys-$(CONFIG_MACH_SMDKV210):= 0x30000100

(4)同步、编译,重新下载运行查看结果

  • 自解压代码解压打印信息已经出来;
  • 内核还是没有运行;

(5)问题分析

  • 定义的物理地址不对,从20000000改到30000000即可。


二、内核中机器码的确定

1、MACHINE_START宏

  • 这个宏用来定义一个机器码的数据结构。
  • 这个宏的使用其实是用来定义一个结构体类型为machine_desc类型的结构体变量,名为__mach_desc_SMDKV210。
  • 这个结构体变量会被定义到一个特定段.arch.info.init,因此这个结构体变量将来会被链接器链接到这个.arch.info.init段中。
static const struct machine_desc __mach_desc_SMDKV210\ __used\ __attribute__((__section__(".arch.info.init"))) = {\.nr= MACH_TYPE_SMDKV210,\.name= "SMDKV210",.phys_io= S3C_PA_UART & 0xfff00000,.io_pg_offst= (((u32)S3C_VA_UART) >> 18) & 0xfffc,.boot_params= S5P_PA_SDRAM + 0x100,.init_irq= s5pv210_init_irq,.map_io= smdkv210_map_io,.init_machine= smdkv210_machine_init,.timer= &s5p_systimer,};

(1)mach-xxx.c文件定义了一个机器码的开发板的machine_desc结构体变量

  • 这个结构体变量放到.arch.info.init段中后,表示当前内核可以支持这个机器码的开发板。

(2)落实到当前开发板和当前内核中来分析,当前我们移植的目标开发板使用S5PV210的CPU,开发板名字叫X210。

  • 在三星官方版本的内核中找不到mach-x210.c;
  • 但不想从零开始移植,因此在三星移植的mach-s5pv210目录下找一个mach-xx.c,使得该开发板和X210开发板最为接近,然后以此为基础来移植。

(3)经过查看,发现mach-s5pc110.c和mach-s5pv210.c与X210开发板最为接近。

  • 寻找原则:开发板和三星官方的哪个开发板最为相似。
  • X210开发板抄的是三星的SMDKV210,因此要找SMDKV210对应的文件。

(4)结合mach-s5pv210目录下的Makefile来分析,得知.config中定义CONFIG_MACH_SMDKV210后,实际绑定的是mach-smdkc110.c这个文件。

  • 因此实际上mach-smdkv210.c这个文件根本没用到。

2、硬件驱动的加载和初始化函数执行

.init_machine= smdkc110_machine_init,

  • 定义了一个机器硬件初始化函数;
  • 这个函数非常重要,这个函数中绑定了我们这个开发板linux内核启动过程中会初始化的各种硬件的信息。


三、解决内核启动中的错误

1、认识内核启动OOPS

(1)内核启动后会有打印信息,打印信息中隐藏了问题所在。

  • 认真的去分析这个打印信息,从中找到对的或者错误的一些信息片段,才能帮助我们找到问题,从而解决问题。

(2)内核启动中的错误信息的特征

Unable to handle kernel NULL pointer dereference at virtual address 00000060Internal error: Oops: 5 [#1] PREEMPTPC is at dev_driver_string+0xc/0x44LR is at max8698_pmic_probe+0x150/0x32c

  • 由PC和LR的值可以看出,程序是执行到dev_driver_string或者max8698_pmic_probe(这两个是函数或者汇编中的标号)符号部分的时候出错了。
  • 我们从这两个符号出发去寻找、思考可能出错的地方然后试图去解决。

2、错误追溯及问题解决

(1)max8698_pmic_probe

  • max8698这个电源管理IC的驱动安装函数部分出错;
  • 开发板系统中配置了支持这个电源管理IC,于是启动时去加载它的驱动,结果驱动在加载执行的过程中出错。

(2)这个驱动加载时为什么会出错?

  • 结合X210开发板的硬件实际情况来分析:X210开发板上根本就没有max8698这个电源管理IC,既然硬件都没有驱动执行了肯定会出错。
  • 之前移植三星版本的uboot时,在uboot的lowlevel_init.S中也有调用电源管理IC初始化函数(PMIC_init),结果会报错,屏蔽掉该函数的调用,uboot就可以成功运行下去。

(5)为什么uboot和内核中,都默认调用这个电源管理IC的初始化代码?

  • 因为三星的SMDKV210开发板中用了max8698这个电源管理IC,因此三星的uboot和kernel中都默认支持这个。
  • 但是X210中是没用的,因此uboot和内核中都需要去掉该代码模块。

(6)怎么解决?

  • 在uboot中,是直接改源代码,即屏蔽掉那个初始化函数解决的;
  • 在kernel中,不能直接修改源代码。
  • 因为linux kernel是高度模块化高度可配置的,内核中每一个模块都被配置项条件编译;
  • 因此要去掉对某个模块的支持,需要重新配置,配置时去掉选项即可,不用改源代码。
  • 因此关键就是要找它对应的配置项。

(7)操作

  • 先make menuconfig;
  • 然后/搜索"MAX8698"这几个关键字,然后看到这个配置项的路径,然后到路径下去按N键去掉这个模块的支持,保存,重新编译即可。

(8)重新编译、下载运行

  • 此问题被解决了;
  • 内核再次启动后直接运行到挂载rootfs才出错。

3、分析及总结

分析:根本原因在于CONFIG_MFD_MAX8698这个配置宏。这个配置宏决定了很多东西

  • 第一:这个配置宏决定了drivers目录下的max8698对应的驱动程序源代码是否被编译;
  • 第二:这个配置宏决定了kernel启动过程中是否会调用一些max8698的相关的代码;

总结:kernel是高度模块化和可配置化的,所以在内核中做任何事情(添加一个模块、更改一个模块、去掉一个模块)都必须按照内核设定的方案和流程。



四、iNand的问题和安排

1、错误分析

(1)内核错误信息:Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)。

  • 内核试图挂载根文件系统时失败,失败的原因是unknown-block(不能识别的块设备)

(2)backstrace分析,可以得知错误信息的来源,再结合之前的内核启动流程分析,就更加确定了出错的地方。

(3)为什么unknown-block(0,0)?

  • 在kernel启动时,uboot给内核传递一个cmdline。
  • 其中root=xx指定rootfs在哪个设备上,内核会到相应的地方去挂载rootfs。
  • 譬如root=/dev/mmcblk0p2,这里的/dev/mmcblk0p2就是rootfs的设备的地址。
  • 表示mmc设备0的第2个分区(设备0,即在SD0通道上的设备,即iNand)。
  • 这里的问题就是没找到mmc设备0的第2分区。

(4)为什么没找到mmc设备0的第2分区?

  • 一定是因为kernel启动过程中加载mmc驱动的时候有问题,驱动没有发现mmc设备0。
  • 问题定位在MMC相关的驱动方面。

(5)对比九鼎版本的内核启动信息

  • 可发现此内核启动时,没有找到MMC设备(内置的iNand和外置的SD卡都没找到);
  • 没找到肯定是驱动的问题,因此要移植MMC驱动。

2、问题阐述

(1)SD/iNand都是由一个一个的扇区组成的

  • BL1从SD卡的1扇区开始往后存放;
  • SD卡的0扇区是不用的,SD卡的0扇区用来放置MBR(主引导记录)。

(2)MBR用来描述块设备的分区信息

  • 事先定义了一个通用的数据结构来描述块设备的分区;
  • 只要将分区信息写入MBR中,即可完成对该设备的分区;
  • MBR默认存放在块设备的第0个扇区。

(3)内核如何知道iNand分了4个分区?哪里对inand进行了分区?

  • uboot中有一个命令fdisk,fdisk -c 0时,对iNand进行分区;
  • fdisk命令对iNand的分区已经写死,内核通过读取MBR,就可以知道分区信息了;
  • 由iNand本身通过MBR给内核传递分区信息,因此uboot给内核传参时,不用传递分区表信息。

(4)如果开发板使用的是nandFlash,分区表一般是在内核中用代码构建的。

  • 因此内核移植的时候,一般都需要移植、更改nand分区表。

3、解决安排

暂时解决不了这个问题。



五、网卡驱动的移植和添加实验

1、移植标准

(1)网卡驱动移植ok时,启动信息为

[    1.452008] dm9000 Ethernet Driver, V1.31[    1.455870] eth0: dm9000c at e08f4300,e08f8304 IRQ 42 MAC: 00:09:c0:ff:ec:48 (platform data)

(2)当前内核中网卡驱动尚未移植,因此内核启动时有错误的打印信息:

[    1.130308] dm9000 Ethernet Driver, V1.31[    1.133113] ERROR : resetting [    1.135700] dm9000 dm9000.0: read wrong id 0x2b2a2928[    1.140915] dm9000 dm9000.0: read wrong id 0x2b2a2928[    1.145941] dm9000 dm9000.0: read wrong id 0x2b2a2928[    1.150963] dm9000 dm9000.0: read wrong id 0x2b2a2928[    1.155992] dm9000 dm9000.0: read wrong id 0x2b2a2928[    1.161018] dm9000 dm9000.0: read wrong id 0x2b2a2928[    1.166041] dm9000 dm9000.0: read wrong id 0x2b2a2928[    1.171070] dm9000 dm9000.0: read wrong id 0x2b2a2928[    1.176092] dm9000 dm9000.0: wrong id: 0x2b2a2928[    1.180774] dm9000 dm9000.0: not found (-19).

(3)移植的目标

  • 让此版本的内核可以打印出正确情况下的启动信息

2、make menuconfig中添加DM9000支持

  • menuconfig中选择Y;

3、mach-smdkc110.c中逻辑分析

(1)mach-smdkc110.c中的smdkc110_machine_init是整个开发板的所有硬件的初始化函数

  • 在这里被加载的硬件,在将来启动时就会被初始化;
  • 在这里没被加载的硬件,在将来启动时就不管。
(2)smdkc110_devices和smdkc110_dm9000_set()这两个地方是和DM9000有关的,要分别去做移植。

  • smdkc110_dm9000_set(),是和DM9000相关的SROM bank的寄存器设置;
  • 其相当于uboot中dm9000移植时的dm9000_pre_init函数,只是读写寄存器的函数名称不同了。

4、修改相应的配置参数

(1)DM9000相关的数据配置在arch/arm/plat-s5p/devs.c中;

(2)在arch/arm/mach-s5pv210/include/mach/map.h中定义了DM9000的IO基地址,和DM9000接在哪个bank有关。

(3)+2改成+4,IRQ_EINT9改成10即可。

5、同步、编译、下载,查看启动信息



六、内核启动第一阶段的调试方法

1、调试的原因

(1)内核启动在head.S中首先进行三个校验(CPU id的校验、机器码的校验、tag的校验),然后创建页表,然后做了一些不太会出错的事情,然后b start_kernel。基本上能运行到start_kernel,内核移植就不太会出问题了。

(2)有时候移植的内核启动后的现象是:根本没有启动信息出来。

  • 有可能是内核启动运行了,但是运行出错了没启动起来所以没有打印信息;
  • 有可能是内核根本没运行;
  • 希望能有一种调试手段来确定问题所在。

2、调试方法和原理

(1)调试方法就是在内核启动的第一阶段,添加汇编操作led点亮/熄灭的方法来标明代码运行的轨迹。

(2)将led点亮和熄灭的代码,复制粘贴到head.S中合适位置,然后内核启动后根据led的表现来标明代码有无运行。

3、动手测试

(1)在head.S中合适的地方添加led这个函数,然后在head.S的内核起始运行阶段添加调用led函数,然后重新编译内核,运行内核看这段代码有无被运行。

(3)分析思路

  • 如果被运行,证明在调用led的步骤之前的部分都是没问题的,如果有错,错误肯定在后边;
  • 如果没有被运行则证明错误在之前,那么就要去之前的部分debug。