Merge branch 'riscv/sbi' into 'master'

Mainly riscv stuff, but also limits.h and pseudo random numbers

See merge request kvm-unit-tests/kvm-unit-tests!65
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index cae3e74..67a9a15 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -133,18 +133,44 @@
       | tee results.txt
  - if grep -q FAIL results.txt ; then exit 1 ; fi
 
-# build-riscv32:
-# Fedora doesn't package a riscv32 compiler for QEMU. Oh, well.
+build-riscv32:
+ extends: .outoftree_template
+ script:
+ - dnf install -y qemu-system-riscv gcc-riscv64-linux-gnu
+ - mkdir build
+ - cd build
+ - ../configure --arch=riscv32 --cross-prefix=riscv64-linux-gnu-
+ - make -j2
+ - printf "FOO=foo\nBAR=bar\nBAZ=baz\nMVENDORID=0\nMARCHID=0\nMIMPID=0\n" >test-env
+ - ACCEL=tcg KVM_UNIT_TESTS_ENV=test-env ./run_tests.sh
+      selftest
+      sbi
+      | tee results.txt
+ - grep -q PASS results.txt && ! grep -q FAIL results.txt
 
-# Select 'rv64' with PROCESSOR_OVERRIDE in case QEMU is too old to have 'max'
 build-riscv64:
  extends: .intree_template
  script:
  - dnf install -y qemu-system-riscv gcc-riscv64-linux-gnu
  - ./configure --arch=riscv64 --cross-prefix=riscv64-linux-gnu-
  - make -j2
- - printf "FOO=foo\nBAR=bar\nBAZ=baz\nMVENDORID=0\n" >test-env
- - PROCESSOR_OVERRIDE=rv64 ACCEL=tcg KVM_UNIT_TESTS_ENV=test-env ./run_tests.sh
+ - printf "FOO=foo\nBAR=bar\nBAZ=baz\nMVENDORID=0\nMARCHID=0\nMIMPID=0\n" >test-env
+ - ACCEL=tcg KVM_UNIT_TESTS_ENV=test-env ./run_tests.sh
+      selftest
+      sbi
+      | tee results.txt
+ - grep -q PASS results.txt && ! grep -q FAIL results.txt
+
+build-riscv64-efi:
+ extends: .intree_template
+ script:
+ - dnf install -y edk2-riscv64 qemu-system-riscv gcc-riscv64-linux-gnu
+ - cp /usr/share/edk2/riscv/RISCV_VIRT_CODE.fd .
+ - truncate -s 32M RISCV_VIRT_CODE.fd
+ - ./configure --arch=riscv64 --cross-prefix=riscv64-linux-gnu- --enable-efi
+ - make -j2
+ - printf "FOO=foo\nBAR=bar\nBAZ=baz\nMVENDORID=0\nMARCHID=0\nMIMPID=0\n" >test-env
+ - ACCEL=tcg KVM_UNIT_TESTS_ENV=test-env ./run_tests.sh
       selftest
       sbi
       | tee results.txt
diff --git a/Makefile b/Makefile
index 5b7998b..3d51cb7 100644
--- a/Makefile
+++ b/Makefile
@@ -28,6 +28,7 @@
 	lib/printf.o \
 	lib/string.o \
 	lib/abort.o \
+	lib/rand.o \
 	lib/report.o \
 	lib/stack.o
 
diff --git a/configure b/configure
index db15e85..27ae9cc 100755
--- a/configure
+++ b/configure
@@ -418,12 +418,16 @@
 asm="asm-generic"
 if [ -d "$srcdir/lib/$arch/asm" ]; then
 	asm="$srcdir/lib/$arch/asm"
+	mkdir -p "lib/$arch"
+elif [ -d "$srcdir/lib/$arch_libdir/asm" ]; then
+	asm="$srcdir/lib/$arch_libdir/asm"
+	mkdir -p "lib/$arch_libdir"
 elif [ -d "$srcdir/lib/$testdir/asm" ]; then
 	asm="$srcdir/lib/$testdir/asm"
+	mkdir -p "lib/$testdir"
 fi
-mkdir -p lib
 ln -sf "$asm" lib/asm
-
+mkdir -p lib/generated lib/libfdt
 
 # create the config
 cat <<EOF > config.mak
