aboutsummaryrefslogtreecommitdiff
path: root/arch/riscv/supervisor/hsm
diff options
context:
space:
mode:
Diffstat (limited to 'arch/riscv/supervisor/hsm')
-rw-r--r--arch/riscv/supervisor/hsm/Makefile63
-rw-r--r--arch/riscv/supervisor/hsm/README.md126
-rw-r--r--arch/riscv/supervisor/hsm/compiler.h12
-rw-r--r--arch/riscv/supervisor/hsm/head.S44
-rw-r--r--arch/riscv/supervisor/hsm/hsm.c215
-rw-r--r--arch/riscv/supervisor/hsm/hsm.ld.S48
-rw-r--r--arch/riscv/supervisor/hsm/sbi.c29
-rw-r--r--arch/riscv/supervisor/hsm/sbi.h73
8 files changed, 610 insertions, 0 deletions
diff --git a/arch/riscv/supervisor/hsm/Makefile b/arch/riscv/supervisor/hsm/Makefile
new file mode 100644
index 0000000..b799cf1
--- /dev/null
+++ b/arch/riscv/supervisor/hsm/Makefile
@@ -0,0 +1,63 @@
+V =
+ifeq ($(strip $(V)),)
+ E = @echo
+ Q = @
+else
+ E = @\#
+ Q =
+endif
+
+CC = $(CROSS_COMPILE)gcc$(CC_SUFFIX)
+LD = $(CROSS_COMPILE)ld
+QEMU ?= qemu-system-riscv64
+
+SRC = $(filter-out hsm.ld.S, $(wildcard *.S *.c))
+OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(SRC)))
+LINKER = hsm.ld
+KERNEL = hsm
+
+ISA ?= rv64imafdc_zicntr_zicsr_zifencei_zihpm_zca_zcd_zba_zbb
+ASFLAGS = -march=$(ISA) -mabi=lp64d -mcmodel=medany -fno-PIE -g
+CCFLAGS = $(ASFLAGS) -std=gnu17 -nostdinc -nostdlib -O0
+WARNINGS = -Werror -Wpedantic -Wall -Wextra -Wcast-align -Wcast-qual -Winit-self \
+ -Wmissing-include-dirs -Wredundant-decls -Wshadow -Wsign-conversion \
+ -Wswitch-default -Wundef -Wunreachable-code
+CCFLAGS += $(WARNINGS)
+LDFLAGS = -static -melf64lriscv -z noexecstack -T $(LINKER)
+QEMU_FLAGS += -nographic
+
+CPUS ?= 4
+
+.PHONY: hsm
+$(KERNEL): config $(OBJ)
+ $(Q) $(CC) -E $(LINKER).S -Iinclude/ -o $(LINKER)
+
+ $(E) " LD " $@
+ $(Q) $(LD) $(LDFLAGS) $(OBJ) -o $(KERNEL)
+
+.PHONY: config
+config:
+ $(Q) rm -f config.h
+ $(Q) echo "#ifndef __CONFIG_H__" >> config.h
+ $(Q) echo "#define __CONFIG_H__" >> config.h
+ $(Q) echo -e "\n#define NR_CPUS $(CPUS)\n" >> config.h
+ $(Q) echo "#if (NR_CPUS < 2 || NR_CPUS > 9)" >> config.h
+ $(Q) echo " #error \"Use a value of 'CPUS' > 1 and < 10\"" >> config.h
+ $(Q) echo -e "#endif // #if (NR_CPUS < 2 || NR_CPUS > 9)\n" >> config.h
+ $(Q) echo "#endif // __CONFIG_H__" >> config.h
+
+.c.o:
+ $(E) " CC " $(basename $@)
+ $(Q) $(CC) $(CCFLAGS) -c $< -o $@
+
+.S.o:
+ $(E) " CC " $(basename $@)
+ $(Q) $(CC) $(CCFLAGS) -c $< -o $@
+
+.PHONY: clean
+clean:
+ $(Q) rm -f *.o $(KERNEL) $(LINKER) config.h
+
+.PHONY: qemu
+qemu: clean $(KERNEL)
+ $(Q) $(QEMU) $(QEMU_FLAGS) -smp $(CPUS) -machine virt -kernel $(KERNEL)
diff --git a/arch/riscv/supervisor/hsm/README.md b/arch/riscv/supervisor/hsm/README.md
new file mode 100644
index 0000000..a950152
--- /dev/null
+++ b/arch/riscv/supervisor/hsm/README.md
@@ -0,0 +1,126 @@
+# Messing with the Hart State Management Extension ("HSM") from the SBI
+
+This is a simple kernel that messes with the HSM Extension from the SBI v2.0. In
+general, and according to the RISC-V specification, harts can enter Supervisor
+mode at any time. This makes thing simple both for the RISC-V specification and
+machines implementing the architecture. For the kernel this means that you never
+know which hart is going to start first, and in cases like the Linux kernel, you
+actually want to ensure that only one hart is running for initialization
+purposes before waking up the rest. In order to guarantee this, the Linux kernel
+runs a lottery.
+
+To run a lottery on this context means that the first hart to appear will
+actually take a lock in the kernel, which will block any other "secondary" hart
+that appears later. This lock is going to be released once the "main" hart
+guarantees that the kernel has been initialized up to a point where other harts
+can come in. This lock is implemented with an atomic variable that holds how
+many harts have been seen (hence, the first hart will read a zero value from
+this variable, and the rest will read a non-zero one).
+
+All of that being said, the SBI specification v2.0 comes with a cleaner
+approach: the HSM extension. With this extension a Supervisor will be able to
+manage the state of any hart of the system, which is either `started`, `stopped`
+or `suspended` (plus transition states). Moreover, there is the guarantee that
+only one hart will be initialized with the `started` state, whereas the rest
+will be on `stopped`. This means that a kernel can drop the whole idea of a
+lottery and have the guarantee that only one hart will run on start. Whenever
+the kernel has been initialized to a specific stage, it will be able to change
+the state of the rest of harts to `started`.
+
+## This example
+
+This example uses the HSM extension but it also keeps the idea of a lottery.
+That is, the first hart will "acquire" the lock ("win" the lottery), and will be
+responsible for bringing the rest up. At first this hart will simply print
+information on the system. For example, on a system with four harts you will get
+something like:
+
+```
+Hello, world!
+Hart that won the lottery: 1
+Harts still asleep: 0, 2, 3
+
+How many harts have _participated_ in the lottery? 1
+How many harts have _failed_ in the lottery? 0
+```
+
+In this example, only one hart is available: one that "won" the lottery, and the
+rest have not even appeared. The hart with ID '1' will be responsible for waking
+up '0', '2' and '3'. After making some initial checks, it will wake up the first
+secondary hart (no reason to just wake up one, just doing it this way for the
+show):
+
+```
+Waking up first secondary hart
+How many harts have _participated_ in the lottery? 2
+How many harts have _failed_ in the lottery? 1
+```
+
+Now two harts are available, but the one that woke up is a "secondary" one, so
+it lost the lottery and is stuck in `head.S` on an infinite loop doing nothing.
+After doing that, the main hart will wake up the rest:
+
+```
+Waking up the rest
+How many harts have _participated_ in the lottery? 4
+How many harts have _failed_ in the lottery? 3
+```
+
+Now all four harts are up, but three of them (the "secondary" ones) are simply
+stuck in an infinite loop since they have "lost" the lottery. At this point this
+example does something weird for a kernel: it will return from the main
+function. Doing so allows `head.S` to count the main hart as a "failed" one, and
+it will print some final messages for it:
+
+```
+Goodbye, cruel world!
+How many harts have _participated_ in the lottery? 4
+How many harts have _failed_ in the lottery? 4
+
+THE END
+```
+
+At the point where "THE END" is printed, we will then perform an SBI call to the
+System Reset Extension ("SRST") to gracefully shutdown the system.
+
+## Test
+
+To test this yourself you need a recent enough QEMU which is able to run as a
+RISC-V system, and you need to set the `CROSS_COMPILE` environment variable as
+you would on the Linux Kernel if you are doing this on a non-RISC-V machine.
+After that, you can simply run `make` to build the binary, or simply run `make
+qemu` to both build it and run QEMU.
+
+Notice also that the Makefile accepts the `CPUS` variable, which by default is
+set to 4. Change this variable to something else to get a different number of
+harts on the example (but lesser than 10, since I haven't bothered to account
+for that when printing numerical values). For example, `make qemu CPUS=8`:
+
+``` shell
+Hello, world!
+Hart that won the lottery: 5
+Harts still asleep: 0, 1, 2, 3, 4, 6, 7
+How many harts have _participated_ in the lottery? 1
+How many harts have _failed_ in the lottery? 0
+
+Waking up first secondary hart
+How many harts have _participated_ in the lottery? 2
+How many harts have _failed_ in the lottery? 1
+
+Waking up the rest
+How many harts have _participated_ in the lottery? 8
+How many harts have _failed_ in the lottery? 7
+
+Goodbye, cruel world!
+How many harts have _participated_ in the lottery? 8
+How many harts have _failed_ in the lottery? 8
+
+THE END
+```
+
+## Rationale
+
+This example came after exploring the topic of SMP on
+[fbos](https://github.com/mssola/fbos). Even if in there I did not explore this
+extension further (because of the needs of the project), I decided that I wanted
+a fresh simple kernel to mess with this extension. And hence this project.
diff --git a/arch/riscv/supervisor/hsm/compiler.h b/arch/riscv/supervisor/hsm/compiler.h
new file mode 100644
index 0000000..f86b0da
--- /dev/null
+++ b/arch/riscv/supervisor/hsm/compiler.h
@@ -0,0 +1,12 @@
+#ifndef __COMPILER_H__
+#define __COMPILER_H__
+
+typedef unsigned long uint64_t;
+typedef unsigned long uintptr_t;
+typedef unsigned long size_t;
+
+typedef struct {
+ unsigned int value;
+} atomic_t;
+
+#endif // __COMPILER_H__
diff --git a/arch/riscv/supervisor/hsm/head.S b/arch/riscv/supervisor/hsm/head.S
new file mode 100644
index 0000000..e912b18
--- /dev/null
+++ b/arch/riscv/supervisor/hsm/head.S
@@ -0,0 +1,44 @@
+.global _start
+.section .head.text
+
+_start:
+ // Start by simply disabling interrupts.
+ csrw sie, zero
+ csrw sip, zero
+ fence.i
+
+ // Initialize the 'a4' register, which is going to be used for printing out
+ // the end message by the main hart.
+ li a4, 0
+
+ // Hart lottery! The main hart will win it, the rest will simply jump into
+ // .Lhalt. See hsm.c for more details.
+ la a3, hart_lottery
+ li a2, 1
+ amoadd.w a3, a2, (a3)
+ bnez a3, .Lhalt
+
+ // Setup a dummy stack and call the kernel.
+ la sp, _STACK_PTR
+ call start_kernel
+
+ // We actually expect the main kernel routine to quit. Here we make the
+ // .Lhalt part of the code to print special messages for that case.
+ addi a4, a4, 1
+
+.Lhalt:
+ // Increase the number of hart that lost the lottery.
+ la a3, hart_failed_lottery
+ li a2, 1
+ amoadd.w zero, a2, (a3)
+
+ // If it's the main hart that is bailing out, first print some final
+ // messages.
+ beqz a4, .Lend
+ call print_lottery
+ call end
+
+ // Die Bart, die!
+.Lend:
+ wfi
+ j .Lend
diff --git a/arch/riscv/supervisor/hsm/hsm.c b/arch/riscv/supervisor/hsm/hsm.c
new file mode 100644
index 0000000..0b2daca
--- /dev/null
+++ b/arch/riscv/supervisor/hsm/hsm.c
@@ -0,0 +1,215 @@
+#include "compiler.h"
+#include "config.h"
+#include "sbi.h"
+
+// Implemented in head.S. Here we need the symbol in order to pass the address
+// of the function when starting secondary harts.
+extern void _start(void);
+
+/*
+ * Similar to the Linux kernel and what I did in https://github.com/mssola/fbos,
+ * harts will enter in a lottery when starting. See the README.md file for more
+ * details on this.
+ */
+atomic_t hart_lottery __attribute__((__section__(".sdata")));
+atomic_t hart_failed_lottery __attribute__((__section__(".sdata")));
+
+// Fake a delay so it's easier on the eyes.
+#define SLEEP() \
+ for (int i = 0; i < 1000000000; i++) { \
+ }
+
+/*
+ * Printing utilities.
+ */
+
+void printk(const char *const message);
+
+void write(const char *const message, size_t n)
+{
+ struct sbi_ret ret = sbi_ecall2(DBCN_EXT, DBCN_WRITE, n, (unsigned long)message);
+ if (ret.error != SBI_SUCCESS) {
+ for (;;) {
+ ;
+ }
+ }
+}
+
+size_t strlen(const char *const message)
+{
+ size_t i = 0;
+
+ for (; message[i] != '\0'; i++)
+ ;
+
+ return i;
+}
+
+__attribute__((__noreturn__)) void die(const char *const message)
+{
+ if (message) {
+ printk(message);
+ }
+
+ for (;;)
+ ;
+}
+
+void print_digit(uint64_t digit)
+{
+ char buffer[2];
+
+ if (digit > 9) {
+ die("We cannot print numbers with two or more digits :D\n");
+ }
+
+ buffer[0] = '0' + digit;
+ buffer[1] = '\0';
+ write(buffer, 2);
+}
+
+void printk(const char *const message)
+{
+ size_t len = strlen(message);
+ if (!len) {
+ return;
+ }
+
+ write(message, len);
+}
+
+/*
+ * Printing/asserting the stats of the lottery.
+ */
+
+void assert_lottery(uint64_t participated, uint64_t failed)
+{
+ if (hart_lottery.value != participated) {
+ die("Bad number for participating harts\n");
+ }
+ if (hart_failed_lottery.value != failed) {
+ die("Bad number for failed harts\n");
+ }
+}
+
+void print_lottery(void)
+{
+ printk("How many harts have _participated_ in the lottery? ");
+ print_digit(hart_lottery.value);
+ write("\n", 1);
+
+ printk("How many harts have _failed_ in the lottery? ");
+ print_digit(hart_failed_lottery.value);
+ write("\n", 1);
+}
+
+// Called at the very end of the program. It will shutdown the machine.
+void end(void)
+{
+ printk("\nTHE END\n");
+
+ // SBI call to the SRST extension to gracefully shutdown the machine.
+ sbi_ecall2(SRST_EXT, SRST_RESET, SRST_SHUTDOWN, SRST_NO_REASON);
+}
+
+void start_kernel(uint64_t hart_id, uintptr_t opaque)
+{
+ printk("Hello, world!\n");
+
+ /*
+ * Figure out the harts that are meant to be stopped for now.
+ */
+
+ unsigned int other_harts[NR_CPUS - 1];
+ struct sbi_ret ret;
+
+ printk("Hart that won the lottery: ");
+ print_digit(hart_id);
+ write("\n", 1);
+
+ printk("Hart still asleep: ");
+ for (unsigned int i = 0, j = 0; i < NR_CPUS; i++) {
+ if (i != hart_id) {
+ print_digit(i);
+ if (j + 1 < NR_CPUS - 1) {
+ write(", ", 2);
+ }
+ other_harts[j++] = i;
+ }
+ }
+ write("\n", 1);
+
+ /*
+ * ASSERT: all harts in 'other_harts' are marked as stopped by HSM.
+ */
+
+ for (unsigned int i = 0; i < NR_CPUS - 1; i++) {
+ ret = sbi_ecall1(HSM_EXT, HSM_HART_GET_STATUS, other_harts[i]);
+
+ if (ret.error != SBI_SUCCESS) {
+ die("HSM_HART_GET_STATUS: unexpected error from SBI\n");
+ }
+ if (ret.value != HSM_HART_STOPPED) {
+ die("HSM_HART_GET_STATUS: hart that was supposed to be stopped is not!\n");
+ }
+ }
+
+ // Initial state of the lottery.
+ print_lottery();
+ assert_lottery(1, 0);
+ write("\n", 1);
+
+ /*
+ * Wake up the first hart.
+ */
+
+ printk("Waking up first secondary hart\n");
+
+ ret = sbi_ecall3(HSM_EXT, HSM_HART_START, other_harts[0], (uintptr_t)&_start, opaque);
+ if (ret.error != SBI_SUCCESS) {
+ die("HSM_HART_START: unexpected error from SBI\n");
+ }
+
+ SLEEP();
+
+ ret = sbi_ecall1(HSM_EXT, HSM_HART_GET_STATUS, other_harts[0]);
+ if (ret.error != SBI_SUCCESS) {
+ die("HSM_HART_GET_STATUS: unexpected error from SBI\n");
+ }
+ if (ret.value != HSM_HART_STARTED) {
+ die("HSM_HART_GET_STATUS: hart that was supposed to be started is not!\n");
+ }
+
+ // Another hart should have entered the lottery and failed.
+ print_lottery();
+ assert_lottery(2, 1);
+ write("\n", 1);
+
+ /*
+ * Now wake up the rest.
+ */
+
+ printk("Waking up the rest\n");
+
+ for (unsigned int i = 1; i < NR_CPUS - 1; i++) {
+ ret = sbi_ecall3(HSM_EXT, HSM_HART_START, other_harts[i], (uintptr_t)&_start, opaque);
+ if (ret.error != SBI_SUCCESS) {
+ die("HSM_HART_START: unexpected error from SBI\n");
+ }
+ }
+
+ SLEEP();
+
+ // All awake, only one good.
+ print_lottery();
+ assert_lottery(NR_CPUS, NR_CPUS - 1);
+ write("\n", 1);
+
+ /*
+ * Kernels usually don't quit, but let's do it for the heck of it; head.S
+ * will handle it.
+ */
+
+ printk("Goodbye, cruel world!\n");
+ SLEEP();
+}
diff --git a/arch/riscv/supervisor/hsm/hsm.ld.S b/arch/riscv/supervisor/hsm/hsm.ld.S
new file mode 100644
index 0000000..db83dc9
--- /dev/null
+++ b/arch/riscv/supervisor/hsm/hsm.ld.S
@@ -0,0 +1,48 @@
+#define PAGE_SIZE 0x1000
+#define STACK_SIZE PAGE_SIZE
+
+#define LOAD_BASE_ADDR 0x80000000
+#define LOAD_BASE_OFFSET 0x00200000
+#define LOAD_OFFSET (LOAD_BASE_ADDR + LOAD_BASE_OFFSET)
+
+OUTPUT_ARCH(riscv)
+ENTRY(_start)
+
+SECTIONS {
+ . = LOAD_OFFSET;
+ _start = .;
+ . = ALIGN(PAGE_SIZE);
+
+ .text : {
+ _text = .;
+ KEEP(*(.head.text))
+ . = ALIGN(PAGE_SIZE);
+ *(.text)
+ }
+ . = ALIGN(PAGE_SIZE);
+
+ .data : {
+ *(.data)
+ }
+ . = ALIGN(8);
+
+ .rodata : {
+ *(.rodata)
+ }
+ . = ALIGN(8);
+
+ .sdata : {
+ *(.sdata*)
+ }
+ . = ALIGN(8);
+
+ .bss : {
+ *(.bss)
+ }
+
+ . = ALIGN(8);
+ . = . + STACK_SIZE;
+ _STACK_PTR = .;
+
+ _end = .;
+}
diff --git a/arch/riscv/supervisor/hsm/sbi.c b/arch/riscv/supervisor/hsm/sbi.c
new file mode 100644
index 0000000..72c1521
--- /dev/null
+++ b/arch/riscv/supervisor/hsm/sbi.c
@@ -0,0 +1,29 @@
+#include "compiler.h"
+#include "sbi.h"
+
+// Implementation taken from the Linux kernel (6.12).
+struct sbi_ret __sbi_ecall(unsigned long arg0, unsigned long arg1, unsigned long arg2,
+ unsigned long arg3, unsigned long arg4, unsigned long arg5, int fid,
+ int ext)
+{
+ struct sbi_ret ret;
+
+ register uintptr_t a0 asm("a0") = (uintptr_t)(arg0);
+ register uintptr_t a1 asm("a1") = (uintptr_t)(arg1);
+ register uintptr_t a2 asm("a2") = (uintptr_t)(arg2);
+ register uintptr_t a3 asm("a3") = (uintptr_t)(arg3);
+ register uintptr_t a4 asm("a4") = (uintptr_t)(arg4);
+ register uintptr_t a5 asm("a5") = (uintptr_t)(arg5);
+ register uintptr_t a6 asm("a6") = (uintptr_t)(fid);
+ register uintptr_t a7 asm("a7") = (uintptr_t)(ext);
+
+ asm volatile("ecall"
+ : "+r"(a0), "+r"(a1)
+ : "r"(a2), "r"(a3), "r"(a4), "r"(a5), "r"(a6), "r"(a7)
+ : "memory");
+
+ ret.error = (long)a0;
+ ret.value = (long)a1;
+
+ return ret;
+}
diff --git a/arch/riscv/supervisor/hsm/sbi.h b/arch/riscv/supervisor/hsm/sbi.h
new file mode 100644
index 0000000..7891f76
--- /dev/null
+++ b/arch/riscv/supervisor/hsm/sbi.h
@@ -0,0 +1,73 @@
+#ifndef __SBI_H__
+#define __SBI_H__
+
+enum sbi_ret_error {
+ SBI_SUCCESS = 0,
+ SBI_ERR_FAILED = -1,
+ SBI_ERR_NOT_SUPPORTED = -2,
+ SBI_ERR_INVALID_PARAM = -3,
+ SBI_ERR_DENIED = -4,
+ SBI_ERR_INVALID_ADDRESS = -5,
+ SBI_ERR_ALREADY_AVAILABLE = -6,
+ SBI_ERR_ALREADY_STARTED = -7,
+ SBI_ERR_ALREADY_STOPPED = -8,
+ SBI_ERR_NO_SHMEM = -9,
+};
+
+enum sbi_ext {
+ HSM_EXT = 0x0048534D,
+ DBCN_EXT = 0x4442434E,
+ SRST_EXT = 0x53525354,
+};
+
+enum hsm_actions {
+ HSM_HART_START = 0x00,
+ HSM_HART_STOP = 0x01,
+ HSM_HART_GET_STATUS = 0x02,
+ HSM_HART_SUSPEND = 0x03,
+};
+
+enum hsm_states {
+ HSM_HART_STARTED = 0,
+ HSM_HART_STOPPED = 1,
+ HSM_HART_START_PENDING = 2,
+ HSM_HART_STOP_PENDING = 3,
+ HSM_HART_SUSPENDED = 4,
+ HSM_HART_SUSPEND_PENDING = 5,
+ HSM_HART_RESUME_PENDING = 6,
+};
+
+enum dbcn_actions {
+ DBCN_WRITE = 0x00,
+};
+
+enum srst_actions {
+ SRST_RESET = 0,
+};
+
+enum srst_type {
+ SRST_SHUTDOWN = 0,
+ SRST_COLD_REBOOT = 1,
+ SRST_WARM_REBOOT = 2,
+};
+
+enum srst_reason {
+ SRST_NO_REASON = 0,
+ SRST_SYS_FAILURE = 1,
+};
+
+struct sbi_ret {
+ long error;
+ long value;
+};
+
+struct sbi_ret __sbi_ecall(unsigned long arg0, unsigned long arg1, unsigned long arg2,
+ unsigned long arg3, unsigned long arg4, unsigned long arg5, int fid,
+ int ext);
+
+#define sbi_ecall0(ext, fid) __sbi_ecall(0, 0, 0, 0, 0, 0, fid, ext)
+#define sbi_ecall1(ext, fid, arg0) __sbi_ecall(arg0, 0, 0, 0, 0, 0, fid, ext)
+#define sbi_ecall2(ext, fid, arg0, arg1) __sbi_ecall(arg0, arg1, 0, 0, 0, 0, fid, ext)
+#define sbi_ecall3(ext, fid, arg0, arg1, arg2) __sbi_ecall(arg0, arg1, arg2, 0, 0, 0, fid, ext)
+
+#endif // __SBI_H__