移植linux-5.4.26到jz2440

1 内核移植概述

linux kernel支持多种硬件,所谓移植概括的说,就是修改kernel中硬件相关的源码以适应自己的硬件。linux中硬件相关的代码主要集中在arch目录(体系结构相关、单板相关)以及drivers目录(设备驱动)。linux的移植对于产业链上下游的企业来说,要做的事情也不同,我个人的理解是:

  • IP核厂商:以ARM为例,ARM负责提供指令集以及支持该指令集的硬件实现——CPU核(公版)。ARM在移植kernel的时候,需要实现体系结构相关的代码,比如kernel启动的汇编阶段;
  • SoC厂商:以ARM阵营为例,SoC厂商基于ARM的CPU核(当然也有自研的),添加一些片内外设形成自己的SoC。并且,一般还会基于自家的SoC做公版的开发板,从而方便推广自家产品。因此SoC厂商移植kernel的时候需要做的是,提供平台设备的驱动(即片内外设的驱动)、提供板级支持代码(arch/arm/mach-xxx)。
  • 外设芯片厂商:这里的外设芯片指的是诸如ADC、各类传感器芯片等,比如TI的at24c0x芯片,一个完整的产品需要SoC+各路板载外设协同工作。外设芯片厂商为了推广自己的芯片(降低下游开发成本),需要为芯片提供各种环境的驱动程序,对于linux,他们要做的就是为linux提供其芯片产品的驱动。
  • 电子产品厂商:下游应用厂商主要做方案整合(当然也有通吃全产业链的企业),即采购SoC+各种板载外设芯片,设计自己的电路板。他们要做的是,参考SoC厂商的公版开发板的板级支持代码,实现自己的板级支持代码。对于片内外设,根据SoC厂商的驱动来写相应的设备树;对于板载外设,根据外设芯片厂商的驱动来写设备树。所以底层这块,下游厂商其实发挥空间不大,底层支持主要还是看上游厂商,下游厂商的重点在于行业应用。因此,网上有下游厂商的底软开发者调侃自己是“设备树工程师”。不过,即便是在下游厂商工作,熟悉kernel的原理也是比较重要的,毕竟你不能保证任何时候只用简单修改就能完成工作交付。

作为嵌软及linux爱好者的我,目前还没有参加工作,对我来说移植主要是出于兴趣、以及加深对linux的认识。我现在手中的开发板是百问网出的jz2440,其中厂商已经提供了移植好的内核,我想做的是移植新版的内核,然后在新版内核基础上做一些实验,看看kernel当下的模样。其实,这么做的工作量已经不大了,大部分的事情是上游厂商做的。好了,废话就到此为止,下面开始移植。

2 移植过程

2.1 选择编译工具

在移植u-boot-2019.10时,我使用的交叉编译工具版本是4.3.2,虽然有些磕磕绊绊,但也怼过来了。所以我仍然尝试使用这个编译工具链来编译内核,但出师未捷,一开始就报错(连主机程序都还没编译过去呢):

cc1: error: unrecognized command line option "-Wno-unused-but-set-variable"
make[2]: *** [scripts/mod/empty.o] Error 1
make[1]: *** [prepare0] Error 2
make: *** [sub-make] Error 2

看来,不同于u-boot,kernel怕是真的用到了新版工具链的特性,再头铁也不能守旧了,于是我决定换新的工具链。选哪个版本呢?本着咱要么不换,要换就换最新的原则,在linaro的工具链的下载界面选择了7.5版本的工具链,版本信息如下:

