aboutsummaryrefslogtreecommitdiff
path: root/arch/riscv/machine/machine.S
diff options
context:
space:
mode:
Diffstat (limited to 'arch/riscv/machine/machine.S')
-rw-r--r--arch/riscv/machine/machine.S125
1 files changed, 125 insertions, 0 deletions
diff --git a/arch/riscv/machine/machine.S b/arch/riscv/machine/machine.S
new file mode 100644
index 0000000..8a7ce8d
--- /dev/null
+++ b/arch/riscv/machine/machine.S
@@ -0,0 +1,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 loop.
+ 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@"