[riscv] Add support for the SBI debug console

Add the ability to issue Supervisor Binary Interface (SBI) calls via
the ECALL instruction, and use the SBI DBCN extension to implement a
debug console.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
diff --git a/src/arch/riscv/Makefile b/src/arch/riscv/Makefile
index 668d7db..d3ae4e8 100644
--- a/src/arch/riscv/Makefile
+++ b/src/arch/riscv/Makefile
@@ -10,6 +10,7 @@
 # RISCV-specific directories containing source files
 #
 SRCDIRS		+= arch/riscv/core
+SRCDIRS		+= arch/riscv/interface/sbi
 
 # RISCV-specific flags
 #
diff --git a/src/arch/riscv/include/ipxe/sbi.h b/src/arch/riscv/include/ipxe/sbi.h
new file mode 100644
index 0000000..529f1d0
--- /dev/null
+++ b/src/arch/riscv/include/ipxe/sbi.h
@@ -0,0 +1,157 @@
+#ifndef _IPXE_SBI_H
+#define _IPXE_SBI_H
+
+/** @file
+ *
+ * Supervisor Binary Interface (SBI)
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <stdint.h>
+
+/** An SBI function return value */
+struct sbi_return {
+	/** Error status (returned in a0) */
+	long error;
+	/** Data value (returned in a1) */
+	long value;
+};
+
+/**
+ * @defgroup sbierrors SBI errors
+ *
+ * *{
+ */
+#define SBI_SUCCESS 0			/**< Completed successfully */
+#define SBI_ERR_FAILED -1		/**< Failed */
+#define SBI_ERR_NOT_SUPPORTED -2	/**< Not supported */
+#define SBI_ERR_INVALID_PARAM -3	/**< Invalid parameter(s) */
+#define SBI_ERR_DENIED -4		/**< Denied or not allowed */
+#define SBI_ERR_INVALID_ADDRESS -5	/**< Invalid address(es) */
+#define SBI_ERR_ALREADY_AVAILABLE -6	/**< Already available */
+#define SBI_ERR_ALREADY_STARTED -7	/**< Already started */
+#define SBI_ERR_ALREADY_STOPPED -8	/**< Already stopped */
+#define SBI_ERR_NO_SHMEM -9		/**< Shared memory not available */
+#define SBI_ERR_INVALID_STATE -10	/**< Invalid state */
+#define SBI_ERR_BAD_RANGE -11		/**< Bad (or invalid) range */
+#define SBI_ERR_TIMEOUT -12		/**< Failed due to timeout */
+#define SBI_ERR_IO -13			/**< Input/output error */
+/** @} */
+
+/** Construct SBI extension ID */
+#define SBI_EID( c1, c2, c3, c4 ) \
+	( (int) ( ( (c1) << 24 ) | ( (c2) << 16 ) | ( (c3) << 8 ) | (c4) ) )
+
+/**
+ * Call supervisor with no parameters
+ *
+ * @v eid		Extension ID
+ * @v fid		Function ID
+ * @ret ret		Return value
+ */
+static inline __attribute__ (( always_inline )) struct sbi_return
+sbi_ecall_0 ( int eid, int fid ) {
+	register unsigned long a7 asm ( "a7" ) = ( ( long ) eid );
+	register unsigned long a6 asm ( "a6" ) = ( ( long ) fid );
+	register unsigned long a0 asm ( "a0" );
+	register unsigned long a1 asm ( "a1" );
+	struct sbi_return ret;
+
+	__asm__ __volatile__ ( "ecall"
+			       : "=r" ( a0 ), "=r" ( a1 )
+			       : "r" ( a6 ), "r" ( a7 )
+			       : "memory" );
+	ret.error = a0;
+	ret.value = a1;
+	return ret;
+}
+
+/**
+ * Call supervisor with one parameter
+ *
+ * @v eid		Extension ID
+ * @v fid		Function ID
+ * @v param0		Parameter 0
+ * @ret ret		Return value
+ */
+static inline __attribute__ (( always_inline )) struct sbi_return
+sbi_ecall_1 ( int eid, int fid, unsigned long p0 ) {
+	register unsigned long a7 asm ( "a7" ) = ( ( long ) eid );
+	register unsigned long a6 asm ( "a6" ) = ( ( long ) fid );
+	register unsigned long a0 asm ( "a0" ) = p0;
+	register unsigned long a1 asm ( "a1" );
+	struct sbi_return ret;
+
+	__asm__ __volatile__ ( "ecall"
+			       : "+r" ( a0 ), "=r" ( a1 )
+			       : "r" ( a6 ), "r" ( a7 )
+			       : "memory" );
+	ret.error = a0;
+	ret.value = a1;
+	return ret;
+}
+
+/**
+ * Call supervisor with two parameters
+ *
+ * @v eid		Extension ID
+ * @v fid		Function ID
+ * @v param0		Parameter 0
+ * @v param1		Parameter 1
+ * @ret ret		Return value
+ */
+static inline __attribute__ (( always_inline )) struct sbi_return
+sbi_ecall_2 ( int eid, int fid, unsigned long p0, unsigned long p1 ) {
+	register unsigned long a7 asm ( "a7" ) = ( ( long ) eid );
+	register unsigned long a6 asm ( "a6" ) = ( ( long ) fid );
+	register unsigned long a0 asm ( "a0" ) = p0;
+	register unsigned long a1 asm ( "a1" ) = p1;
+	struct sbi_return ret;
+
+	__asm__ __volatile__ ( "ecall"
+			       : "+r" ( a0 ), "+r" ( a1 )
+			       : "r" ( a6 ), "r" ( a7 )
+			       : "memory" );
+	ret.error = a0;
+	ret.value = a1;
+	return ret;
+}
+
+/**
+ * Call supervisor with three parameters
+ *
+ * @v eid		Extension ID
+ * @v fid		Function ID
+ * @v param0		Parameter 0
+ * @v param1		Parameter 1
+ * @v param2		Parameter 2
+ * @ret ret		Return value
+ */
+static inline __attribute__ (( always_inline )) struct sbi_return
+sbi_ecall_3 ( int eid, int fid, unsigned long p0, unsigned long p1,
+	      unsigned long p2 ) {
+	register unsigned long a7 asm ( "a7" ) = ( ( long ) eid );
+	register unsigned long a6 asm ( "a6" ) = ( ( long ) fid );
+	register unsigned long a0 asm ( "a0" ) = p0;
+	register unsigned long a1 asm ( "a1" ) = p1;
+	register unsigned long a2 asm ( "a2" ) = p2;
+	struct sbi_return ret;
+
+	__asm__ __volatile__ ( "ecall"
+			       : "+r" ( a0 ), "+r" ( a1 )
+			       : "r" ( a2 ), "r" ( a6 ), "r" ( a7 )
+			       : "memory" );
+	ret.error = a0;
+	ret.value = a1;
+	return ret;
+}
+
+/** Debug console extension */
+#define SBI_DBCN SBI_EID ( 'D', 'B', 'C', 'N' )
+#define SBI_DBCN_WRITE 0x00		/**< Console Write */
+#define SBI_DBCN_READ 0x01		/**< Console Read */
+#define SBI_DBCN_WRITE_BYTE 0x02	/**< Console Write Byte */
+
+#endif /* _IPXE_SBI_H */
diff --git a/src/arch/riscv/interface/sbi/sbi_console.c b/src/arch/riscv/interface/sbi/sbi_console.c
new file mode 100644
index 0000000..8352555
--- /dev/null
+++ b/src/arch/riscv/interface/sbi/sbi_console.c
@@ -0,0 +1,101 @@
+/*
+ * 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
+ *
+ * SBI debug console
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <ipxe/sbi.h>
+#include <ipxe/io.h>
+#include <ipxe/console.h>
+#include <config/console.h>
+
+/* Set default console usage if applicable */
+#if ! ( defined ( CONSOLE_SBI ) && CONSOLE_EXPLICIT ( CONSOLE_SBI ) )
+#undef CONSOLE_SBI
+#define CONSOLE_SBI ( CONSOLE_USAGE_ALL & ~CONSOLE_USAGE_LOG )
+#endif
+
+/** Buffered input character (if any) */
+static unsigned char sbi_console_input;
+
+/**
+ * Print a character to SBI console
+ *
+ * @v character		Character to be printed
+ */
+static void sbi_putchar ( int character ) {
+
+	/* Write byte to console */
+	sbi_ecall_1 ( SBI_DBCN, SBI_DBCN_WRITE_BYTE, character );
+}
+
+/**
+ * Get character from SBI console
+ *
+ * @ret character	Character read from console, if any
+ */
+static int sbi_getchar ( void ) {
+	int character;
+
+	/* Consume and return buffered character, if any */
+	character = sbi_console_input;
+	sbi_console_input = 0;
+	return character;
+}
+
+/**
+ * Check for character ready to read from SBI console
+ *
+ * @ret True		Character available to read
+ * @ret False		No character available to read
+ */
+static int sbi_iskey ( void ) {
+	struct sbi_return ret;
+
+	/* Do nothing if we already have a buffered character */
+	if ( sbi_console_input )
+		return sbi_console_input;
+
+	/* Read and buffer byte from console, if any */
+	ret = sbi_ecall_3 ( SBI_DBCN, SBI_DBCN_READ,
+			    sizeof ( sbi_console_input ),
+			    virt_to_phys ( &sbi_console_input ), 0 );
+	if ( ret.error )
+		return 0;
+
+	/* Return number of characters read and buffered */
+	return ret.value;
+}
+
+/** SBI console */
+struct console_driver sbi_console_driver __console_driver = {
+	.putchar = sbi_putchar,
+	.getchar = sbi_getchar,
+	.iskey = sbi_iskey,
+	.usage = CONSOLE_SBI,
+};
diff --git a/src/config/config.c b/src/config/config.c
index 0f950eb..4cfa5dd 100644
--- a/src/config/config.c
+++ b/src/config/config.c
@@ -78,6 +78,9 @@
 #ifdef CONSOLE_DEBUGCON
 REQUIRE_OBJECT ( debugcon );
 #endif
+#ifdef CONSOLE_SBI
+REQUIRE_OBJECT ( sbi_console );
+#endif
 
 /*
  * Drag in all requested network protocols
diff --git a/src/config/console.h b/src/config/console.h
index 9f770d0..0ff328b 100644
--- a/src/config/console.h
+++ b/src/config/console.h
@@ -41,6 +41,7 @@
 //#define	CONSOLE_VMWARE		/* VMware logfile console */
 //#define	CONSOLE_DEBUGCON	/* Bochs/QEMU/KVM debug port console */
 //#define	CONSOLE_INT13		/* INT13 disk log console */
+//#define	CONSOLE_SBI		/* RISC-V SBI debug console */
 
 /*
  * Very obscure console types