• ARM开发板实现双系统引导的一种方法——基于迅为iTOP-4412开发板


    前言

      本文所用的uboot代码为迅为官方提供,开发板是迅为iTOP-4412开发板(不带设备树)。

      首先,我们确定一下系统启动的流程:首先启动uboot,uboot启动内核并挂载rootfs(根文件系统),内核启动完成且rootfs工作完成后,挂载emmc上的文件系统,操作系统正式开始工作。(读者要弄懂根文件系统和普通文件系统的区别与联系,网上资料很多,本文不作赘述。)

      本文实现的双系统引导,都是基于Linux的,即两个系统使用同一个内核、同一个根文件系统,只是emmc上的文件系统有所不同。第一个系统是一个最小Linux系统,第二个系统是一个带Qt/E的Linux系统。uboot启动后会从emmc的特定位置读取一定长度的字符串,若字符串是“qte”,则启动带Qt/E的Linux系统;若字符串是“lin”,则启动最小Linux系统。

    1.分区信息

      

      上图是emmc的分区信息。可以看到,分成了两个部分“Raw区域”和“主要分区”。Raw区域中我们主要关注bootloader、kernel、ramdisk这三个分区。ramdisk中存放根文件系统(rootfs),kernel中存放内核,bootloader中存放uboot。在主要分区中,mmcblk0p1指的是emmc的第一个分区,mmcblk0p2指的是emmc的第二个分区,mmcblk0p3指的是emmc的第三个分区,mmcblk0p4指的是emmc的第四个分区。在本文的环境中,mmcblk0指emmc,mmcblk1指SD卡。

      (1) 打开开发板和串口终端,摁回车进入uboot模式,在串口终端输入命令“fdisk -p mmc”,可以看到开发板emmc的“主要分区”信息,如下图所示。

      

      mmcblk0p1占12536MB,mmcblk0p2占1024MB,mmcblk0p3占1024MB,mmcblk0p4占300MB。

      (2) 在串口终端输入命令“fastboot”,可以看到“Raw区域”和“主要分区”的详细信息,如下图所示。

      

      其中,“bootloader”、“kernel”、“ramdisk”、“Recovery”分别对应“Raw区域”中的四个分区,“system”、“userdata”、“cache”、“fat”分别对应“主要分区”中的mmcblk0p2、mmcblk0p3、mmcblk0p4、mmcblk0p1四个分区。“system”中存放的就是操作系统的文件系统,在默认情况下,uboot引导起内核,内核启动完成后,会挂载位于“system”中的文件系统。要实现双系统引导,我们可以把第一个系统存放在“system”分区中,把第二个系统存放在“userdata”分区中,在启动时,让内核在两个分区的文件系统中进行选择。

      (3) 在串口终端输入命令“printenv”,可以看到环境变量信息,如下图所示。

      

      这里我们主要分析“bootcmd=movi read kernel 40008000;movi read rootfs 40df0000 100000;bootm 40008000 40df0000”这个语句,该语句的作用是把“movi read kernel 40008000;movi read rootfs 40df0000 100000;bootm 40008000 40df0000”赋给bootcmd变量。

      而bootcmd变量的值就是uboot启动后要执行的命令:先把内核从kernel分区中读取到内存的0x40008000处;再从ramdisk分区中(此处的rootfs分区实际就是指ramdisk分区,因为uboot判断分区时只判断首字母,所以即使写rootfs,仍然会导向ramdisk分区,这里写成rootfs是为了方便用户理解)读取0x100000个字节到内存的0x40df0000处;最后使用bootm命令,启动(挂载)已经读取到内存中的内核和根文件系统。

      在正常情况下,环境变量中还应有一个bootargs变量,bootargs的值就是uboot要传递给内核的参数,但是在上图的环境变量信息中并没有发现它,所以我们猜测迅为给的uboot源码中并没有给bootargs变量赋值。

    2.uboot源码分析

      (1) 在uboot源码的“iTop4412_uboot/include/movi.h”文件中可以看到Raw区域的信息,如下。  

    #define MAGIC_NUMBER_MOVI    (0x24564236)
    
    #define SS_SIZE            (16 * 1024)
    
    #define eFUSE_SIZE        (1 * 512)    // 512 Byte eFuse, 512 Byte reserved
    
    #define MOVI_BLKSIZE        (1<<9) 
    //mj defined
    #define FWBL1_SIZE        (8* 1024) //IROM BL1 SIZE 8KB
    #define BL2_SIZE        (16 * 1024)//uboot BL2 16KB
    
    /* partition information */
    #define PART_SIZE_UBOOT        (495 * 1024)
    #define PART_SIZE_KERNEL    (6 * 1024 * 1024)   
    
    #define PART_SIZE_ROOTFS    (2 * 1024 * 1024)//  2M
    #define RAW_AREA_SIZE        (16 * 1024 * 1024)// 16MB
    
    #define MOVI_RAW_BLKCNT        (RAW_AREA_SIZE / MOVI_BLKSIZE)   
    #define MOVI_FWBL1_BLKCNT    (FWBL1_SIZE / MOVI_BLKSIZE)    
    #define MOVI_BL2_BLKCNT        (BL2_SIZE / MOVI_BLKSIZE)    
    #define MOVI_ENV_BLKCNT        (CONFIG_ENV_SIZE / MOVI_BLKSIZE)    
    #define MOVI_UBOOT_BLKCNT    (PART_SIZE_UBOOT / MOVI_BLKSIZE)
    #define MOVI_ZIMAGE_BLKCNT    (PART_SIZE_KERNEL / MOVI_BLKSIZE)
    #define ENV_START_BLOCK        (544*1024)/MOVI_BLKSIZE
    
    #define MOVI_UBOOT_POS        ((eFUSE_SIZE / MOVI_BLKSIZE) + MOVI_FWBL1_BLKCNT + MOVI_BL2_BLKCNT)
    
    #define MOVI_ROOTFS_BLKCNT    (PART_SIZE_ROOTFS / MOVI_BLKSIZE)

      Raw区域总大小为16MB,包含了BL1、BL2、环境变量、内核、rootfs、uboot等信息。我们主要关注kernel、rootfs、uboot这三部分。内核存放在kernel分区中,根文件系统(rootfs)存放在ramdisk分区中、uboot存放在bootloader分区中。

      (2) 而对“Raw区域”和“主要分区”的操作,则在“iTop4412_uboot/common/cmd_fastboot.c”文件的“set_partition_table_sdmmc”函数中,如下(请看注释)。  

    static int set_partition_table_sdmmc()
    {
        int start, count;
        unsigned char pid;
    
        pcount = 0;
    
    #if defined(CONFIG_FUSED)
        /* FW BL1 for fused chip */
        strcpy(ptable[pcount].name, "fwbl1");
        ptable[pcount].start = 0;
        ptable[pcount].length = 0;
        ptable[pcount].flags = FASTBOOT_PTENTRY_FLAGS_USE_MOVI_CMD;
        pcount++;
    #endif
    
        /* Bootloader */
        strcpy(ptable[pcount].name, "bootloader");    //Raw区域中的bootloader分区,存放uboot
        ptable[pcount].start = 0;
        ptable[pcount].length = 0;
        ptable[pcount].flags = FASTBOOT_PTENTRY_FLAGS_USE_MOVI_CMD;
        pcount++;
    
        /* Kernel */
        strcpy(ptable[pcount].name, "kernel");    //Raw区域中的kernel分区,存放内核
        ptable[pcount].start = 0;
        ptable[pcount].length = 0;
        ptable[pcount].flags = FASTBOOT_PTENTRY_FLAGS_USE_MOVI_CMD;
        pcount++;
    
        /* Ramdisk */
        strcpy(ptable[pcount].name, "ramdisk");    //Raw区域中的ramdisk分区,存放rootfs(根文件系统)
        ptable[pcount].start = 0;
        ptable[pcount].length = 0x300000;
        ptable[pcount].flags = FASTBOOT_PTENTRY_FLAGS_USE_MOVI_CMD;
        pcount++;
    
        /* Recovery*/
        #ifdef CONFIG_RECOVERY //mj 
        strcpy(ptable[pcount].name, "Recovery");    //Raw区域中的Recovery分区
        ptable[pcount].start = 0;
        ptable[pcount].length = 0x600000; //6MB
        ptable[pcount].flags = FASTBOOT_PTENTRY_FLAGS_USE_MOVI_CMD;
        pcount++;
    
        /* System */
        get_mmc_part_info((dev_number_write == 0)?"0":"1", 2, &start, &count, &pid);    //主要分区中的mmcblk0p2分区,默认情况下存放操作系统的文件系统
        if (pid != 0x83)                                        
            goto part_type_error;
        strcpy(ptable[pcount].name, "system");    //分区名称为system
        ptable[pcount].start = start * CFG_FASTBOOT_SDMMC_BLOCKSIZE;
        ptable[pcount].length = count * CFG_FASTBOOT_SDMMC_BLOCKSIZE;
        ptable[pcount].flags = FASTBOOT_PTENTRY_FLAGS_USE_MMC_CMD;
        pcount++;
    
        /* User Data */
        get_mmc_part_info((dev_number_write == 0)?"0":"1", 3, &start, &count, &pid);    //主要分区中的mmcblk0p3分区,默认情况下存放用户数据,我们也可以在该分区存放另一个系统
        if (pid != 0x83)                                          
            goto part_type_error;
        strcpy(ptable[pcount].name, "userdata");    //分区名称为userdata
        ptable[pcount].start = start * CFG_FASTBOOT_SDMMC_BLOCKSIZE;
        ptable[pcount].length = count * CFG_FASTBOOT_SDMMC_BLOCKSIZE;
        ptable[pcount].flags = FASTBOOT_PTENTRY_FLAGS_USE_MMC_CMD;
        pcount++;
    
        /* Cache */
        get_mmc_part_info((dev_number_write == 0)?"0":"1", 4, &start, &count, &pid);    //主要分区中的mmcblk0p4分区
        if (pid != 0x83)
            goto part_type_error;
        strcpy(ptable[pcount].name, "cache");    //分区名称为cache
        ptable[pcount].start = start * CFG_FASTBOOT_SDMMC_BLOCKSIZE;
        ptable[pcount].length = count * CFG_FASTBOOT_SDMMC_BLOCKSIZE;
        ptable[pcount].flags = FASTBOOT_PTENTRY_FLAGS_USE_MMC_CMD;
        pcount++;
    
        /* Fat */
        get_mmc_part_info((dev_number_write == 0)?"0":"1", 1, &start, &count, &pid);    //主要分区中的mmcblk0p1分区
        if (pid != 0xc)
            goto part_type_error;
        strcpy(ptable[pcount].name, "fat");    //分区名称为fat
        ptable[pcount].start = start * CFG_FASTBOOT_SDMMC_BLOCKSIZE;
        ptable[pcount].length = count * CFG_FASTBOOT_SDMMC_BLOCKSIZE;
        ptable[pcount].flags = FASTBOOT_PTENTRY_FLAGS_USE_MMC_CMD;
        pcount++;
    
    #if 1 // Debug
        fastboot_flash_dump_ptn();
    #endif
    
        LCD_setleftcolor(0x8a2be2);
    
        return 0;
    
    part_type_error:
        printf("Error: No MBR is found at SD/MMC.
    ");
        printf("Hint: use fdisk command to make partitions.
    ");
    
        return -1;
    }

      (3) 在“iTop4412_uboot/board/samsung/smdkc210/smdkc210.c”文件的“board_late_init”函数中,可以看到关于bootcmd变量的信息,如下。    

    int board_late_init (void)
    {
        int ret = check_bootmode();
        if ((ret == BOOT_MMCSD || ret == BOOT_EMMC441 || ret == BOOT_EMMC43 )
                    && boot_mode == 0) {
            //printf("board_late_init
    ");
            char boot_cmd[100];
    
    #if 0
            sprintf(boot_cmd, "movi read kernel 40008000;movi read rootfs 40d00000 100000;bootm 40008000 40d00000");    //这条语句不会编译
    #else
    #ifdef SMDK4412_SUPPORT_UBUNTU
            sprintf(boot_cmd, "movi read kernel 40008000;bootm 40008000 40d00000");    //这条语句不会编译
    #else
            sprintf(boot_cmd, "movi read kernel 40008000;movi read rootfs 40df0000 100000;bootm 40008000 40df0000");    //只有这条关于bootcmd变量的语句会编译
    #endif
    #endif
    /* end modify */
            setenv("bootcmd", boot_cmd);
            }
    
        return 0;
    }

     3.修改uboot源码

      大概思路已经在前面讲过,为了方便读者理解,再详述一下双系统引导的思路。两个系统共用同一套uboot、内核、rootfs(根文件系统),在mmcblk0p2分区中存放一个系统,在mmcblk0p3中存放另一个系统,uboot每次启动时,会从mmcblk0p4分区中读取定长的字符串,根据字符串的内容来决定该引导哪一个系统。修改后的分区信息如下图所示。

      

      (1) 打开“iTop4412_uboot/common/cmd_fastboot.c”文件,修改“set_partition_table_sdmmc”函数中有关mmcblk0p2分区和mmcblk0p3分区的命名部分(只修改分区名称,其他的不做修改),将原先的“system”和“userdata”两个分区名称改为“system_linux”和“system_qte”,方便记忆。    

      /* Qt/E System */
        get_mmc_part_info((dev_number_write == 0)?"0":"1", 2, &start, &count, &pid);    //主要分区中的mmcblk0p2分区,用于存放最小Qt/E系统
        if (pid != 0x83)
            goto part_type_error;
        strcpy(ptable[pcount].name, "system_qte");  //分区名改为system_qte
        ptable[pcount].start = start * CFG_FASTBOOT_SDMMC_BLOCKSIZE;
        ptable[pcount].length = count * CFG_FASTBOOT_SDMMC_BLOCKSIZE;
        ptable[pcount].flags = FASTBOOT_PTENTRY_FLAGS_USE_MMC_CMD;
        pcount++;
    
        /* Linux System */
        get_mmc_part_info((dev_number_write == 0)?"0":"1", 3, &start, &count, &pid);    //主要分区中的mmcblk0p3分区,用于存放最小Linux系统
        if (pid != 0x83)
            goto part_type_error;
        //strcpy(ptable[pcount].name, "userdata");
        strcpy(ptable[pcount].name, "system_linux");    //分区名改为system_linux
        ptable[pcount].start = start * CFG_FASTBOOT_SDMMC_BLOCKSIZE;
        ptable[pcount].length = count * CFG_FASTBOOT_SDMMC_BLOCKSIZE;
        ptable[pcount].flags = FASTBOOT_PTENTRY_FLAGS_USE_MMC_CMD;
        pcount++;

      (2) 打开“iTop4412_uboot/common/main.c”文件,在“main_loop”函数中添加如下代码。  

        char bootargstr[10];
        run_command("mmc read 0 40008000 408000 10", 0);    //从emmc的0x408000块处读取0x10个块的数据到内存的0x40008000处,至于为什么从emmc的0x408000处读,请看后文的解析
        memcpy(bootargstr, (char*)0x40008000, 3);    //把读取到的数据赋给bootargstr
        if(!strncmp(bootargstr, "qte", 3))    //判断bootargstr的内容,如果是字符串“qte”
            sprintf(bootargstr, "root=/dev/mmcblk0p2");    //把mmcblk0p2设置为系统的根,即启动带Qt/E的Linux系统
        else
            sprintf(bootargstr, "root=/dev/mmcblk0p3");    //把mmcblk0p3设置为根,即启动最小Linux系统
        setenv("bootargs", bootargstr);    //设置环境变量bootargs,把bootargstr字符串的内容赋给环境变量bootargs

      添加位置如下图所示。

      

      为什么要从emmc的0x408000块处读字符串呢?首先我们明确一下“mmc read”命令的用法:mmc read <device num> addr blk# cnt [partition],即从某设备的第blk#个块(一个块为512B)开始,读取cnt个块的数据,将数据存放到内存的addr位置。其次,在前文中我们曾经获取过emmc的“主要分区”信息,如下图。

      

      可以看到mmcblk0p4分区的起始块(block start #)是4227072,这是个十进制数,我们把它转换为十六进制,便得到了0x408000。

     4.编译和烧写

      (1) 编译修改后的uboot,用fastboot工具将其烧写到开发板中。

      

      烧写完成后,重启开发板,并进入uboot模式。在串口终端输入命令“printenv”,可以看到环境变量中又添加了一个bootargs变量,它是用来告诉内核要挂载哪个分区中的文件系统的,挂载不同的文件系统,就实现了不同系统的引导。根据bootargs变量的值可知,目前要引导的系统是位于mmcblk0p3分区中的系统,即最小Linux系统。

      

      在串口终端输入命令“fastboot”,可以看到原先的“system”和“userdata”分区名称已经被修改为“system_qte”和“system_linux”,如下图所示。

      

      (2) 用fastboot工具把两个系统分别烧写进各自的分区中,如下图所示。

      

      (3) 重启开发板(不要进入uboot模式),可以看到开发板进入到了最小Linux系统。

      在串口终端输入命令“echo "qte" > /dev/mmcblk0p4”,该命令表示向mmcblk0p4分区的起始位置写入字符串“qte”。

      

      然后重启开发板,可以看到开发板进入到了Qt/E系统。

      再在串口终端输入命令“echo "lin" > /dev/mmcblk0p4”,该命令表示向mmcblk0p4分区的起始位置写入字符串“lin”。

      

      重启开发板,可以看到开发板又回到了最小Linux系统。

      双系统引导成功!

  • 相关阅读:
    写代码如坐禅:你是哪一类程序员
    开发企业应用系统需要掌握的知识技能(转)
    (转)谈谈多线程的思维方式
    深入理解JavaScript定时机制
    (转)javascritp对fckeditor编辑器操作
    JavaScript异步编程的Promise模式
    (转)jquery调WCF
    (转)《代码大全》学习笔记:第10,11,12,13章 关于变量的使用,命名等
    编程:是一门艺术(转)
    禅修程序员十诫(转)
  • 原文地址:https://www.cnblogs.com/UnfriendlyARM/p/13605321.html
Copyright © 2020-2023  润新知