ARM開發板實現雙系統引導的一種方法——基於迅為iTOP-4412開發板
前言
本文所用的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"); 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"); 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"); 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"); 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); //setenv("bootargs", "root=/dev/mmcblk0p2"); } 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系統。
雙系統引導成功!