diff --git a/lib/limits.h b/lib/limits.h
new file mode 100644
index 0000000..650085c
--- /dev/null
+++ b/lib/limits.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _LIMITS_H_
+#define _LIMITS_H_
+
+#if __CHAR_BIT__ == 8
+# if __CHAR_UNSIGNED__
+#  define CHAR_MIN	0
+#  define CHAR_MAX	__UINT8_MAX__
+# else
+#  define CHAR_MAX	__INT8_MAX__
+#  define CHAR_MIN	(-CHAR_MAX - 1)
+# endif
+#endif
+
+#if __SHRT_WIDTH__ == 16
+# define SHRT_MAX	__INT16_MAX__
+# define SHRT_MIN	(-SHRT_MAX - 1)
+# define USHRT_MAX	__UINT16_MAX__
+#endif
+
+#if __INT_WIDTH__ == 32
+# define INT_MAX	__INT32_MAX__
+# define INT_MIN	(-INT_MAX - 1)
+# define UINT_MAX	__UINT32_MAX__
+#endif
+
+#if __LONG_WIDTH__ == 64
+# define LONG_MAX	__INT64_MAX__
+# define LONG_MIN	(-LONG_MAX - 1)
+# define ULONG_MAX	__UINT64_MAX__
+#elif __LONG_WIDTH__ == 32
+# define LONG_MAX	__INT32_MAX__
+# define LONG_MIN	(-LONG_MAX - 1)
+# define ULONG_MAX	__UINT32_MAX__
+#endif
+
+#if __LONG_LONG_WIDTH__ == 64
+# define LLONG_MAX	__INT64_MAX__
+# define LLONG_MIN	(-LLONG_MAX - 1)
+# define ULLONG_MAX	__UINT64_MAX__
+#endif
+
+#endif /* _LIMITS_H_ */
diff --git a/lib/memregions.h b/lib/memregions.h
index 1600530..04027f6 100644
--- a/lib/memregions.h
+++ b/lib/memregions.h
@@ -10,6 +10,7 @@
 #define MR_F_CODE			BIT(1)
 #define MR_F_RESERVED			BIT(2)
 #define MR_F_PERSISTENT			BIT(3)
+#define MR_F_UNUSED			BIT(4)
 #define MR_F_UNKNOWN			BIT(31)
 
 struct mem_region {
diff --git a/lib/rand.c b/lib/rand.c
new file mode 100644
index 0000000..78032b8
--- /dev/null
+++ b/lib/rand.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * (pseudo) random functions
+ * Currently uses SHA-256 to scramble the PRNG state.
+ *
+ * Copyright IBM Corp. 2024
+ */
+
+#include "libcflat.h"
+#include "rand.h"
+#include <string.h>
+
+/* Begin SHA-256 related definitions */
+
+#define INITAL_HASH { \
+	0x6a09e667, \
+	0xbb67ae85, \
+	0x3c6ef372, \
+	0xa54ff53a, \
+	0x510e527f, \
+	0x9b05688c, \
+	0x1f83d9ab, \
+	0x5be0cd19, \
+}
+
+static const uint32_t K[] = {
+	0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+	0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+	0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+	0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+	0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+	0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+	0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+	0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
+};
+
+static inline uint32_t ch(uint32_t x, uint32_t y, uint32_t z)
+{
+	return (x & y) ^ ((~x) & z);
+}
+
+static inline uint32_t maj(uint32_t x, uint32_t y, uint32_t z)
+{
+	return (x & y) ^ (x & z) ^ (y & z);
+}
+
+static inline uint32_t rot(uint32_t value, unsigned int count)
+{
+	return value >> count | value << (32 - count);
+}
+
+static inline uint32_t upper_sig0(uint32_t x)
+{
+	return rot(x, 2) ^ rot(x, 13) ^ rot(x, 22);
+}
+
+static inline uint32_t upper_sig1(uint32_t x)
+{
+	return rot(x, 6) ^ rot(x, 11) ^ rot(x, 25);
+}
+
+static inline uint32_t lower_sig0(uint32_t x)
+{
+	return rot(x, 7) ^ rot(x, 18) ^ (x >> 3);
+}
+
+static inline uint32_t lower_sig1(uint32_t x)
+{
+	return rot(x, 17) ^ rot(x, 19) ^ (x >> 10);
+}
+
+enum alphabet { A, B, C, D, E, F, G, H, };
+
+static void sha256_chunk(const uint32_t (*chunk)[16], uint32_t (*hash)[8])
+{
+	uint32_t w[64];
+	uint32_t w_hash[8];
+
+	memcpy(w, chunk, sizeof(*chunk));
+
+	for (int i = 16; i < 64; i++)
+		w[i] = lower_sig1(w[i - 2]) + w[i - 7] + lower_sig0(w[i - 15]) + w[i - 16];
+
+	memcpy(w_hash, hash, sizeof(*hash));
+
+	for (int i = 0; i < 64; i++) {
+		uint32_t t1, t2;
+
+		t1 = w_hash[H] +
+		     upper_sig1(w_hash[E]) +
+		     ch(w_hash[E], w_hash[F], w_hash[G]) +
+		     K[i] +
+		     w[i];
+
+		t2 = upper_sig0(w_hash[A]) + maj(w_hash[A], w_hash[B], w_hash[C]);
+
+		w_hash[H] = w_hash[G];
+		w_hash[G] = w_hash[F];
+		w_hash[F] = w_hash[E];
+		w_hash[E] = w_hash[D] + t1;
+		w_hash[D] = w_hash[C];
+		w_hash[C] = w_hash[B];
+		w_hash[B] = w_hash[A];
+		w_hash[A] = t1 + t2;
+	}
+
+	for (int i = 0; i < 8; i++)
+		(*hash)[i] += w_hash[i];
+}
+
+/**
+ * sha256_hash - Calculate SHA-256 of input. Only a limited subset of inputs supported.
+ * @n: Number of words to hash, must be <= 13
+ * @input: Input data to hash
+ * @hash: Output hash as a word array, ordered such that the first word contains
+ *        the first/leftmost bits of the 256 bit hash
+ *
+ * Calculate the SHA-256 hash of the input where the input must be a multiple of
+ * 4 bytes and at most 52 long. The input is used without any adjustment, so,
+ * should the caller want to hash bytes it needs to interpret the bytes in the
+ * ordering as defined by the specification, that is big endian.
+ * The same applies to interpreting the output array as bytes.
+ * The function computes the same as: printf "%08x" ${input[@]} | xxd -r -p | sha256sum .
+ */
+static void sha256_hash(unsigned int n, const uint32_t (*input)[n], uint32_t (*hash)[8])
+{
+	/*
+	 * Pad according to SHA-2 specification.
+	 * First set up length in bits.
+	 */
+	uint32_t chunk[16] = {
+		[15] = sizeof(*input) * 8,
+	};
+
+	memcpy(chunk, input, sizeof(*input));
+	/* Then add separator */
+	chunk[n] = 1 << 31;
+	memcpy(hash, (uint32_t[])INITAL_HASH, sizeof(*hash));
+	sha256_chunk(&chunk, hash);
+}
+
+/* End SHA-256 related definitions */
+
+prng_state prng_init(uint64_t seed)
+{
+	prng_state state = { .next_word = 0 };
+	uint32_t seed_arr[2] = { seed >> 32, seed };
+
+	sha256_hash(ARRAY_SIZE(seed_arr), &seed_arr, &state.hash);
+	return state;
+}
+
+static void prng_scramble(prng_state *state)
+{
+	uint32_t input[8];
+
+	memcpy(input, state->hash, sizeof(state->hash));
+	sha256_hash(ARRAY_SIZE(input), &input, &state->hash);
+	state->next_word = 0;
+}
+
+uint32_t prng32(prng_state *state)
+{
+	if (state->next_word < ARRAY_SIZE(state->hash))
+		return state->hash[state->next_word++];
+
+	prng_scramble(state);
+	return prng32(state);
+}
+
+uint64_t prng64(prng_state *state)
+{
+	/* explicitly evaluate the high word first */
+	uint64_t high = prng32(state);
+
+	return high << 32 | prng32(state);
+}
diff --git a/lib/rand.h b/lib/rand.h
new file mode 100644
index 0000000..cdce8bd
--- /dev/null
+++ b/lib/rand.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * (pseudo) random functions
+ *
+ * Copyright IBM Corp. 2024
+ */
+#ifndef _RAND_H_
+#define _RAND_H_
+
+#include <stdint.h>
+
+/* Non cryptographically secure PRNG */
+typedef struct {
+	uint32_t hash[8];
+	uint8_t next_word;
+} prng_state;
+prng_state prng_init(uint64_t seed);
+uint32_t prng32(prng_state *state);
+uint64_t prng64(prng_state *state);
+
+#endif /* _RAND_H_ */
diff --git a/lib/riscv/asm/asm.h b/lib/riscv/asm/asm.h
new file mode 100644
index 0000000..763b28e
--- /dev/null
+++ b/lib/riscv/asm/asm.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _ASMRISCV_ASM_H_
+#define _ASMRISCV_ASM_H_
+
+#if __riscv_xlen == 64
+#define __REG_SEL(a, b) a
+#elif __riscv_xlen == 32
+#define __REG_SEL(a, b) b
+#else
+#error "Unexpected __riscv_xlen"
+#endif
+
+#define REG_L	__REG_SEL(ld, lw)
+#define REG_S	__REG_SEL(sd, sw)
+#define SZREG	__REG_SEL(8, 4)
+
+#define FP_SIZE 16
+
+#endif /* _ASMRISCV_ASM_H_ */
diff --git a/lib/riscv/asm/io.h b/lib/riscv/asm/io.h
index 37a130e..a48a9aa 100644
--- a/lib/riscv/asm/io.h
+++ b/lib/riscv/asm/io.h
@@ -77,10 +77,10 @@
 void __iomem *ioremap(phys_addr_t phys_addr, size_t size);
 
 #define virt_to_phys virt_to_phys
