| 1 | /* |
| 2 | * Minimal AArch64 system boot code. |
| 3 | * |
| 4 | * Copyright Linaro Ltd 2019 |
| 5 | * |
| 6 | * Loosely based on the newlib/libgloss setup stubs. Using semihosting |
| 7 | * for serial output and exit functions. |
| 8 | */ |
| 9 | |
| 10 | /* |
| 11 | * Semihosting interface on ARM AArch64 |
| 12 | * See "Semihosting for AArch32 and AArch64 Release 2.0" by ARM |
| 13 | * w0 - semihosting call number |
| 14 | * x1 - semihosting parameter |
| 15 | */ |
| 16 | #define semihosting_call hlt 0xf000 |
| 17 | #define SYS_WRITEC 0x03 /* character to debug channel */ |
| 18 | #define SYS_WRITE0 0x04 /* string to debug channel */ |
| 19 | #define SYS_GET_CMDLINE 0x15 /* get command line */ |
| 20 | #define SYS_EXIT 0x18 |
| 21 | |
| 22 | .align 12 |
| 23 | |
| 24 | .macro ventry label |
| 25 | .align 7 |
| 26 | b \label |
| 27 | .endm |
| 28 | |
| 29 | vector_table: |
| 30 | /* Current EL with SP0. */ |
| 31 | ventry curr_sp0_sync /* Synchronous */ |
| 32 | ventry curr_sp0_irq /* Irq/vIRQ */ |
| 33 | ventry curr_sp0_fiq /* Fiq/vFIQ */ |
| 34 | ventry curr_sp0_serror /* SError/VSError */ |
| 35 | |
| 36 | /* Current EL with SPx. */ |
| 37 | ventry curr_spx_sync /* Synchronous */ |
| 38 | ventry curr_spx_irq /* IRQ/vIRQ */ |
| 39 | ventry curr_spx_fiq /* FIQ/vFIQ */ |
| 40 | ventry curr_spx_serror /* SError/VSError */ |
| 41 | |
| 42 | /* Lower EL using AArch64. */ |
| 43 | ventry lower_a64_sync /* Synchronous */ |
| 44 | ventry lower_a64_irq /* IRQ/vIRQ */ |
| 45 | ventry lower_a64_fiq /* FIQ/vFIQ */ |
| 46 | ventry lower_a64_serror /* SError/VSError */ |
| 47 | |
| 48 | /* Lower EL using AArch32. */ |
| 49 | ventry lower_a32_sync /* Synchronous */ |
| 50 | ventry lower_a32_irq /* IRQ/vIRQ */ |
| 51 | ventry lower_a32_fiq /* FIQ/vFIQ */ |
| 52 | ventry lower_a32_serror /* SError/VSError */ |
| 53 | |
| 54 | .text |
| 55 | .align 4 |
| 56 | |
| 57 | /* Common vector handling for now */ |
| 58 | curr_sp0_sync: |
| 59 | curr_sp0_irq: |
| 60 | curr_sp0_fiq: |
| 61 | curr_sp0_serror: |
| 62 | curr_spx_sync: |
| 63 | #ifdef LOGGING_VECTOR_TABLE |
| 64 | sub sp, sp, #16 |
| 65 | stp x0, x1, [sp, #0] |
| 66 | mrs x0, ESR_EL3 |
| 67 | lsr x0, x0, #26 |
| 68 | and x0, x0, #0x3f |
| 69 | cmp x0, #37 |
| 70 | beq data_fault |
| 71 | cmp x0, #30 |
| 72 | beq gpc_fault |
| 73 | b generic_exception |
| 74 | |
| 75 | data_fault: |
| 76 | mrs x0, FAR_EL3 |
| 77 | adrp x1, exception_log |
| 78 | str x0, [x1] |
| 79 | ldr x0, =0x1001 |
| 80 | str x0, [x1, #8] |
| 81 | b skip_return |
| 82 | gpc_fault: |
| 83 | mrs x0, FAR_EL3 |
| 84 | adrp x1, exception_log |
| 85 | str x0, [x1] |
| 86 | ldr x0, =0x1002 |
| 87 | str x0, [x1, #8] |
| 88 | /* Fall through */ |
| 89 | skip_return: |
| 90 | mrs x0, ELR_EL3 |
| 91 | add x0, x0, #4 /* Skip faulting instruction */ |
| 92 | msr ELR_EL3, x0 |
| 93 | ldp x0, x1, [sp, #0] |
| 94 | add sp, sp, #16 |
| 95 | eret |
| 96 | #endif |
| 97 | curr_spx_irq: |
| 98 | curr_spx_fiq: |
| 99 | curr_spx_serror: |
| 100 | lower_a64_sync: |
| 101 | lower_a64_irq: |
| 102 | lower_a64_fiq: |
| 103 | lower_a64_serror: |
| 104 | lower_a32_sync: |
| 105 | lower_a32_irq: |
| 106 | lower_a32_fiq: |
| 107 | lower_a32_serror: |
| 108 | generic_exception: |
| 109 | adr x1, .unexp_excp |
| 110 | exit_msg: |
| 111 | mov x0, SYS_WRITE0 |
| 112 | semihosting_call |
| 113 | mov x0, 1 /* EXIT_FAILURE */ |
| 114 | bl _exit |
| 115 | /* never returns */ |
| 116 | |
| 117 | .section .rodata |
| 118 | .unexp_excp: |
| 119 | .string "Unexpected exception.\n" |
| 120 | .high_el_msg: |
| 121 | .string "Started in lower EL than requested.\n" |
| 122 | .unexp_el0: |
| 123 | .string "Started in invalid EL.\n" |
| 124 | |
| 125 | .align 8 |
| 126 | .get_cmd: |
| 127 | .quad cmdline |
| 128 | .quad 128 |
| 129 | |
| 130 | .text |
| 131 | .align 4 |
| 132 | .global __start |
| 133 | __start: |
| 134 | /* |
| 135 | * Initialise the stack for whatever EL we are in before |
| 136 | * anything else, we need it to be able to _exit cleanly. |
| 137 | * It's smaller than the stack we pass to the C code but we |
| 138 | * don't need much. |
| 139 | */ |
| 140 | adrp x0, system_stack_end |
| 141 | add x0, x0, :lo12:system_stack_end |
| 142 | mov sp, x0 |
| 143 | |
| 144 | /* |
| 145 | * The test can set the semihosting command line to the target |
| 146 | * EL needed for the test. However if no semihosting args are set we will |
| 147 | * end up with -kernel/-append data (see semihosting_arg_fallback). |
| 148 | * Keep the normalised target in w11. |
| 149 | */ |
| 150 | mov x0, SYS_GET_CMDLINE |
| 151 | adr x1, .get_cmd |
| 152 | semihosting_call |
| 153 | adrp x10, cmdline |
| 154 | add x10, x10, :lo12:cmdline |
| 155 | ldrb w11, [x10] |
| 156 | |
| 157 | /* sanity check, normalise char to EL, clamp to 1 if outside range */ |
| 158 | subs w11, w11, #'0' |
| 159 | b.lt el_default |
| 160 | cmp w11, #3 |
| 161 | b.gt el_default |
| 162 | b 1f |
| 163 | |
| 164 | el_high: |
| 165 | adr x1, .high_el_msg |
| 166 | b exit_msg |
| 167 | |
| 168 | el_default: |
| 169 | mov w11, #1 |
| 170 | |
| 171 | 1: |
| 172 | /* Determine current Exception Level */ |
| 173 | mrs x0, CurrentEL |
| 174 | lsr x0, x0, #2 /* CurrentEL[3:2] contains the current EL */ |
| 175 | |
| 176 | /* Are we already in a lower EL than we want? */ |
| 177 | cmp w11, w0 |
| 178 | bgt el_high |
| 179 | |
| 180 | /* Branch based on current EL */ |
| 181 | cmp x0, #3 |
| 182 | b.eq setup_el3 |
| 183 | cmp x0, #2 |
| 184 | b.eq setup_el2 |
| 185 | cmp x0, #1 |
| 186 | b.eq at_testel /* Already at EL1, skip transition */ |
| 187 | |
| 188 | /* Should not be at EL0 - error out */ |
| 189 | adr x1, .unexp_el0 |
| 190 | b exit_msg |
| 191 | |
| 192 | setup_el3: |
| 193 | /* Ensure we trap if we get anything wrong */ |
| 194 | adr x0, vector_table |
| 195 | msr vbar_el3, x0 |
| 196 | |
| 197 | /* Does the test want to be at EL3? */ |
| 198 | cmp w11, #3 |
| 199 | beq at_testel |
| 200 | |
| 201 | /* Configure EL3 to for lower states (EL2 or EL1) */ |
| 202 | mrs x0, scr_el3 |
| 203 | orr x0, x0, #(1 << 10) /* RW = 1: EL2/EL1 execution state is AArch64 */ |
| 204 | orr x0, x0, #(1 << 0) /* NS = 1: Non-secure state */ |
| 205 | msr scr_el3, x0 |
| 206 | |
| 207 | /* |
| 208 | * We need to check if EL2 is actually enabled via ID_AA64PFR0_EL1, |
| 209 | * otherwise we should just jump straight to EL1. |
| 210 | */ |
| 211 | mrs x0, id_aa64pfr0_el1 |
| 212 | ubfx x0, x0, #8, #4 /* Extract EL2 field (bits 11:8) */ |
| 213 | cbz x0, el2_not_present /* If field is 0 no EL2 */ |
| 214 | |
| 215 | |
| 216 | /* Prepare SPSR for exception return to EL2 */ |
| 217 | mov x0, #0x3c9 /* DAIF bits and EL2h mode (9) */ |
| 218 | msr spsr_el3, x0 |
| 219 | |
| 220 | /* Set EL2 entry point */ |
| 221 | adr x0, setup_el2 |
| 222 | msr elr_el3, x0 |
| 223 | |
| 224 | /* Return to EL2 */ |
| 225 | eret |
| 226 | |
| 227 | el2_not_present: |
| 228 | /* Initialize SCTLR_EL1 with reset value */ |
| 229 | msr sctlr_el1, xzr |
| 230 | |
| 231 | /* Set EL1 entry point */ |
| 232 | adr x0, at_testel |
| 233 | msr elr_el3, x0 |
| 234 | |
| 235 | /* Prepare SPSR for exception return to EL1h with interrupts masked */ |
| 236 | mov x0, #0x3c5 /* DAIF bits and EL1h mode (5) */ |
| 237 | msr spsr_el3, x0 |
| 238 | |
| 239 | isb /* Synchronization barrier */ |
| 240 | eret /* Jump to EL1 */ |
| 241 | |
| 242 | setup_el2: |
| 243 | /* Ensure we trap if we get anything wrong */ |
| 244 | adr x0, vector_table |
| 245 | msr vbar_el2, x0 |
| 246 | |
| 247 | /* Does the test want to be at EL2? */ |
| 248 | cmp w11, #2 |
| 249 | beq at_testel |
| 250 | |
| 251 | /* Configure EL2 to allow transition to EL1 */ |
| 252 | mrs x0, hcr_el2 |
| 253 | orr x0, x0, #(1 << 31) /* RW = 1: EL1 execution state is AArch64 */ |
| 254 | msr hcr_el2, x0 |
| 255 | |
| 256 | /* Initialize SCTLR_EL1 with reset value */ |
| 257 | msr sctlr_el1, xzr |
| 258 | |
| 259 | /* Set EL1 entry point */ |
| 260 | adr x0, at_testel |
| 261 | msr elr_el2, x0 |
| 262 | |
| 263 | /* Prepare SPSR for exception return to EL1 */ |
| 264 | mov x0, #(0x5 << 0) /* EL1h (SPx), with interrupts disabled */ |
| 265 | msr spsr_el2, x0 |
| 266 | |
| 267 | /* Return to EL1 */ |
| 268 | eret |
| 269 | |
| 270 | /* |
| 271 | * At the target EL for the test, usually EL1. Note we still |
| 272 | * set everything up as if we were at EL1. |
| 273 | */ |
| 274 | at_testel: |
| 275 | /* Installs a table of exception vectors to catch and handle all |
| 276 | exceptions by terminating the process with a diagnostic. */ |
| 277 | adr x0, vector_table |
| 278 | msr vbar_el1, x0 |
| 279 | |
| 280 | /* Page table setup (identity mapping). */ |
| 281 | adrp x0, ttb |
| 282 | add x0, x0, :lo12:ttb |
| 283 | msr ttbr0_el1, x0 |
| 284 | |
| 285 | /* |
| 286 | * Setup a flat address mapping page-tables. Stage one simply |
| 287 | * maps RAM to the first Gb. The stage2 tables have two 2mb |
| 288 | * translation block entries covering a series of adjacent |
| 289 | * 4k pages. |
| 290 | */ |
| 291 | |
| 292 | /* Stage 1 entry: indexed by IA[38:30] */ |
| 293 | adr x1, . /* phys address */ |
| 294 | bic x1, x1, #(1 << 30) - 1 /* 1GB alignment*/ |
| 295 | add x2, x0, x1, lsr #(30 - 3) /* offset in l1 page table */ |
| 296 | |
| 297 | /* point to stage 2 table [47:12] */ |
| 298 | adrp x0, ttb_stage2 |
| 299 | orr x1, x0, #3 /* ptr to stage 2 */ |
| 300 | str x1, [x2] |
| 301 | |
| 302 | /* Stage 2 entries: indexed by IA[29:21] */ |
| 303 | ldr x5, =(((1 << 9) - 1) << 21) |
| 304 | |
| 305 | /* First block: .text/RO/execute enabled */ |
| 306 | adr x1, . /* phys address */ |
| 307 | bic x1, x1, #(1 << 21) - 1 /* 2mb block alignment */ |
| 308 | and x4, x1, x5 /* IA[29:21] */ |
| 309 | add x2, x0, x4, lsr #(21 - 3) /* offset in l2 page table */ |
| 310 | ldr x3, =0x401 /* attr(AF, block) */ |
| 311 | orr x1, x1, x3 |
| 312 | str x1, [x2] /* 1st 2mb (.text & rodata) */ |
| 313 | |
| 314 | /* Second block: .data/RW/no execute */ |
| 315 | adrp x1, .data |
| 316 | add x1, x1, :lo12:.data |
| 317 | bic x1, x1, #(1 << 21) - 1 /* 2mb block alignment */ |
| 318 | and x4, x1, x5 /* IA[29:21] */ |
| 319 | add x2, x0, x4, lsr #(21 - 3) /* offset in l2 page table */ |
| 320 | ldr x3, =(3 << 53) | 0x401 /* attr(AF, NX, block) */ |
| 321 | orr x1, x1, x3 |
| 322 | str x1, [x2] /* 2nd 2mb (.data & .bss)*/ |
| 323 | |
| 324 | /* Third block: at 'mte_page', set in kernel.ld */ |
| 325 | adrp x1, mte_page |
| 326 | add x1, x1, :lo12:mte_page |
| 327 | bic x1, x1, #(1 << 21) - 1 |
| 328 | and x4, x1, x5 |
| 329 | add x2, x0, x4, lsr #(21 - 3) |
| 330 | /* attr(AF, NX, block, AttrIndx=Attr1) */ |
| 331 | ldr x3, =(3 << 53) | 0x401 | (1 << 2) |
| 332 | orr x1, x1, x3 |
| 333 | str x1, [x2] |
| 334 | |
| 335 | /* Setup/enable the MMU. */ |
| 336 | |
| 337 | /* |
| 338 | * TCR_EL1 - Translation Control Registers |
| 339 | * |
| 340 | * IPS[34:32] = 40-bit PA, 1TB |
| 341 | * TG0[14:15] = b00 => 4kb granuale |
| 342 | * ORGN0[11:10] = Outer: Normal, WB Read-Alloc No Write-Alloc Cacheable |
| 343 | * IRGN0[9:8] = Inner: Normal, WB Read-Alloc No Write-Alloc Cacheable |
| 344 | * T0SZ[5:0] = 2^(64 - 25) |
| 345 | * |
| 346 | * The size of T0SZ controls what the initial lookup level. It |
| 347 | * would be nice to start at level 2 but unfortunately for a |
| 348 | * flat-mapping on the virt machine we need to handle IA's |
| 349 | * with at least 1gb range to see RAM. So we start with a |
| 350 | * level 1 lookup. |
| 351 | */ |
| 352 | ldr x0, = (2 << 32) | 25 | (3 << 10) | (3 << 8) |
| 353 | msr tcr_el1, x0 |
| 354 | |
| 355 | mov x0, #0xee /* Inner/outer cacheable WB */ |
| 356 | msr mair_el1, x0 |
| 357 | isb |
| 358 | |
| 359 | /* |
| 360 | * SCTLR_EL1 - System Control Register |
| 361 | * |
| 362 | * WXN[19] = 0 = no effect, Write does not imply XN (execute never) |
| 363 | * I[12] = Instruction cachability control |
| 364 | * SA[3] = SP alignment check |
| 365 | * C[2] = Data cachability control |
| 366 | * M[0] = 1, enable stage 1 address translation for EL0/1 |
| 367 | */ |
| 368 | mrs x0, sctlr_el1 |
| 369 | ldr x1, =0x100d /* bits I(12) SA(3) C(2) M(0) */ |
| 370 | bic x0, x0, #(1 << 1) /* clear bit A(1) */ |
| 371 | bic x0, x0, #(1 << 19) /* clear WXN */ |
| 372 | orr x0, x0, x1 /* set bits */ |
| 373 | |
| 374 | dsb sy |
| 375 | msr sctlr_el1, x0 |
| 376 | isb |
| 377 | |
| 378 | /* |
| 379 | * Enable FP/SVE registers. The standard C pre-amble will be |
| 380 | * saving these and A-profile compilers will use AdvSIMD |
| 381 | * registers unless we tell it not to. |
| 382 | */ |
| 383 | mrs x0, cpacr_el1 |
| 384 | orr x0, x0, #(3 << 20) |
| 385 | orr x0, x0, #(3 << 16) |
| 386 | msr cpacr_el1, x0 |
| 387 | |
| 388 | /* |
| 389 | * Setup some stack space before we enter the test code. |
| 390 | * Assume everything except the return value is garbage when we |
| 391 | * return, we won't need it. |
| 392 | */ |
| 393 | adrp x0, stack_end |
| 394 | add x0, x0, :lo12:stack_end |
| 395 | mov sp, x0 |
| 396 | bl main |
| 397 | |
| 398 | /* pass return value to sys exit */ |
| 399 | _exit: |
| 400 | mov x1, x0 |
| 401 | ldr x0, =0x20026 /* ADP_Stopped_ApplicationExit */ |
| 402 | stp x0, x1, [sp, #-16]! |
| 403 | mov x1, sp |
| 404 | mov x0, SYS_EXIT |
| 405 | semihosting_call |
| 406 | /* never returns */ |
| 407 | |
| 408 | /* |
| 409 | * Helper Functions |
| 410 | */ |
| 411 | |
| 412 | /* Output a single character to serial port */ |
| 413 | .global __sys_outc |
| 414 | __sys_outc: |
| 415 | stp x0, x1, [sp, #-16]! |
| 416 | /* pass address of c on stack */ |
| 417 | mov x1, sp |
| 418 | mov x0, SYS_WRITEC |
| 419 | semihosting_call |
| 420 | ldp x0, x1, [sp], #16 |
| 421 | ret |
| 422 | |
| 423 | .data |
| 424 | |
| 425 | .align 8 |
| 426 | cmdline: |
| 427 | .space 128, 0 |
| 428 | |
| 429 | .align 12 |
| 430 | |
| 431 | /* Translation table |
| 432 | * @4k granuale: 9 bit lookup, 512 entries |
| 433 | */ |
| 434 | ttb: |
| 435 | .space 4096, 0 |
| 436 | |
| 437 | .align 12 |
| 438 | ttb_stage2: |
| 439 | .space 4096, 0 |
| 440 | |
| 441 | .align 12 |
| 442 | .global realms_gpt0 |
| 443 | /* GPT stage 0 table */ |
| 444 | realms_gpt0: |
| 445 | .space 4096, 0 |
| 446 | .align 17 |
| 447 | .global realms_gpt1 |
| 448 | /* GPT stage 1 table, initialised to all 0xFF (full access) */ |
| 449 | realms_gpt1: |
| 450 | .space 524288, 0xFF |
| 451 | #ifdef LOGGING_VECTOR_TABLE |
| 452 | .align 12 |
| 453 | .global exception_fault_address |
| 454 | .type exception_fault_address, @object |
| 455 | .size exception_fault_address, 8 |
| 456 | .global exception_type_code |
| 457 | .type exception_type_code, @object |
| 458 | .size exception_type_code, 8 |
| 459 | /* |
| 460 | * These fields record details of the last exception, if |
| 461 | * LOGGING_VECTOR_TABLE is defined. |
| 462 | */ |
| 463 | exception_log: |
| 464 | exception_fault_address: |
| 465 | /* The contents of FAR_EL3 when an exception is taken. */ |
| 466 | .space 8, 0 |
| 467 | exception_type_code: |
| 468 | /* A generic code indicating what type of exception occurred. */ |
| 469 | .space 8, 0 |
| 470 | #endif |
| 471 | .align 12 |
| 472 | system_stack: |
| 473 | .space 4096, 0 |
| 474 | system_stack_end: |
| 475 | |
| 476 | stack: |
| 477 | .space 65536, 0 |
| 478 | stack_end: |