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
|
.section .text
# Returns the physical address that was identified to be for the serial port. If
# any error was found then it just returns 0. This function accepts one argument
# which is a pointer to the FDT structure as given by the firmware. You can
# consider this function to have this equivalent C signature:
#
# uint64_t get_serial_address(void *fdt);
#
.type get_serial_address, @function
get_serial_address:
# Leave early if the caller passed a NULL pointer.
beqz a0, .parse_error
# First loop. This one simply iterates over the FDT structure in search for
# 'serial@', which marks the beginning of the address definition.
.search_serial_loop_reset:
la t1, .serial
.search_serial_loop:
lbu t2, (t1)
beqz t2, .parse_hex
lbu t3, (a0)
addi a0, a0, 1
bne t2, t3, .search_serial_loop_reset
addi t1, t1, 1
j .search_serial_loop
# Second loop. On each digit shift left one nibble and add the character
# converted to an integer. If the character cannot be converted, then we go
# to `parse_error` which sets the return value to 0.
.parse_hex:
mv t1, zero
li t2, 10
li t3, 0x30 # Character '0'
.parse_hex_loop:
# The FDT specification guarantees that strings are NULL-terminated. Thus,
# whenever we find the NULL character, then we are done parsing.
lbu t0, (a0)
beqz t0, .get_serial_address_end
# Trying to parse a numeric character. Note that this parser is grossly
# uncapable of handling Aa-Ff hexadecimal values, and only knows numeric
# digits. This can be improved but I did not have an example to work
# on this case.
sub t0, t0, t3
bltz t0, .parse_error
blt t2, t0, .parse_error
# Shift one nibble on the accumulator and add the computed value to it.
sll t1, t1, 4
add t1, t1, t0
# Next iteration.
addi a0, a0, 1
j .parse_hex_loop
.parse_error:
mv t1, zero
.get_serial_address_end:
mv a0, t1
ret
# Send the `message` to the given `address` so it's printed there. You can
# consider this function to have this equivalent C signature:
#
# void printm(uint64_t address, char *message);
#
.type printm, @function
printm:
mv t1, a1
.printm_loop:
lbu t0, (t1)
beqz t0, .printm_end
sb t0, (a0)
addi t1, t1, 1
j .printm_loop
.printm_end:
ret
.section .text.bios
# The entry point: the firmware will blindly jump here. We expect the firmware
# to pass up two arguments, the first one being the hart ID, and the other being
# a pointer to the FDT describing this machine. You can consider this function
# to have this equivalent C signature:
#
# void _start(uint64_t hart_id, void *fdt) __attribute__((noreturn));
#
.global _start
.type _start, @function
_start:
# We expect the configuration to be given as a devicetree blob pointed by
# the second argument (a1). Hence, the configuration pointer from RISC-V
# should be set to NULL indicating that. If that's not the case, then it's
# not a supported scenario and we jump to the infinite loop gracefully.
csrr t0, mconfigptr
bnez t0, .end
# We don't care about the hard ID, but we need the FDT pointer as an
# argument for the `get_serial_address` function.
mv a0, a1
call get_serial_address
# If the return value of `get_serial_address` is a NULL value, then
# something went wrong there and we just have to end it here.
beqz a0, .end
# And now we can print our message.
la a1, .msg1
call printm
# Infinite loop so not to crash :)
.end:
j .end
.section .rodata
.msg1:
.string "Hello, world!\n"
.serial:
.string "serial@"
|