移植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 測試

    重新編譯啓動內核,有如下輸出:
   

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