Linux和android在燒寫kernel前需要燒寫以下文件:
Bootparam_sa0*: 需要傳遞給linux kernel 的param
究竟是如何完成boot param 的傳遞呢:
都知道linux kernel C語言的入口函數是start_kernel()
在start_kernel()函數里面會有setup_arch(&command_line) ,
之后調用setup_processor(), setup_machine_fdt(__atags_pointer), setup_machine_tags(machine_arch_type).
這些調用流程如果有Jtag 的話,可以清晰的看到調用流程,同時需要注意的是linux kernel 編譯的時候會進行GNU 編譯優化, 如果一個函數是static 類型,而且只被調用過一次,那么這個函數就會被優化掉,不會產生棧幀,類似有內聯函數的處理。
接着說setup_machine_tags()
這個函數中會有如下代碼:
if(__atags_pointer)
tags = phys_to_virt(__atags_poniter);
這里解釋一下,__atags_pointer 是在head-common.S 中進行的賦值操作。
也就是說在start_kernel 打上斷點的話就能看到__atags_pointer 的值,在OMAP4430開發板上就是0x80000100,這是物理地址。
因此,需要使用phy_to_virt() 函數把物理地址轉換成虛擬地址。轉換后一般就是0xc0000100,這樣在這個地址上就能看到boot param 了!
Bl2*:BL2 image將會為后續image的加載執行相關的初始化操作。主要是內存,MMU,串口以及EL3軟件運行環境的設置,並且加載bl3x的image到RAM中
1:通過查看bl2.ld.S文件就可以發現,bl2 image的入口函數是bl2_entrypoint。該函數定義在bl2/aarch64/bl2_entrypoint.S文件中。
bl2_entrypoint:該函數最終會出發smc操作,從bl1中將CPU的控制權轉交給bl31
2:bl2_main
該函數主要實現將bl3x的image加載RAM中,並通過smc調用執行bl1中指定的smc handle將CPU的全向交給bl31。
3: bl2_load_images
該函數用來加載bl3x的image到RAM中,返回一個具有image入口信息的變量。smc handle根據該變量跳轉到bl31進行執行
4: REGISTER_BL_IMAGE_DESCS(bl2_mem_params_descs)
該宏的執行將會初始化組成bl2加載bl3x image的列表使用到的重要全局變量, 其中bl2_mem_params_descs變量的定義如下:
static bl_mem_params_node_t bl2_mem_params_descs[] = {
#ifdef SCP_BL2_BASE
/* Fill SCP_BL2 related information if it exists */
{
.image_id = SCP_BL2_IMAGE_ID,
SET_STATIC_PARAM_HEAD(ep_info, PARAM_IMAGE_BINARY,
VERSION_2, entry_point_info_t, SECURE | NON_EXECUTABLE),
SET_STATIC_PARAM_HEAD(image_info, PARAM_IMAGE_BINARY,
VERSION_2, image_info_t, 0),
.image_info.image_base = SCP_BL2_BASE,
.image_info.image_max_size = PLAT_CSS_MAX_SCP_BL2_SIZE,
.next_handoff_image_id = INVALID_IMAGE_ID,
},
#endif /* SCP_BL2_BASE */
#ifdef EL3_PAYLOAD_BASE
/* Fill EL3 payload related information (BL31 is EL3 payload)*/
{
.image_id = BL31_IMAGE_ID,
SET_STATIC_PARAM_HEAD(ep_info, PARAM_EP,
VERSION_2, entry_point_info_t,
SECURE | EXECUTABLE | EP_FIRST_EXE),
.ep_info.pc = EL3_PAYLOAD_BASE,
.ep_info.spsr = SPSR_64(MODE_EL3, MODE_SP_ELX,
DISABLE_ALL_EXCEPTIONS),
SET_STATIC_PARAM_HEAD(image_info, PARAM_EP,
VERSION_2, image_info_t,
IMAGE_ATTRIB_PLAT_SETUP | IMAGE_ATTRIB_SKIP_LOADING),
.next_handoff_image_id = INVALID_IMAGE_ID,
},
#else /* EL3_PAYLOAD_BASE */
/* Fill BL31 related information */
{
.image_id = BL31_IMAGE_ID,
SET_STATIC_PARAM_HEAD(ep_info, PARAM_EP,
VERSION_2, entry_point_info_t,
SECURE | EXECUTABLE | EP_FIRST_EXE),
.ep_info.pc = BL31_BASE,
.ep_info.spsr = SPSR_64(MODE_EL3, MODE_SP_ELX,
DISABLE_ALL_EXCEPTIONS),
#if DEBUG
.ep_info.args.arg1 = ARM_BL31_PLAT_PARAM_VAL,
#endif
SET_STATIC_PARAM_HEAD(image_info, PARAM_EP,
VERSION_2, image_info_t, IMAGE_ATTRIB_PLAT_SETUP),
.image_info.image_base = BL31_BASE,
.image_info.image_max_size = BL31_LIMIT - BL31_BASE,
# ifdef BL32_BASE
.next_handoff_image_id = BL32_IMAGE_ID,
# else
.next_handoff_image_id = BL33_IMAGE_ID,
# endif
},
# ifdef BL32_BASE
/* Fill BL32 related information */
{
.image_id = BL32_IMAGE_ID,
SET_STATIC_PARAM_HEAD(ep_info, PARAM_EP,
VERSION_2, entry_point_info_t, SECURE | EXECUTABLE),
.ep_info.pc = BL32_BASE,
SET_STATIC_PARAM_HEAD(image_info, PARAM_EP,
VERSION_2, image_info_t, 0),
.image_info.image_base = BL32_BASE,
.image_info.image_max_size = BL32_LIMIT - BL32_BASE,
.next_handoff_image_id = BL33_IMAGE_ID,
},
# endif /* BL32_BASE */
/* Fill BL33 related information */
{
.image_id = BL33_IMAGE_ID,
SET_STATIC_PARAM_HEAD(ep_info, PARAM_EP,
VERSION_2, entry_point_info_t, NON_SECURE | EXECUTABLE),
# ifdef PRELOADED_BL33_BASE
.ep_info.pc = PRELOADED_BL33_BASE,
SET_STATIC_PARAM_HEAD(image_info, PARAM_EP,
VERSION_2, image_info_t, IMAGE_ATTRIB_SKIP_LOADING),
# else
.ep_info.pc = PLAT_ARM_NS_IMAGE_OFFSET,
SET_STATIC_PARAM_HEAD(image_info, PARAM_EP,
VERSION_2, image_info_t, 0),
.image_info.image_base = PLAT_ARM_NS_IMAGE_OFFSET,
.image_info.image_max_size = ARM_DRAM1_SIZE,
# endif /* PRELOADED_BL33_BASE */
.next_handoff_image_id = INVALID_IMAGE_ID,
}
#endif /* EL3_PAYLOAD_BASE */
};
在該變量中規定了SCP_BL2, EL3_payload, bl32, bl33 image的相關信息,例如:
image的入口地址信息:ep_info
image在RAM中的基地址:image_base
image的基本信息:image_info
image的ID值:image_id
3:cert_header:加載認證程序
bl31*:arm trusted firmware
在bl2中通過調用smc指令后會跳轉到bl31中進行執行,bl31最終主要的作用是建立EL3 runtime software,在該階段會建立各種類型的smc調用注冊並完成對應的cortex狀態切換。該階段主要執行在monitor中。
1:bl31_entrypoint
通過bl31.ld.S文件可知, bl31的入口函數是:bl31_entrypoint函數
執行完bl31_entrypoint函數后,將跳轉到bl33中執行,即執行bootloader
2:bl31_main
該函數主要完成必要初始化操作,配置EL3中的各種smc操作,以便在后續順利響應在CA和TA中產生的smc操作
3: runtime_svc_init
該函數主要用來建立smc索引表並執行EL3中提供的service的初始化操作
4:DECLARE_RT_SVC
該宏用來在編譯的時候將EL3中的service編譯進rt_svc_descs段中,該宏定義如下:
#define DECLARE_RT_SVC(_name, _start, _end, _type, _setup, _smch) \
static const rt_svc_desc_t __svc_desc_ ## _name \
__section("rt_svc_descs") __used = { \
.start_oen = _start, \
.end_oen = _end, \
.call_type = _type, \
.name = #_name, \
.init = _setup, \
.handle = _smch }
start_oen:該service的起始內部number
end.oen: 該service的末尾number
call_type: 調用的smc的類型
name: 該service的名字
init: 該service在執行之前需要被執行的初始化操作
handle: 當觸發了call type的調用時調用的handle該請求的函數
5: 以OP-TEE為例從bl31跳轉到OP-TEE
實現從bl31到OP-TEE的跳轉是通過執行opteed_setup函數來實現的,該函數在執行runtime_svc_int中對各service做service->init()函數來實現,而OPTEE這個service就是通過DECALARE_RT_SVC被注冊到tr_svc_descs段中,代碼存在service/spd/opteed/opteed_main.c文件中,內容如下:
Tee*:
驅動與secure world之間的數據交互是通過共享內存來完成的,在OP-TEE啟動的時候會將作為共享內存的物理內存塊reserve出來,具體的可以查看OP-TEE啟動代碼中的core_init_mmu_map函數。在OP-TEE驅動初始化階段會將reserve出來作為共享內存的物理內存配置成驅動的內存池,並且通知OP-TEE OS執行相同的動作。配置完成之后,secure world就能從共享內存中獲取到來自於REE端的數據。
1:配置驅動與OP-TEE之間的共享內存
在驅動做probe操作時,會調用到optee_config_shm_memremap函數來完成OP-TEE驅動和OP-TEE之間共享內存的配置。該函數定義在linux/drivers/tee/optee/core.c文件中。
在secure world中reserve出來的內存塊作為驅動與sercure world之間的共享內存使用。在驅動端將會建立一個內存池,以便驅動在需要的使用通過調用驅動的alloc函數來完成共享內存的分配。而共享內存池的建立是通過調用tee_shm_pool_alloc_res_mem來實現的。
2:分配和設置OP-TEE驅動作為被libteec和tee_supplicant使用的設備信息結構體
在OP-TEE驅動做probe操作時會分配和設置兩個tee_device結構體變量,分別用來表示被libteec和tee_supplicant使用的設備。分別通過調用tee_device_alloc(&optee_desc, NULL, pool, optee)和tee_device_alloc(&optee_supp_desc, NULL, pool, optee)來實現,主要是設置驅動作為被libteec和tee_supplicant使用的設備的具體操作和表示該設備對應的名稱等信息,
當libteec調用文件操作函數執行打開,關閉等操作/dev/tee0設備文件的時,其最終將調用到optee_desc中具體的函數來實現對應操作。
當tee_supplicant調用文件操作函數執行打開,關閉等操作/dev/teepriv0設備文件的時,其最終將調用到optee_supp_desc中具體的函數來實現對應操作。
上述配置操作都是通過調用tee_device_all函數來實現的
3:設備注冊
完成版本檢查,共享內存池配置,不同設備的配置之后就需要將這些配置好的設備注冊到系統中去。對於被liteec和tee_supplicant使用的設備分別通過調用tee_device_register(optee->teedev)和tee_device_register(optee->supp_teedev)來實現。其中optee->teedev和optee->supp_teedev就是在上一章中被配置好的分別被libteec和tee_supplicant使用的設備結構體。調用tee_device_register函數來實現將設備注冊到系統的目的,
4:兩個隊列的建立
在OP-TEE驅動提供兩個設備,分別被libteec使用的/dev/tee0和被tee_supplicant使用的/dev/teepriv0。為確保normal world與secure world之間數據交互便利和能在normal world端進行異步處理。OP-TEE驅動在掛載的時候會建立兩個類似於消息隊列的隊列,用於保存normal world的請求數據以及secure world請求。optee_wait_queue_init用於初始化/dev/tee0設備使用的隊列,optee_supp_init用於初始化/dev/teepriv0設備使用的隊列
5:通知op-tee是能以下共享內存的cache
當一切做完之后,最終就剩下通知OP-TEE使能共享內存的cache了,在驅動掛載過程中調用optee_enable_shm_cache函數來實現
6:fast smc與std smc
在OP-TEE驅動的掛載過程中會使用fast smc的方式從OP-TEE中獲取到相關數據,例如從secure world中獲取reserve的共享內存信息時就是通過調用如下函數來實現的:
invoke_fn(OPTEE_SMC_GET_SHM_CONFIG, 0, 0, 0, 0, 0, 0, 0, &res.smccc);
在支持smc操作的32位系統中該函數等價於
arm_smccc_smc(OPTEE_SMC_GET_SHM_CONFIG, 0, 0, 0, 0, 0, 0, 0, &res.smccc);
而OPTEE_SMC_ENABLE_SHM_CACHE的定義如下:
#define OPTEE_SMC_FUNCID_GET_SHM_CONFIG 7
#define OPTEE_SMC_GET_SHM_CONFIG OPTEE_SMC_FAST_CALL_VAL(OPTEE_SMC_FUNCID_GET_SHM_CONFIG)
完全展開之后OPTEE_SMC_GET_SHM_CONFIG 宏的值的各個bit中的數值如下如所示:
在執行smc操作時,cortex會解讀第一個參數中的相關位來決定進入到monitor模式后的相關操作,在ARM官方定義了第一個參數(a0)的定義如下
當bit31為1時,則表示進入monitor模式后會執行fast smc的中斷處理函數,而在不帶ATF的ARCH32中,monitor的中斷向量表如下:
FUNC thread_vector_table , :
UNWIND( .fnstart)
UNWIND( .cantunwind)
b vector_std_smc_entry
b vector_fast_smc_entry
b vector_cpu_on_entry
b vector_cpu_off_entry
b vector_cpu_resume_entry
b vector_cpu_suspend_entry
b vector_fiq_entry
b vector_system_off_entry
b vector_system_reset_entry
UNWIND( .fnend)
END_FUNC thread_vector_table
由此可以見,驅動在掛載的時候請求共享內存配置數據的請求將會被vector_fast_smc_entry處理。而當請求來自於CA的請求時,驅動會將第一個參數的bi31設置成0,也就是CA的請求會被vector_std_smc_entry進行處理
總結:
OP-TEE的驅動的掛載過程來看,OP-TEE驅動會分別針對libteec和tee_supplicant建立不同的設備/dev/tee0和/dev/teepriv0。並且為兩個設備中的des執行各自獨有的operation,並建立類似消息隊列來存放normal world與secure world之間的請求,這樣libteec和tee_supplicant使用OP-TEE驅動的時候就能做到相對的獨立。secure world與OP-TEE驅動之間使用共享內存進行數據交互。用於作為共享內存的物理內存塊在OP-TEE啟動的時做MMU初始化時需要被reserve出來,在OP-TEE掛載過程中需要將該內存塊映射到系統內存中。
U-boot:目標功能是從flash中讀出內核,放到內存中,啟動內核
1:第一階段
硬件設備初始化;
為加載 Bootloader 的第二階段代碼准備 RAM 空間;
復制 Bootloader 的第二階段代碼到 RAM 空間中;
設置好棧;
跳轉到第二階段代碼的 C 入口點(start_armboot);
備注:在第一階段進行的硬件初始化一般包括:關閉 WATCHDOG、關中斷、設置 CPU的速度和時鍾頻率、 RAM 初始化等。這些並不都是必須的,比如 S3C2410/S3C2440的開發板所使用的 U-Boot 中,就將 CPU的速度和時鍾頻率放在第二階段進行設置。
2:第二階段
初始化本階段要使用到的硬件設備;
檢測系統內存映射( Memory map );
將內核映像和根文件系統映像從 Flash上讀到 RAM 空間中;
為內核設置啟動參數;
調用內核;
備注:為了方便開發,初始化一個串口以便程序員與 Bootloader 進行交互。
common/cmd_nand.c文件提供了操作NAND Flash的各種命令,這些命令調用drivers/nand/nand_base.c中的擦除、讀寫函數來實現;而這些函數是針對NAND Flash的共性做的封裝,與平台/開發板相關的代碼用宏或外部函數代替;平台相關則在cpu/xxx,開發板相關則在board/xxx。
以增加yaffs文件系統映像功能為例,先在common下的cmd_nand.c增加命令,比如nand write.yaffs,這個命令調用/drivers/nand/nand_util.c中的函數,而這些函數依賴於drivers/nand/nand_base.c、cpu/arm920t/s3c24x0/nand_flash.c文件中的相關函數
所謂檢測內存映射,就是確定板上使用了多少內存、他們的地址空間是什么。由於嵌入式開發中的 Bootloader 多是針對某類板子進行編寫,所以可以根據板子的情況直接設置,不需要考慮可以適用於各類情況的復雜算法。
Flash上的內核映像有可能是經過壓縮的,在讀到 RAM 之后,還需要進行解壓。當然,對於有自解壓功能的內核,不需要 Bootloader 來解壓。
將根文件系統映像復制到RAM中並不是必須的,這取決於是什么類型的根文件系統,以及內核訪問它的方法。
將內核放在適當的位置后,在跳入執行內核之前,需要根據內核啟動的需求,配置相應的啟動參數。