aboutsummaryrefslogtreecommitdiff
path: root/arch/riscv/user
diff options
context:
space:
mode:
Diffstat (limited to 'arch/riscv/user')
-rw-r--r--arch/riscv/user/basics/Makefile19
-rw-r--r--arch/riscv/user/basics/basics.c162
-rw-r--r--arch/riscv/user/basics/factorial.S28
-rw-r--r--arch/riscv/user/basics/float.S40
-rw-r--r--arch/riscv/user/basics/string.S111
-rw-r--r--arch/riscv/user/hwprobe/Makefile60
-rw-r--r--arch/riscv/user/hwprobe/probe.c294
7 files changed, 714 insertions, 0 deletions
diff --git a/arch/riscv/user/basics/Makefile b/arch/riscv/user/basics/Makefile
new file mode 100644
index 0000000..b4752a3
--- /dev/null
+++ b/arch/riscv/user/basics/Makefile
@@ -0,0 +1,19 @@
+CC = $(CROSS_COMPILE)gcc
+CCFLAGS =
+EXE = basics
+
+DEBUG =
+ifneq ($(strip $(DEBUG)),)
+ CCFLAGS += -g
+endif
+
+.PHONY: all
+all: clean $(EXE)
+
+.PHONY: $(EXE)
+$(EXE):
+ $(CC) $(CCFLAGS) -o $(EXE) basics.c factorial.S string.S float.S
+
+.PHONY: clean
+clean:
+ @rm -f $(EXE)
diff --git a/arch/riscv/user/basics/basics.c b/arch/riscv/user/basics/basics.c
new file mode 100644
index 0000000..1d822c3
--- /dev/null
+++ b/arch/riscv/user/basics/basics.c
@@ -0,0 +1,162 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+/*
+ * Returns the factorial for the given unsigned 64-bit integer.
+ *
+ * Implemented in factorial.S
+ */
+int factorial(uint64_t n);
+
+void test_factorial(void)
+{
+ assert(factorial(0) == 0);
+ assert(factorial(1) == 1);
+ assert(factorial(2) == 2);
+ assert(factorial(3) == 6);
+ assert(factorial(4) == 24);
+
+ printf("factorial:\t\tOK\n");
+}
+
+/*
+ * Returns a pointer with the same given string but reversed. Note that the
+ * string cannot be in read-only space since the reversal is done in-place.
+ *
+ * Implemented in string.S
+ */
+char *reverse_string(char *str);
+
+void test_reverse_string(void)
+{
+ assert(reverse_string(NULL) == NULL);
+ assert(strlen(reverse_string("")) == 0);
+
+ char s1[] = "This is a string.";
+ assert(strcmp(reverse_string(s1), ".gnirts a si sihT") == 0);
+
+ char s2[] = ".";
+ assert(strcmp(reverse_string(s2), ".") == 0);
+
+ printf("reverse_string:\t\tOK\n");
+}
+
+/*
+ * Returns true if the given string is a palindrome, false otherwise.
+ *
+ * Implemented in string.S
+ */
+bool is_palindrome(char *str);
+
+void test_is_palindrome(void)
+{
+ assert(is_palindrome(NULL) == 0);
+ assert(is_palindrome("") == 0);
+ assert(is_palindrome("aba") == 1);
+ assert(is_palindrome("aaa") == 1);
+ assert(is_palindrome("This is a a si sihT") == 1);
+
+ printf("is_palindrome:\t\tOK\n");
+}
+
+/*
+ * This function computes the following: ((a + b) * b) + b. Don't try to make
+ * sense of it, that's the computation I ended up while messing with RISC-V
+ * floating point instructions. Anyways, with this in mind, it will return if
+ * the computed value is greater than 10.
+ *
+ * Implemented in float.S
+ */
+bool greater_than_ten(double a, uint64_t b);
+
+void test_greater_than_ten(void)
+{
+ assert(!greater_than_ten(2.3, 1)); // 4.3
+ assert(greater_than_ten(2.3, 2)); // 10.6
+
+ printf("greater_than_ten:\tOK\n");
+}
+
+/*
+ * Atomically add the integer pointed by `a` with the given `value`.
+ * this is a function, I've checked that the assembly produced by GCC on a
+ * decent optimization level actually inlines this.
+ */
+static inline void atomic_add(uint64_t *a, uint64_t value)
+{
+ /*
+ * The execution is a mere `amoadd` instruction, but it will set on `t0` the
+ * result. If it fails (e.g. the address was already being used), then `t0 =
+ * 0` and `beqz` will instruct it to try again.
+ *
+ * Note that on the board I'm using the 'Zawrs' extension is not available.
+ * Otherwise I could have re-arranged to code to something like this:
+ *
+ * amoadd.d t0, %1, %0
+ * bnez t0, .Latomic_add_done
+ * wrs.nto
+ * .Latomic_add_done:
+ *
+ * This is an extra instruction but it saves on power if the memory address
+ * happens to be used at access time.
+ *
+ * As for the 'A' RISC-V specific constraint, it means "An address that is
+ * held in a general-purpose register". GCC will then do the magic and
+ * convert it to `amoadd.d t0, a1, (a0)` or something like that. Note that
+ * we have to pass the '+' constraint modifier because the address will be
+ * both read and written atomically.
+ *
+ * See: https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html.
+ */
+ asm volatile(".Latomic_add_retry:\n\t"
+ "amoadd.d t0, %1, %0\n\t"
+ "beqz t0, .Latomic_add_retry"
+ : "+A"(*a)
+ : "r"(value)
+ : "t0", "memory");
+}
+
+void test_atomic_add(void)
+{
+ uint64_t i = 4;
+
+ atomic_add(&i, 0);
+ assert(i == 4);
+
+ atomic_add(&i, 2);
+ assert(i == 6);
+
+ printf("atomic_add:\t\tOK\n");
+}
+
+extern int mstrcmp(const char *s1, const char *s2);
+
+void test_mstrcmp(void)
+{
+ char *strings[] = {
+ "hello", "", "iello", "helloa", "contammusaaquellhomedegranarditquetantissimerra",
+ };
+
+ for (uint64_t i = 0; i < 1000000000; i++) {
+ assert(mstrcmp(strings[0], strings[0]) == 0);
+ assert(mstrcmp(strings[0], strings[1]) > 0);
+ assert(mstrcmp(strings[0], strings[2]) < 0);
+ assert(mstrcmp(strings[0], strings[3]) < 0);
+ assert(mstrcmp(strings[0], strings[4]) > 0);
+ }
+
+ printf("mstrcmp:\t\tOK\n");
+}
+
+int main()
+{
+ test_factorial();
+ test_reverse_string();
+ test_is_palindrome();
+ test_greater_than_ten();
+ test_atomic_add();
+ test_mstrcmp();
+}
diff --git a/arch/riscv/user/basics/factorial.S b/arch/riscv/user/basics/factorial.S
new file mode 100644
index 0000000..7e3df12
--- /dev/null
+++ b/arch/riscv/user/basics/factorial.S
@@ -0,0 +1,28 @@
+.text
+.globl factorial
+.type factorial, @function
+
+factorial:
+ // If input <= 1, just return the given thing.
+ addi t0, zero, 1
+ bgt a0, t0, else
+ jr ra
+else:
+ // Preserve the argument and the return address before the call.
+ addi sp, sp, -16
+ sd a0, 0(sp)
+ sd ra, 8(sp)
+
+ // Call the same function but with a value of argument-1.
+ addi a0, a0, -1
+ call factorial
+
+ // Restore back the original argument and the return address. Then multiply
+ // the original argument with the returned one from the previous call.
+ ld t1, 0(sp)
+ ld ra, 8(sp)
+ addi sp, sp, 16
+ mul a0, t1, a0
+
+ // And return to the caller.
+ jr ra
diff --git a/arch/riscv/user/basics/float.S b/arch/riscv/user/basics/float.S
new file mode 100644
index 0000000..f481f3e
--- /dev/null
+++ b/arch/riscv/user/basics/float.S
@@ -0,0 +1,40 @@
+.text
+.globl greater_than_ten
+.type greater_than_ten, @function
+
+// bool greater_than_ten(double a, uint64_t b);
+greater_than_ten:
+ // Upon entry a -> fa0; and b -> a0. That is, the ABI increments the
+ //(f)a<n> register per argument type.
+
+ // Change the rounding to "Rounds Towards Zero" (bits: 0b001).
+ li t0, 0b001
+ fsrm t0
+
+ // Convert the second parameter to double.
+ fcvt.d.lu ft0, a0
+
+ // ret = a + b
+ fadd.d fa0, fa0, ft0
+
+ // ret = (ret * b) + b
+ fmv.d ft1, ft0
+ fmadd.d fa0, fa0, ft0, ft1
+
+ // Now we need to compare the number with 10.0. There are multiple ways to
+ // load a floating point immediate:
+ // 1. Loading an integer immediate and then convert it into a double. This
+ // is discouraged on the RISC-V assembly manual.
+ // 2. Load a double with `fld` from a memory location on the data section.
+ // 3. Load a floating point immediate with `fli` (requires the `Zfa`
+ // extension).
+ // I don't have the `Zfa` extension on my board, so I'm opting for the
+ // second way.
+ fld ft1, .TEN, t0
+ fle.d a0, ft1, fa0
+
+ ret
+
+.data
+.TEN:
+ .double 10.0
diff --git a/arch/riscv/user/basics/string.S b/arch/riscv/user/basics/string.S
new file mode 100644
index 0000000..3878074
--- /dev/null
+++ b/arch/riscv/user/basics/string.S
@@ -0,0 +1,111 @@
+.text
+
+.globl reverse_string
+.type reverse_string, @function
+
+// char * reverse_string(char *str);
+reverse_string:
+ // Return early on null pointer.
+ beq a0, zero, end
+
+ // Preserve the original pointer.
+ addi sp, sp, -8
+ sd a0, 0(sp)
+
+ // Set `t0` to point to the end of the string.
+ add t0, a0, zero
+set_end_ptr:
+ lbu t2, 0(t0)
+ beq t2, zero, end_ptr_done
+ addi t0, t0, 1
+ j set_end_ptr
+
+ // We are done iterating, if `a0` and `t0` are equal, then there's nothing
+ // to be done and we can return early. Otherwise decrement `t0` so it points
+ // to the byte right before the null termination.
+end_ptr_done:
+ beq a0, t0, reverse_done
+ addi t0, t0, -1
+
+reverse_loop:
+ // Swap values between the two pointers.
+ lb t1, 0(a0)
+ lb t2, 0(t0)
+ sb t1, 0(t0)
+ sb t2, 0(a0)
+
+ // Move pointers and check whether the pointers have already crossed. If
+ // they have not crossed yet there is still looping to be done. Otherwise
+ // we are done.
+ addi a0, a0, 1
+ addi t0, t0, -1
+ bltu a0, t0, reverse_loop
+
+reverse_done:
+ // Restore things back and return to the caller.
+ ld a0, 0(sp)
+ addi sp, sp, 8
+end:
+ jr ra
+
+.globl is_palindrome
+.type is_palindrome, @function
+
+// bool is_palindrome(char *str);
+is_palindrome:
+ // Return early on null pointer.
+ beq a0, zero, palindrome_no
+
+ // Set `t0` to point to the end of the string.
+ add t0, a0, zero
+pal_set_end_ptr:
+ lbu t2, 0(t0)
+ beq t2, zero, pal_end_ptr_done
+ addi t0, t0, 1
+ j pal_set_end_ptr
+
+ // We are done iterating, if `a0` and `t0` are equal, then there's nothing
+ // to be done and we can return early. Otherwise decrement `t0` so it points
+ // to the byte right before the null termination.
+pal_end_ptr_done:
+ beq a0, t0, palindrome_no
+ addi t0, t0, -1
+
+pal_loop:
+ // Swap values between the two pointers.
+ lb t1, 0(a0)
+ lb t2, 0(t0)
+ bne t1, t2, palindrome_no
+
+ // Move pointers and check whether the pointers have already crossed. If
+ // they have not crossed yet there is still looping to be done. Otherwise
+ // we are done.
+ addi a0, a0, 1
+ addi t0, t0, -1
+ bleu a0, t0, pal_loop
+
+palindrome_yes:
+ li a0, 1
+ jr ra
+
+palindrome_no:
+ li a0, 0
+ jr ra
+
+.globl mstrcmp
+.type mstrcmp, @function
+
+// int mstrcmp(const char *, const char *);
+mstrcmp:
+1:
+ lbu t0, 0(a0)
+ lbu t1, 0(a1)
+ bne t0, t1, 2f
+ addi a0, a0, 1
+ addi a1, a1, 1
+ bnez t0, 1b
+ li a0, 0
+ ret
+2:
+ sub a0, t0, t1
+ ret
diff --git a/arch/riscv/user/hwprobe/Makefile b/arch/riscv/user/hwprobe/Makefile
new file mode 100644
index 0000000..9b813ff
--- /dev/null
+++ b/arch/riscv/user/hwprobe/Makefile
@@ -0,0 +1,60 @@
+##
+# By default everything is silent. If you want to change this behavior, simply
+# assign V=1 when calling make.
+
+V =
+ifeq ($(strip $(V)),)
+ E = @echo
+ Q = @
+else
+ E = @\#
+ Q =
+endif
+
+##
+# Compile options. You can use CROSS_COMPILE just like on the Linux Kernel. You
+# can also determine the ISA to be used, which defaults to my VisionFive2 board.
+# I did not go too much into the rabbit hole of platform-specific flags because
+# everything is in assembly anyways and everything is statically linked. Hence
+# no `-mcpu`, no `-mtune`, no funny business.
+
+CC = $(CROSS_COMPILE)gcc
+LD = $(CROSS_COMPILE)ld
+ISA ?= rv64imafdc_zicntr_zicsr_zifencei_zihpm_zca_zcd_zba_zbb
+WARNINGS = -Werror -Wpedantic -Wall -Wextra -Wcast-align -Wcast-qual -Winit-self \
+ -Wmissing-include-dirs -Wredundant-decls -Wshadow -Wsign-conversion \
+ -Wswitch-default -Wundef -Wunreachable-code -Wmissing-noreturn
+CCFLAGS = $(WARNINGS) -march=$(ISA)
+LDFLAGS =
+
+
+# You can pass an optional `DEBUG` variable to manipulate the build type.
+DEBUG =
+ifeq ($(strip $(DEBUG)),)
+ CCFLAGS += -O3
+else
+ CCFLAGS += -g
+endif
+
+##
+# Paths.
+
+SRC = $(wildcard *.c)
+EXES = $(SRC:.c=)
+
+##
+# Targets
+
+.PHONY: all
+all: clean $(EXES)
+
+.PHONY: build
+build: $(EXES)
+
+.c:
+ $(E) " CC " $(*F)
+ $(Q) $(CC) $(CCFLAGS) $< -o $@
+
+.PHONY: clean
+clean:
+ $(Q) rm -f $(EXES)
diff --git a/arch/riscv/user/hwprobe/probe.c b/arch/riscv/user/hwprobe/probe.c
new file mode 100644
index 0000000..9e2e978
--- /dev/null
+++ b/arch/riscv/user/hwprobe/probe.c
@@ -0,0 +1,294 @@
+#define _GNU_SOURCE
+
+#include <sched.h>
+#include <asm/hwprobe.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#define NR_RISCV_HWPROBE 258
+
+// Depend on Linux patches that have not been merged upstream...
+#define RISCV_HWPROBE_EXT_ZICNTR (1ULL << 49)
+#define RISCV_HWPROBE_EXT_ZIHPM (1ULL << 50)
+#define RISCV_HWPROBE_EXT_ZICSR (1ULL << 51)
+#define RISCV_HWPROBE_EXT_ZIFENCEI (1ULL << 52)
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr)[0])
+
+struct ext_pair_t {
+ char *id;
+ uint64_t value;
+};
+
+static struct ext_pair_t ext_pairs[] = {
+ {
+ .id = "F, D",
+ .value = RISCV_HWPROBE_IMA_FD,
+ },
+ {
+ .id = "C",
+ .value = RISCV_HWPROBE_IMA_C,
+ },
+ {
+ .id = "V",
+ .value = RISCV_HWPROBE_IMA_V,
+ },
+ {
+ .id = "ZBA",
+ .value = RISCV_HWPROBE_EXT_ZBA,
+ },
+ {
+ .id = "ZBB",
+ .value = RISCV_HWPROBE_EXT_ZBB,
+ },
+ {
+ .id = "ZBS",
+ .value = RISCV_HWPROBE_EXT_ZBS,
+ },
+ {
+ .id = "ZICBOZ",
+ .value = RISCV_HWPROBE_EXT_ZICBOZ,
+ },
+ {
+ .id = "ZBC",
+ .value = RISCV_HWPROBE_EXT_ZBC,
+ },
+ {
+ .id = "ZBKB",
+ .value = RISCV_HWPROBE_EXT_ZBKB,
+ },
+ {
+ .id = "ZBKC",
+ .value = RISCV_HWPROBE_EXT_ZBKC,
+ },
+ {
+ .id = "ZBKX",
+ .value = RISCV_HWPROBE_EXT_ZBKX,
+ },
+ {
+ .id = "ZKND",
+ .value = RISCV_HWPROBE_EXT_ZKND,
+ },
+ {
+ .id = "ZKNE",
+ .value = RISCV_HWPROBE_EXT_ZKNE,
+ },
+ {
+ .id = "ZKNH",
+ .value = RISCV_HWPROBE_EXT_ZKNH,
+ },
+ {
+ .id = "ZKSED",
+ .value = RISCV_HWPROBE_EXT_ZKSED,
+ },
+ {
+ .id = "ZKSH",
+ .value = RISCV_HWPROBE_EXT_ZKSH,
+ },
+ {
+ .id = "ZKT",
+ .value = RISCV_HWPROBE_EXT_ZKT,
+ },
+ {
+ .id = "ZVBB",
+ .value = RISCV_HWPROBE_EXT_ZVBB,
+ },
+ {
+ .id = "ZVBC",
+ .value = RISCV_HWPROBE_EXT_ZVBC,
+ },
+ {
+ .id = "ZVKB",
+ .value = RISCV_HWPROBE_EXT_ZVKB,
+ },
+ {
+ .id = "ZVKG",
+ .value = RISCV_HWPROBE_EXT_ZVKG,
+ },
+ {
+ .id = "ZVKNED",
+ .value = RISCV_HWPROBE_EXT_ZVKNED,
+ },
+ {
+ .id = "ZVKNHA",
+ .value = RISCV_HWPROBE_EXT_ZVKNHA,
+ },
+ {
+ .id = "ZVKNHB",
+ .value = RISCV_HWPROBE_EXT_ZVKNHB,
+ },
+ {
+ .id = "ZVKSED",
+ .value = RISCV_HWPROBE_EXT_ZVKSED,
+ },
+ {
+ .id = "ZVKSH",
+ .value = RISCV_HWPROBE_EXT_ZVKSH,
+ },
+ {
+ .id = "ZVKT",
+ .value = RISCV_HWPROBE_EXT_ZVKT,
+ },
+ {
+ .id = "ZFH",
+ .value = RISCV_HWPROBE_EXT_ZFH,
+ },
+ {
+ .id = "ZFHMIN",
+ .value = RISCV_HWPROBE_EXT_ZFHMIN,
+ },
+ {
+ .id = "ZIHINTNTL",
+ .value = RISCV_HWPROBE_EXT_ZIHINTNTL,
+ },
+ {
+ .id = "ZVFH",
+ .value = RISCV_HWPROBE_EXT_ZVFH,
+ },
+ {
+ .id = "ZVFHMIN",
+ .value = RISCV_HWPROBE_EXT_ZVFHMIN,
+ },
+ {
+ .id = "ZFA",
+ .value = RISCV_HWPROBE_EXT_ZFA,
+ },
+ {
+ .id = "ZTSO",
+ .value = RISCV_HWPROBE_EXT_ZTSO,
+ },
+ {
+ .id = "ZACAS",
+ .value = RISCV_HWPROBE_EXT_ZACAS,
+ },
+ {
+ .id = "ZICOND",
+ .value = RISCV_HWPROBE_EXT_ZICOND,
+ },
+ {
+ .id = "ZIHINTPAUSE",
+ .value = RISCV_HWPROBE_EXT_ZIHINTPAUSE,
+ },
+ {
+ .id = "ZVE32X",
+ .value = RISCV_HWPROBE_EXT_ZVE32X,
+ },
+ {
+ .id = "ZVE32F",
+ .value = RISCV_HWPROBE_EXT_ZVE32F,
+ },
+ {
+ .id = "ZVE64X",
+ .value = RISCV_HWPROBE_EXT_ZVE64X,
+ },
+ {
+ .id = "ZVE64F",
+ .value = RISCV_HWPROBE_EXT_ZVE64F,
+ },
+ {
+ .id = "ZVE64D",
+ .value = RISCV_HWPROBE_EXT_ZVE64D,
+ },
+ {
+ .id = "ZIMOP",
+ .value = RISCV_HWPROBE_EXT_ZIMOP,
+ },
+ {
+ .id = "ZCA",
+ .value = RISCV_HWPROBE_EXT_ZCA,
+ },
+ {
+ .id = "ZCB",
+ .value = RISCV_HWPROBE_EXT_ZCB,
+ },
+ {
+ .id = "ZCD",
+ .value = RISCV_HWPROBE_EXT_ZCD,
+ },
+ {
+ .id = "ZCF",
+ .value = RISCV_HWPROBE_EXT_ZCF,
+ },
+ {
+ .id = "ZCMOP",
+ .value = RISCV_HWPROBE_EXT_ZCMOP,
+ },
+ {
+ .id = "ZAWRS",
+ .value = RISCV_HWPROBE_EXT_ZAWRS,
+ },
+ {
+ .id = "ZICNTR",
+ .value = RISCV_HWPROBE_EXT_ZICNTR,
+ },
+ {
+ .id = "ZIHPM",
+ .value = RISCV_HWPROBE_EXT_ZIHPM,
+ },
+ {
+ .id = "ZICSR",
+ .value = RISCV_HWPROBE_EXT_ZICSR,
+ },
+ {
+ .id = "ZIFENCEI",
+ .value = RISCV_HWPROBE_EXT_ZIFENCEI,
+ },
+};
+
+void print_bits(size_t size, void *ptr)
+{
+ unsigned char *b = (unsigned char *)ptr;
+ unsigned char byte;
+ int i, j;
+
+ for (i = size - 1; i >= 0; i--) {
+ for (j = 7; j >= 0; j--) {
+ byte = (b[i] >> j) & 1;
+ printf("%u", byte);
+ }
+ }
+}
+
+int main()
+{
+ struct riscv_hwprobe pairs;
+ long rc;
+
+ // base: rv64ima
+
+ pairs = (struct riscv_hwprobe){
+ .key = RISCV_HWPROBE_KEY_BASE_BEHAVIOR,
+ };
+ rc = syscall(NR_RISCV_HWPROBE, &pairs, 1, 0, NULL, 0);
+ if (rc) {
+ perror("could not fetch the base behavior for the system");
+ exit(EXIT_FAILURE);
+ }
+ printf("Supports base extensions I, M, A? %s\n", (bool)pairs.value ? "yes" : "no");
+
+ // Extensions
+
+ pairs = (struct riscv_hwprobe){
+ .key = RISCV_HWPROBE_KEY_IMA_EXT_0,
+ };
+ rc = syscall(NR_RISCV_HWPROBE, &pairs, 1, 0, NULL, 0);
+ if (rc) {
+ perror("could not fetch extensions as given by hwprobe");
+ exit(EXIT_FAILURE);
+ }
+
+ printf("\nWe got back this bit map: ");
+ print_bits(sizeof(long), &pairs.value);
+
+ puts("; which means:\n");
+ for (size_t i = 0; i < ARRAY_SIZE(ext_pairs); i++) {
+ if ((ext_pairs[i].value & pairs.value) == ext_pairs[i].value) {
+ printf("%s: OK\n", ext_pairs[i].id);
+ }
+ }
+
+ exit(EXIT_SUCCESS);
+}