-unsigned long virt_to_phys(volatile void *address);
+phys_addr_t virt_to_phys(volatile void *address);
 
 #define phys_to_virt phys_to_virt
-void *phys_to_virt(unsigned long address);
+void *phys_to_virt(phys_addr_t address);
 
 #include <asm-generic/io.h>
 
diff --git a/lib/riscv/asm/mmu.h b/lib/riscv/asm/mmu.h
index bb60f08..28c332f 100644
--- a/lib/riscv/asm/mmu.h
+++ b/lib/riscv/asm/mmu.h
@@ -6,6 +6,9 @@
 #include <asm/page.h>
 #include <asm/pgtable.h>
 
+#define PHYS_MASK	((phys_addr_t)SATP_PPN << PAGE_SHIFT | (PAGE_SIZE - 1))
+#define PHYS_PAGE_MASK	(~((phys_addr_t)PAGE_SIZE - 1))
+
 static inline pgd_t *current_pgtable(void)
 {
 	return (pgd_t *)((csr_read(CSR_SATP) & SATP_PPN) << PAGE_SHIFT);
diff --git a/lib/riscv/asm/sbi.h b/lib/riscv/asm/sbi.h
index 73ab543..47e9102 100644
--- a/lib/riscv/asm/sbi.h
+++ b/lib/riscv/asm/sbi.h
@@ -19,6 +19,7 @@
 	SBI_EXT_TIME = 0x54494d45,
 	SBI_EXT_HSM = 0x48534d,
 	SBI_EXT_SRST = 0x53525354,
+	SBI_EXT_DBCN = 0x4442434E,
 };
 
 enum sbi_ext_base_fid {
@@ -42,6 +43,12 @@
 	SBI_EXT_TIME_SET_TIMER = 0,
 };
 
+enum sbi_ext_dbcn_fid {
+	SBI_EXT_DBCN_CONSOLE_WRITE = 0,
+	SBI_EXT_DBCN_CONSOLE_READ,
+	SBI_EXT_DBCN_CONSOLE_WRITE_BYTE,
+};
+
 struct sbiret {
 	long error;
 	long value;
diff --git a/lib/riscv/mmu.c b/lib/riscv/mmu.c
index 165a703..577c66a 100644
--- a/lib/riscv/mmu.c
+++ b/lib/riscv/mmu.c
@@ -18,9 +18,16 @@
 	return (vaddr >> (PGDIR_BITS * level + PAGE_SHIFT)) & PGDIR_MASK;
 }
 
+static phys_addr_t pteval_to_phys_addr(pteval_t pteval)
+{
+	return (phys_addr_t)((pteval & PTE_PPN) >> PPN_SHIFT) << PAGE_SHIFT;
+}
+
 static pte_t *pteval_to_ptep(pteval_t pteval)
 {
-	return (pte_t *)(((pteval & PTE_PPN) >> PPN_SHIFT) << PAGE_SHIFT);
+	phys_addr_t paddr = pteval_to_phys_addr(pteval);
+	assert(paddr == __pa(paddr));
+	return (pte_t *)__pa(paddr);
 }
 
 static pteval_t ptep_to_pteval(pte_t *ptep)
@@ -57,7 +64,8 @@
 	assert(!(ppn & ~PTE_PPN));
 
 	ptep = get_pte(pgtable, vaddr);
-	*ptep = __pte(pte | pgprot_val(prot) | _PAGE_PRESENT | _PAGE_ACCESSED | _PAGE_DIRTY);
+	pte |= pgprot_val(prot) | _PAGE_PRESENT | _PAGE_ACCESSED | _PAGE_DIRTY;
+	WRITE_ONCE(*ptep, __pte(pte));
 
 	if (flush)
 		local_flush_tlb_page(vaddr);
@@ -67,9 +75,11 @@
 
 pteval_t *install_page(pgd_t *pgtable, phys_addr_t phys, void *virt)
 {
-	phys_addr_t paddr = phys & PAGE_MASK;
+	phys_addr_t paddr = phys & PHYS_PAGE_MASK;
 	uintptr_t vaddr = (uintptr_t)virt & PAGE_MASK;
 
+	assert(phys == (phys & PHYS_MASK));
+
 	return __install_page(pgtable, paddr, vaddr,
 			      __pgprot(_PAGE_READ | _PAGE_WRITE), true);
 }
