56611e17d2
To make it easier to test 32 bit Arm softmmu issues implement a basic boot.S so we can build the multiarch tests. Currently CHECK_UNALIGNED is disabled as I haven't got the right magic set for it to work. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-Id: <20231120150833.2552739-12-alex.bennee@linaro.org>
320 lines
6.4 KiB
ArmAsm
320 lines
6.4 KiB
ArmAsm
/*
|
|
* Minimal ArmV7 system boot code.
|
|
*
|
|
* Using semihosting for serial output and exit functions.
|
|
*/
|
|
|
|
/*
|
|
* Semihosting interface on ARM AArch32
|
|
* R0 - semihosting call number
|
|
* R1 - semihosting parameter
|
|
*/
|
|
#define semihosting_call svc 0x123456
|
|
#define SYS_WRITEC 0x03 /* character to debug channel */
|
|
#define SYS_WRITE0 0x04 /* string to debug channel */
|
|
#define SYS_EXIT 0x18
|
|
|
|
#define ADP_Stopped_ApplicationExit 0x20026
|
|
#define ADP_Stopped_InternalError 0x20024
|
|
|
|
/*
|
|
* Helper macro for annotating functions with elf type and size.
|
|
*/
|
|
.macro endf name
|
|
.global \name
|
|
.type \name, %function
|
|
.size \name, . - \name
|
|
.endm
|
|
|
|
.section .interrupt_vector, "ax"
|
|
.align 5
|
|
|
|
vector_table:
|
|
b reset /* reset vector */
|
|
b undef_instr /* undefined instruction vector */
|
|
b software_intr /* software interrupt vector */
|
|
b prefetch_abort /* prefetch abort vector */
|
|
b data_abort /* data abort vector */
|
|
nop /* reserved */
|
|
b IRQ_handler /* IRQ vector */
|
|
b FIQ_handler /* FIQ vector */
|
|
|
|
endf vector_table
|
|
|
|
.text
|
|
__start:
|
|
ldr r0, =vector_table
|
|
mcr p15, 0, r0, c12, c0, 0 /* Set up VBAR */
|
|
|
|
ldr sp, =stack_end /* Set up the stack */
|
|
bl mmu_setup /* Set up the MMU */
|
|
bl main /* Jump to main */
|
|
|
|
endf __start
|
|
|
|
_exit:
|
|
cmp r0, #0
|
|
ite EQ // if-then-else. "EQ" is for if equal, else otherwise
|
|
ldreq r1, =ADP_Stopped_ApplicationExit // if r0 == 0
|
|
ldrne r1, =ADP_Stopped_InternalError // else
|
|
mov r0, #SYS_EXIT
|
|
semihosting_call
|
|
|
|
endf _exit
|
|
|
|
/*
|
|
* Helper Functions
|
|
*/
|
|
|
|
mmu_setup:
|
|
/*
|
|
* The MMU setup for this is very simple using two stage one
|
|
* translations. The first 1Mb section points to the text
|
|
* section and the second points to the data and rss.
|
|
* Currently the fattest test only needs ~50k for that so we
|
|
* have plenty of space.
|
|
*
|
|
* The short descriptor Section format is as follows:
|
|
*
|
|
* PA[31:20] - Section Base Address
|
|
* NS[19] - Non-secure bit
|
|
* 0[18] - Section (1 for Super Section)
|
|
* nG[17] - Not global bit
|
|
* S[16] - Shareable
|
|
* TEX[14:12] - Memory Region Attributes
|
|
* AP[15, 11:10] - Access Permission Bits
|
|
* IMPDEF[9]
|
|
* Domain[8:5]
|
|
* XN[4] - Execute never bit
|
|
* C[3] - Memory Region Attributes
|
|
* B[2] - Memory Region Attributes
|
|
* 1[1]
|
|
* PXN[0] - Privileged Execute Never
|
|
*
|
|
* r0 - point at the table
|
|
* r1 - address
|
|
* r2 - entry
|
|
* r3 - common section bits
|
|
* r4 - scratch
|
|
*/
|
|
|
|
/*
|
|
* Memory Region Bits
|
|
*
|
|
* TEX[14:12] = 000
|
|
* C[3] = 1
|
|
* B[2] = 1
|
|
*
|
|
* Outer and Inner WB, no write allocate
|
|
*/
|
|
mov r3, #0
|
|
ldr r4, =(3 << 2)
|
|
orr r3, r4, r4
|
|
|
|
/* Section bit */
|
|
orr r3, r3, #2
|
|
|
|
/* Page table setup (identity mapping). */
|
|
ldr r0, =ttb
|
|
|
|
/* First block: .text/RO/execute enabled */
|
|
ldr r1, =.text
|
|
ldr r2, =0xFFF00000 /* 1MB block alignment */
|
|
and r2, r1, r2
|
|
orr r2, r2, r3 /* common bits */
|
|
orr r2, r2, #(1 << 15) /* AP[2] = 1 */
|
|
orr r2, r2, #(1 << 10) /* AP[0] = 1 => RO @ PL1 */
|
|
|
|
lsr r4, r2, #(20 - 2)
|
|
str r2, [r0, r4, lsl #0] /* write entry */
|
|
|
|
/* Second block: .data/RW/no execute */
|
|
ldr r1, =.data
|
|
ldr r2, =0xFFF00000 /* 1MB block alignment */
|
|
and r2, r1, r2
|
|
orr r2, r2, r3 /* common bits */
|
|
orr r2, r2, #(1 << 10) /* AP[0] = 1 => RW @ PL1 */
|
|
orr r2, r2, #(1 << 4) /* XN[4] => no execute */
|
|
|
|
lsr r4, r2, #(20 - 2)
|
|
str r2, [r0, r4, lsl #0] /* write entry */
|
|
|
|
/*
|
|
* DACR - Domain Control
|
|
*
|
|
* Enable client mode for domain 0 (we don't use any others)
|
|
*/
|
|
ldr r0, =0x1
|
|
mcr p15, 0, r0, c3, c0, 0
|
|
|
|
/*
|
|
* TTCBR - Translation Table Base Control Register
|
|
*
|
|
* EAE[31] = 0, 32-bit translation, short descriptor format
|
|
* N[2:0] = 5 ( TTBRO uses 31:14-5 => 9 bit lookup stage )
|
|
*/
|
|
ldr r0, =0x5
|
|
mcr p15, 0, r0, c1, c0, 2
|
|
|
|
/*
|
|
* TTBR0 -Translation Table Base Register 0
|
|
*
|
|
* [31:9] = Base address of table
|
|
*
|
|
* QEMU doesn't really care about the cache sharing
|
|
* attributes so we don't need to either.
|
|
*/
|
|
ldr r0, =ttb
|
|
mcr p15, 0, r0, c2, c0, 0
|
|
|
|
/*
|
|
* SCTLR- System Control Register
|
|
*
|
|
* TE[30] = 0, exceptions to A32 state
|
|
* AFE[29] = 0, AP[0] is the access permissions bit
|
|
* EE[25] = 0, Little-endian
|
|
* WXN[19] = 0 = no effect, Write does not imply XN (execute never)
|
|
* I[12] = Instruction cachability control
|
|
* C[2] = Data cachability control
|
|
* M[0] = 1, enable stage 1 address translation for EL0/1
|
|
*
|
|
* At this point virtual memory is enabled.
|
|
*/
|
|
ldr r0, =0x1005
|
|
mcr p15, 0, r0, c1, c0, 0
|
|
|
|
isb
|
|
|
|
mov pc, lr /* done, return to caller */
|
|
|
|
endf mmu_setup
|
|
|
|
/* Output a single character to serial port */
|
|
__sys_outc:
|
|
STMFD sp!, {r0-r1} // push r0, r1 onto stack
|
|
mov r1, sp
|
|
mov r0, #SYS_WRITEC
|
|
semihosting_call
|
|
LDMFD sp!, {r0-r1} // pop r0, r1 from stack
|
|
bx lr
|
|
|
|
endf __sys_outc
|
|
|
|
reset:
|
|
ldr r1, =reset_error
|
|
b exception_handler
|
|
|
|
endf reset
|
|
|
|
undef_instr:
|
|
ldr r1, =undef_intr_error
|
|
b exception_handler
|
|
|
|
endf undef_instr
|
|
|
|
software_intr:
|
|
ldr r1, =software_intr_error
|
|
b exception_handler
|
|
|
|
endf software_intr
|
|
|
|
prefetch_abort:
|
|
ldr r1, =prefetch_abort_error
|
|
b exception_handler
|
|
|
|
endf prefetch_abort
|
|
|
|
data_abort:
|
|
ldr r1, =data_abort_error
|
|
b exception_handler
|
|
|
|
endf data_abort
|
|
|
|
IRQ_handler:
|
|
ldr r1, =irq_error
|
|
b exception_handler
|
|
|
|
endf IRQ_handler
|
|
|
|
FIQ_handler:
|
|
ldr r1, =fiq_error
|
|
b exception_handler
|
|
|
|
endf FIQ_handler
|
|
|
|
/*
|
|
* Initiate a exit semihosting call whenever there is any exception
|
|
* r1 already holds the string.
|
|
*/
|
|
exception_handler:
|
|
mov r0, #SYS_WRITE0
|
|
semihosting_call
|
|
mov r0, #SYS_EXIT
|
|
mov r1, #1
|
|
semihosting_call
|
|
|
|
endf exception_handler
|
|
|
|
/*
|
|
* We implement a stub raise() function which errors out as tests
|
|
* shouldn't trigger maths errors.
|
|
*/
|
|
.global raise
|
|
raise:
|
|
mov r0, #SYS_WRITE0
|
|
ldr r1, =maths_error
|
|
semihosting_call
|
|
mov r0, #SYS_EXIT
|
|
ldr r1, =ADP_Stopped_InternalError
|
|
semihosting_call
|
|
|
|
endf raise
|
|
|
|
.data
|
|
|
|
.data
|
|
|
|
reset_error:
|
|
.ascii "Reset exception occurred.\n\0"
|
|
|
|
undef_intr_error:
|
|
.ascii "Undefined Instruction Exception Occurred.\n\0"
|
|
|
|
software_intr_error:
|
|
.ascii "Software Interrupt Occurred.\n\0"
|
|
|
|
prefetch_abort_error:
|
|
.ascii "Prefetch Abort Occurred.\n\0"
|
|
|
|
data_abort_error:
|
|
.ascii "Data Abort Occurred.\n\0"
|
|
|
|
irq_error:
|
|
.ascii "IRQ exception occurred.\n\0"
|
|
|
|
fiq_error:
|
|
.ascii "FIQ exception occurred.\n\0"
|
|
|
|
maths_error:
|
|
.ascii "Software maths exception.\n\0"
|
|
|
|
|
|
/*
|
|
* 1st Stage Translation table
|
|
* 4096 entries, indexed by [31:20]
|
|
* each entry covers 1Mb of address space
|
|
* aligned on 16kb
|
|
*/
|
|
.align 15
|
|
ttb:
|
|
.space (4096 * 4), 0
|
|
|
|
.align 12
|
|
|
|
/* Space for stack */
|
|
.align 5
|
|
.section .bss
|
|
stack:
|
|
.space 65536, 0
|
|
stack_end:
|