移植linux-3.4.2到JZ2440(上:uboot如何启动内核与创建单板)

目录

1. uboot如何启动内核与创建单板
    1.1 uboot如何启动内核

    1.2 准备工作
        1.2.1 环境
        1.2.2 获取linux-3.4.2源码
    1.3 创建单板
        1.3.1 创建JZ2440相关单板文件夹
        1.3.2 测试
        1.3.3 分析为什么输出乱码
        1.3.4 解决乱码
        1.3.5 修改MTD分区
            1.3.5.1 分析调用过程
            1.3.5.2 修改分区        
        1.3.6 测试


1. uboot如何启动内核与创建单板

1.1 uboot如何启动内核

    在移植u-boot-2012.04.01到JZ2440中我们已经实现了移植新uboot并启动内核,不过使用的是韦东山老师制作的内核,首先来分析uboot是如何启动内核的。
    当uboot启动完成后如果设置了bootcmd环境变量的如下:

bootcmd=nand read 0x30000000 kernel; bootm 0x30000000        //从0x30000000处启动内核

    uboot会执行这两条命令,第一条先从NAND Flash里的kernel分区读出内核到0x30000000地址;第二条bootm 0x30000000命令,下面具体分析该命令的执行过程。bootm命令定义在common/cmd_bootm.c文件中:
   
    可以看到实际调用得是do_bootm()函数(common/cmd_bootm.c文件):

bootm_headers_t images;		/* 指向 os/initrd/fdt类型的内核映像文件 */

int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
        boot_os_fn *boot_fn;             //boot_fn是个数组函数
        ... ..
    	if (bootm_start(cmdtp, flag, argc, argv))    /* 得到内核头部,填充images结构体 */
		return 1;
        ... ...
        boot_fn = boot_os[images.os.os];    /* 通过头部标识从boot_os数组选择启动内核函数 */
        ... ...

        boot_fn(0, argc, argv, &images);    /* 调用启动内核函数 */
        ... ...
}

    其中boot_os数组如下:
   
    对于我们使用的uImage使用的是do_bootm_linux函数,该函数代码如下(common/bootm.c文件)

int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
{
         /* No need for those on ARM */
         if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
                   return -1;
         if (flag & BOOTM_STATE_OS_PREP) {
                   boot_prep_linux(images);
                   return 0;
         }
         if (flag & BOOTM_STATE_OS_GO) {
                   boot_jump_linux(images);
                   return 0;
         }

         /* 该函数会将各个tag参数保存在指定位置,比如:内存tag、bootargs环境变量tag、串口tag等 */
         boot_prep_linux(images); 
            
         /* 该函数会跳转到内核起始地址 */
         boot_jump_linux(images);     
         return 0;
}

    最终调用boot_jump_linux()函数跳到内核起始地址运行内核,boot_jump_linux()函数代码如下(common/bootm.c文件)

static void boot_jump_linux(bootm_headers_t *images)
{
         unsigned long machid = gd->bd->bi_arch_number;     //获取机器ID
         char *s;
         void (*kernel_entry)(int zero, int arch, uint params);
         unsigned long r2;
         kernel_entry = (void (*)(int, int, uint))images->ep;  //设置kernel_entry()的地址为0x30000000
         s = getenv("machid");                     //判断环境变量machid是否设置,若设置则使用环境变量里的值   
         if (s) {       
                   strict_strtoul(s, 16, &machid);      //重新获取机器ID
                   printf("Using machid 0x%lx from environment\n", machid);  //使用环境变量的machid
         }
     ... ...
        r2 = gd->bd->bi_boot_params;     /* 获取tag参数地址, gd->bd->bi_boot_params在boot启动第二阶段board_init_r函数里调用board_init函数里被设置为0x30000100 */

        kernel_entry(0, machid, r2);     //跳转到0x30000000,r0=0,r1=机器ID,r2=tag参数地址
}

    运行内核后工作内容如下:
      1. 比较机器ID;
      2. 解析uboot传入的启动参数;
      3. 挂接根文件系统、执行第一个应用程序。

1.2 准备工作

1.2.1 环境

    交叉编译工具:arm-linux-gcc-4.4.3
    开发板:JZ2440V3

1.2.2 获取linux-3.4.2源码

    从官方下载源码,地址:https://mirrors.edge.kernel.org/pub/linux/kernel/v3.x/,找到linux-3.4.2.tar.bz2并下载。
    解压该文件,得到如下:
   
    下面简单介绍一下这些文件夹的功能:

    arch:体系结构相关代码,对于每个架构的CPUarch目录下有一个对应的子目录,比如arch/arm
    block块设备的通用函数;
    crypto:常用加密和散列算法,还有一些压缩和CRC校验算法;
    Documentation:内核文档;
    drivers:所有的设备驱动程序,里面每一个子目录对应一类驱动程序,比如drivers/block/为块设备驱动程序,drivers/char/为字符设备驱动程序,drivers/mtd/NOR Flash、NAND Flash等存储设备的驱动程序。
    fs:Linux支持的文件系统的代码,每个字目录对应一种文件系统,比如fs/jffs2/、fs/ext2
    include:这 个目录包含linux源代码目录树中绝大部分头文件,每个体系架构都在该目录下对应一个子目录,该子目录中包含了给定体系结构所必需的宏定义和内联函数。
    init:该目录中存放的是系统核心初始化代码,内核初始化入口函数start_kernel就是在该目录下的文件main.c内实现的。内核初始化入口函数start_kernel负责调用其它模块的初始化函数,完成系统的初始化工作。
    ipc:用于实现System V的进程间通信(Inter Process Communication,IPC)模块。
    kernel:用于存放特定体系结构特有信号量的实现代码和对称多处理器(Symmetric MultiProccessing,简称SMP)相关模块。
    lib:内核用到的一些库函数代码,比如crc32.c、string.c,与处理器相关的库函数代码位于arch/*/lib目录下;
    mm:内存管理代码,与处理器相关的内存管理代码位于arch/*/mm目录下;
    net:网络支持代码,每个子目录对应于网络的一个方面;
    samples:内核编程的例子;
    scripts:该目录下没有内核代码,只包含了用来配置内核的脚本文件。当运行make menuconfig或者make xconfig之类的命令配置内核时,用户就是和位于这个目录下的脚本进行交互的。
    security:这个目录包括了不同的Linux安全模型的代码,比如NSA Security-Enhanced Linux;
    sound:音频设备的驱动程序
    tools:与内核交互,以便在用户态时测试相关内核功能;
    usr:目录下是initramfs相关的,和linux内核的启动有关.实现了用于打包和压缩的的cpio等。

1.3 创建单板

   下面开始创建针对JZ2440开发板的相关单板文件。

1.3.1 创建JZ2440相关单板文件夹

    1. 在服务器上解压linux-3.4.2.tar.bz2文件,执行如下命令:
      tar xjf linux-3.4.2.tar.bz2
    2. 进入根目录,vi Makefile修改顶层Makefile,修改如下内容:

ARCH		?= $(SUBARCH)
CROSS_COMPILE	?= $(CONFIG_CROSS_COMPILE:"%"=%)

    改为:

ARCH		?= arm
CROSS_COMPILE	?= arm-linux-

    3. 配置编译,执行如下命令:
      make s3c2410_defconfig    (使用相近单板配置文件进行配置,在arch/arm/configs下有许多配置文件选择)
      make uImage
   
这样在arch/arm/boot目录下就会生成uImage

1.3.2 测试

    使用移植u-boot-2012.04.01到JZ2440移植的uboot下载uImage开发板上运行:
      tftp 30000000 uImage    (或者将uImage拷贝到/work/nfs_root/, nfs 30000000 192.168.1.13:/work/nfs_root/uImage)
      bootm 30000000
   

    可以看到输出一堆乱码。可能是波特率设置问题,所以重启开发板在uboot里输入如下命令添加设置波特率:
      set bootargs console=ttySAC0,115200 root=/dev/mtdblock3
      save
   
重新下载启动内核发现还是输出乱码,下面分析原因。

