summaryrefslogtreecommitdiff
path: root/arch/arm64/kernel/start.S
blob: 2d4b5c0c913999027acc76480e6de4701ef5251a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
/* SPDX-License-Identifier: GPL-2.0+ */
/*
 * tashaboot arm64 entry. handles whatever EL the firmware left us in,
 * EL3, EL2 or EL1, with the MMU either on or off, and arrives at a
 * clean EL1 with the MMU off before calling C.
 *
 * the secondary cores park, spin table bringup is a later problem.
 *
 * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
 */

#include <asm/macro.h>

.section .text.boot
.globl _start
_start:
	b	reset

	.balign 8
.globl _text_base
_text_base:
	.quad	0x40000000

reset:
	/* keep the dtb pointer before anything clobbers x0 */
	mov	x19, x0

	/* park secondary cores, they have nothing to do yet */
	mrs	x0, mpidr_el1
	and	x0, x0, #0xff
	cbnz	x0, park

	/* which EL are we in, 0x8 per level shifted into bits 3:2 */
	mrs	x0, CurrentEL
	lsr	x0, x0, #2
	cmp	x0, #3
	b.eq	from_el3
	cmp	x0, #2
	b.eq	from_el2
	cmp	x0, #1
	b.eq	mmu_check
	b	park

from_el3:
	/*
	 * EL3 holds the security state. the kernel runs non-secure, so
	 * set SCR_EL3.NS before dropping to EL2, which the kernel
	 * prefers (booting.rst, EL2 RECOMMENDED).
	 */
	mrs	x0, scr_el3
	orr	x0, x0, #1		/* SCR_EL3.NS = 1, non-secure */
	msr	scr_el3, x0
	isb

	mov	x0, #0x3c9		/* EL2h, DAIF masked */
	msr	spsr_el3, x0
	adr	x0, from_el2
	msr	elr_el3, x0
	eret

from_el2:
	/*
	 * stay at EL2: the kernel wants it for the virtualization
	 * extensions and hands off from there. everything below scrubs
	 * the EL2 state so the kernel starts clean.
	 */

	/* EL1 will be aarch64 when the kernel drops itself down */
	mov	x0, #(1 << 31)		/* HCR_EL2.RW = 1 */
	msr	hcr_el2, x0

	/* let EL1 reach the counter, booting.rst demands it */
	mrs	x0, cnthctl_el2
	orr	x0, x0, #(3 << 0)	/* EL1PCTEN | EL1PCEN */
	msr	cnthctl_el2, x0

	/* no traps to EL2 behind EL1's back */
	msr	cptr_el2, xzr
	msr	hstr_el2, xzr
	msr	vpidr_el2, xzr

	b	mmu_check

mmu_check:
	/*
	 * whether the firmware left an MMU on: M bit, bit 0, of sctlr at
	 * the current EL. writing the register off would not fault, but
	 * the page tables it built are in its own memory, better to kill
	 * it here than trip over a stale mapping.
	 */
	mrs	x0, CurrentEL
	lsr	x0, x0, #2
	cmp	x0, #2
	b.lt	mmu_el1
	mrs	x0, sctlr_el2
	tbz	x0, #0, c_entry

	mov	x0, xzr
	msr	sctlr_el2, x0
	isb
	tlbi	alle2
	dsb	sy
	isb
	b	c_entry

mmu_el1:
	mrs	x0, sctlr_el1
	tbz	x0, #0, c_entry

	mov	x0, xzr
	msr	sctlr_el1, x0
	isb
	ic	iallu
	dsb	sy
	tlbi	vmalle1
	dsb	sy
	isb

c_entry:
	/*
	 * program the counter frequency, the kernel reads CNTFRQ right
	 * away (booting.rst). qemu virt runs the system counter at
	 * 62.5 MHz. the register is RW only at the highest implemented EL.
	 */
	mrs	x0, CurrentEL
	lsr	x0, x0, #2
	cmp	x0, #2
	b.lt	1f
	ldr	x0, =62500000
	msr	cntfrq_el0, x0
	isb
1:
	/* our own vectors, so aborts print instead of vanishing */
	adr	x0, vectors
	mrs	x1, CurrentEL
	lsr	x1, x1, #2
	cmp	x1, #2
	b.lt	2f
	msr	vbar_el2, x0
	b	3f
2:
	msr	vbar_el1, x0
3:
	isb

	/* stack for the bootloader, its own region above the bss */
	ldr	x0, =__stack_top
	mov	sp, x0

	/* clear bss */
	ldr	x0, =__bss_start
	ldr	x1, =__bss_end
1:	cmp	x0, x1
	b.hs	2f
	str	xzr, [x0], #8
	b	1b
2:

	/* FP/SIMD access, some kernels assume it is on */
	mov	x0, #(3 << 20)
	msr	cpacr_el1, x0
	isb

	/* dtb pointer into C arg 0 */
	mov	x0, x19
	bl	tashaboot_main

	/* if main returns there is nothing sensible to do */
park:
	wfe
	b	park

/*
 * exception vectors, the armv8 layout: 16 slots, 128 bytes each, in
 * the order the manual fixes. taken from EL1h the interesting slots
 * are 0x200 sync and 0x380 SError, irq and fiq just park, the
 * bootloader never enables interrupts on purpose.
 */
	.balign	2048
vectors:
	/* 0x000: current EL, SP_EL0 */
	.align	7
	b	exc_sync
	.align	7
	b	exc_park_irq
	.align	7
	b	exc_park_irq
	.align	7
	b	exc_serr

	/* 0x200: current EL, SP_ELx */
	.align	7
	b	exc_sync
	.align	7
	b	exc_park_irq
	.align	7
	b	exc_park_irq
	.align	7
	b	exc_serr

	/* 0x400: lower EL, AArch64 */
	.align	7
	b	exc_sync
	.align	7
	b	exc_park_irq
	.align	7
	b	exc_park_irq
	.align	7
	b	exc_serr

	/* 0x600: lower EL, AArch32 */
	.align	7
	b	exc_sync
	.align	7
	b	exc_park_irq
	.align	7
	b	exc_park_irq
	.align	7
	b	exc_serr

exc_sync:
	stp	x29, x30, [sp, #-16]!
	mov	x29, sp
	mrs	x3, CurrentEL
	lsr	x3, x3, #2
	cmp	x3, #2
	b.lt	1f
	mrs	x0, esr_el2
	mrs	x1, far_el2
	b	2f
1:
	mrs	x0, esr_el1
	mrs	x1, far_el1
2:
	mov	x2, lr
	bl	exc_report
	ldp	x29, x30, [sp], #16
	b	park

exc_serr:
	stp	x29, x30, [sp, #-16]!
	mov	x29, sp
	mrs	x3, CurrentEL
	lsr	x3, x3, #2
	cmp	x3, #2
	b.lt	1f
	mrs	x0, esr_el2
	b	2f
1:
	mrs	x0, esr_el1
2:
	mov	x1, #0
	mov	x2, lr
	bl	exc_report
	ldp	x29, x30, [sp], #16
	b	park

exc_park_irq:
	b	park