Using built-in specs.
COLLECT_GCC=arm-linux-gnueabi-gcc
COLLECT_LTO_WRAPPER=/usr/local/arm/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabi/bin/../libexec/gcc/arm-linux-gnueabi/7.5.0/lto-wrapper
Target: arm-linux-gnueabi
Configured with: '/home/tcwg-buildslave/workspace/tcwg-make-release_0/snapshots/gcc.git~linaro-7.5-2019.12/configure' SHELL=/bin/bash --with-mpc=/home/tcwg-buildslave/workspace/tcwg-make-release_0/_build/builds/destdir/x86_64-unknown-linux-gnu --with-mpfr=/home/tcwg-buildslave/workspace/tcwg-make-release_0/_build/builds/destdir/x86_64-unknown-linux-gnu --with-gmp=/home/tcwg-buildslave/workspace/tcwg-make-release_0/_build/builds/destdir/x86_64-unknown-linux-gnu --with-gnu-as --with-gnu-ld --disable-libmudflap --enable-lto --enable-shared --without-included-gettext --enable-nls --with-system-zlib --disable-sjlj-exceptions --enable-gnu-unique-object --enable-linker-build-id --disable-libstdcxx-pch --enable-c99 --enable-clocale=gnu --enable-libstdcxx-debug --enable-long-long --with-cloog=no --with-ppl=no --with-isl=no --disable-multilib --with-float=soft --with-mode=thumb --with-tune=cortex-a9 --with-arch=armv7-a --enable-threads=posix --enable-multiarch --enable-libstdcxx-time=yes --enable-gnu-indirect-function --with-build-sysroot=/home/tcwg-buildslave/workspace/tcwg-make-release_0/_build/sysroots/arm-linux-gnueabi --with-sysroot=/home/tcwg-buildslave/workspace/tcwg-make-release_0/_build/builds/destdir/x86_64-unknown-linux-gnu/arm-linux-gnueabi/libc --enable-checking=release --disable-bootstrap --enable-languages=c,c++,fortran,lto --build=x86_64-unknown-linux-gnu --host=x86_64-unknown-linux-gnu --target=arm-linux-gnueabi --prefix=/home/tcwg-buildslave/workspace/tcwg-make-release_0/_build/builds/destdir/x86_64-unknown-linux-gnu
Thread model: posix
gcc version 7.5.0 (Linaro GCC 7.5-2019.12)

上述这段版本信息中,有值得我们关注的地方:--with-tune=cortex-a9 --with-arch=armv7-a[3],而我们需要的是生成armv4t指令集中的汇编指令。不过,也不用担心这个问题,kernel已经帮我们考虑到了,我们把配置弄对就行,具体的:


arch/arm/Makefile

arch-$(CONFIG_CPU_32v4T)        =-D__LINUX_ARM_ARCH__=4 -march=armv4t

arch/arm/mm/Kconfig

# ARM920T
config CPU_ARM920T
        bool
        select CPU_32v4T
        select CPU_ABRT_EV4T
        select CPU_CACHE_V4WT
        select CPU_CACHE_VIVT
        select CPU_COPY_V4WB if MMU
        select CPU_CP15_MMU
        select CPU_PABRT_LEGACY
        select CPU_THUMB_CAPABLE
        select CPU_TLB_V4WBI if MMU
        help
          The ARM920T is licensed to be produced by numerous vendors,
          and is used in the Cirrus EP93xx and the Samsung S3C2410.

          Say Y if you want support for the ARM920T processor.
          Otherwise, say N.

arch/arm/mach-s3c24xx/Kconfig

config CPU_S3C2440
        bool "SAMSUNG S3C2440"
        select CPU_ARM920T
        select S3C2410_COMMON_CLK
        select S3C2410_PM if PM_SLEEP
        help
          Support for S3C2440 Samsung Mobile CPU based systems.

arch/arm/configs/s3c2410_defconfig

CONFIG_CPU_S3C2440=y

2.2 尝试使用s3c2410_defconfig

工具链确定后,接下来要做的就是配置、编译了。linux-5.4.26对2440的支持还是比较到位的,配置文件s3c2410_defconfig支持很多单板,包括2440、2410,所以不妨先用这个配置试试看,然后烧写启动之后,哪里有问题再改。配置编译无需赘述,就是那些个命令。

生成uImage镜像后,下载到kernel分区。在使用u-boot启动之前,还有两件事情要做:

  1. 设置启动参数bootargs=console=ttySAC0,115200 root=/dev/mtdblock3 rootfstype=jffs2
  2. 设置机器ID(暂时先不使用DTB启动):可使用set machid xxx(16进制)命令来设置机器ID;在不设置machid环境变量时,u-boot会使用默认的机器ID,默认ID在board_init函数中设置:gd->bd->bi_arch_number = MACH_TYPE_xxx;

如何确定机器ID呢?对jz2440的支持并未合并入kernel源码,因此我们只能退而求其次,找最接近的单板SMDK2440。在arch/arm/mach-s3c24xx/mach-smdk2440.c中,struct machine_desc定义如下:

MACHINE_START(S3C2440, "SMDK2440")
	/* Maintainer: Ben Dooks <ben-linux@fluff.org> */
	.atag_offset	= 0x100,

	.init_irq	= s3c2440_init_irq,
	.map_io		= smdk2440_map_io,
	.init_machine	= smdk2440_machine_init,
	.init_time	= smdk2440_init_time,
MACHINE_END

显然,SMDK2440使用的机器ID是MACH_TYPE_S3C2440。具体的数字可以在arch/arm/tools/mach-types文件中找到(kernel在编译过程中会根据此文件生成相应的头文件供源码使用),具体数字是16a

设置好机器ID后,启动内核,串口上输出了一大堆乱码。对于乱码,大家首先都会想到:可能是波特率的问题。不过,之前在启动参数中已经指定了波特率,kernel应该是按照115200来设置的,至少可以说kernel认为自己使用的就是115200。那么问题到底在哪里?波特率最终来源是晶振经过时钟树上的一系列倍频、分频。kernel会按照115200的要求来设置倍频、分频系数。那么,唯一可能出问题的就是晶振了。

2.3 修改晶振频率以及kernel的分区

接着上文,我们使用的是12MHz的晶振,因此在arch/arm/mach-s3c24xx/mach-smdk2440.c中,将时钟初始化时传入的晶振频率修改为12000000:

static void __init smdk2440_init_time(void)
{
	s3c2440_init_clocks(12000000);
	samsung_timer_init();
}

修改之后再次编译烧写,启动后终于有启动信息输出了。通过启动信息,不难发现,此时kernel的分区与我们在u-boot-2019.10中的分区设置不符,因此我们要修改kernel的分区。kernel的分区在哪里呢?不妨根据启动时输出的分区信息来查找,在arch/arm/mach-s3c24xx目录下(分区是板级相关的)搜索S3C2410 flash partition 1,不难找到在arch/arm/mach-s3c24xx/common-smdk.c中的分区代码,修改如下:

static struct mtd_partition smdk_default_nand_part[] = {
	[0] = {
		.name	= "u-boot",
		.size	= SZ_512K,
		.offset	= 0,
	},
	[1] = {
		.name	= "env",
		.offset = MTDPART_OFS_APPEND,
		.size	= SZ_128K,
	},
	[2] = {
		.name	= "kernel",
		.offset = MTDPART_OFS_APPEND,
		.size	= SZ_4M,
	},
	[3] = {
		.name	= "rootfs",
		.offset	= MTDPART_OFS_APPEND,
		.size	= MTDPART_SIZ_FULL,
	}
};

2.4 调整kernel的配置

2.4.1 CONFIG_AEABI

修改完分区后,再次编译烧写uImage,kernel启动到最后提示没有找到根文件系统,因为我们还没烧写rootfs呢。烧写以前做好的jffs2格式的文件系统,然后再次启动内核,此时kernel可以挂接根文件系统,不过在运行init进程时报错:

Run /sbin/init as init process
Kernel panic - not syncing: Attempted to kill init! exitcode=0x00000004

这是因为,当前我们还没有配置EBAI,kernel还不支持ARM EABI,解决也很简单,选中配置项CONFIG_AEABI即可。

2.4.2 CONFIG_UEVENT_HELPER

配置了EABI后,kernel成功运行init进程,并启动了shell。不过,在执行rcS文件中的命令时出现了问题,如下:

/etc/init.d/rcS: line 7: can't create /proc/sys/kernel/hotplug: nonexistent directory

这和linux的热插拔配置有关,之前在完善基于qemu的linux开发环境的第4节就说过,不再赘述。

2.5 支持yaffs2文件系统

yaffs2是一种非常NandFlash的文件系统,不过kernel并未支持它(可能是因为已经有了ubifs),本节我们将移植yaffs2到linux-5.4.26:

  1. 下载源码:从官网下载源码;
  2. 打补丁:根据源码目录下的文档README-linux的说明,使用脚本patch-ker.sh为kernel打上yaffs的补丁;
  3. 配置内核:选中位于File systems->Miscellaneous filesystems下面的yaffs2配置项;
  4. 编译并解决错误:yaffs2在官网上仅维护了一份,而kernel的版本确在迭代中不断增加,在这个过程中,kernel中一些旧的接口被弃用,同时新的接口被引入,这给yaffs2的移植带来了一定的工作量,需要根据编译时的错误信息来解决(一般不太可能直接编译通过);
  5. 烧写支持yaffs2的kernel镜像:编译通过后,烧写新的镜像,这没什么好说的。

