x86: allow using memory above 4 GiB
diff --git a/lib/alloc_phys.h b/lib/alloc_phys.h
index 7cf59a6..ea38f91 100644
--- a/lib/alloc_phys.h
+++ b/lib/alloc_phys.h
@@ -44,4 +44,10 @@
  */
 extern void phys_alloc_get_unused(phys_addr_t *p_base, phys_addr_t *p_top);
 
+/*
+ * Search for memory that can only be used when the MMU is on, and reinitialize
+ * the physical memory allocator using it.
+ */
+extern void find_highmem(void);
+
 #endif /* _ALLOC_PHYS_H_ */
diff --git a/lib/vmalloc.c b/lib/vmalloc.c
index b583786..5022a31 100644
--- a/lib/vmalloc.c
+++ b/lib/vmalloc.c
@@ -86,6 +86,10 @@
 	.align_min = PAGE_SIZE,
 };
 
+void __attribute__((__weak__)) find_highmem(void)
+{
+}
+
 void setup_vm()
 {
 	phys_addr_t base, top;
@@ -95,11 +99,24 @@
 
 	phys_alloc_get_unused(&base, &top);
 	assert(base != top || page_alloc_initialized());
+	/*
+	 * Give low memory immediately to the page allocator,
+	 * so that it can be used to allocate page tables.
+	 */
 	if (!page_alloc_initialized()) {
 		base = (base + PAGE_SIZE - 1) & -PAGE_SIZE;
 		top = top & -PAGE_SIZE;
 		free_pages(phys_to_virt(base), top - base);
 	}
+
+	find_highmem();
+	phys_alloc_get_unused(&base, &top);
 	page_root = setup_mmu(top);
+	if (base != top) {
+		base = (base + PAGE_SIZE - 1) & -PAGE_SIZE;
+		top = top & -PAGE_SIZE;
+		free_pages(phys_to_virt(base), top - base);
+	}
+
 	alloc_ops = &vmalloc_ops;
 }
diff --git a/lib/x86/setup.c b/lib/x86/setup.c
index 3a2741a..b5941cd 100644
--- a/lib/x86/setup.c
+++ b/lib/x86/setup.c
@@ -20,7 +20,8 @@
 	u32 cmdline;
 	u32 mods_count;
 	u32 mods_addr;
-	u32 reserved[5];   /* 28-47 */
+	u32 reserved[4];   /* 28-43 */
+	u32 mmap_length;
 	u32 mmap_addr;
 	u32 reserved0[3];  /* 52-63 */
 	u32 bootloader;
@@ -34,6 +35,13 @@
 	u32 unused;
 };
 
+struct mbi_mem {
+	u32 size;
+	u64 base_addr;
+	u64 length;
+	u32 type;
+} __attribute__((packed));
+
 #define ENV_SIZE 16384
 
 void setup_env(char *env, int size);
@@ -44,14 +52,55 @@
 u32 initrd_size;
 
 static char env[ENV_SIZE];
+static struct mbi_bootinfo *bootinfo;
 
-void setup_multiboot(struct mbi_bootinfo *bootinfo)
+#define HUGEPAGE_SIZE (1 << 21)
+
+#ifdef __x86_64__
+void find_highmem(void)
+{
+	/* Memory above 4 GB is only supported on 64-bit systems.  */
+	if (!(bootinfo->flags & 64))
+	    	return;
+
+	u64 upper_end = bootinfo->mem_upper * 1024ull;
+	u64 best_start = (uintptr_t) &edata;
+	u64 best_end = upper_end;
+	bool found = false;
+
+	uintptr_t mmap = bootinfo->mmap_addr;
+	while (mmap < bootinfo->mmap_addr + bootinfo->mmap_length) {
+		struct mbi_mem *mem = (void *)mmap;
+		mmap += mem->size + 4;
+		if (mem->type != 1)
+			continue;
+		if (mem->base_addr <= (uintptr_t) &edata ||
+		    (mem->base_addr <= upper_end && mem->base_addr + mem->length <= upper_end))
+			continue;
+		if (mem->length < best_end - best_start)
+			continue;
+		best_start = mem->base_addr;
+		best_end = mem->base_addr + mem->length;
+		found = true;
+	}
+
+	if (found) {
+		best_start = (best_start + HUGEPAGE_SIZE - 1) & -HUGEPAGE_SIZE;
+		best_end = best_end & -HUGEPAGE_SIZE;
+		phys_alloc_init(best_start, best_end - best_start);
+	}
+}
+#endif
+
+void setup_multiboot(struct mbi_bootinfo *bi)
 {
 	struct mbi_module *mods;
 
-	/* TODO: use e820 */
-	u64 end_of_memory = bootinfo->mem_upper * 1024ull;
-	phys_alloc_init((uintptr_t) &edata, end_of_memory - (uintptr_t) &edata);
+	bootinfo = bi;
+
+	u64 best_start = (uintptr_t) &edata;
+	u64 best_end = bootinfo->mem_upper * 1024ull;
+	phys_alloc_init(best_start, best_end - best_start);
 
 	if (bootinfo->mods_count != 1)
 		return;