平台简介
开发板:tiny4412ADK + S700 + 4GB Flash
要移植的内核版本:Linux-4.4.0 (支持device tree)
u-boot版本:友善之臂自带的 U-Boot 2010.12 (为支持uImage启动,做了少许改动)
busybox版本:busybox 1.25
交叉编译工具链: arm-none-linux-gnueabi-gcc
(gcc version 4.8.3 20140320 (prerelease) (Sourcery CodeBench Lite 2014.05-29))
注意
继续上文。
到目前为止,板子已经可以起来了,接下来就可以针对板子的情况移植驱动程序了。这个放在以后再做,下面是我折腾过程中得到的一些知识,分享一下。
一、设备树反编译
在内核目录下当我们执行make dtbs后,就会在arch/arm/boot/dts下生成一些.dtb文件,那这个文件里是什么东西呢?我们可以用dtc命令反编译这些dtb文件,这里的可执行程序dtc在Linux内核源码中已经提供了,具体路径是:scripts/dtc/,可以使用下面的命令从Linux源码中编译出这个工具:
make CROSS_COMPILE=arm-none-linux-gnueabi- ARCH=arm scripts
这样就会在scripts/dtc下生成可执行程序dtc。
当然,如果没有Linux源码,也可以使用apt-get命令安装这个工具,命令如下:
sudo apt-get install device-tree-compiler
下面以exynos4412-tiny4412.dtb为例:
命令:
dtc -I dtb -O dts -o tiny4412.dts arch/arm/boot/dts/exynos4412-tiny4412.dtb
然后就会生成反编译后的文件 tiny4412.dts,部分内容如下:
/dts-v1/;
/ {
#address-cells = ;
#size-cells = ;
interrupt-parent = ;
compatible = "friendlyarm,tiny4412", "samsung,exynos4412", "samsung,exynos4";
model = "FriendlyARM TINY4412 board based on Exynos4412";
chosen {
stdout-path = "/serial@13800000";
bootargs = "root=/dev/ram0 rw rootfstype=ext4 console=ttySAC0,115200 init=/linuxrc earlyprintk";
};
aliases {
spi0 = "/spi@13920000";
spi1 = "/spi@13930000";
spi2 = "/spi@13940000";
i2c0 = "/i2c@13860000";
i2c1 = "/i2c@13870000";
i2c2 = "/i2c@13880000";
i2c3 = "/i2c@13890000";
i2c4 = "/i2c@138A0000";
i2c5 = "/i2c@138B0000";
i2c6 = "/i2c@138C0000";
i2c7 = "/i2c@138D0000";
i2c8 = "/i2c@138E0000";
csis0 = "/camera/csis@11880000";
csis1 = "/camera/csis@11890000";
fimc0 = "/camera/fimc@11800000";
fimc1 = "/camera/fimc@11810000";
fimc2 = "/camera/fimc@11820000";
fimc3 = "/camera/fimc@11830000";
serial0 = "/serial@13800000";
serial1 = "/serial@13810000";
serial2 = "/serial@13820000";
serial3 = "/serial@13830000";
pinctrl0 = "/pinctrl@11400000";
pinctrl1 = "/pinctrl@11000000";
pinctrl2 = "/pinctrl@03860000";
pinctrl3 = "/pinctrl@106E0000";
fimc-lite0 = "/camera/fimc-lite@12390000";
fimc-lite1 = "/camera/fimc-lite@123A0000";
mshc0 = "/mmc@12550000";
};
memory {
device_type = "memory";
reg = <0x40000000 0x40000000>;
};
clock-controller@03810000 {
compatible = "samsung,exynos4210-audss-clock";
reg = <0x3810000 0xc>;
#clock-cells = ;
linux,phandle = ;
phandle = ;
};
i2s@03830000 {
这个方法对于学习设备树很有帮助。
二、在u-boot打印信息
在u-boot中很多文件中是通过debug(… …)来打印信息,默认情况下这些log是打印不出来的。这个函数的定义是在include/common.h中:
#ifdef DEBUG
#define debug(fmt,args...) printf (fmt ,##args)
#define debugX(level,fmt,args...) if (DEBUG>=level) printf(fmt,##args);
#else
#define debug(fmt,args...)
#define debugX(level,fmt,args...)
#endif /* DEBUG */
所以可以在调用debug函数的C文件的最上面添加 #define DEBUG 即可。这个方法在Linux内核以及Android当中也很常用。
三、打开Linux内核启动早期的log
有时会遇到当在u-boot中执行完bootm后,打印出start kernel后串口就没有再输出任何信息了。此时就需要打开内核早期的log:
make menuconfig
Kernel hacking --->
[*] Kernel low-level debugging functions (read help!)
Kernel low-level debugging port (Use Samsung S3C UART 0 for low-level debug)
[*] Early printk
对于earlyprintk,还需要在bootargs中添加参数earlyprintk才能生效,有了上面这几个配置,会有下面几个宏生效:
CONFIG_DEBUG_LL=y
CONFIG_DEBUG_S3C_UART0=y
CONFIG_DEBUG_LL_INCLUDE="debug/exynos.S"
CONFIG_DEBUG_UNCOMPRESS=y
CONFIG_UNCOMPRESS_INCLUDE="debug/uncompress.h"
CONFIG_EARLY_PRINTK=y
关于earlyprintk的解析在文件arch/arm/kernel/early_printk.c中:
1: extern void printch(int);
2:
3: static void early_write(const char *s, unsigned n)
4: {
5: while (n-- >; 0) {
6: if (*s == 'n')
7: printch('r');
8: printch(*s);
9: s++;
10: }
11: }
12:
13: static void early_console_write(struct console *con, const char *s, unsigned n)
14: {
15: early_write(s, n);
16: }
17:
18: static struct console early_console_dev = {
19: .name = "earlycon",
20: .write = early_console_write,
21: .flags = CON_PRINTBUFFER | CON_BOOT,
22: .index = -1,
23: };
24:
25: static int __init setup_early_printk(char *buf)
26: {
27: early_console = &;early_console_dev;
28: register_console(&;early_console_dev);
29: return 0;
30: }
31:
32: early_param("earlyprintk", setup_early_printk);
其中printch都是通过汇编语言实现的。
在arch/arm/Kconfig.debug中可以看到:
config DEBUG_LL
bool "Kernel low-level debugging functions (read help!)"
depends on DEBUG_KERNEL
help
Say Y here to include definitions of printascii, printch, printhex
in the kernel. This is helpful if you are debugging code that
executes before the console is initialized.
config DEBUG_S3C_UART0
depends on PLAT_SAMSUNG
select DEBUG_EXYNOS_UART if ARCH_EXYNOS
select DEBUG_S3C24XX_UART if ARCH_S3C24XX
select DEBUG_S5PV210_UART if ARCH_S5PV210
bool "Use Samsung S3C UART 0 for low-level debug"
help
Say Y here if you want the debug print routines to direct
their output to UART 0. The port must have been initialised
by the boot-loader before use.
config DEBUG_LL_INCLUDE
string
……
default "debug/exynos.S" if DEBUG_EXYNOS_UART
config EARLY_PRINTK
bool "Early printk"
depends on DEBUG_LL
help
Say Y here if you want to have an early console using the
kernel low-level debugging functions. Add earlyprintk to your
kernel parameters to enable this console.
从上面的信息我们可以知道:
在串口终端尚未注册时,内核定义了printascii、printch以及printhex用于调试;
early console使用的也是上面定义的函数,需要在传递给内核的参数中添加earlyprintk参数
Linux内核早期的print函数的输出串口要跟u-boot下使用的一致,即内核不再负责初始化了,让u-boot来做,所以二者一定要一致,否则那些print函数以及earlyprintk都没法输出信息;
可以参考arch/arm/kernel/debug.S,printascii、printch以及printhex都是在这里定义的;
在kernel进入C函数(start_kernel)后可以调用early_print来打印信息,它是在arch/arm/kernel/setup.c中定义的:
1: void __init early_print(const char *str, ...)
2: {
3: extern void printascii(const char *);
4: char buf[256];
5: va_list ap;
6:
7: va_start(ap, str);
8: vsnprintf(buf, sizeof(buf), str, ap);
9: va_end(ap);
10:
11: #ifdef CONFIG_DEBUG_LL
12: printascii(buf);
13: #endif
14: printk("%s", buf);
15: }
可以看到,early_print也会调用printascii和printk,意思是用early_print打印的信息可能会重复出现在终端上(printk会缓冲一部分,当bootconsole注册后,会将printk缓冲区中的内容输出)。
上面所说的打印函数只能在内核自解压后的函数中才能使用,那么内核自解压过程中的信息是不是也可以打印呢?可以,内核自解压相关的文件在arch/arm/boot/compressed/下面,我们所熟知的:
Uncompressing Linux... done, booting the kernel.
就是这个目录下的代码打印出来的,具体代码如下:
arch/arm/boot/compressed/misc.c
1: void
2: decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p,
3: unsigned long free_mem_ptr_end_p,
4: int arch_id)
5: {
6: ......
7: putstr("Uncompressing Linux...");
8: ret = do_decompress(input_data, input_data_end - input_data,
9: output_data, error);
10: ......
11: putstr(" done, booting the kernel.n");
12: }
其中,putstr的定义如下:
1: static void putstr(const char *ptr)