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.)
- Os acessos não alinhados falharão. Construímos o código Rust para o alvo
- 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.