目錄
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:體系結構相關代碼,對於每個架構的CPU,arch目錄下有一個對應的子目錄,比如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 測試中傳入內核的ID是193,啓動內核時查找內核裏的機器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文件,所以應該設置機器ID爲16a。設置ID可以修改UBOOT源碼(按照1.3.3 分析爲什麼輸出亂碼中調用過程修改宏MACH_TYPE_SMDK2410或CONFIG_MACH_TYPE),也可以直接設置環境變量來修改機器ID。
如果設置機器ID爲16a,那麼使用的單板文件就是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 測試
重新編譯啓動內核,有如下輸出: