diff --git a/lib/riscv/asm/io.h b/lib/riscv/asm/io.h
index 6fe111289102..37a130e533c9 100644
--- a/lib/riscv/asm/io.h
+++ b/lib/riscv/asm/io.h
@@ -76,6 +76,12 @@ static inline u64 __raw_readq(const volatile void __iomem *addr)
 #define ioremap ioremap
 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);
+
+#define phys_to_virt phys_to_virt
+void *phys_to_virt(unsigned long address);
+
 #include <asm-generic/io.h>
 
 #endif /* _ASMRISCV_IO_H_ */
diff --git a/lib/riscv/asm/mmu.h b/lib/riscv/asm/mmu.h
index 02703f607511..66e993800b45 100644
--- a/lib/riscv/asm/mmu.h
+++ b/lib/riscv/asm/mmu.h
@@ -18,8 +18,6 @@ void __mmu_enable(unsigned long satp);
 void mmu_enable(unsigned long mode, pgd_t *pgtable);
 void mmu_disable(void);
 
-void setup_mmu(void);
-
 static inline void local_flush_tlb_page(unsigned long addr)
 {
 	asm volatile("sfence.vma %0" : : "r" (addr) : "memory");
diff --git a/lib/riscv/mmu.c b/lib/riscv/mmu.c
index 6b1af518ddd8..e2fb15359773 100644
--- a/lib/riscv/mmu.c
+++ b/lib/riscv/mmu.c
@@ -5,6 +5,7 @@
 #include <libcflat.h>
 #include <alloc_page.h>
 #include <memregions.h>
+#include <vmalloc.h>
 #include <asm/csr.h>
 #include <asm/io.h>
 #include <asm/mmu.h>
@@ -54,6 +55,15 @@ static pteval_t *__install_page(pgd_t *pgtable, phys_addr_t paddr,
 	return (pteval_t *)ptep;
 }
 
+pteval_t *install_page(pgd_t *pgtable, phys_addr_t phys, void *virt)
+{
+	phys_addr_t paddr = phys & PAGE_MASK;
+	uintptr_t vaddr = (uintptr_t)virt & PAGE_MASK;
+
+	return __install_page(pgtable, paddr, vaddr,
+			      __pgprot(_PAGE_READ | _PAGE_WRITE), true);
+}
+
 void mmu_set_range_ptes(pgd_t *pgtable, uintptr_t virt_offset,
 			phys_addr_t phys_start, phys_addr_t phys_end,
 			pgprot_t prot, bool flush)
@@ -93,7 +103,7 @@ void mmu_enable(unsigned long mode, pgd_t *pgtable)
 	__mmu_enable(satp);
 }
 
-void setup_mmu(void)
+void *setup_mmu(phys_addr_t top, void *opaque)
 {
 	struct mem_region *r;
 	pgd_t *pgtable;
@@ -115,6 +125,8 @@ void setup_mmu(void)
 	}
 
 	mmu_enable(SATP_MODE_DEFAULT, pgtable);
+
+	return pgtable;
 }
 
 void __iomem *ioremap(phys_addr_t phys_addr, size_t size)
@@ -138,3 +150,46 @@ void __iomem *ioremap(phys_addr_t phys_addr, size_t size)
 
 	return (void __iomem *)(unsigned long)phys_addr;
 }
+
+phys_addr_t virt_to_pte_phys(pgd_t *pgtable, void *virt)
+{
+	uintptr_t vaddr = (uintptr_t)virt;
+	pte_t *ptep = pgtable;
+
+	assert(pgtable && !((uintptr_t)pgtable & ~PAGE_MASK));
+
+	for (int level = NR_LEVELS - 1; level > 0; --level) {
+		pte_t *next = &ptep[pte_index(vaddr, level)];
+		if (!(*next & _PAGE_PRESENT))
+			return 0;
+		ptep = pte_to_ptep(*next);
+	}
+	ptep = &ptep[pte_index(vaddr, 0)];
+
+	if (!(*ptep & _PAGE_PRESENT))
+		return 0;
+
+	return (((phys_addr_t)*ptep & PTE_PPN) >> PPN_SHIFT) << PAGE_SHIFT;
+}
+
+unsigned long 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;
+}
+
+void *phys_to_virt(unsigned long address)
+{
+	/* @address must have an identity mapping for this to work. */
+	assert(virt_to_phys(__va(address)) == address);
+	return __va(address);
+}
diff --git a/lib/riscv/setup.c b/lib/riscv/setup.c
index c4c1bd58b337..40ff26a24cfc 100644
--- a/lib/riscv/setup.c
+++ b/lib/riscv/setup.c
@@ -9,10 +9,12 @@
 #include <alloc_page.h>
 #include <alloc_phys.h>
 #include <argv.h>
+#include <auxinfo.h>
 #include <cpumask.h>
 #include <devicetree.h>
 #include <memregions.h>
 #include <on-cpus.h>
+#include <vmalloc.h>
 #include <asm/csr.h>
 #include <asm/mmu.h>
 #include <asm/page.h>
@@ -20,6 +22,11 @@
 #include <asm/setup.h>
 
 #define VA_BASE			((phys_addr_t)3 * SZ_1G)
+#if __riscv_xlen == 64
+#define VA_TOP			((phys_addr_t)4 * SZ_1G)
+#else
+#define VA_TOP			((phys_addr_t)0)
+#endif
 
 #define MAX_DT_MEM_REGIONS	16
 #define NR_MEM_REGIONS		(MAX_DT_MEM_REGIONS + 16)
@@ -106,6 +113,8 @@ static void mem_init(phys_addr_t freemem_start)
 		freemem_end = VA_BASE;
 	assert(freemem_end - freemem_start >= SZ_1M * 16);
 
+	init_alloc_vpage(__va(VA_TOP));
+
 	/*
 	 * TODO: Remove the need for this phys allocator dance, since, as we
 	 * can see with the assert, we could have gone straight to the page
@@ -137,7 +146,7 @@ void setup(const void *fdt, phys_addr_t freemem_start)
 	int ret;
 
 	assert(sizeof(long) == 8 || freemem_start < VA_BASE);
-	freemem = (void *)(unsigned long)freemem_start;
+	freemem = __va(freemem_start);
 
 	/* Move the FDT to the base of free memory */
 	fdt_size = fdt_totalsize(fdt);
@@ -156,7 +165,7 @@ void setup(const void *fdt, phys_addr_t freemem_start)
 		freemem += initrd_size;
 	}
 
-	mem_init(PAGE_ALIGN((unsigned long)freemem));
+	mem_init(PAGE_ALIGN(__pa(freemem)));
 	cpu_init();
 	thread_info_init();
 	io_init();
@@ -172,7 +181,8 @@ void setup(const void *fdt, phys_addr_t freemem_start)
 		setup_env(env, initrd_size);
 	}
 
-	setup_mmu();
+	if (!(auxinfo.flags & AUXINFO_MMU_OFF))
+		setup_vm();
 
 	banner();
 }
diff --git a/riscv/Makefile b/riscv/Makefile
index 821891b719e7..61a1ff88d8ec 100644
--- a/riscv/Makefile
+++ b/riscv/Makefile
@@ -13,7 +13,7 @@ endif
 tests =
 tests += $(TEST_DIR)/sbi.$(exe)
 tests += $(TEST_DIR)/selftest.$(exe)
-#tests += $(TEST_DIR)/sieve.$(exe)
+tests += $(TEST_DIR)/sieve.$(exe)
 
 all: $(tests)
 
@@ -27,6 +27,7 @@ cflatobjs += lib/alloc_phys.o
 cflatobjs += lib/devicetree.o
 cflatobjs += lib/memregions.o
 cflatobjs += lib/on-cpus.o
+cflatobjs += lib/vmalloc.o
 cflatobjs += lib/riscv/bitops.o
 cflatobjs += lib/riscv/io.o
 cflatobjs += lib/riscv/mmu.o
diff --git a/riscv/sieve.c b/riscv/sieve.c
new file mode 120000
index 000000000000..8f14a5c3d4aa
--- /dev/null
+++ b/riscv/sieve.c
@@ -0,0 +1 @@
+../x86/sieve.c
\ No newline at end of file
