Подготовка к работе с Rust
Прежде чем мы сможем запускать код Rust, нужно выполнить некоторую инициализацию.
/**
* This is a generic entry point for an image. It carries out the
* operations required to prepare the loaded image to be run.
* Specifically, it
*
* - sets up the MMU with an identity map of virtual to physical
* addresses, and enables caching
* - enables floating point
* - zeroes the bss section using registers x25 and above
* - prepares the stack, pointing to a section within the image
* - sets up the exception vector
* - branches to the Rust `main` function
*
* It preserves x0-x3 for the Rust entry point, as these may contain
* boot parameters.
*/
.section .init.entry, "ax"
.global entry
entry:
/*
* Load and apply the memory management configuration, ready to
* enable MMU and caches.
*/
adrp x30, idmap
msr ttbr0_el1, x30
mov_i x30, .Lmairval
msr mair_el1, x30
mov_i x30, .Ltcrval
/* Copy the supported PA range into TCR_EL1.IPS. */
mrs x29, id_aa64mmfr0_el1
bfi x30, x29, #32, #4
msr tcr_el1, x30
mov_i x30, .Lsctlrval
/*
* Ensure everything before this point has completed, then
* invalidate any potentially stale local TLB entries before they
* start being used.
*/
isb
tlbi vmalle1
ic iallu
dsb nsh
isb
/*
* Configure sctlr_el1 to enable MMU and cache and don't proceed
* until this has completed.
*/
msr sctlr_el1, x30
isb
/* Disable trapping floating point access in EL1. */
mrs x30, cpacr_el1
orr x30, x30, #(0x3 << 20)
msr cpacr_el1, x30
isb
/* Zero out the bss section. */
adr_l x29, bss_begin
adr_l x30, bss_end
0: cmp x29, x30
b.hs 1f
stp xzr, xzr, [x29], #16
b 0b
1: /* Prepare the stack. */
adr_l x30, boot_stack_end
mov sp, x30
/* Set up exception vector. */
adr x30, vector_table_el1
msr vbar_el1, x30
/* Call into Rust code. */
bl main
/* Loop forever waiting for interrupts. */
2: wfi
b 2b
Этот код находится в src/bare-metal/aps/examples/src/entry.S. Понимать его в деталях не обязательно — главное, что для выполнения ожиданий Rust о системе нужна некоторая низкоуровневая настройка.
- Это то же самое, что было бы для C: инициализация состояния процессора, обнуление BSS и настройка указателя стека.
- BSS (block starting symbol, по историческим причинам) — часть объектного файла, которая содержит статически выделенные переменные, инициализированные нулём. Их не включают в образ, чтобы не тратить место на нули. Компилятор предполагает, что загрузчик позаботится об их обнулении.
- BSS может быть уже обнулён в зависимости от того, как инициализируется память и загружается образ, но мы обнуляем его, чтобы быть уверенными.
- Нам нужно включить MMU и кэш до чтения или записи любой памяти. Если этого не сделать:
- Невыровненный доступ вызовет ошибку. Мы собираем код Rust для цели
aarch64-unknown-noneс+strict-align, чтобы компилятор не генерировал невыровненных обращений, поэтому в данном случае всё должно быть в порядке, но в общем случае это не обязательно так. - Если бы это работало в виртуальной машине, это могло бы привести к проблемам когерентности кэша. Проблема в том, что ВМ обращается к памяти напрямую при выключенном кэше, а у хоста есть кэшируемые алиасы к той же памяти. Даже если хост явно не обращается к памяти, спекулятивные обращения могут привести к заполнению кэша, и тогда изменения одной из сторон будут потеряны при очистке кэша или включении кэша ВМ. (Кэш индексируется по физическому адресу, а не по VA или IPA.)
- Невыровненный доступ вызовет ошибку. Мы собираем код Rust для цели
- Для простоты мы используем жёстко заданную таблицу страниц (см.
idmap.S), которая отображает тождественно первый 1 ГиБ адресного пространства для устройств, следующий 1 ГиБ для DRAM и ещё 1 ГиБ выше для других устройств. Это соответствует раскладке памяти, которую использует QEMU. - Мы также настраиваем вектор исключений (
vbar_el1), о котором подробнее поговорим позже. - Все примеры сегодня днём предполагают, что мы работаем на уровне исключений 1 (EL1). Если нужно работать на другом уровне исключений, нужно соответствующим образом изменить
entry.S.