ARM開發板實現雙系統引導的一種方法——基於迅為iTOP-4412開發板

山無言發表於2020-09-03

前言

  本文所用的uboot程式碼為迅為官方提供,開發板是迅為iTOP-4412開發板。本文如有錯誤,歡迎指正。

  首先,我們確定一下系統啟動的流程:首先啟動uboot,uboot啟動核心並掛載rootfs(根檔案系統),核心啟動完成且rootfs工作完成後,掛載emmc上的檔案系統,作業系統正式開始工作。(讀者要弄懂根檔案系統和普通檔案系統的區別與聯絡,網上資料很多,本文不作贅述。)

  本文實現的雙系統引導,都是基於Linux的,即兩個系統使用同一個核心、同一個根檔案系統,只是emmc上的檔案系統有所不同。第一個系統是一個最小Linux系統,第二個系統是一個帶Qt/E的Linux系統。uboot啟動後會從mmcblk0p4分割槽中讀取一定長度的字串,若字串是“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.\n");
    printf("Hint: use fdisk command to make partitions.\n");

    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\n");
        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”,方便記憶。    

  /* Linux System */
    get_mmc_part_info((dev_number_write == 0)?"0":"1", 2, &start, &count, &pid);    //主要分割槽中的mmcblk0p2分割槽,用於存放最小Linux系統
    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++;

    /* Qt/E System */
    get_mmc_part_info((dev_number_write == 0)?"0":"1", 3, &start, &count, &pid);    //主要分割槽中的mmcblk0p3分割槽,用於存放Qt/E系統
    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系統。

  雙系統引導成功!

相關文章