这里我会记录下编译yaffs2的过程中都遇到了哪些错误,以及如何去解决:

2.5.1 current_kernel_time64被移除

错误信息如下:

fs/yaffs2/yaffs_vfs.c: In function ‘yaffs_mknod’:
fs/yaffs2/yaffs_vfs.c:277:37: error: implicit declaration of function ‘current_kernel_time64’; did you mean ‘core_kernel_text’? [-Werror=implicit-function-declaration]
   (dir)->i_ctime = (dir)->i_mtime = current_kernel_time64(); \

从上述信息来看,很可能是current_kernel_time64这个接口被移除了,那我们就需要找到其替代者。最快速的解决方法就是去网上搜索,大概率这类错误已经被前人解决了[4][5]。这里给结论:使用ktime_get_coarse_real_ts64替代。其实最权威的资料是内核文档,遇到这类问题直接搜文档,比如这里的时间接口,见linux-5.4.26\Documentation\core-api\timekeeping.rst
在这里插入图片描述

2.5.2 MS_RDONLY未声明

错误信息如下:

fs/yaffs2/yaffs_vfs.c:2741:25: error: ‘MS_RDONLY’ undeclared (first use in this function); did you mean ‘IS_RDONLY’?

解决方案很简单,添加头文件包含#include <uapi/linux/mount.h>

2.5.3 current_kernel_time被移除

报错信息如下:

fs/yaffs2/yaffs_attribs.c: In function ‘yaffs_load_current_time’:
fs/yaffs2/yportenv.h:67:24: error: implicit declaration of function ‘current_kernel_time’; did you mean ‘current_time’? [-Werror=implicit-function-declaration]
 #define Y_CURRENT_TIME current_kernel_time().tv_sec

问题实质上等同于2.5.1节,用新的接口重新定义这个宏即可:

#define Y_CURRENT_TIME \
	({ struct timespec64 ts64; ktime_get_coarse_real_ts64(&ts64); ts64.tv_sec; })

进行上述修改后,编译通过,看来yaffs维护的还是挺及时的。接下来烧写镜像测试,测试结果:

......
yaffs: dev is 32505859 name is "mtdblock3" rw
yaffs: passed flags ""
random: fast init done
VFS: Mounted root (yaffs2 filesystem) on device 31:3.
Freeing unused kernel memory: 236K
This architecture does not have kernel memory protection.
Run /sbin/init as init process

Please press Enter to activate this console.
/ #

至此,完成了对yaffs文件系统的支持。

2.6 内核裁剪

嵌入式设备的资源有限,因此需要对内核进行裁剪,实质上就是利用kernel的配置体系,将项目没有用到的配置项给去掉,从而减少kernel镜像的体积。kernel提供了非常方便的图形化配置(menuconfig),因此裁剪的操作本身很简单,裁剪真正关键的是对各内核配置项的了解:知道配置项是干什么用的,对于自己的项目,哪些有用、哪些没用。这些知识需要积累,也没什么好说的。

3 根文件系统

上文中我们已经烧写了根文件系统,使用busybox制作根文件系统并不困难,因此这里就不再重新做一遍了,而是直接使用以前做好的。值得一提的是,之前我做该根文件系统的时候使用的工具链是4.3.2的,不过,实测下来,工作正常,能挂载,也能正常使用shell,我分析可能是由于汇编指令都是armv4t的,且都使用相同的EABI(AAPCS),所以能正常工作。不过,最好还是保持编译内核和根文件系统的工具链相同。如果使用新工具链重做根文件系统,有一个麻烦的地方,那就是根文件系统中的动态库还得用新工具链重做,而之前做好的根文件系统的动态库可以直接从4.3.2版本的工具链中拷贝。如此一来,我们之后编写在开发板上运行的应用程序时,就需要使用4.3.2的工具链。

参考文献

[1] linux-5.4.26源码
[2] 韦东山老师的教程
[3] GCC中–with-abi和–with-arch的实现分析
[4] What is the equivalent of current_kernel_time in Linux kernel v5?
[5] Linux 5.0: implicit declaration of function current_kernel_time64 #8258

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值