一、内核移植初体验
1、三星官方移植版内核获取
(1) 从网盘下载源码包。
(2) 这个文件最初是来自于三星的 SMDKV210 开发板附带的光盘资料。
2、构建移植环境
(1) Windows下建立工程。
(2) ubuntu下解压。
3、配置编译下载尝试
(1) 检查 Makefile 中 ARCH 和 CROSS_COMPILE。
(2) make smdkv210_android_defconfig。
(3) make menuconfig。
(4) make -j4。
默认情况下直接 make,则会直接单线程编译。但是如果 make -j4, 则会 4 线程编译。
4、后续要做的事情:
(1) 编译得到的 zImage 去下载运行,看结果。
(2) 根据结果去分析问题原因,然后去尝试解决这些问题。
注意:使用的 uboot,是 x210 的 uboot。
二、初步移植以看到启动信息
1、分析问题
(1) 根据运行结果,分析发现:linux 内核的自解压代码都没有运行(因为没有看到:Uncompressing Linux… done, booting the kernel.)
(2) 说明 zImage 根本没有被解压成功,内核代码根本就没有被运行,当然没有输出信息了。所以问题出在解压相关的部分。
(3) 问题出在,内核配置的解压后代码放置的内存地址处。
(4) 内核配置的解压地址,应该等于链接地址,否则自解压之后内核无法运行。
现在问题变成:
第一,内核的链接地址等于多少?
第二,内核中配置的解压地址是多少?
(5) 这里面还有个问题:内核的链接地址是一个虚拟地址,而自解压代码解压内核时,需要物理地址;因此上面说的,其实是链接地址对应的物理地址等于自解压地址。
(6) 链接地址和它对应的物理地址在 head.S 中可以查到,分别是 0xC0008000 和 0x30008000 。那么自解压代码配置的解压地址应该是 0x30008000.
(7) 自解压代码对应的自解压地址在 mach/Makefile.boot 文件中。在其中修改,加入两行:
# override for SMDKV210
zreladdr-$(CONFIG_MACH_SMDKV210) := 0x30008000
params_phys-$(CONFIG_MACH_SMDKV210) := 0x30000100
(8) 同步代码,并且编译,得到的 zImage 复制到 tftp 服务器,然后重新下载运行查看结果。
三、内核中机器码的确定
1、MACHINE_START 宏
(1) 这个宏 用来定义一个机器码的数据结构的。这个宏的使用,其实是用来定义一个结构体类型为 machine_desc 类型的结构体变量,名为 __mach_desc__type 。这个结构体变量,会被定义到一个特定段 .arch.info.init,因此这个结构体变量将来会被链接器链接到这个 .arch.info.init 段中。
(2) 经过分析,发现一个 mach-xxx.c 文件中定义了一个机器码的开发板的machine_desc 结构体变量,这个结构体变量放到 .arch.info.init 段中后,那么就表示当前内核可以支持这个机器码的开发板。
(3) 落实到当前开发板和当前内核中来分析,当前我们移植的目标开发板使用 S5PV210 的 CPU,开发板名字叫 X210。
我们在三星官方版本的内核中是找不到 mach-x210.c 的,所以我们又不想从零开始去移植,因此我们的思路是,在三星移植的 mach-s5pv210 目录下找一个 mach-xx.c,这个开发板和我们的 X210 开发板最为接近,然后以此为基础来移植。
(4) 经过查看,发现 mach-s5pc110.c 和 mach-s5pv210.c 和我们的 X210 开发板最为接近。我们一般确定的一个原则是:看我们的开发板和三星官方的哪个开发板最为相似。
我们的 X210 开发板抄的是三星的 SMDKV210,因此要找这个对应的那个文件。
(5) 结合 mach-s5pv210 目录下的 Makefile 来分析,得知 .config 中定义了CONFIG_MACH_SMDKV210 后,实际绑定的是 mach-smdkc110.c 这个文件。
所以实际上,mach-smdkv210.c 这个文件根本没用到。启示就是:不要光看名字。
2、硬件驱动的加载和初始化函数执行
(1)
.init_machine = smdkc110_machine_init,
(2) 这个元素定义了一个机器硬件初始化函数,这个函数非常重要,这个函数中绑定了我们这个开发板 linux 内核启动过程中,会初始化的各种硬件的信息。
四、解决内核启动中的错误
1、认识内核启动OOPS
(1) 内核启动后会有打印信息,打印信息中隐藏了问题所在。认真的去分析这个打印信息,从中找到对的或者错误的一些信息片段,才能帮助我们找到问题,从而解决问题。
(2) 内核启动中的错误信息有一些特征:
(3) 从以上错误信息中的 PC 和 LR 的值可以看出,程序是执行到 dev_driver_string 或者 max8698_pmic_probe(这两个是函数或者汇编中的标号)符号部分的时候出错了。我们就从这两个符号出发去寻找、思考可能出错的地方然后试图去解决。
2、错误追溯及问题解决
(1) max8698_pmic_probe 看名字是 max8698 这个电源管理 IC 的驱动安装函数部分出错了,应该是我们的开发板系统中配置了支持这个电源管理 IC,于是启动时,去加载它的驱动,结果驱动在加载执行的过程中出错了 OOPS 了。
(2) 我们为什么要配置支持这个驱动?这个驱动加载为什么要出错?
(3) 结合我们 X210 开发板的硬件实际情况来分析:我们 X210 开发板上根本就没有 max8698 这个电源管理 IC,既然硬件都没有,驱动执行了肯定会出错。
(4) 回忆当时从三星版本的 uboot 移植的时候,在 uboot 的 lowlevel_init.S 中也有调用过电源管理 IC 初始化函数(PMIC_init),后来解决的办法就是屏蔽掉了这个函数的调用,uboot 就成功运行下去了。
(5) 为什么我们的 uboot 和内核中默认都调用了这个电源管理 IC的初始化代码?
原因就是,三星的 SMDKV210 开发板中是用了 max8698 这个电源管理 IC 的,所以三星的 uboot 和 kernel 中都有默认支持这个。但是 X210 中是没用的,因此都需要去掉。
(6) 怎么解决?
在 uboot 中是直接改源代码,屏蔽掉那个初始化函数解决的;
在内核中不能这么干?因为 linux kernel 是高度模块化高度可配置的,内核中每一个模块都是被配置项条件编译了的,因此要去掉某个模块的支持,只需要重新配置去掉选项即可,不用改源代码。所以我们的关键就是要找它对应的配置项。
(7) 我们的做法:make menuconfig,然后斜杠搜索(/MAX8698)这几个关键字,然后看到这个配置项的路径,然后到路径下去按 N 键去掉这个模块的支持,保存,重新编译即可。
(8) 实践证明问题被解决了,而且内核再次启动后直接运行到挂载 rootfs 才出错。
3、分析及总结
(1) 分析:问题究竟是怎么被解决的?涉及哪几个方面。
根本原因在于 CONFIG_MFD_MAX8698 这个配置宏。这个配置宏决定了很多东西。
第一:这个配置宏 决定了 drivers 目录下的 max8698 对应的驱动程序源代码是否被编译。
第二:这个配置宏 决定了 kernel 启动过程中是否会调用一些 max8698 的相关的代码。
(2) 总结:kernel 是高度模块化和可配置化的,所以在内核中做任何事情(添加一个模块、更改一个模块、去掉一个模块)都必须按照内核设定的方案和流程来走。
五、iNand 的问题和安排
1、错误分析
(1) 得到的内核错误信息:
Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
从错误信息字面意思来分析,就是内核试图挂载根文件系统时失败,失败的原因是unknown-block(不能识别的块设备)。
(2) Backtrace 分析,可以得知错误信息的来源,再结合之前的内核启动流程分析,就更加确定了出错的地方。
(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 驱动了。
九鼎 kernel :
三星官方 kernel:
2、问题阐述
(1) SD/iNand 本身都是由一个一个的扇区组成的,回忆裸机中讲到的 210 的启动时,BL1 在 SD 卡的 1 扇区开始往后存放,SD 卡的 0 扇区是不用的。SD 卡的 0 扇区是用来放置 MBR 的。
(2) MBR 就是用来描述块设备的分区信息的, 事先定义了一个通用的数据结构来描述块设备的分区,我们只要按照这个标准,将分区信息写入 MBR 中,即可对该设备完成分区。MBR 默认就是在块设备的第 0 个扇区上存放的。
(3) 我们内核中读到 iNand 分 4 个分区,我们是在哪里分区的?uboot 中有一个命令 fdisk -c 0 时,就对 iNand 进行了分区。uboot 的 fdisk 命令内部已经写死了 iNand 的分区表,到内核中时,内核直接读取 MBR 就知道了分区。所以在 uboot 和内核之间 iNand 设备的分区信息是靠 iNand 自己传递的,所以 uboot 不用给内核传参时 传递分区表信息。
(4) 如果开发板用的是 nandFlash 的话,分区表一般是在内核中自己用代码构建的。所以 nand 版本的内核移植的时候,一般都需要去移植更改 nand 分区表。
3、解决安排
(1) 暂时解决不了这个问题。
4、后续课程安排
(1) 一节课搞定网卡驱动的移植,一节课讲述一些内核移植的小方法和技巧,然后课程结束。
(2) 整体移植的课程结束,进入根文件系统部分。
六、网卡驱动的移植和添加实验
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) 当前内核中网卡驱动尚未移植,因此内核启动时有错误的打印信息:
(3) 移植的目标就是,让我们的版本的内核可以打印出正确情况下的启动信息,那我们就相信内核启动后网卡是可以工作的。
2、make menuconfig 中添加 DM9000 支持
(1) menuconfig 中选择 Y。
(2) 其实这一步本来就是 Y,所以在我们这里是不用管的。但是你自己遇到的一个内核可能默认不是 Y,因此要设置。
3、mach-smdkc110.c 中逻辑分析
(1) mach-smdkc110.c 中的 smdkc110_machine_init 是整个开发板的所有硬件的初始化函数,在这里加载了的硬件, 将来启动时就会被初始化,在这里没有的,将来启动时就不管。
(2) smdkc110_devices 和 smdkc110_dm9000_set() 这两个地方是和 DM9000 有关的,要分别去做移植。
(3) 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) 同步代码、编译生成 zImage。
(2) 下载启动后看启动信息。
七、内核启动第一阶段的调试方法
1、问题点描述
(1) 内核启动在 head.S 中首先进行了三个校验(CPU id 的校验、机器码的校验、tag 的校验),然后创建页表,然后做了一些不太会出错的事情,然后 b start_kernel。基本上能运行到 start_kernel ,内核移植就不太会出问题了。
(2) 有时候,移植的内核启动后的现象是:根本没有启动信息出来。
这时候,有可能是内核启动运行了,但是运行出错了没启动起来,所以没有打印信息;
也有可能是内核根本没得以运行。
都有可能但是没法确定。我们希望能有一种调试手段来确定问题所在。
2、调试方法和原理
(1) 调试方法就是,在内核启动的第一阶段添加汇编操作 led 点亮/熄灭的方法,来标明代码运行的轨迹。
(2) 我们找之前裸机中汇编操作 led 点亮/熄灭的代码过来,复制粘贴到 head.S 中合适位置。然后内核启动后,根据 led 的表现来标明代码有无运行。
3、动手测试
(1) 整理好 led 操作的代码段,在 head.S 中合适的地方添加 led 这个函数,然后在 head.S 的内核起始运行阶段添加调用 led 函数,然后重新编译内核,运行内核看这段代码有无被运行。
(2) 如果被运行了,证明在这个调用 led 的步骤之前的部分,都是没问题的,那么如果有错,肯定错误在后边;
如果没有被运行,则证明错误在之前,那么就要去之前的部分debug。
4、实验验证
源自朱有鹏老师.