@@ -78,10 +88,12 @@
 			phys_addr_t phys_start, phys_addr_t phys_end,
 			pgprot_t prot, bool flush)
 {
-	phys_addr_t paddr = phys_start & PAGE_MASK;
+	phys_addr_t paddr = phys_start & PHYS_PAGE_MASK;
 	uintptr_t vaddr = virt_offset & PAGE_MASK;
 	uintptr_t virt_end = phys_end - paddr + vaddr;
 
+	assert(phys_start == (phys_start & PHYS_MASK));
+	assert(phys_end == (phys_end & PHYS_MASK));
 	assert(phys_start < phys_end);
 
 	for (; vaddr < virt_end; vaddr += PAGE_SIZE, paddr += PAGE_SIZE)
@@ -106,7 +118,7 @@
 
 void mmu_enable(unsigned long mode, pgd_t *pgtable)
 {
-	unsigned long ppn = (unsigned long)pgtable >> PAGE_SHIFT;
+	unsigned long ppn = __pa(pgtable) >> PAGE_SHIFT;
 	unsigned long satp = mode | ppn;
 
 	assert(!(ppn & ~SATP_PPN));
@@ -118,6 +130,9 @@
 	struct mem_region *r;
 	pgd_t *pgtable;
 
+	/* The initial page table uses an identity mapping. */
+	assert(top == __pa(top));
+
 	if (!__initial_pgtable)
 		__initial_pgtable = alloc_page();
 	pgtable = __initial_pgtable;
@@ -141,12 +156,13 @@
 
 void __iomem *ioremap(phys_addr_t phys_addr, size_t size)
 {
-	phys_addr_t start = phys_addr & PAGE_MASK;
+	phys_addr_t start = phys_addr & PHYS_PAGE_MASK;
 	phys_addr_t end = PAGE_ALIGN(phys_addr + size);
 	pgd_t *pgtable = current_pgtable();
 	bool flush = true;
 
-	assert(sizeof(long) == 8 || !(phys_addr >> 32));
+	/* I/O is always identity mapped. */
+	assert(end == __pa(end));
 
 	if (!pgtable) {
 		if (!__initial_pgtable)
@@ -158,7 +174,7 @@
 	mmu_set_range_ptes(pgtable, start, start, end,
 			   __pgprot(_PAGE_READ | _PAGE_WRITE), flush);
 
-	return (void __iomem *)(unsigned long)phys_addr;
+	return (void __iomem *)__pa(phys_addr);
 }
 
 phys_addr_t virt_to_pte_phys(pgd_t *pgtable, void *virt)
@@ -179,27 +195,24 @@
 	if (!pte_val(*ptep))
 		return 0;
 
-	return __pa(pteval_to_ptep(pte_val(*ptep)));
+	return pteval_to_phys_addr(pte_val(*ptep)) | offset_in_page(virt);
 }
 
-unsigned long virt_to_phys(volatile void *address)
+phys_addr_t virt_to_phys(volatile void *address)
 {
 	unsigned long satp = csr_read(CSR_SATP);
 	pgd_t *pgtable = (pgd_t *)((satp & SATP_PPN) << PAGE_SHIFT);
-	phys_addr_t paddr;
 
 	if ((satp >> SATP_MODE_SHIFT) == 0)
 		return __pa(address);
 
-	paddr = virt_to_pte_phys(pgtable, (void *)address);
-	assert(sizeof(long) == 8 || !(paddr >> 32));
-
-	return (unsigned long)paddr | offset_in_page(address);
+	return virt_to_pte_phys(pgtable, (void *)address);
 }
 
-void *phys_to_virt(unsigned long address)
+void *phys_to_virt(phys_addr_t address)
 {
 	/* @address must have an identity mapping for this to work. */
+	assert(address == __pa(address));
 	assert(virt_to_phys(__va(address)) == address);
 	return __va(address);
 }
diff --git a/lib/riscv/setup.c b/lib/riscv/setup.c
index e0b5f6f..9a16f00 100644
--- a/lib/riscv/setup.c
+++ b/lib/riscv/setup.c
@@ -85,12 +85,13 @@
 	cpu0_calls_idle = true;
 }
 
-static void mem_allocator_init(phys_addr_t freemem_start, phys_addr_t freemem_end)
+static void mem_allocator_init(struct mem_region *freemem, phys_addr_t freemem_start)
 {
+	phys_addr_t freemem_end = freemem->end;
 	phys_addr_t base, top;
 
 	freemem_start = PAGE_ALIGN(freemem_start);
-	freemem_end &= PAGE_MASK;
+	freemem_end &= PHYS_PAGE_MASK;
 
 	/*
 	 * The assert below is mostly checking that the free memory doesn't
@@ -100,8 +101,14 @@
 	 *
 	 * TODO: Allow the VA range to shrink and move.
 	 */
-	if (freemem_end > VA_BASE)
+	if (freemem_end > VA_BASE) {
+		struct mem_region *curr, *rest;
 		freemem_end = VA_BASE;
+		memregions_split(VA_BASE, &curr, &rest);
+		assert(curr == freemem);
+		if (rest)
+			rest->flags = MR_F_UNUSED;
+	}
 	assert(freemem_end - freemem_start >= SZ_1M * 16);
 
 	init_alloc_vpage(__va(VA_TOP));
@@ -135,7 +142,7 @@
 	freemem = memregions_find(freemem_start);
 	assert(freemem && !(freemem->flags & (MR_F_IO | MR_F_CODE)));
 
-	mem_allocator_init(freemem_start, freemem->end);
+	mem_allocator_init(freemem, freemem_start);
 }
 
 static void freemem_push_fdt(void **freemem, const void *fdt)
@@ -193,7 +200,7 @@
 	const char *bootargs;
 	int ret;
 
-	assert(sizeof(long) == 8 || freemem_start < VA_BASE);
+	assert(freemem_start < VA_BASE);
 	freemem = __va(freemem_start);
 
 	freemem_push_fdt(&freemem, fdt);
@@ -248,7 +255,7 @@
 		freemem_push_fdt(&freemem, efi_bootinfo->fdt);
 
 	mmu_disable();
-	mem_allocator_init((unsigned long)freemem, freemem_mr->end);
+	mem_allocator_init(freemem_mr, (unsigned long)freemem);
 
 	return EFI_SUCCESS;
 }
diff --git a/lib/riscv/smp.c b/lib/riscv/smp.c
index 7e4bb5b..4d373e0 100644
--- a/lib/riscv/smp.c
+++ b/lib/riscv/smp.c
@@ -8,6 +8,7 @@
 #include <alloc_page.h>
 #include <cpumask.h>
 #include <asm/csr.h>
+#include <asm/io.h>
 #include <asm/mmu.h>
 #include <asm/page.h>
 #include <asm/processor.h>
@@ -36,6 +37,7 @@
 static void __smp_boot_secondary(int cpu, secondary_func_t func)
 {
 	struct secondary_data *sp = alloc_pages(1) + SZ_8K - 16;
+	phys_addr_t sp_phys;
 	struct sbiret ret;
 
 	sp -= sizeof(struct secondary_data);
@@ -43,7 +45,10 @@
 	sp->stvec = csr_read(CSR_STVEC);
 	sp->func = func;
 
-	ret = sbi_hart_start(cpus[cpu].hartid, (unsigned long)&secondary_entry, __pa(sp));
+	sp_phys = virt_to_phys(sp);
+	assert(sp_phys == __pa(sp_phys));
+
+	ret = sbi_hart_start(cpus[cpu].hartid, (unsigned long)&secondary_entry, __pa(sp_phys));
 	assert(ret.error == SBI_SUCCESS);
 }
 
