3.2 Quy trình khởi động BL2
Quy trình khởi động của BL2 tương tự như BL1, điểm khác biệt chính là quá trình khởi tạo của BL2 đơn giản hơn nhưng có thể cần tải nhiều hình ảnh hơn như BL31, BL32 và BL33. Nhiệm vụ chính của BL2 là thực hiện các thao tác khởi tạo liên quan đến việc tải các hình ảnh tiếp theo (chủ yếu là bộ nhớ DDR, MMU, Nand Flash, cổng nối tiếp và cấu hình môi trường chạy phần mềm EL3), đồng thời chịu trách nhiệm tải firmware tiếp theo vào RAM. Một điểm khác biệt đáng chú ý là BL2 nằm trên Flash như một firmware bên ngoài, sự tin cậy của nó dựa trên quá trình xác minh từ BL1.
Như đã thấy từ phần trước, ở giai đoạn trước đó, toàn bộ mã BL1 chạy ở cấp EL3 và chỉ thực thi trên lõi CPU chính; còn BL2 thì khác, BL2 có thể chạy ở cấp EL3 hoặc S-EL1, nhưng vẫn yêu cầu lõi CPU chính thực thi mã BL2. Cần lưu ý rằng cấp độ ngoại lệ mà BL2 chạy phụ thuộc vào quy trình khởi động hệ thống và thiết kế các giai đoạn boot, nói chung ở cấp độ ngoại lệ thấp hơn sẽ không có quyền truy cập cấp độ cao hơn. Dưới đây chúng ta sẽ lấy ví dụ BL2 chạy ở cấp S-EL1.
Kiểm tra file bl2.ld.S cho thấy hàm nhập của hình ảnh BL2 là bl2_entrypoint, định vị mã tại /atf/bl2/aarch64/bl2_entrypoint.S
function bl2_entrypoint
/*-------------------------------------------------
* Lưu các đối số x0 - x3 từ BL1 để sử dụng sau này.
* -------------------------------------------------
*/
/* Lưu các tham số truyền từ bl1 */
mov x25, x0
mov x26, x1
mov x27, x2
mov x28, x3
/* Thiết lập địa chỉ cơ sở bảng vector ngoại lệ EL1 */
adr x0, early_exception_vectors
msr vbar_el1, x0
isb
/*-------------------------------------------------
* Kích hoạt ngắt SError ngay khi bảng ngoại lệ
* đã được thiết lập.
* -------------------------------------------------
*/
msr daifclr, #DAIF_ERR_BIT
/*-------------------------------------------------
* Kích hoạt cache lệnh, con trỏ ngăn xếp
* và kiểm tra sự căn chỉnh truy cập dữ liệu, tắt
* tải dữ liệu dự đoán.
* -------------------------------------------------
*/
/* Cấu hình các thanh ghi liên quan đến sctlr_el1 */
mov x1, #(SCTLR_INST_BIT | SCTLR_ALIGN_BIT | SCTLR_SP_ALIGN_BIT)
mrs x0, sctlr_el1
orr x0, x0, x1
bic x0, x0, #SCTLR_SPEC_LOAD_BIT
msr sctlr_el1, x0
isb
/* Khởi tạo môi trường runtime C, bao gồm cấu hình bộ nhớ RW,
xóa đoạn BSS, cấu hình ngăn xếp, giống với xử lý của BL1 */
/*-------------------------------------------------
* Vô hiệu hóa bộ nhớ đọc/ghi được sử dụng bởi hình ảnh BL2.
* Điều này bao gồm các đoạn dữ liệu và NOBITS. Việc này được
* thực hiện để bảo vệ chống lại sự hỏng hóc có thể xảy ra
* do các dòng cache bẩn trong cache hệ thống do việc sử dụng
* bởi giai đoạn bootloader trước đó.
* -------------------------------------------------
*/
adr x0, __DATA_START__
adr x1, __DATA_END__
sub x1, x1, x0
bl invalidate_data_cache_range
/*-------------------------------------------------
* Xóa các đoạn NOBITS. Có 2 đoạn như vậy:
* - đoạn .bss;
* - đoạn bộ nhớ đồng nhất.
* -------------------------------------------------
*/
adrp x0, __BSS_START_ADDR__
add x0, x0, :lo12:__BSS_START_ADDR__
adrp x1, __BSS_END_ADDR__
add x1, x1, :lo12:__BSS_END_ADDR__
sub x1, x1, x0
bl clear_memory_region
/*--------------------------------------------------
* Phân bổ ngăn xếp mà bộ nhớ của nó sẽ được đánh dấu
* là Normal-IS-WBWA khi MMU được kích hoạt.
* Không có rủi ro đọc dữ liệu ngăn xếp cũ sau khi
* kích hoạt MMU vì chỉ có CPU chính đang chạy lúc này.
* --------------------------------------------------
*/
bl setup_current_cpu_stack
/*-------------------------------------------------
* Thực hiện thiết lập BL2
* -------------------------------------------------
*/
/* Chuyển đến cấu hình nền tảng BL2 */
mov x0, x25
mov x1, x26
mov x2, x27
mov x3, x28
bl initialize_bl2_platform
/*-------------------------------------------------
* Nhảy đến hàm chính.
* -------------------------------------------------
*/
/* BL2 tải các hình ảnh tiếp theo, hoàn thành việc truyền tham số
và thực hiện nhảy đến hình ảnh tiếp theo */
bl execute_bl2_main
/*-------------------------------------------------
* Không nên đạt đến điểm này.
* -------------------------------------------------
*/
no_return panic_platform_handler
endfunc bl2_entrypoint
Tóm tắt quy trình khởi động BL2 có thể chia thành các phần sau:
3.2.1 Khởi tạo tham số cơ bản cho BL2
(1) Lưu trữ tham số
Thông qua mã nguồn dễ dàng nhận thấy BL1 định nghĩa các thanh ghi x0 – x7 dùng để truyền tham số cho BL2, nhưng ở đây BL2 chỉ lưu x0 - x3 bốn thanh ghi. Và sau đó trước bl2_setup, khôi phục lại các thanh ghi tương ứng, mục đích của việc này là gì?
Theo thông tin từ tài liệu, BL2 thực tế chỉ sử dụng x0-x3 bốn thanh ghi làm tham số đầu vào, do đó số lượng tham số thực tế không vượt quá bốn. Lý do phải lưu lại rồi khôi phục sau là do trong quá trình gọi hàm ARMv8, x0 – 18 là các thanh ghi lưu bởi người gọi (caller saved), x19 – x30 là các thanh ghi lưu bởi người được gọi (callee saved). Các thanh ghi caller saved là những thanh ghi trong quá trình gọi hàm con, nếu nội dung của chúng cần được lưu giữ, thì người gọi hàm phải tự lưu chúng, chương trình con có thể sử dụng tự do các thanh ghi này mà không cần khôi phục giá trị của chúng sau khi gọi xong. Ngược lại các thanh ghi callee saved, nếu chương trình con cần sử dụng, thì phải lưu giá trị gốc trước, sau đó khôi phục sau khi gọi xong. Vì tham số của BL1 được lưu trong các thanh ghi caller x0-x7, cần lưu chúng vào các thanh ghi callee để ngăn chặn việc các cuộc gọi hàm con làm thay đổi các giá trị này.
Vì tham số của BL1 nằm trong các thanh ghi caller, nên cần lưu chúng vào các thanh ghi callee để đảm bảo các cuộc gọi hàm con phía sau không làm thay đổi giá trị của các thanh ghi này.
mov x25, x0
mov x26, x1
mov x27, x2
mov x28, x3
(2) Thiết lập bảng vector ngoại lệ
Đọc cơ chế bảng early_exceptions và cấu hình vào vbar_el1, sau khi thiết lập bảng vector ngoại lệ, BL2 sẽ kích hoạt các ngoại lệ serror và external abort, tương ứng với việc hỗ trợ sử dụng MMU.
adr x0, early_exception_vectors
msr vbar_el1, x0
isb
/*-------------------------------------------------
* Kích hoạt ngắt SError ngay khi bảng ngoại lệ
* đã được thiết lập.
* -------------------------------------------------
*/
msr daifclr, #DAIF_ERR_BIT
(3) Cấu hình thanh ghi sctl_el1
Tham khảo cấu hình sctl_el3 ở giai đoạn BL1, không khó hiểu rằng ở đây đang kích hoạt các tính năng cache lệnh, kiểm tra căn chỉnh và kiểm tra căn chỉnh ngăn xếp:
- SCTLR_SP_ALIGN_BIT: Kiểm tra lỗi căn chỉnh ngăn xếp
- SCTLR_ALIGN_BIT: Kiểm tra lỗi căn chỉnh
- SCTLR_INST_BIT: Bit kích hoạt chức năng cache
mov x1, #(SCTLR_INST_BIT | SCTLR_ALIGN_BIT | SCTLR_SP_ALIGN_BIT)
mrs x0, sctlr_el1
orr x0, x0, x1 /* Lệnh OR logic */
bic x0, x0, #SCTLR_SPEC_LOAD_BIT
msr sctlr_el1, x0
isb
(4) Cấu hình môi trường runtime C
Ở đây có thể giống với _init_c_runtime trong BL1, thực hiện cấu hình đoạn code, xóa đoạn BSS, cấu hình ngăn xếp, v.v., để tránh lặp lại không mở rộng thêm.
3.2.2 Khởi tạo giai đoạn BL2: initialize_bl2_platform
Hãy xem logic khởi tạo của BL2, có thể so sánh với bl1_setup, về cấu trúc đều trỏ đến nền tảng:
void initialize_bl2_platform(u_register_t param0, u_register_t param1, u_register_t param2,
u_register_t param3)
{
/* Thực hiện thiết lập nền tảng cụ thể giai đoạn sớm */
bl2_platform_early_init(param0, param1, param2, param3);
/* Thực hiện thiết lập nền tảng cụ thể giai đoạn muộn */
bl2_platform_arch_init();
#if CTX_INCLUDE_PAUTH_REGS
/*
* Xác nhận rằng các thanh ghi ARMv8.3-PAuth tồn tại hoặc sẽ gây ra
* lỗi truy cập khi chúng được lưu hoặc khôi phục.
*/
assert(check_armv8_3_pauth_availability());
#endif /* CTX_INCLUDE_PAUTH_REGS */
}
Hãy xem những việc nào được thực hiện trong cấu hình nền tảng tương ứng với giai đoạn BL2, khác biệt gì với giai đoạn BL1:
(1) bl2_platform_early_init Thiết lập nền tảng sớm
Vẫn lấy ví dụ thực hiện nền tảng ARM, mã thực hiện và phân tích như sau, công việc nền tảng bao gồm:
- Khởi tạo cổng nối tiếp để cung cấp hỗ trợ gỡ lỗi sớm
- Cấu hình thông tin bố trí bộ nhớ BL2
- Khởi tạo thiết bị IO và đăng ký thiết bị IO nền tảng
- Tải bảng phân vùng GPT
- Khởi tạo hệ thống thời gian nền tảng ARM generic timer (khung thời gian chuẩn ARM cung cấp, bộ đếm thời gian chung)
void arm_bl2_platform_early_init(uintptr_t fw_config,
struct meminfo *memory_layout)
{
int __maybe_unused result;
/* Khởi tạo console */
arm_serial_console_init();
/* Cấu hình bố trí bộ nhớ BL2 */
bl2_secure_ram_layout = *memory_layout;
config_base = fw_config;
/* Khởi tạo thiết bị IO */
platform_arm_io_init();
/* Tải bảng phân vùng GPT */
#if ARM_GPT_SUPPORT
result = gpt_partition_initialization();
if (result != 0) {
ERROR("Khởi tạo phân vùng GPT thất bại!\n");
panic();
}
#endif /* ARM_GPT_SUPPORT */
}
void bl2_platform_early_init(u_register_t param0, u_register_t param1, u_register_t param2, u_register_t param3)
{
arm_bl2_platform_early_init((uintptr_t)param0, (meminfo_t *)param1);
generic_delay_timer_initialization();
}
Việc khởi tạo Generic Timer cũng phụ thuộc vào cấp độ ngoại lệ (EL) mà nó chạy, chi tiết tìm hiểu và cấu hình có thể tham khảo tài liệu dưới đây.
Learn the architecture - Generic Timer
Generic Timer.pdf
(2) bl2_platform_arch_init Thiết lập nền tảng muộn
Kết hợp với việc khởi tạo BL1 không khó hiểu, ở đây là thiết lập bảng trang MMU cho các địa chỉ mà BL2 cần truy cập.
void platform_bl2_arch_init(void)
{
if (!(read_sctlr_el1() & SCTLR_M_BIT)) {
const mmap_region_t bl_regions[] = {
MAP_REGION_FLAT(bl2_secure_ram_layout.total_base,
bl2_secure_ram_layout.total_size,
MT_MEMORY | MT_RW | MT_SECURE),
MAP_REGION_FLAT(BL_CODE_START,
BL_CODE_LIMIT - BL_CODE_START,
MT_CODE | MT_SECURE),
MAP_REGION_FLAT(BL_RO_DATA_START,
BL_RO_DATA_LIMIT - BL_RO_DATA_START,
MT_RO_DATA | MT_SECURE),
{0}
};
setup_page_tables(bl_regions, plat_vendor_get_mmap());
enable_mmu_el1(0);
}
}
void bl2_platform_arch_init(void)
{
plat_vendor_bl2_arch_init(); /* Không có hành động */
platform_bl2_arch_init();
}
3.2.3 Tải hình ảnh BL2: execute_bl2_main
Dưới đây là quy trình tải hình ảnh BL2, kiểm tra nhanh quy trình liên quan cơ bản giống với BL1, không mở rộng chi tiết ở đây.
/* Kích hoạt quyền truy cập thanh ghi fp và simd trên kiến trúc aarch64 */
bl2_architecture_setup();
/* Khởi tạo mô-đun mã hóa, bảo mật giống BL1 */
crypto_module_initialization();
/* Khởi tạo mô-đun xác thực */
auth_module_initialization();
/* Khởi tạo hàm khởi động đo lường */
bl2_platform_measure_boot_init();
/* Khởi tạo cấu hình động */
bl2_platform_preload_configuration();
/* Tải hình ảnh tiếp theo, gọi load_auth_image để thực hiện quy trình
* tải hình ảnh thực tế, giống hoàn toàn với quy trình tải hình ảnh BL1 */
next_boot_ep_info = bl2_load_all_images();
/* Dọn dẹp chương trình khởi động đo lường */
bl2_platform_measure_boot_cleanup();
NOTICE("BL2: Đang khởi động " NEXT_BOOT_IMAGE "\n");
show_entry_point_details(next_boot_ep_info);
/* Làm sạch tất cả dữ liệu trong cổng nối tiếp trước khi thoát BL2 */
console_flush();
/* Chuẩn bị trước khi chạy hình ảnh tiếp theo */
bl2_execute_next_stage(next_boot_ep_info);
3.2.4 Tổng kết
Hai bước quan trọng nhất đều hoàn thành trong hàm execute_bl2_main: khởi tạo phần cứng và tìm BL31.
Trong bl2_platform_preload_configuration() sẽ khởi tạo một loạt phần cứng, bao gồm đọc RCW để khởi tạo Serdes (cổng mạng), mã khởi tạo DDR: dram_init(), ở đây mã khởi tạo DDR là dạng Binary, không có mã nguồn, việc khởi tạo DDR4 chỉ thiết lập thanh ghi timing và Kích thước, không có quá trình Training bộ nhớ. Sau khi BL2 khởi tạo phần cứng, bắt đầu tìm kiếm các hình ảnh BL3: BL31, BL32 và BL33. Nó tìm BL31 trước, xác minh chữ ký nó, cuối cùng chuyển sang BL31.
BL2 nằm trong SRAM, chạy ở Secure EL1, công việc chính bao gồm:
- Khởi tạo kiến trúc: Kích hoạt EL1/EL0.
- Khởi tạo nền tảng: Khởi tạo console (cổng nối tiếp), khởi tạo thiết bị lưu trữ, MMU, cấu hình bảo mật thiết bị liên quan
- SCP_BL2: Tải hình ảnh lõi điều khiển hệ thống, xử lý riêng điều khiển tiêu thụ điện, đồng hồ, reset, v.v.
- Tải hình ảnh BL31: BL2 tắt MMU và cache; chuyển quyền điều khiển cho BL31. BL2 chuyển quyền điều khiển cho BL1; BL1 tắt MMU và cache; BL1 chuyển quyền điều khiển cho BL31. (EL3>S.EL1>EL3)
- Tải hình ảnh BL32: BL32 chạy trong thế giới an toàn, BL2 dựa vào BL31 để chuyển quyền điều khiển cho BL32. SPSR được khởi tạo thông qua Secure-EL1 Payload Dispatcher. (EL3>S.EL1>S.EL0>EL3)
- Tải hình ảnh BL33: BL2 dựa vào BL31 để chuyển quyền điều khiển cho BL33. (EL3>N.EL1)