[sbi] Add support for running as a RISC-V SBI payload

Add basic support for running directly on top of SBI, with no UEFI
firmware present.  Build as e.g.:

  make CROSS=riscv64-linux-gnu- bin-riscv64/ipxe.sbi

The resulting binary can be tested in QEMU using e.g.:

  qemu-system-riscv64 -M virt -cpu max -serial stdio \
                      -kernel bin-riscv64/ipxe.sbi

No drivers or executable binary formats are supported yet, but the
unit test suite may be run successfully.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
diff --git a/src/Makefile.housekeeping b/src/Makefile.housekeeping
index ba61e21..2b4356d 100644
--- a/src/Makefile.housekeeping
+++ b/src/Makefile.housekeeping
@@ -352,6 +352,8 @@
 # Determine build platform
 DEFAULT_PLATFORM_i386 := pcbios
 DEFAULT_PLATFORM_x86_64 := pcbios
+DEFAULT_PLATFORM_riscv32 := sbi
+DEFAULT_PLATFORM_riscv64 := sbi
 DEFAULT_PLATFORM := $(DEFAULT_PLATFORM_$(ARCH))
 PLATFORM	:= $(firstword $(BIN_PLATFORM) $(DEFAULT_PLATFORM) none)
 CFLAGS		+= -DPLATFORM=$(PLATFORM) -DPLATFORM_$(PLATFORM)
diff --git a/src/arch/riscv/Makefile b/src/arch/riscv/Makefile
index d3ae4e8..324e140 100644
--- a/src/arch/riscv/Makefile
+++ b/src/arch/riscv/Makefile
@@ -11,6 +11,7 @@
 #
 SRCDIRS		+= arch/riscv/core
 SRCDIRS		+= arch/riscv/interface/sbi
+SRCDIRS		+= arch/riscv/prefix
 
 # RISCV-specific flags
 #
