[riscv] Add support for checking CPU extensions reported via device tree

RISC-V seems to allow for direct discovery of CPU features only from
M-mode (e.g. by setting up a trap handler and then attempting to
access a CSR), with S-mode code expected to read the resulting
constructed ISA description from the device tree.

Add the ability to check for the presence of named extensions listed
in the "riscv,isa" property of the device tree node corresponding to
the boot hart.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
diff --git a/src/arch/riscv/core/hart.c b/src/arch/riscv/core/hart.c
new file mode 100644
index 0000000..c4f2bd0
--- /dev/null
+++ b/src/arch/riscv/core/hart.c
@@ -0,0 +1,100 @@
+/*
+ * 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
+ *
+ * Hardware threads (harts)
+ *
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <ipxe/fdt.h>
+#include <ipxe/hart.h>
+
+/** Boot hart ID */
+unsigned long boot_hart;
+
+/** Colour for debug messages */
+#define colour &boot_hart
+
+/**
+ * Find boot hart node
+ *
+ * @v offset		Boot hart node offset
+ * @ret rc		Return status code
+ */
+static int hart_node ( unsigned int *offset ) {
+	char path[27 /* "/cpus/cpu@XXXXXXXXXXXXXXXX" + NUL */ ];
+	int rc;
+
+	/* Construct node path */
+	snprintf ( path, sizeof ( path ), "/cpus/cpu@%lx", boot_hart );
+
+	/* Find node */
+	if ( ( rc = fdt_path ( path, offset ) ) != 0 ) {
+		DBGC ( colour, "HART could not find %s: %s\n",
+		       path, strerror ( rc ) );
+		return rc;
+	}
+
+	return 0;
+}
+
+/**
+ * Check for supported extension
+ *
+ * @v ext		Extension name (including leading underscore)
+ * @ret rc		Return status code
+ */
+int hart_supported ( const char *ext ) {
+	unsigned int offset;
+	const char *isa;
+	const char *tmp;
+	int rc;
+
+	/* Find boot hart node */
+	if ( ( rc = hart_node ( &offset ) ) != 0 )
+		return rc;
+
+	/* Get ISA description */
+	isa = fdt_string ( offset, "riscv,isa" );
+	if ( ! isa ) {
+		DBGC ( colour, "HART could not identify ISA\n" );
+		return -ENOENT;
+	}
+	DBGC ( colour, "HART supports %s\n", isa );
+
+	/* Check for presence of extension */
+	tmp = isa;
+	while ( ( tmp = strstr ( tmp, ext ) ) != NULL ) {
+		tmp += strlen ( ext );
+		if ( ( *tmp == '\0' ) || ( *tmp == '_' ) )
+			return 0;
+	}
+
+	return -ENOTSUP;
+}
diff --git a/src/arch/riscv/include/bits/errfile.h b/src/arch/riscv/include/bits/errfile.h
index e97c71a..03f98c2 100644
--- a/src/arch/riscv/include/bits/errfile.h
+++ b/src/arch/riscv/include/bits/errfile.h
@@ -15,6 +15,7 @@
  */
 
 #define ERRFILE_sbi_reboot	( ERRFILE_ARCH | ERRFILE_CORE | 0x00000000 )
+#define ERRFILE_hart		( ERRFILE_ARCH | ERRFILE_CORE | 0x00010000 )
 
 /** @} */
 
diff --git a/src/arch/riscv/include/ipxe/hart.h b/src/arch/riscv/include/ipxe/hart.h
new file mode 100644
index 0000000..c201b6c
--- /dev/null
+++ b/src/arch/riscv/include/ipxe/hart.h
@@ -0,0 +1,16 @@
+#ifndef _IPXE_HART_H
+#define _IPXE_HART_H
+
+/** @file
+ *
+ * Hardware threads (harts)
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+extern unsigned long boot_hart;
+
+extern int hart_supported ( const char *ext );
+
+#endif /* _IPXE_HART_H */