1. 项目概述
作为一名嵌入式开发工程师,最近我完成了一个基于三星SMDKV210开发板的Linux内核移植项目。这个过程中遇到了不少挑战,也积累了一些经验,今天就来分享一下完整的移植过程和解决方案。
这个项目的主要目标是将Linux 2.6.35内核从三星官方源码移植到SMDKV210开发板上。选择这个版本的内核是因为它相对稳定,且三星官方提供了针对该开发板的支持。整个移植过程包括内核获取、工程配置、编译调试、硬件适配等多个环节。
2. 环境准备与内核获取
2.1 开发环境搭建
在开始之前,我们需要准备好开发环境。我使用的是Ubuntu 20.04 LTS作为开发主机,主要工具包括:
- 交叉编译工具链:arm-linux-gcc 4.5.1
- 源码阅读工具:Source Insight 4.0
- 调试工具:串口终端工具(如minicom或putty)
- TFTP服务器:用于内核映像传输
提示:建议使用较新版本的Ubuntu系统,但需要注意内核版本与编译工具链的兼容性问题。
2.2 获取三星官方内核源码
三星提供了针对SMDKV210开发板的内核源码包,我们可以从官方渠道获取:
code复制android_kernel_2.6.35_smdkv210
这个源码包包含了三星对标准Linux内核的修改和适配,特别是针对S5PV210处理器的支持。下载后解压源码包,我们可以看到标准的Linux内核目录结构。
3. 工程配置与精简
3.1 源码目录精简
为了减少编译时间和避免不必要的干扰,我们可以对内核源码进行精简。主要精简原则是保留与目标平台相关的代码,删除其他架构和平台的代码。
3.1.1 架构相关代码精简
首先处理arch目录下的内容:
bash复制# 保留arm架构,删除其他架构
cd arch
rm -rf avr32 blackfin cris frv h8300 ia64 m32r m68k mips microblaze
rm -rf mn10300 parisc powerpc s390 score sh sparc um x86 xtensa
3.1.2 ARM平台代码精简
接下来精简arm目录下的内容:
bash复制cd arm
# 删除不相关的mach-xxx目录
find mach-* | grep -v mach-s5pv210 | xargs rm -rf
# 删除不相关的plat-xxx目录
find plat-* | grep -v plat-s5p | xargs rm -rf
注意:精简代码时要特别小心,确保不会删除目标平台依赖的文件。建议在删除前先备份整个源码树。
3.2 建立Source Insight工程
为了方便代码阅读和修改,我使用Source Insight建立了工程:
- 新建工程,选择内核源码根目录
- 添加所有C和头文件到工程
- 设置交叉编译工具链路径
- 配置代码索引
Source Insight的工程配置对于大型项目如Linux内核非常重要,良好的配置可以显著提高代码阅读和修改效率。
4. 内核编译与配置
4.1 基础编译配置
在编译前,我们需要设置正确的环境变量:
bash复制export ARCH=arm
export CROSS_COMPILE=arm-linux-
然后检查Makefile中的相关配置:
makefile复制# 确保ARCH和CROSS_COMPILE设置正确
ARCH ?= arm
CROSS_COMPILE ?= arm-linux-
4.2 使用默认配置
SMDKV210开发板有官方提供的默认配置:
bash复制# 查看可用的默认配置
ls arch/arm/configs/
# 使用SMDKV210的默认配置
make smdkv210_android_defconfig
4.3 图形化配置
虽然使用了默认配置,但我们仍然需要进行一些定制化设置:
bash复制make menuconfig
在图形化界面中,我们可以:
- 查看当前配置
- 修改特定选项
- 搜索特定配置项
提示:在menuconfig界面中,按"/"键可以搜索配置项,这对于大型项目特别有用。
4.4 编译内核
配置完成后,就可以开始编译了:
bash复制make -j16
使用-j参数可以启用多线程编译,显著加快编译速度。数字16表示使用16个线程,可以根据主机CPU核心数调整。
5. 常见问题与解决方案
5.1 Perl语法兼容性问题
在编译过程中,我遇到了第一个错误:
code复制Can't use 'defined(@array)'
这是因为新版本Perl不再支持这种语法,而老内核还在使用。解决方法:
- 找到出问题的文件:kernel/timeconst.pl
- 修改第373行附近的代码:
perl复制# 原代码
if (!defined(@val)) {
# 修改为
if (!@val) {
这个修改使语法兼容新版本Perl,同时保持原有逻辑不变。
5.2 TFTP服务配置
为了方便调试,我配置了TFTP服务来传输内核映像:
bash复制# 安装TFTP服务
sudo apt install -y tftpd-hpa tftp-hpa
# 配置TFTP服务
sudo vim /etc/default/tftpd-hpa
# 修改配置如下:
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/tftpboot"
TFTP_ADDRESS=":69"
TFTP_OPTIONS="--secure"
# 设置权限并重启服务
sudo chown -R tftp:tftp /tftpboot
sudo systemctl restart tftpd-hpa
配置完成后,将编译好的内核映像复制到TFTP目录:
bash复制cp arch/arm/boot/zImage /tftpboot/ -f
5.3 开发板网络配置
在开发板U-Boot中,需要正确设置网络参数:
bash复制# 设置开发板IP
setenv ipaddr 192.168.1.10
# 设置服务器IP(开发主机IP)
setenv serverip 192.168.1.141
# 保存配置
saveenv
确保开发板和开发主机在同一网段,并且可以互相ping通。
6. 内核启动参数与地址配置
6.1 内核地址空间配置
内核启动失败的首要原因是地址空间配置不正确。我们需要检查并修改以下关键参数:
- 内核虚拟地址(KERNEL_RAM_VADDR):0xC0008000
- 内核物理地址(KERNEL_RAM_PADDR):0x30008000
这些定义通常在arch/arm/mach-xxx/include/mach/memory.h文件中。
6.2 自解压地址配置
内核自解压代码需要知道将内核解压到哪个地址,这个配置在:
arch/arm/mach-s5pv210/mach-smdkv210.c
c复制/* 修改自解压地址 */
zreladdr-y := 0x30008000
params_phys-y := 0x30000100
这个修改确保自解压代码将内核解压到正确的物理地址。
7. 硬件驱动问题排查
7.1 电源管理驱动问题
内核启动后遇到了第一个硬件相关错误:
code复制[ 0.758376] PC is at dev_driver_string+0xc/0x44
[ 0.762878] LR is at max8698_pmic_probe+0x150/0x32c
这是电源管理芯片(MAX8698)驱动的问题。解决方法:
- 进入menuconfig
- 搜索"8698"
- 找到相关配置项并禁用
bash复制make menuconfig
# 搜索8698,找到并禁用相关驱动
7.2 根文件系统挂载问题
解决电源管理问题后,又遇到了根文件系统挂载失败:
code复制[ 1.678399] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
解决方法是在U-Boot启动参数中添加rootwait:
bash复制setenv bootargs console=ttySAC2,115200 root=/dev/mmcblk0p2 rw rootfstype=ext3 init=/linuxrc rootwait
saveenv
rootwait参数让内核等待存储设备初始化完成后再尝试挂载根文件系统。
7.3 SD卡版本兼容性问题
老版本内核(2.6.35)对新SD卡的支持有问题,需要修改驱动代码:
c复制// 修改文件:drivers/mmc/core/mmc.c
// 将版本检查条件放宽
if (card->ext_csd.rev > 7) 改为 if (card->ext_csd.rev > 9)
这个修改使内核能够识别新版本的SD卡。
8. 网卡驱动调试
8.1 网卡初始化问题
内核启动后,网卡驱动加载失败:
code复制[ 1.134207] dm9000 Ethernet Driver, V1.31
[ 1.137006] ERROR : resetting
[ 1.139591] dm9000 dm9000.0: read wrong id 0x2b2a2928
这个问题需要从以下几个方面解决:
- 检查网卡硬件连接
- 验证电源和时钟配置
- 检查总线接口设置
8.2 网卡驱动修改
具体修改点在以下文件:
arch/arm/mach-s5pv210/mach-smdkv210.c
c复制static void __init smdkc110_dm9000_set(void)
{
unsigned int tmp;
/* 配置GPIO为输入模式 */
s3c_gpio_cfgpin(S5PV210_GPH1(2), S3C_GPIO_INPUT);
s3c_gpio_setpull(S5PV210_GPH1(2), S3C_GPIO_PULL_NONE);
/* 请求GPIO并配置 */
ret = gpio_request(S5PV210_GPH1(2), "GPH1");
if(ret)
printk("mach-x210: request gpio GPH1(2) fail");
else {
s3c_gpio_cfgpin(S5PV210_GPH1(2), 0xf);
s3c_gpio_setpull(S5PV210_GPH1(2), S3C_GPIO_PULL_NONE);
}
/* 配置总线时序 */
tmp = ((0<<28)|(0<<24)|(5<<16)|(0<<12)|(0<<8)|(0<<4)|(0<<0));
__raw_writel(tmp, S5P_SROM_BC1);
tmp = __raw_readl(S5P_SROM_BW);
tmp &= ~(0xf << 4);
tmp |= (1<<7) | (1<<6) | (1<<5) | (1<<4);
__raw_writel(tmp, S5P_SROM_BW);
tmp = __raw_readl(S5PV210_MP01CON);
tmp &= ~(0xf << 4);
tmp |= (2 << 4);
__raw_writel(tmp, S5PV210_MP01CON);
}
8.3 网卡资源定义
还需要修改网卡的资源定义:
c复制static struct resource smdkc110_dm9000_resources[] = {
[0] = {
.start = S5P_PA_DM9000,
.end = S5P_PA_DM9000 + 3,
.flags = IORESOURCE_MEM
},
[1] = {
.start = S5P_PA_DM9000 + 4,
.end = S5P_PA_DM9000 + 7,
.flags = IORESOURCE_MEM
},
[2] = {
.start = IRQ_EINT(10),
.end = IRQ_EINT(10),
.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL,
}
};
这些修改确保网卡使用正确的地址空间和中断号。
9. 移植结果验证
完成上述所有修改后,重新编译并下载内核,可以看到内核正常启动:
code复制[ 1.134273] dm9000 Ethernet Driver, V1.31
[ 1.137824] eth0: dm9000b at e088a300,e088e304 IRQ 42 MAC: 00:09:c0:ff:ec:48 (platform data)
网卡初始化成功,系统可以正常挂载根文件系统并完成启动过程。
10. 经验总结与注意事项
通过这次移植过程,我总结了以下几点经验:
- 内核配置:使用厂商提供的默认配置作为起点,可以节省大量时间
- 地址空间:确保物理地址和虚拟地址配置正确,特别是自解压地址
- 驱动调试:遇到硬件问题时,从简单到复杂逐步排查
- 版本兼容性:老内核对新硬件的支持可能有问题,需要适当修改驱动
- 调试工具:善用printk和串口调试工具,可以快速定位问题
重要提示:在进行内核修改时,建议每次只修改一处并测试效果,这样更容易定位问题来源。
最后,这个移植项目还有一些可以改进的地方:
- 可以尝试更新到更新的内核版本
- 可以优化启动参数,加快启动速度
- 可以进一步完善其他外设驱动
希望这篇分享对正在进行类似项目的开发者有所帮助。内核移植是一个需要耐心和细心的过程,但成功后的成就感也是巨大的。