The initrd now works.

We round up to the next 4k. For older kernels, you need to set the
device to 0x100; for newer kernels, set type_of_loader to 0xff.

This is tested with a simple busybox initramfs from u-root.

Change-Id: If14bcf613fdb06ef4147b6f27dc27c691f46bbaa
Signed-off-by: Ronald G. Minnich <rminnich@gmail.com>
Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
diff --git a/tests/dune/dune.c b/tests/dune/dune.c
index 38dc0c5..e5446b6 100644
--- a/tests/dune/dune.c
+++ b/tests/dune/dune.c
@@ -442,7 +442,7 @@
 	auxc = auxc + extrac;
 
 	if (!test) {
-		entry = load_elf(argv[0], MinMemory);
+		entry = load_elf(argv[0], MinMemory, NULL);
 		if (entry == 0) {
 			fprintf(stderr, "Unable to load kernel %s\n", argv[0]);
 			exit(1);
diff --git a/tests/vmm/vmrunkernel.c b/tests/vmm/vmrunkernel.c
index ceaa200..61879b5 100644
--- a/tests/vmm/vmrunkernel.c
+++ b/tests/vmm/vmrunkernel.c
@@ -373,6 +373,7 @@
 	int debug = 0;
 	unsigned long long memsize = GiB;
 	uintptr_t memstart = MinMemory;
+	uintptr_t memend;
 	struct boot_params *bp;
 	char cmdline_default[512] = {0};
 	char *cmdline_extra = "\0";
@@ -395,6 +396,9 @@
 	uint64_t num_pcs = 1;
 	bool is_greedy = FALSE;
 	bool is_scp = FALSE;
+	char *initrd = NULL;
+	uint64_t initrd_start = 0, initrd_size = 0;
+	uint64_t kernel_max_address;
 
 	static struct option long_options[] = {
 		{"debug",         no_argument,       0, 'd'},
@@ -404,6 +408,7 @@
 		{"memstart",      required_argument, 0, 'M'},
 		{"cmdline_extra", required_argument, 0, 'c'},
 		{"greedy",        no_argument,       0, 'g'},
+		{"initrd",        required_argument, 0, 'i'},
 		{"scp",           no_argument,       0, 's'},
 		{"image_file",    required_argument, 0, 'f'},
 		{"cmdline",       required_argument, 0, 'k'},
@@ -435,7 +440,7 @@
 		fprintf(stderr, "static initializers are broken\n");
 	memsize = GiB;
 
-	while ((c = getopt_long(argc, argv, "dvm:M:c:gsf:k:N:n:t:hR:",
+	while ((c = getopt_long(argc, argv, "dvi:m:M:c:gsf:k:N:n:t:hR:",
 				long_options, &option_index)) != -1) {
 		switch (c) {
 		case 'd':
@@ -471,6 +476,9 @@
 		case 'f':	/* file to pass to blk_init */
 			disk_image_file = optarg;
 			break;
+		case 'i':
+			initrd = optarg;
+			break;
 		case 'k':	/* specify file to get cmdline args from */
 			cmdline_fd = open(optarg, O_RDONLY);
 			if (cmdline_fd < 0) {
@@ -538,7 +546,8 @@
 
 	alloc_intr_pages();
 
-	if ((uintptr_t)(memstart + memsize) >= (uintptr_t)BRK_START) {
+	memend = memstart + memsize - 1;
+	if (memend >= BRK_START) {
 		fprintf(stderr,
 		        "memstart 0x%llx memsize 0x%llx -> 0x%llx is too large; overlaps BRK_START at %p\n",
 		        memstart, memsize, memstart + memsize, BRK_START);
@@ -547,7 +556,7 @@
 
 	mmap_memory(vm, memstart, memsize);
 
-	entry = load_elf(argv[0], 0);
+	entry = load_elf(argv[0], 0, &kernel_max_address);
 	if (entry == 0) {
 		fprintf(stderr, "Unable to load kernel %s\n", argv[0]);
 		exit(1);
@@ -558,6 +567,25 @@
 	bp = a;
 	a = init_e820map(bp, memstart, memsize);
 
+	if (initrd) {
+		initrd_start = ROUNDUP(kernel_max_address, PGSIZE);
+		fprintf(stderr, "kernel_max_address is %#p; Load initrd @ %#p\n",
+		        kernel_max_address, initrd_start);
+		initrd_size = setup_initrd(initrd, (void *)initrd_start,
+		                           memend - initrd_start + 1);
+		if (initrd_size <= 0) {
+			fprintf(stderr, "Unable to load initrd %s\n", initrd);
+			exit(1);
+		}
+
+		bp->hdr.ramdisk_image = initrd_start;
+		bp->hdr.ramdisk_size = initrd_size;
+		bp->hdr.root_dev = 0x100;
+		bp->hdr.type_of_loader = 0xff;
+		fprintf(stderr, "Set bp initrd to %p / %p\n",
+		        initrd_start, initrd_size);
+	}
+
 	/* The MMIO address of the console device is really the address of an
 	 * unbacked EPT page: accesses to this page will cause a page fault that
 	 * traps to the host, which will examine the fault, see it was for the
diff --git a/user/vmm/include/vmm/vmm.h b/user/vmm/include/vmm/vmm.h
index 13c673e..3806de4 100644
--- a/user/vmm/include/vmm/vmm.h
+++ b/user/vmm/include/vmm/vmm.h
@@ -95,8 +95,8 @@
                   uint64_t *regp, int store);
 int vmm_interrupt_guest(struct virtual_machine *vm, unsigned int gpcoreid,
                         unsigned int vector);
-uintptr_t load_elf(char *filename, uint64_t offset);
-
+uintptr_t load_elf(char *filename, uint64_t offset, uint64_t *highest);
+ssize_t setup_initrd(char *filename, void *membase, size_t memsize);
 /* Lookup helpers */
 
 static struct virtual_machine *gth_to_vm(struct guest_thread *gth)
diff --git a/user/vmm/initrd.c b/user/vmm/initrd.c
new file mode 100644
index 0000000..d8a7c4b
--- /dev/null
+++ b/user/vmm/initrd.c
@@ -0,0 +1,59 @@
+/* Copyright (c) 2017 Google Inc.
+ * See LICENSE for details.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <err.h>
+
+/* initrd loads the initrd and returns its place in the world. It has
+ * to avoid the already loaded kernel. */
+ssize_t setup_initrd(char *filename, void *membase, size_t memsize)
+{
+	int fd;
+	struct stat buf;
+	void *where = membase;
+	int amt;
+	int tot = 0;
+
+	if (!filename)
+		return 0;
+
+	fd = open(filename, O_RDONLY);
+	if (fd < 0) {
+		fprintf(stderr, "Can't open %s: %r\n", filename);
+		return 0;
+	}
+
+	if (fstat(fd, &buf) < 0) {
+		fprintf(stderr, "Can't stat %s: %r\n", filename);
+		close(fd);
+		return 0;
+	}
+
+	if (buf.st_size > memsize) {
+		fprintf(stderr,
+		        "file is %d bytes, but we only have %d bytes to place it\n",
+		        buf.st_size, memsize);
+		close(fd);
+		return 0;
+	}
+
+	while (tot < buf.st_size) {
+		amt = read(fd, where, buf.st_size - tot);
+		if (amt < 0) {
+			tot = 0;
+			break;
+		}
+		where += amt;
+		tot += amt;
+	}
+
+	close(fd);
+	return tot;
+}
diff --git a/user/vmm/load_elf.c b/user/vmm/load_elf.c
index e16baee..69cfdbf 100644
--- a/user/vmm/load_elf.c
+++ b/user/vmm/load_elf.c
@@ -10,9 +10,10 @@
 
 /* load_elf loads and ELF file. This is almost always a kernel.
  * We assume that memory is set up correctly, and it will go hard
- * with you if it is not. */
+ * with you if it is not. The reference parameter records the highest
+ * address we wrote. The initrd can go there.*/
 uintptr_t
-load_elf(char *filename, uint64_t offset)
+load_elf(char *filename, uint64_t offset, uint64_t *highest)
 {
 	Elf64_Ehdr *ehdr;
 	Elf *elf;
@@ -20,6 +21,7 @@
 	Elf64_Phdr *hdrs;
 	int fd;
 	uintptr_t ret;
+	uintptr_t kern_end = 0;
 
 	elf_version(EV_CURRENT);
 	fd = open(filename, O_RDONLY);
@@ -64,7 +66,7 @@
 		uintptr_t pa;
 
 		fprintf(stderr,
-		        "%d: type 0x%lx flags 0x%lx  offset 0x%lx vaddr 0x%lx paddr 0x%lx size 0x%lx  memsz 0x%lx align 0x%lx\n",
+		        "%d: type 0x%lx flags 0x%lx  offset 0x%lx vaddr 0x%lx\npaddr 0x%lx size 0x%lx  memsz 0x%lx align 0x%lx\n",
 		        i,
 		        h->p_type,              /* Segment type */
 		        h->p_flags,             /* Segment flags */
@@ -98,11 +100,15 @@
 			        filename, tot, h->p_filesz);
 			goto fail;
 		}
+		if ((h->p_paddr + h->p_memsz) > kern_end)
+			kern_end = h->p_paddr + h->p_memsz;
 	}
 
 	close(fd);
 	ret = ehdr->e_entry + offset;
 	elf_end(elf);
+	if (highest)
+		*highest = kern_end;
 	return ret;
 fail:
 	close(fd);
diff --git a/user/vmm/pagetables.c b/user/vmm/pagetables.c
index 6a69f89..023511d 100644
--- a/user/vmm/pagetables.c
+++ b/user/vmm/pagetables.c
@@ -42,9 +42,10 @@
 	nptp += npml2;
 
 	fprintf(stderr,
-	        "Memstart is %llx, memsize is %llx, memstart + memsize is %llx; ",
+	        "Memstart is %llx, memsize is %llx,"
+		"memstart + memsize is %llx; \n",
 	        memstart, memsize, memstart + memsize);
-	fprintf(stderr, " %d pml4 %d pml3 %d pml2\n",
+	fprintf(stderr, "\t%d pml4 %d pml3 %d pml2\n",
 	        npml4, npml3, npml2);
 
 	/* Place these page tables right after VM memory. We