Підготовка до Rust

Перш ніж ми зможемо запускати код Rust, нам потрібно виконати деяку ініціалізацію.

.section .init.entry, "ax"
.global entry
entry:
    /*
     * Завантаження та застосування конфігурації керування пам'яттю, готової
     * до ввімкнення MMU та кешів.
     */
    adrp x30, idmap
    msr ttbr0_el1, x30

    mov_i x30, .Lmairval
    msr mair_el1, x30

    mov_i x30, .Ltcrval
    /* Скопіювати підтримуваний діапазон PA у TCR_EL1.IPS. */
    mrs x29, id_aa64mmfr0_el1
    bfi x30, x29, #32, #4

    msr tcr_el1, x30

    mov_i x30, .Lsctlrval

    /*
     * Перевірити все до завершення цього пункту, а потім зробити недійсними всі
     * потенційно застарілі локальні записи TLB до того, як вони почнуть використовуватися.
     */
    isb
    tlbi vmalle1
    ic iallu
    dsb nsh
    isb

    /*
     * Налаштувати sctlr_el1 на ввімкнення MMU та кешу і не продовжувати,  доки це
     * не буде зроблено.
     */
    msr sctlr_el1, x30
    isb

    /* Вимкнути перехоплення доступу з плаваючою комою в EL1. */
    mrs x30, cpacr_el1
    orr x30, x30, #(0x3 << 20)
    msr cpacr_el1, x30
    isb

    /* Обнуліть секцію bss. */
    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:  /* Підготувати стек. */
    adr_l x30, boot_stack_end
    mov sp, x30

    /* Налаштування вектора виключень. */
    adr x30, vector_table_el1
    msr vbar_el1, x30

    /* Виклик коду Rust. */
    bl main

    /* Постійно циклічно чекаємо на переривання. */
2:  wfi
    b 2b
  • Це те саме, що було б для C: ініціалізація стану процесора, обнулення BSS і налаштування покажчика стека.
    • BSS (символ початку блоку, з історичних причин) — це частина об’єктного файлу, яка містить статично виділені змінні, які ініціалізуються нулем. Вони пропущені на зображенні, щоб не витрачати місце на зайві нулі. Компілятор припускає, що завантажувач подбає про їх обнулення.
  • BSS може бути вже обнулено, залежно від того, як ініціалізовано пам’ять і завантажено зображення, але ми обнуляємо його, щоб бути впевненими.
  • Нам потрібно ввімкнути MMU та кеш перед читанням або записом пам’яті. Якщо ми цього не зробимо:
    • Невирівняні доступи призведуть до помилки. Ми створюємо код Rust для цілі aarch64-unknown-none, яка встановлює +strict-align, щоб запобігти створенню компілятором невирівняних доступів, тому в цьому випадку це має бути гаразд, але це не обов’язково так загалом.
    • Якщо це було запущено у віртуальній машині, це може призвести до проблеми з узгодженістю кешу. Проблема полягає в тому, що віртуальна машина звертається до пам'яті безпосередньо з вимкненим кешем, в той час як хост має кешовані псевдоніми до тієї ж пам'яті. Навіть якщо хост не має явного доступу до пам’яті, спекулятивні доступи можуть призвести до заповнення кешу, а потім зміни з того чи іншого будуть втрачені, коли кеш буде очищено або віртуальна машина ввімкне кеш. (Кеш використовується за фізичною адресою, а не VA чи IPA.)
  • Для спрощення ми просто використовуємо жорстко закодовану таблицю сторінок (дивиться idmap.S), яка ідентифікує перший 1 ГіБ адресного простору для пристроїв, наступний 1 ГіБ для DRAM і ще 1 ГіБ вище для інших пристроїв. Це відповідає розміщенню пам'яті, яке використовує QEMU.
  • Ми також встановили вектор виключень (vbar_el1), про який ми розповімо більше пізніше.
  • Усі приклади цього дня припускають, що ми будемо працювати на рівні виключення 1 (EL1). Якщо вам потрібно запустити на іншому рівні виключення, вам потрібно буде відповідно змінити entry.S.