1.3.3 分析为什么输出乱码

    执行bootm 0x30000000命令会调用到boot_jump_linux()函数,该函数里有如下代码:
   
    移植u-boot-2012.04.01到JZ2440移植的uboot时,在 u-boot-2012.04.01/board/samsung/jz2440/jz2440.c中设置了默认ID
   
    宏MACH_TYPE_SMDK2410定义在u-boot-2012.04.01/arch/arm/include/asm/mach-types.h文件中,如下:
   

    同时在board_f.c (common)board_init_f()函数中也有设置ID
   
    只不过uboot配置文件jz2440.h(include/configs)没有定义CONFIG_MACH_TYPE该宏,所以使用的是前面定义的MACH_TYPE_SMDK2410宏也就是193=0xc1
    在uboot任意设置一个机器ID值(set machid 33333),这样再次下载启动内核时,内核识别不出来,就会打印出当前的内核配置所支持的单板机器ID,如下(内核支持的所有机器ID定义在linux-3.4.2/include/generated/mach-types.h文件中)
     
   

    由于之前配置(make s3c2410_defconfig),编译器就会将该类型的单板文件编译进内核,在arch/arm目录下输入"find -name "mach*.o"",如下:

   
    启动内核时内核就会匹配uboot传入的机器ID,根据不同的ID使用不同mach-*.c文件,比如在1.3.2 测试中传入内核的ID193,启动内核时查找内核里的机器ID单板文件,结果与如下定义匹配(arch/arm/mach-s3c24xx/mach-smdk2410.c文件中)
   
    MACHINE_START定义如下:
   
    用MACHINE_START宏替换后mach-smdk2410.c文件的.nr=MACH_TYPE_SMDK2410,查看该宏定义(include/generated/mach-types.h文件中)刚好就是193,所以调用的machine_desc结构体成员函数是使用arch/arm/mach-s3c24xx/mach-smdk2410.c文件里的machine_desc结构体成员map_io里设置了时钟,如下:
       
    在该文件里设置晶振频率为0,本人使用的JZ2440开发板上用的是12MHZ晶振,所以1.3.2 测试中输出乱码。

1.3.4 解决乱码

    我们不使用mach-smdk2410.c该单板文件,使用更相近的arch/arm/mach-s3c24xx/mach-smdk2440.c文件,所以应该设置机器ID16a。设置ID可以修改UBOOT源码(按照1.3.3 分析为什么输出乱码中调用过程修改宏MACH_TYPE_SMDK2410CONFIG_MACH_TYPE),也可以直接设置环境变量来修改机器ID
    如果设置机器ID16a,那么使用的单板文件就是arch/arm/mach-s3c24xx/mach-smdk2440.c文件,该文件的时钟设置如下:
   
    可以看出晶振频率设置也不对,将其中的晶振频率修改为12MHZ也就是12000000,如下:
   
    然后重新编译内核(make uImage)
    重启开发板在uboot命令行输入如下命令:
      set machid 16a    (如果没有设置波特率还要设置:set bootargs console=ttySAC0,115200 root=/dev/mtdblock3)
      save
    重新下载启动内核:
      tftp 30000000 uImage    (或者将uImage拷贝到/work/nfs_root/, nfs 30000000 192.168.1.13:/work/nfs_root/uImage)
      bootm 30000000
   
如下内核正确输出信息了:
   

1.3.5 修改MTD分区

    上面启动内核后还有如下输出:
   
    uboot传递的文件系统路径root=/dev/mtdblock3,所以内核便卡死在启动文件系统上。内核默认创建了8个分区,显然与我们之前在uboot中划分的分区不符,下面分析内核划分分区的过程。

1.3.5.1 分析调用过程

    内核启动后,会调用如下arch_initcall标示的函数:

static int __init customize_machine(void)
{
	/* customizes platform devices, or adds new ones */
	if (machine_desc->init_machine)
		machine_desc->init_machine();
	return 0;
}
arch_initcall(customize_machine);

    由于之前我们设置机器ID为对应arch/arm/mach-s3c24xx/mach-smdk2440.c文件,所以customize_machine()函数调用的init_machine()函数就是mach-smdk2440.c文件中如下代码:
  
    smdk_machine_init()函数代码如下:

static struct platform_device __initdata *smdk_devs[] = {
	&s3c_device_nand,
	&smdk_led4,
	&smdk_led5,
	&smdk_led6,
	&smdk_led7,
};

void __init smdk_machine_init(void)
{
        ... ...
	s3c_nand_set_platdata(&smdk_nand_info);    /* 设置platform数据 */
	platform_add_devices(smdk_devs, ARRAY_SIZE(smdk_devs));    /* 注册platform_device */
	... ...
}

    该函数会注册一些platform_device,其中s3c_device_nand结构体定义如下(arch/arm/plat-samsung/devs.c文件中)
   
    而该name成员会被s3c244x_map_io函数调用s3c_nand_setname函数修改为"s3c2440-nand",如下(arch/arm/mach-s3c24xx/s3c244x.c文件中)
   
    所以内核注册了名为"s3c2410-nand"的platform_device结构。我们之前使用的内核配置文件arch/arm/configs/s3c2410_defconfig中有如下代码:
   
    而在drivers/mtd/nand/Makefile文件中有如下代码:
   
    所以内核启动时会调用s3c2410.c驱动,在该驱动入口函数中会调用platform_driver_register()函数注册platform_driver结构,通过id_table匹配到之前注册的名为"s3c2440-nand"的platform_device结构,根据平台总线设备驱动模型,最终调用.probe函数也就是s3c24xx_nand_probe()函数,过程如下(drivers/mtd/nand/s3c2410.c文件中)
   
    s3c24xx_nand_probe()函数最终会调用到add_mtd_partitions()函数添加分区(详细内容可以看十七、Linux驱动之nand flash驱动,使用的参数就是前面smdk_machine_init()函数调用的s3c_nand_set_platdata()函数里设置的smdk_nand_info结构体,如下(arch/arm/mach-s3c24xx/common-smdk.c文件中)
   
    最终add_mtd_partitions()函数添加分区就是使用的smdk_default_nand_part数组里的参数,该数组定义如下(arch/arm/mach-s3c24xx/common-smdk.c文件中)

static struct mtd_partition smdk_default_nand_part[] = {
	[0] = {
		.name	= "Boot Agent",
		.size	= SZ_16K,
		.offset	= 0,
	},
	[1] = {
		.name	= "S3C2410 flash partition 1",
		.offset = 0,
		.size	= SZ_2M,
	},
	[2] = {
		.name	= "S3C2410 flash partition 2",
		.offset = SZ_4M,
		.size	= SZ_4M,
	},
	[3] = {
		.name	= "S3C2410 flash partition 3",
		.offset	= SZ_8M,
		.size	= SZ_2M,
	},
	[4] = {
		.name	= "S3C2410 flash partition 4",
		.offset = SZ_1M * 10,
		.size	= SZ_4M,
	},
	[5] = {
		.name	= "S3C2410 flash partition 5",
		.offset	= SZ_1M * 14,
		.size	= SZ_1M * 10,
	},
	[6] = {
		.name	= "S3C2410 flash partition 6",
		.offset	= SZ_1M * 24,
		.size	= SZ_1M * 24,
	},
	[7] = {
		.name	= "S3C2410 flash partition 7",
		.offset = SZ_1M * 48,
		.size	= MTDPART_SIZ_FULL,
	}
};

    可以看到这里划分了8个分区,与我们启动内核打印出的信息一致。

1.3.5.2 修改分区

    修改smdk_default_nand_part[]数组如下(arch/arm/mach-s3c24xx/common-smdk.c文件中)

static struct mtd_partition smdk_default_nand_part[] = {
	[0] = {
		.name	= "bootloader",
		.size	= SZ_256K,
		.offset	= 0,
	},
	[1] = {
		.name	= "params",
		.offset = MTDPART_OFS_APPEND,
		.size	= SZ_128K,
	},
	[2] = {
		.name	= "kernel",
		.offset = MTDPART_OFS_APPEND,
		.size	= SZ_2M,
	},
	[3] = {
		.name	= "rootfs",
		.offset	= MTDPART_OFS_APPEND,
		.size	= MTDPART_SIZ_FULL,
	}
};

1.3.6 测试

    重新编译启动内核,有如下输出:
   

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章