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
|
#include <fbos/image.h>
#include <fbos/mm.h>
.global _start
.global _start_kernel
.section .head.text
// Since this kernel boots in virtualization environments and machines that are
// expecting a Linux kernel, we have to adhere to how bootloaders expect Linux
// kernel images to look like. In particular, the entry of the kernel is
// actually a Boot image header, as defined in:
//
// https://docs.kernel.org/arch/riscv/boot-image-header.html
//
// Conveniently, for machines who don't care about this (e.g. QEMU), the first
// 64 bits correspond to two instructions that can be set up, and hence entering
// here will simply jumpt to `_start_kernel`, our real entry.
_start:
// The first two words give us room for two executable instructions. Linux
// uses that on EFI support to first allocate a magic value for UEFI and
// then have a `j _start_kernel` instruction. Otherwise it just allocates
// the first one for `j _start_kernel` and leaves the second word empty. The
// latter is what we do here as well.
j _start_kernel
.word 0
// Ensure alignment for the next double word.
.balign 8
// Load offset. Note that this matches the LOAD_BASE_OFFSET as defined in
// `include/mm.h`.
.dword LOAD_BASE_OFFSET
// Size of the image. This is *mandatory* as per bootloader request.
.dword _end - _start
// Flags. As defined by Linux, only one bit matters here, which is related
// to endianness. Setting 0 means little-endian.
.dword 0
// Header version.
.word RISCV_HEADER_VERSION
// Reserved fields.
.word 0
.dword 0
// Deprecated image magic.
.ascii RISCV_IMAGE_MAGIC
.balign 4
// Good image magic, in little-endian format.
.ascii RISCV_IMAGE_MAGIC2
// Reserved field.
.word 0
_start_kernel:
// Mask all interrupts
csrw sie, zero
csrw sip, zero
// Flush the instruction cache
fence.i
// Run the hart lottery. If this is not the first time that it happens, then
// stall this hart forever: on this simple kernel we only want one hart
// available to avoid SMP shenanigans. See explanation on fbos/init.h.
la a3, hart_lottery
li a2, 1
amoadd.w a3, a2, (a3)
beqz a3, .Lhart_proceed
.Lhart_wait:
wfi
j .Lhart_wait
.Lhart_proceed:
// Store the hart id as we will tamper with the 'a0' register later when
// performing the call to `start_kernel`.
la a3, hart_id
sw a0, 0(a3)
// Reset all registers except ra, a0, a1.
li sp, 0
li gp, 0
li tp, 0
li t0, 0
li t1, 0
li t2, 0
li s0, 0
li s1, 0
li a2, 0
li a3, 0
li a4, 0
li a5, 0
li a6, 0
li a7, 0
li s2, 0
li s3, 0
li s4, 0
li s5, 0
li s6, 0
li s7, 0
li s8, 0
li s9, 0
li s10, 0
li s11, 0
li t3, 0
li t4, 0
li t5, 0
li t6, 0
csrw sscratch, 0
// Point 'tp' to the init task. The 'tp' register will always point to the
// current process being executed, and it will be shown on debug when
// printing out messages.
la tp, tasks
// Point 'sp' the our general stack.
la sp, stack + STACK_SIZE
// The `start_kernel` function requires an argument to be passed, which is
// the pointer to the `fdt` blob. The bootloader puts this on the `a1`
// register, so let's move it now to `a0`.
mv a0, a1
// Start the kernel.
tail start_kernel
// We really shouldn't reach this point, but just in case, just loop
// infinitely here.
j .
|