ARM64架構啟動流程


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中並不是必須的,這取決於是什么類型的根文件系統,以及內核訪問它的方法。
將內核放在適當的位置后,在跳入執行內核之前,需要根據內核啟動的需求,配置相應的啟動參數。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM