Preparando-se para o Rust

Antes de podermos começar a executar o código Rust, precisamos fazer alguma

.section .init.entry, "ax"
.global entry
entry:
    /*
     * Carregue e aplique a configuração de gerenciamento de memória, pronto para
     * habilitar MMU e caches.
    adrp x30, idmap
    msr ttbr0_el1, x30

    mov_i x30, .Lmairval
    msr mair_el1, x30

    mov_i x30, .Ltcrval
    /* Copie o intervalo de PA suportado para TCR_EL1.IPS. */
    mrs x29, id_aa64mmfr0_el1
    bfi x30, x29, #32, #4

    msr tcr_el1, x30

    mov_i x30, .Lsctlrval

    /*
     * Garanta que tudo antes deste ponto tenha sido concluído, então invalida
     * quaisquer entradas locais de TLB potencialmente obsoletas antes que elas
     */
    isb
    tlbi vmalle1
    ic iallu
    dsb nsh
    isb

    /*
     * Configure sctlr_el1 para habilitar MMU e cache e não prossiga até isto
     * tenha sido concluído.
     */
    msr sctlr_el1, x30
    isb

    /* Desative a captura de acesso de ponto flutuante em EL1. */
    mrs x30, cpacr_el1
    orr x30, x30, #(0x3 << 20)
    msr cpacr_el1, x30
    isb

    /* Zere a seção 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:  /* Prepare a pilha. */
    adr_l x30, boot_stack_end
    mov sp, x30

    /* Configure o vetor de exceção. */
    adr x30, vector_table_el1
    msr vbar_el1, x30

    /* Chame o código Rust. */
    bl main

    /* Loop infinito esperando por interrupções. */
2:  wfi
    b 2b
  • Isso é o mesmo que seria para C: inicializando o estado do processador, zerando o BSS e configurando o ponteiro da pilha.
    • O BSS (bloco de símbolo inicial, por razões históricas) é a parte do arquivo objeto que contém variáveis alocadas estaticamente que são inicializadas como zero. Eles são omitidos da imagem, para evitar desperdício de espaço em zeros. O compilador assume que o carregador cuidará de zerá-los.
  • O BSS pode já estar zerado, dependendo de como a memória é inicializada e a imagem é carregada, mas o zeramos para ter certeza.
  • É necessário habilitar a MMU e o cache antes de ler ou gravar qualquer memória. Se não fizermos isso:
    • Os acessos não alinhados falharão. Construímos o código Rust para o alvo aarch64-unknown-none que define +strict-align para evitar que o compilador gere acessos não alinhados, portanto, deve estar tudo bem neste caso, mas este não é necessariamente o caso em geral.
    • Se estivesse sendo executado em uma VM, isso pode levar a problemas de coerência de cache. O problema é que a VM está acessando a memória diretamente com o cache desabilitado, enquanto o host tem aliases cacheáveis ​​para a mesma memória. Mesmo que o host não acesse explicitamente a memória, acessos especulativos podem levar a preenchimentos de cache e, em seguida, alterações de um ou de outro serão perdidas quando o cache for limpo ou a VM habilitar o cache. (O cache é indexado pelo endereço físico, não VA ou IPA.)
  • Para simplificar, usamos apenas uma tabela de páginas codificada (consulte idmap.S) que mapeia a identidade dos primeiros 1 GiB do espaço de endereços para dispositivos, os próximos 1 GiB para DRAM e mais 1 GiB mais acima para mais dispositivos. Isso corresponde ao layout de memória que o QEMU usa.
  • Também configuramos o vetor de exceção (vbar_el1), que veremos mais tarde.
  • Todos os exemplos desta tarde assumem que estaremos executando no nível de exceção 1 (EL1). Se você precisar executar em um nível de exceção diferente, você precisará modificar entry.S de acordo.