diff --git a/src/arch/riscv/Makefile.sbi b/src/arch/riscv/Makefile.sbi
new file mode 100644
index 0000000..dee1b6e
--- /dev/null
+++ b/src/arch/riscv/Makefile.sbi
@@ -0,0 +1,16 @@
+# -*- makefile -*- : Force emacs to use Makefile mode
+
+# Build a position-independent executable, with relocations required
+# only for data values.  Runtime relocations are applied by the
+# prefix code.
+#
+CFLAGS		+= -mcmodel=medany -fpie
+LDFLAGS		+= -pie --no-dynamic-linker
+
+# Linker script
+#
+LDSCRIPT	= arch/riscv/scripts/sbi.lds
+
+# Media types
+#
+MEDIA		+= sbi
diff --git a/src/arch/riscv/core/stack.S b/src/arch/riscv/core/stack.S
new file mode 100644
index 0000000..1cd1da7
--- /dev/null
+++ b/src/arch/riscv/core/stack.S
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ */
+
+	FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL )
+
+/** @file
+ *
+ * Internal stack
+ *
+ */
+
+	.section ".note.GNU-stack", "", @progbits
+	.text
+
+#define STACK_ALIGN 16
+
+#define STACK_SIZE 8192
+
+	.section ".stack", "aw", @nobits
+	.balign STACK_ALIGN
+	.globl _stack
+_stack:
+	.space STACK_SIZE
+	.globl _estack
+_estack:
diff --git a/src/arch/riscv/include/bits/umalloc.h b/src/arch/riscv/include/bits/umalloc.h
new file mode 100644
index 0000000..a7171ca
--- /dev/null
+++ b/src/arch/riscv/include/bits/umalloc.h
@@ -0,0 +1,14 @@
+#ifndef _BITS_UMALLOC_H
+#define _BITS_UMALLOC_H
+
+/** @file
+ *
+ * RISCV-specific user memory allocation API implementations
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <ipxe/sbi_umalloc.h>
+
+#endif /* _BITS_UMALLOC_H */
diff --git a/src/arch/riscv/include/ipxe/errno/sbi.h b/src/arch/riscv/include/ipxe/errno/sbi.h
new file mode 100644
index 0000000..2428183
--- /dev/null
+++ b/src/arch/riscv/include/ipxe/errno/sbi.h
@@ -0,0 +1,19 @@
+#ifndef _IPXE_ERRNO_SBI_H
+#define _IPXE_ERRNO_SBI_H
+
+/**
+ * @file
+ *
+ * RISC-V SBI platform error codes
+ *
+ * We never need to return SBI error codes ourselves, so we
+ * arbitrarily choose to use the Linux error codes as platform error
+ * codes.
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <ipxe/errno/linux.h>
+
+#endif /* _IPXE_ERRNO_SBI_H */
diff --git a/src/arch/riscv/include/ipxe/sbi_umalloc.h b/src/arch/riscv/include/ipxe/sbi_umalloc.h
new file mode 100644
index 0000000..5763239
--- /dev/null
+++ b/src/arch/riscv/include/ipxe/sbi_umalloc.h
@@ -0,0 +1,18 @@
+#ifndef _IPXE_SBI_UMALLOC_H
+#define _IPXE_SBI_UMALLOC_H
+
+/** @file
+ *
+ * External memory allocation
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#ifdef UMALLOC_SBI
+#define UMALLOC_PREFIX_sbi
+#else
+#define UMALLOC_PREFIX_sbi __sbi_
+#endif
+
+#endif /* _IPXE_SBI_UMALLOC_H */
diff --git a/src/arch/riscv/interface/sbi/sbi_umalloc.c b/src/arch/riscv/interface/sbi/sbi_umalloc.c
new file mode 100644
index 0000000..2f9935a
--- /dev/null
+++ b/src/arch/riscv/interface/sbi/sbi_umalloc.c
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <stdlib.h>
+#include <ipxe/umalloc.h>
+
+/** @file
+ *
+ * iPXE user memory allocation API for SBI
+ *
+ */
+
+/** Equivalent of NOWHERE for user pointers */
+#define UNOWHERE ( ~UNULL )
+
+/**
+ * Reallocate external memory
+ *
+ * @v old_ptr		Memory previously allocated by umalloc(), or UNULL
+ * @v new_size		Requested size
+ * @ret new_ptr		Allocated memory, or UNULL
+ *
+ * Calling realloc() with a new size of zero is a valid way to free a
+ * memory block.
+ */
+static userptr_t sbi_urealloc ( userptr_t old_ptr, size_t new_size ) {
+
+	/* External allocation not yet implemented: allocate from heap */
+	return ( ( userptr_t ) realloc ( ( ( void * ) old_ptr ), new_size ) );
+}
+
+PROVIDE_UMALLOC ( sbi, urealloc, sbi_urealloc );
diff --git a/src/arch/riscv/prefix/sbiprefix.S b/src/arch/riscv/prefix/sbiprefix.S
new file mode 100644
index 0000000..0de0019
--- /dev/null
+++ b/src/arch/riscv/prefix/sbiprefix.S
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ */
+
+	FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL )
+
+/** @file
+ *
+ * SBI position-independent executable prefix
+ *
+ */
+
+	.section ".note.GNU-stack", "", @progbits
+	.text
+
+/* SBI debug console extension */
+#define SBI_DBCN ( ( 'D' << 24 ) | ( 'B' << 16 ) | ( 'C' << 8 ) | 'N' )
+#define SBI_DBCN_WRITE 0x00
+
+/* SBI system reset extension */
+#define SBI_SRST ( ( 'S' << 24 ) | ( 'R' << 16 ) | ( 'S' << 8 ) | 'T' )
+#define SBI_SRST_SYSTEM_RESET 0x00
+#define SBI_RESET_COLD 0x00000001
+
+/* Relative relocation type */
+#define R_RISCV_RELATIVE 3
+
+	/* Layout of a relocation record */
+	.struct 0
+rela_offset:	.space ( __riscv_xlen / 8 )
+rela_type:	.space ( __riscv_xlen / 8 )
+rela_addend:	.space ( __riscv_xlen / 8 )
+rela_len:
+	.previous
+
+	/*
+	 * Display progress message via debug console
+	 */
+	.macro	progress message
+#ifndef NDEBUG
+	.section ".prefix.data", "aw", @progbits
+progress_\@:
+	.ascii	"\message"
+	.equ	progress_\@_len, . - progress_\@
+	.size	progress_\@, . - progress_\@
+	.previous
+	li	a7, SBI_DBCN
+	li	a6, SBI_DBCN_WRITE
+	li	a0, progress_\@_len
+	la	a1, progress_\@
+	mv	a2, zero
+	ecall
+#endif
+	.endm
+
+	/*
+	 * SBI entry point
+	 */
+	.section ".prefix", "ax", @progbits
+	.org 0
+	.globl	_sbi_start
+_sbi_start:
+	/* Preserve arguments */
+	mv	s0, a0
+	mv	s1, a1
+	progress "\nSBI->iPXE"
+
+	/* Apply dynamic relocations */
+	la	t0, _reloc
+	la	t1, _ereloc
+	la	t2, _sbi_start
+1:	/* Read relocation record */
+	LOADN	t3, rela_offset(t0)
+	LOADN	t4, rela_type(t0)
+	LOADN	t5, rela_addend(t0)
+	/* Check relocation type */
+	addi	t4, t4, -R_RISCV_RELATIVE
+	bnez	t4, 2f
+	/* Apply relocation */
+	add	t3, t3, t2
+	add	t5, t5, t2
+	STOREN	t5, (t3)
+2:	/* Loop */
+	addi	t0, t0, rela_len
+	blt	t0, t1, 1b
+	progress " .reloc"
+
+	/* Zero the bss */
+	la	t0, _bss
+	la	t1, _ebss
+1:	STOREN	zero, (t0)
+	addi	t0, t0, ( __riscv_xlen / 8 )
+	blt	t0, t1, 1b
+	progress " .bss"
+
+	/* Set up stack */
+	la	sp, _estack
+	progress " .stack"
+
+	/* Store boot hart */
+	la	t0, boot_hart
+	STOREN	s0, (t0)
+
+	/* Register device tree */
+	mv	a0, s1
+	call	register_fdt
+
+	/* Call main program */
+	progress "\n\n"
+	call	main
+
+	/* We have no return path, since the M-mode SBI implementation
+	 * will have jumped to us by setting our start address in MEPC
+	 * and issuing an MRET instruction.
+	 *
+	 * Attempt a system reset, since there is nothing else we can
+	 * viably do at this point.
+	 */
+	progress "\niPXE->SBI reset\n"
+	li	a7, SBI_SRST
+	li	a6, SBI_SRST_SYSTEM_RESET
+	li	a0, SBI_RESET_COLD
+	mv	a1, zero
+	ecall
+
+	/* If reset failed, lock the system */
+	progress "(reset failed)\n"
+1:	wfi
+	j	1b
+	.size	_sbi_start, . - _sbi_start
+
+	/* File split information for the compressor */
+	.section ".zinfo", "a", @progbits
+	.ascii	"COPY"
+	.word	0
+	.word	_sbi_filesz
+	.word	1
diff --git a/src/arch/riscv/scripts/sbi.lds b/src/arch/riscv/scripts/sbi.lds
new file mode 100644
index 0000000..a65b271
--- /dev/null
+++ b/src/arch/riscv/scripts/sbi.lds
@@ -0,0 +1,119 @@
+/*
+ * Linker script for RISC-V SBI images
+ *
+ */
+
+SECTIONS {
+
+    /* Start at virtual address zero */
+    . = 0;
+
+    /* Weak symbols that need zero values if not otherwise defined */
+    .weak 0x0 : {
+	_weak = .;
+	*(.weak)
+	*(.weak.*)
+	_eweak = .;
+    }
+    _assert = ASSERT ( ( _weak == _eweak ), ".weak is non-zero length" );
+
+    /* Prefix code */
+    .prefix : {
+        _prefix = .;
+	*(.prefix)
+	*(.prefix.*)
+	_eprefix = .;
+    }
+
+    /* Program code */
+    .text : {
+	_text = .;
+	*(.text)
+	*(.text.*)
+	_etext = .;
+    }
+
+    /* Align to page size to allow linker to generate W^X segments */
+    . = ALIGN ( 4096 );
+
+    /* Read-only data */
+    .rodata : {
+	_rodata = .;
+	*(.rodata)
+	*(.rodata.*)
+	_erodata = .;
+    }
+
+    /* Writable data */
+    .data : {
+	_data = .;
+	*(.data)
+	*(.data.*)
+	KEEP(*(SORT(.tbl.*)))	/* Various tables.  See include/tables.h */
+	KEEP(*(.provided))
+	KEEP(*(.provided.*))
+	_edata = .;
+    }
+
+    /* Uninitialised and discardable data */
+    OVERLAY : {
+
+	/* Runtime relocations (discarded after use) */
+	.rela.dyn {
+	    _reloc = .;
+	    *(.rela)
+	    *(.rela.dyn)
+	}
+
+	/* Compressor information block */
+	.zinfo {
+	    _zinfo = .;
+	    KEEP(*(.zinfo))
+	    KEEP(*(.zinfo.*))
+	    _ezinfo = .;
+	}
+
+	/* Uninitialised data */
+	.bss {
+	    _bss = .;
+	    *(.bss)
+	    *(.bss.*)
+	    *(COMMON)
+	    *(.stack)
+	    *(.stack.*)
+	    /* Align to allow for easy zeroing by prefix code */
+	    . = ALIGN ( 16 );
+	    _ebss = .;
+	}
+    }
+
+    /* Calculate end of relocations
+     *
+     * This cannot be done by placing "_ereloc = .;" inside the
+     * .rela.dyn section, since the dynamic relocations are not
+     * present in the input sections but are instead generated during
+     * linking.
+     */
+    _ereloc = ( _reloc + __load_stop_reladyn - __load_start_reladyn );
+
+    /* Length of initialised data */
+    _sbi_filesz = ABSOLUTE ( _ereloc );
+
+    /* Unwanted sections */
+    /DISCARD/ : {
+	*(.comment)
+	*(.comment.*)
+	*(.note)
+	*(.note.*)
+	*(.eh_frame)
+	*(.eh_frame.*)
+	*(.dynamic)
+	*(.dynsym)
+	*(.dynstr)
+	*(.einfo)
+	*(.einfo.*)
+	*(.discard)
+	*(.discard.*)
+	*(.pci_devlist.*)
+    }
+}
diff --git a/src/arch/riscv32/Makefile.sbi b/src/arch/riscv32/Makefile.sbi
new file mode 100644
index 0000000..d622877
--- /dev/null
+++ b/src/arch/riscv32/Makefile.sbi
@@ -0,0 +1,6 @@
+# -*- makefile -*- : Force emacs to use Makefile mode
+
+# Include generic SBI Makefile
+#
+MAKEDEPS	+= arch/riscv/Makefile.sbi
+include arch/riscv/Makefile.sbi
diff --git a/src/arch/riscv32/include/ipxe/sbi/dhcparch.h b/src/arch/riscv32/include/ipxe/sbi/dhcparch.h
new file mode 100644
index 0000000..713d4cf
--- /dev/null
+++ b/src/arch/riscv32/include/ipxe/sbi/dhcparch.h
@@ -0,0 +1,20 @@
+#ifndef _IPXE_SBI_DHCPARCH_H
+#define _IPXE_SBI_DHCPARCH_H
+
+/** @file
+ *
+ * DHCP client architecture definitions
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <ipxe/dhcp.h>
+
+/** DHCP client architecture */
+#define DHCP_ARCH_CLIENT_ARCHITECTURE DHCP_CLIENT_ARCHITECTURE_RISCV32
+
+/** DHCP client network device interface */
+#define DHCP_ARCH_CLIENT_NDI 1 /* UNDI */ , 3, 10 /* v3.10 */
+
+#endif /* _IPXE_SBI_DHCPARCH_H */
diff --git a/src/arch/riscv64/Makefile.sbi b/src/arch/riscv64/Makefile.sbi
new file mode 100644
index 0000000..d622877
--- /dev/null
+++ b/src/arch/riscv64/Makefile.sbi
@@ -0,0 +1,6 @@
+# -*- makefile -*- : Force emacs to use Makefile mode
+
+# Include generic SBI Makefile
+#
+MAKEDEPS	+= arch/riscv/Makefile.sbi
+include arch/riscv/Makefile.sbi
diff --git a/src/arch/riscv64/include/ipxe/sbi/dhcparch.h b/src/arch/riscv64/include/ipxe/sbi/dhcparch.h
new file mode 100644
index 0000000..e172f06
--- /dev/null
+++ b/src/arch/riscv64/include/ipxe/sbi/dhcparch.h
@@ -0,0 +1,20 @@
+#ifndef _IPXE_SBI_DHCPARCH_H
+#define _IPXE_SBI_DHCPARCH_H
+
+/** @file
+ *
+ * DHCP client architecture definitions
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <ipxe/dhcp.h>
+
+/** DHCP client architecture */
+#define DHCP_ARCH_CLIENT_ARCHITECTURE DHCP_CLIENT_ARCHITECTURE_RISCV64
+
+/** DHCP client network device interface */
+#define DHCP_ARCH_CLIENT_NDI 1 /* UNDI */ , 3, 10 /* v3.10 */
+
+#endif /* _IPXE_SBI_DHCPARCH_H */
diff --git a/src/config/config_sbi.c b/src/config/config_sbi.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/config/config_sbi.c
diff --git a/src/config/defaults/sbi.h b/src/config/defaults/sbi.h
new file mode 100644
index 0000000..42fb515
--- /dev/null
+++ b/src/config/defaults/sbi.h
@@ -0,0 +1,36 @@
+#ifndef CONFIG_DEFAULTS_SBI_H
+#define CONFIG_DEFAULTS_SBI_H
+
+/** @file
+ *
+ * Configuration defaults for RISC-V SBI
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#define IOAPI_RISCV
+#define IOMAP_VIRT
+#define DMAAPI_FLAT
+#define UACCESS_FLAT
+#define TIMER_ZICNTR
+#define ENTROPY_ZKR
+
+#define CONSOLE_SBI
+#define REBOOT_SBI
+#define UMALLOC_SBI
+
+#define ACPI_NULL
+#define MPAPI_NULL
+#define NAP_NULL
+#define PCIAPI_NULL
+#define SANBOOT_NULL
+#define SMBIOS_NULL
+#define TIME_NULL
+
+#define IMAGE_SCRIPT
+
+#define REBOOT_CMD
+#define POWEROFF_CMD
+
+#endif /* CONFIG_DEFAULTS_SBI_H */