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

Speaker Notes

  • 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.