diff --git a/riscv/Makefile b/riscv/Makefile
index b0cd613..179a373 100644
--- a/riscv/Makefile
+++ b/riscv/Makefile
@@ -64,13 +64,15 @@
 		$(error $(1) has unsupported reloc types))
 endef
 
-ISA_COMMON = mafdc_zicsr_zifencei_zihintpause
+ISA_COMMON = imac_zicsr_zifencei_zihintpause
 
 ifeq ($(ARCH),riscv64)
-CFLAGS += -march=rv64i$(ISA_COMMON)
-CFLAGS += -DCONFIG_64BIT
+CFLAGS  += -DCONFIG_64BIT
+CFLAGS  += -mabi=lp64 -march=rv64$(ISA_COMMON)
+LDFLAGS += -melf64lriscv
 else ifeq ($(ARCH),riscv32)
-CFLAGS += -march=rv32i$(ISA_COMMON)
+CFLAGS  += -mabi=ilp32 -march=rv32$(ISA_COMMON)
+LDFLAGS += -melf32lriscv
 endif
 CFLAGS += -DCONFIG_RELOC
 CFLAGS += -mcmodel=medany
@@ -78,7 +80,7 @@
 CFLAGS += -std=gnu99
 CFLAGS += -ffreestanding
 CFLAGS += -O2
-CFLAGS += -I $(SRCDIR)/lib -I $(SRCDIR)/lib/libfdt
+CFLAGS += -I $(SRCDIR)/lib -I $(SRCDIR)/lib/libfdt -I lib
 
 asm-offsets = lib/riscv/asm-offsets.h
 include $(SRCDIR)/scripts/asm-offsets.mak
diff --git a/riscv/cstart.S b/riscv/cstart.S
index 10b5da5..d5d8ad2 100644
--- a/riscv/cstart.S
+++ b/riscv/cstart.S
@@ -4,22 +4,10 @@
  *
  * Copyright (C) 2023, Ventana Micro Systems Inc., Andrew Jones <ajones@ventanamicro.com>
  */
+#include <asm/asm.h>
 #include <asm/asm-offsets.h>
 #include <asm/csr.h>
 
-#if __riscv_xlen == 64
-#define __REG_SEL(a, b) a
-#elif __riscv_xlen == 32
-#define __REG_SEL(a, b) b
-#else
-#error "Unexpected __riscv_xlen"
-#endif
-
-#define REG_L	__REG_SEL(ld, lw)
-#define REG_S	__REG_SEL(sd, sw)
-#define SZREG	__REG_SEL(8, 4)
-
-#define FP_SIZE 16
 
 .macro push_fp, ra=ra
 	addi	sp, sp, -FP_SIZE
diff --git a/riscv/sbi.c b/riscv/sbi.c
index 2438c49..36ddfd4 100644
--- a/riscv/sbi.c
+++ b/riscv/sbi.c
@@ -5,17 +5,25 @@
  * Copyright (C) 2023, Ventana Micro Systems Inc., Andrew Jones <ajones@ventanamicro.com>
  */
 #include <libcflat.h>
+#include <alloc_page.h>
 #include <stdlib.h>
+#include <string.h>
 #include <limits.h>
+#include <vmalloc.h>
+#include <memregions.h>
 #include <asm/barrier.h>
 #include <asm/csr.h>
 #include <asm/delay.h>
+#include <asm/io.h>
 #include <asm/isa.h>
+#include <asm/mmu.h>
 #include <asm/processor.h>
 #include <asm/sbi.h>
 #include <asm/smp.h>
 #include <asm/timer.h>
 
+#define	HIGH_ADDR_BOUNDARY	((phys_addr_t)1 << 32)
+
 static void help(void)
 {
 	puts("Test SBI\n");
@@ -32,6 +40,38 @@
 	return sbi_ecall(SBI_EXT_TIME, SBI_EXT_TIME_SET_TIMER, stime_value, 0, 0, 0, 0, 0);
 }
 
