.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@"
