|
.global _start |
|
|
|
.arch armv8-a |
|
.cpu cortex-a53 |
|
|
|
|
|
.set AUX_IRQ, 0x0000 // Auxiliary Interrupt Status |
|
.set AUX_ENABLES, 0x0004 // Auxiliary Enables |
|
.set AUX_MU_IO_REG, 0x0040 // Mini Uart I/O Data |
|
.set AUX_MU_IER, 0x0044 // Mini Uart Interrupt Enable |
|
.set AUX_MU_IIR, 0x0048 // Mini Uart Interrupt Identify |
|
.set AUX_MU_LCR, 0x004C // Mini Uart Line Control |
|
.set AUX_MU_MCR, 0x0050 // Mini Uart Modem Control |
|
.set AUX_MU_LSR, 0x0054 // Mini Uart Line Status |
|
.set AUX_MU_MSR, 0x0058 // Mini Uart Modem Status |
|
.set AUX_MU_SCRATCH, 0x005C // Mini Uart Scratch |
|
.set AUX_MU_CNTL, 0x0060 // Mini Uart Extra Control |
|
.set AUX_MU_STAT, 0x0064 // Mini Uart Extra Status |
|
.set AUX_MU_BAUD, 0x0068 // Mini Uart Baudrate |
|
|
|
|
|
# ------------------------------------------------------------------------------ |
|
# See "BCM2837 ARM Peripherals" datasheet pages 90-104: |
|
# https://cs140e.sergio.bz/docs/BCM2837-ARM-Peripherals.pdf |
|
# ------------------------------------------------------------------------------ |
|
.set GPFSEL1, 0x0004 // GPIO Function Select 1 |
|
.set GPPUD, 0x0094 // GPIO Pin Pull-up/down Enable |
|
.set GPPUDCLK0, 0x0098 // GPIO Pin Pull-up/down Enable Clock 0 |
|
|
|
|
|
.text |
|
.align 2 |
|
_start: |
|
mrs x0, mpidr_el1 // x0 = Multiprocessor Affinity Register value. |
|
ands x0, x0, #0x3 // x0 = core number. |
|
b.ne sleep // Put all cores except core 0 to sleep. |
|
|
|
ldr x0, =0x30d00800 |
|
msr sctlr_el1, x0 // Update "System Control Register (EL1)": |
|
// set RES:1 bits (11, 20, 22, 23, 28, 29) |
|
// disable caches (bits 2, 12) |
|
mov x0, 0x80000000 |
|
msr hcr_el2, x0 // Update "Hypervisor Configuration Register": |
|
// set bit 31 => execution state for EL1 is aarch64 |
|
|
|
mrs x0, currentel |
|
and x0, x0, #0x0c |
|
cmp x0, #0x0c |
|
b.ne 1f |
|
|
|
################################################## |
|
# We are in EL3 (kernel_old=1 in config.txt) |
|
# Move from EL3 to EL1 directly (skip EL2) |
|
mov x0, 0x00000431 |
|
msr scr_el3, x0 // Update "Secure Configuration Register": |
|
// set bit 0 => EL0 and EL1 are in non-secure state |
|
// set RES:1 bits 4, 5 |
|
// set bit 10 => EL2 is aarch64, EL2 controls EL1 and EL0 behaviors |
|
|
|
mov x0, 0x000001c5 |
|
msr spsr_el3, x0 // Update "Saved Program Status Register (EL3)": |
|
// set bit 0 => dedicated stack pointer selected on EL switch to/from EL3 |
|
// set bit 2 (and clear bit 3) => drop to EL1 on eret |
|
// set bit 6 => mask (disable) error (SError) interrupts |
|
// set bit 7 => mask (disable) regular (IRQ) interrupts |
|
// set bit 8 => mask (disable) fast (FIQ) interrupts |
|
adr x0, 2f // Address of label 2: below |
|
msr elr_el3, x0 // Update Exception Link Register (EL3): |
|
// set to return to address of label 2: below |
|
eret |
|
# Move from EL3 to EL1 completed |
|
################################################## |
|
|
|
1: |
|
################################################## |
|
# We are in EL2 (kernel_old=1 /not/ in config.txt) |
|
# Move from EL2 to EL1 |
|
mov x0, 0x000001c5 |
|
msr spsr_el2, x0 // Update "Saved Program Status Register (EL2)": |
|
// set bit 0 => dedicated stack pointer selected on EL switch to/from EL2 |
|
// set bit 2 (and clear bit 3) => drop to EL1 on eret |
|
// set bit 6 => mask (disable) error (SError) interrupts |
|
// set bit 7 => mask (disable) regular (IRQ) interrupts |
|
// set bit 8 => mask (disable) fast (FIQ) interrupts |
|
adr x0, 2f |
|
msr elr_el2, x0 // Update Exception Link Register (EL2): |
|
// set to return address after this `eret` |
|
eret |
|
# Move from EL2 to EL1 completed |
|
################################################## |
|
|
|
2: |
|
# We are in EL1 |
|
mov sp, #0x01000000 |
|
bl uart_init // Initialise UART interface. |
|
bl irq_vector_init |
|
dsb sy // TODO: Not sure if this is needed at all, or if a less aggressive barrier can be used |
|
bl timer_init |
|
dsb sy // TODO: Not sure if this is needed at all, or if a less aggressive barrier can be used |
|
bl enable_ic |
|
dsb sy // TODO: Not sure if this is needed at all, or if a less aggressive barrier can be used |
|
bl enable_irq |
|
|
|
sleep: |
|
wfi // Sleep until woken. |
|
b sleep // Go to sleep; it has been a long day. |
|
|
|
|
|
.align 2 |
|
irq_vector_init: |
|
adr x0, vectors // load VBAR_EL1 with |
|
msr vbar_el1, x0 // vector table address |
|
ret |
|
|
|
|
|
enable_irq: |
|
msr daifclr, #2 |
|
ret |
|
|
|
|
|
enable_ic: |
|
mov w0, #0x00000002 |
|
adrp x1, 0x3f00b000 |
|
str w0, [x1, #0x210] // [0x3f00b210] = 0x00000002 |
|
ret |
|
|
|
|
|
handle_irq: |
|
stp x29, x30, [sp, #-16]! // Push frame pointer, procedure link register on stack. |
|
mov x29, sp // Update frame pointer to new stack location. |
|
adrp x1, 0x3f00b000 |
|
ldr w0, [x1, #0x204] // w0 = [0x3f00b204] |
|
cmp w0, #2 |
|
b.ne 1f |
|
bl handle_timer_irq |
|
1: |
|
ldp x29, x30, [sp], #16 // Pop frame pointer, procedure link register off stack. |
|
ret |
|
|
|
|
|
timer_init: |
|
adrp x0, 0x3f003000 |
|
ldr w1, [x0, #0x04] |
|
mov w2, #0x20000 |
|
add w1, w1, w2 |
|
str w1, [x0, #0x10] // [0x3f003010] += [0x3f003004] + 0x20000 (rpi3) |
|
ret |
|
|
|
|
|
handle_timer_irq: |
|
stp x29, x30, [sp, #-16]! // Push frame pointer, procedure link register on stack. |
|
mov x29, sp // Update frame pointer to new stack location. |
|
bl timer_init // [0x3f003010] += [0x3f003004] + 200000 (rpi3) |
|
mov w1, #0x02 |
|
str w1, [x0] // [0x3f003000] = 2 (rpi3) |
|
bl timed_interrupt |
|
ldp x29, x30, [sp], #0x10 // Pop frame pointer, procedure link register off stack. |
|
ret |
|
|
|
|
|
timed_interrupt: |
|
stp x29, x30, [sp, #-16]! // Push frame pointer, procedure link register on stack. |
|
mov x29, sp // Update frame pointer to new stack location. |
|
mov x0, 'x' // Write 'x' to UART |
|
bl uart_send |
|
ldp x29, x30, [sp], #0x10 // Pop frame pointer, procedure link register off stack. |
|
ret |
|
|
|
|
|
# ------------------------------------------------------------------------------ |
|
# Initialise the Mini UART interface for logging over serial port. |
|
# Note, this is Broadcomm's own UART, not the ARM licenced UART interface. |
|
# ------------------------------------------------------------------------------ |
|
.align 2 |
|
uart_init: |
|
adrp x1, 0x3f215000 |
|
ldr w2, [x1, AUX_ENABLES] // w2 = [AUX_ENABLES] (Auxiliary enables) |
|
orr w2, w2, #1 |
|
str w2, [x1, AUX_ENABLES] // [AUX_ENABLES] |= 0x00000001 => Enable Mini UART. |
|
str wzr, [x1, AUX_MU_IER] // [AUX_MU_IER_REG] = 0x00000000 => Disable Mini UART interrupts. |
|
str wzr, [x1, AUX_MU_CNTL] // [AUX_MU_CNTL_REG] = 0x00000000 => Disable Mini UART Tx/Rx |
|
mov w2, #0x6 // w2 = 6 |
|
str w2, [x1, AUX_MU_IIR] // [AUX_MU_IIR_REG] = 0x00000006 => Mini UART clear Tx, Rx FIFOs |
|
mov w3, #0x3 // w3 = 3 |
|
str w3, [x1, AUX_MU_LCR] // [AUX_MU_LCR_REG] = 0x00000003 => Mini UART in 8-bit mode. |
|
str wzr, [x1, AUX_MU_MCR] // [AUX_MU_MCR_REG] = 0x00000000 => Set UART1_RTS line high. |
|
mov w2, #0x0000010e |
|
str w2, [x1, AUX_MU_BAUD] // [AUX_MU_BAUD_REG] = 0x0000010e (rpi3) |
|
// => baudrate = system_clock_freq/(8*([AUX_MU_BAUD_REG]+1)) |
|
// (as close to 115200 as possible) |
|
adrp x4, 0x3f200000 // x4 = GPIO base = 0x3f200000 (rpi3) |
|
ldr w2, [x4, GPFSEL1] // w2 = [GPFSEL1] |
|
and w2, w2, #0xfffc0fff // Unset bits 12, 13, 14 (FSEL14 => GPIO Pin 14 is an input). |
|
// Unset bits 15, 16, 17 (FSEL15 => GPIO Pin 15 is an input). |
|
orr w2, w2, #0x00002000 // Set bit 13 (FSEL14 => GPIO Pin 14 takes alternative function 5). |
|
orr w2, w2, #0x00010000 // Set bit 16 (FSEL15 => GPIO Pin 15 takes alternative function 5). |
|
str w2, [x4, GPFSEL1] // [GPFSEL1] = updated value => Enable UART 1. |
|
str wzr, [x4, GPPUD] // [GPPUD] = 0x00000000 => GPIO Pull up/down = OFF |
|
mov x5, #0x96 // Wait 150 instruction cycles (as stipulated by datasheet). |
|
1: |
|
subs x5, x5, #0x1 // x0 -= 1 |
|
b.ne 1b // Repeat until x0 == 0. |
|
mov w2, #0xc000 // w2 = 2^14 + 2^15 |
|
str w2, [x4, GPPUDCLK0] // [GPPUDCLK0] = 0x0000c000 => Control signal to lines 14, 15. |
|
mov x0, #0x96 // Wait 150 instruction cycles (as stipulated by datasheet). |
|
2: |
|
subs x0, x0, #0x1 // x0 -= 1 |
|
b.ne 2b // Repeat until x0 == 0. |
|
str wzr, [x4, GPPUDCLK0] // [GPPUDCLK0] = 0x00000000 => Remove control signal to lines 14, 15. |
|
str w3, [x1, AUX_MU_CNTL] // [AUX_MU_CNTL_REG] = 0x00000003 => Enable Mini UART Tx/Rx |
|
ret // Return. |
|
|
|
|
|
# ------------------------------------------------------------------------------ |
|
# Send a byte over Mini UART |
|
# ------------------------------------------------------------------------------ |
|
# On entry: |
|
# x0: char to send |
|
# On exit: |
|
# x1: [aux_base] = 0x3f215000 (rpi3) |
|
# x2: Last read of [AUX_MU_LSR_REG] when waiting for bit 5 to be set |
|
uart_send: |
|
adrp x1, 0x3f215000 |
|
1: |
|
ldr w2, [x1, AUX_MU_LSR] // w2 = [AUX_MU_LSR_REG] |
|
tbz x2, #5, 1b // Repeat last statement until bit 5 is set. |
|
|
|
strb w0, [x1, AUX_MU_IO_REG] // [AUX_MU_IO_REG] = w0 |
|
ret |
|
|
|
|
|
.macro handle_invalid_entry type |
|
kernel_entry |
|
mov w0, 'A' |
|
add x0, x0, #\type |
|
bl uart_send |
|
b sleep |
|
.endm |
|
|
|
.macro ventry label |
|
.align 7 |
|
b \label |
|
.endm |
|
|
|
.macro kernel_entry |
|
sub sp, sp, #16 * 16 |
|
stp x0, x1, [sp, #16 * 0] |
|
stp x2, x3, [sp, #16 * 1] |
|
stp x4, x5, [sp, #16 * 2] |
|
stp x6, x7, [sp, #16 * 3] |
|
stp x8, x9, [sp, #16 * 4] |
|
stp x10, x11, [sp, #16 * 5] |
|
stp x12, x13, [sp, #16 * 6] |
|
stp x14, x15, [sp, #16 * 7] |
|
stp x16, x17, [sp, #16 * 8] |
|
stp x18, x19, [sp, #16 * 9] |
|
stp x20, x21, [sp, #16 * 10] |
|
stp x22, x23, [sp, #16 * 11] |
|
stp x24, x25, [sp, #16 * 12] |
|
stp x26, x27, [sp, #16 * 13] |
|
stp x28, x29, [sp, #16 * 14] |
|
str x30, [sp, #16 * 15] |
|
.endm |
|
|
|
.macro kernel_exit |
|
ldp x0, x1, [sp, #16 * 0] |
|
ldp x2, x3, [sp, #16 * 1] |
|
ldp x4, x5, [sp, #16 * 2] |
|
ldp x6, x7, [sp, #16 * 3] |
|
ldp x8, x9, [sp, #16 * 4] |
|
ldp x10, x11, [sp, #16 * 5] |
|
ldp x12, x13, [sp, #16 * 6] |
|
ldp x14, x15, [sp, #16 * 7] |
|
ldp x16, x17, [sp, #16 * 8] |
|
ldp x18, x19, [sp, #16 * 9] |
|
ldp x20, x21, [sp, #16 * 10] |
|
ldp x22, x23, [sp, #16 * 11] |
|
ldp x24, x25, [sp, #16 * 12] |
|
ldp x26, x27, [sp, #16 * 13] |
|
ldp x28, x29, [sp, #16 * 14] |
|
ldr x30, [sp, #16 * 15] |
|
add sp, sp, #16 * 16 |
|
eret |
|
.endm |
|
|
|
.text |
|
|
|
/* |
|
* Exception vectors. |
|
*/ |
|
.align 11 |
|
vectors: |
|
ventry sync_invalid_el1t // Synchronous EL1t |
|
ventry irq_invalid_el1t // IRQ EL1t |
|
ventry fiq_invalid_el1t // FIQ EL1t |
|
ventry error_invalid_el1t // Error EL1t |
|
|
|
ventry sync_invalid_el1h // Synchronous EL1h |
|
ventry el1_irq // IRQ EL1h |
|
ventry fiq_invalid_el1h // FIQ EL1h |
|
ventry error_invalid_el1h // Error EL1h |
|
|
|
ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 |
|
ventry irq_invalid_el0_64 // IRQ 64-bit EL0 |
|
ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 |
|
ventry error_invalid_el0_64 // Error 64-bit EL0 |
|
|
|
ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 |
|
ventry irq_invalid_el0_32 // IRQ 32-bit EL0 |
|
ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 |
|
ventry error_invalid_el0_32 // Error 32-bit EL0 |
|
|
|
.align 2 |
|
sync_invalid_el1t: |
|
handle_invalid_entry 0 |
|
|
|
irq_invalid_el1t: |
|
handle_invalid_entry 1 |
|
|
|
fiq_invalid_el1t: |
|
handle_invalid_entry 2 |
|
|
|
error_invalid_el1t: |
|
handle_invalid_entry 3 |
|
|
|
sync_invalid_el1h: |
|
handle_invalid_entry 4 |
|
|
|
el1_irq: |
|
kernel_entry |
|
bl handle_irq |
|
kernel_exit |
|
|
|
fiq_invalid_el1h: |
|
handle_invalid_entry 6 |
|
|
|
error_invalid_el1h: |
|
handle_invalid_entry 7 |
|
|
|
sync_invalid_el0_64: |
|
handle_invalid_entry 8 |
|
|
|
irq_invalid_el0_64: |
|
handle_invalid_entry 9 |
|
|
|
fiq_invalid_el0_64: |
|
handle_invalid_entry 10 |
|
|
|
error_invalid_el0_64: |
|
handle_invalid_entry 11 |
|
|
|
sync_invalid_el0_32: |
|
handle_invalid_entry 12 |
|
|
|
irq_invalid_el0_32: |
|
handle_invalid_entry 13 |
|
|
|
fiq_invalid_el0_32: |
|
handle_invalid_entry 14 |
|
|
|
error_invalid_el0_32: |
|
handle_invalid_entry 15 |