+static struct sbiret __dbcn_sbi_ecall(int fid, unsigned long arg0, unsigned long arg1, unsigned long arg2)
+{
+	return sbi_ecall(SBI_EXT_DBCN, fid, arg0, arg1, arg2, 0, 0, 0);
+}
+
+static void split_phys_addr(phys_addr_t paddr, unsigned long *hi, unsigned long *lo)
+{
+	*lo = (unsigned long)paddr;
+	*hi = 0;
+	if (__riscv_xlen == 32)
+		*hi = (unsigned long)(paddr >> 32);
+}
+
+static bool check_addr(phys_addr_t start, phys_addr_t size)
+{
+	struct mem_region *r = memregions_find(start);
+	return r && r->end - start >= size && r->flags == MR_F_UNUSED;
+}
+
+static phys_addr_t get_highest_addr(void)
+{
+	phys_addr_t highest_end = 0;
+	struct mem_region *r;
+
+	for (r = mem_regions; r->end; ++r) {
+		if (r->end > highest_end)
+			highest_end = r->end;
+	}
+
+	return highest_end - 1;
+}
+
 static bool env_or_skip(const char *env)
 {
 	if (!getenv(env)) {
@@ -70,30 +110,30 @@
 	}
 
 	report_prefix_push("spec_version");
-	if (env_or_skip("SPEC_VERSION")) {
-		expected = strtol(getenv("SPEC_VERSION"), NULL, 0);
+	if (env_or_skip("SBI_SPEC_VERSION")) {
+		expected = (long)strtoul(getenv("SBI_SPEC_VERSION"), NULL, 0);
 		gen_report(&ret, 0, expected);
 	}
 	report_prefix_pop();
 
 	report_prefix_push("impl_id");
-	if (env_or_skip("IMPL_ID")) {
-		expected = strtol(getenv("IMPL_ID"), NULL, 0);
+	if (env_or_skip("SBI_IMPL_ID")) {
+		expected = (long)strtoul(getenv("SBI_IMPL_ID"), NULL, 0);
 		ret = __base_sbi_ecall(SBI_EXT_BASE_GET_IMP_ID, 0);
 		gen_report(&ret, 0, expected);
 	}
 	report_prefix_pop();
 
 	report_prefix_push("impl_version");
-	if (env_or_skip("IMPL_VERSION")) {
-		expected = strtol(getenv("IMPL_VERSION"), NULL, 0);
+	if (env_or_skip("SBI_IMPL_VERSION")) {
+		expected = (long)strtoul(getenv("SBI_IMPL_VERSION"), NULL, 0);
 		ret = __base_sbi_ecall(SBI_EXT_BASE_GET_IMP_VERSION, 0);
 		gen_report(&ret, 0, expected);
 	}
 	report_prefix_pop();
 
 	report_prefix_push("probe_ext");
-	expected = getenv("PROBE_EXT") ? strtol(getenv("PROBE_EXT"), NULL, 0) : 1;
+	expected = getenv("SBI_PROBE_EXT") ? (long)strtoul(getenv("SBI_PROBE_EXT"), NULL, 0) : 1;
 	ret = __base_sbi_ecall(SBI_EXT_BASE_PROBE_EXT, SBI_EXT_BASE);
 	gen_report(&ret, 0, expected);
 	report_prefix_push("unavailable");
@@ -104,7 +144,8 @@
 
 	report_prefix_push("mvendorid");
 	if (env_or_skip("MVENDORID")) {
-		expected = strtol(getenv("MVENDORID"), NULL, 0);
+		expected = (long)strtoul(getenv("MVENDORID"), NULL, 0);
+		assert(__riscv_xlen == 32 || !(expected >> 32));
 		ret = __base_sbi_ecall(SBI_EXT_BASE_GET_MVENDORID, 0);
 		gen_report(&ret, 0, expected);
 	}
@@ -112,7 +153,7 @@
 
 	report_prefix_push("marchid");
 	if (env_or_skip("MARCHID")) {
-		expected = strtol(getenv("MARCHID"), NULL, 0);
+		expected = (long)strtoul(getenv("MARCHID"), NULL, 0);
 		ret = __base_sbi_ecall(SBI_EXT_BASE_GET_MARCHID, 0);
 		gen_report(&ret, 0, expected);
 	}
@@ -120,7 +161,7 @@
 
 	report_prefix_push("mimpid");
 	if (env_or_skip("MIMPID")) {
-		expected = strtol(getenv("MIMPID"), NULL, 0);
+		expected = (long)strtoul(getenv("MIMPID"), NULL, 0);
 		ret = __base_sbi_ecall(SBI_EXT_BASE_GET_MIMPID, 0);
 		gen_report(&ret, 0, expected);
 	}
@@ -168,8 +209,8 @@
 	struct sbiret ret;
 	unsigned long begin, end, duration;
 	const char *mask_test_str = mask_timer_irq ? " for mask irq test" : "";
-	unsigned long d = getenv("TIMER_DELAY") ? strtol(getenv("TIMER_DELAY"), NULL, 0) : 200000;
-	unsigned long margin = getenv("TIMER_MARGIN") ? strtol(getenv("TIMER_MARGIN"), NULL, 0) : 200000;
+	unsigned long d = getenv("SBI_TIMER_DELAY") ? strtol(getenv("SBI_TIMER_DELAY"), NULL, 0) : 200000;
+	unsigned long margin = getenv("SBI_TIMER_MARGIN") ? strtol(getenv("SBI_TIMER_MARGIN"), NULL, 0) : 200000;
 
 	d = usec_to_cycles(d);
 	margin = usec_to_cycles(margin);
@@ -248,9 +289,139 @@
 	report_prefix_pop();
 }
 
+#define DBCN_WRITE_TEST_STRING		"DBCN_WRITE_TEST_STRING\n"
+#define DBCN_WRITE_BYTE_TEST_BYTE	((u8)'a')
+
+static void dbcn_write_test(const char *s, unsigned long num_bytes)
+{
+	unsigned long base_addr_lo, base_addr_hi;
+	phys_addr_t paddr = virt_to_phys((void *)s);
+	int num_calls = 0;
+	struct sbiret ret;
+
+	split_phys_addr(paddr, &base_addr_hi, &base_addr_lo);
+
+	do {
+		ret = __dbcn_sbi_ecall(SBI_EXT_DBCN_CONSOLE_WRITE, num_bytes, base_addr_lo, base_addr_hi);
+		num_bytes -= ret.value;
+		paddr += ret.value;
+		split_phys_addr(paddr, &base_addr_hi, &base_addr_lo);
+		num_calls++;
+	} while (num_bytes != 0 && ret.error == SBI_SUCCESS);
+
+	report(ret.error == SBI_SUCCESS, "write success (error=%ld)", ret.error);
+	report_info("%d sbi calls made", num_calls);
+}
+
+static void dbcn_high_write_test(const char *s, unsigned long num_bytes,
+				 phys_addr_t page_addr, size_t page_offset)
+{
+	int nr_pages = page_offset ? 2 : 1;
+	void *vaddr;
+
+	if (page_addr != PAGE_ALIGN(page_addr) || page_addr + PAGE_SIZE < HIGH_ADDR_BOUNDARY ||
+	    !check_addr(page_addr, nr_pages * PAGE_SIZE)) {
+		report_skip("Memory above 4G required");
+		return;
+	}
+
+	vaddr = alloc_vpages(nr_pages);
+
+	for (int i = 0; i < nr_pages; ++i)
+		install_page(current_pgtable(), page_addr + i * PAGE_SIZE, vaddr + i * PAGE_SIZE);
+	memcpy(vaddr + page_offset, DBCN_WRITE_TEST_STRING, num_bytes);
+	dbcn_write_test(vaddr + page_offset, num_bytes);
+}
+
+/*
+ * Only the write functionality is tested here. There's no easy way to
+ * non-interactively test the read functionality.
+ */
+static void check_dbcn(void)
+{
+	unsigned long num_bytes = strlen(DBCN_WRITE_TEST_STRING);
+	unsigned long base_addr_lo, base_addr_hi;
+	bool do_invalid_addr = false;
+	phys_addr_t paddr;
+	struct sbiret ret;
+	const char *tmp;
+	char *buf;
+
+	report_prefix_push("dbcn");
+
+	ret = __base_sbi_ecall(SBI_EXT_BASE_PROBE_EXT, SBI_EXT_DBCN);
+	if (!ret.value) {
+		report_skip("DBCN extension unavailable");
+		report_prefix_pop();
+		return;
+	}
+
+	report_prefix_push("write");
+
+	dbcn_write_test(DBCN_WRITE_TEST_STRING, num_bytes);
+
+	assert(num_bytes < PAGE_SIZE);
+
+	report_prefix_push("page boundary");
+	buf = alloc_pages(1);
+	memcpy(&buf[PAGE_SIZE - num_bytes / 2], DBCN_WRITE_TEST_STRING, num_bytes);
+	dbcn_write_test(&buf[PAGE_SIZE - num_bytes / 2], num_bytes);
+	report_prefix_pop();
+
+	report_prefix_push("high boundary");
+	tmp = getenv("SBI_DBCN_SKIP_HIGH_BOUNDARY");
+	if (!tmp || atol(tmp) == 0)
+		dbcn_high_write_test(DBCN_WRITE_TEST_STRING, num_bytes,
+				     HIGH_ADDR_BOUNDARY - PAGE_SIZE, PAGE_SIZE - num_bytes / 2);
+	else
+		report_skip("user disabled");
+	report_prefix_pop();
+
+	report_prefix_push("high page");
+	tmp = getenv("SBI_DBCN_SKIP_HIGH_PAGE");
+	if (!tmp || atol(tmp) == 0) {
+		paddr = HIGH_ADDR_BOUNDARY;
+		tmp = getenv("HIGH_PAGE");
+		if (tmp)
+			paddr = strtoull(tmp, NULL, 0);
+		dbcn_high_write_test(DBCN_WRITE_TEST_STRING, num_bytes, paddr, 0);
+	} else {
+		report_skip("user disabled");
+	}
+	report_prefix_pop();
+
+	/* Bytes are read from memory and written to the console */
+	report_prefix_push("invalid parameter");
+	tmp = getenv("INVALID_ADDR_AUTO");
+	if (tmp && atol(tmp) == 1) {
+		paddr = get_highest_addr() + 1;
+		do_invalid_addr = true;
+	} else if (env_or_skip("INVALID_ADDR")) {
+		paddr = strtoull(getenv("INVALID_ADDR"), NULL, 0);
+		do_invalid_addr = true;
+	}
+
+	if (do_invalid_addr) {
+		split_phys_addr(paddr, &base_addr_hi, &base_addr_lo);
+		ret = __dbcn_sbi_ecall(SBI_EXT_DBCN_CONSOLE_WRITE, 1, base_addr_lo, base_addr_hi);
+		report(ret.error == SBI_ERR_INVALID_PARAM, "address (error=%ld)", ret.error);
+	}
+	report_prefix_pop();
+
+	report_prefix_pop();
+	report_prefix_push("write_byte");
+
+	puts("DBCN_WRITE TEST CHAR: ");
+	ret = __dbcn_sbi_ecall(SBI_EXT_DBCN_CONSOLE_WRITE_BYTE, (u8)DBCN_WRITE_BYTE_TEST_BYTE, 0, 0);
+	puts("\n");
+	report(ret.error == SBI_SUCCESS, "write success (error=%ld)", ret.error);
+	report(ret.value == 0, "expected ret.value (%ld)", ret.value);
+
+	report_prefix_pop();
+}
+
 int main(int argc, char **argv)
 {
-
 	if (argc > 1 && !strcmp(argv[1], "-h")) {
 		help();
 		exit(0);
@@ -259,6 +430,7 @@
 	report_prefix_push("sbi");
 	check_base();
 	check_time();
+	check_dbcn();
 
 	return report_summary();
 }