On x86, load initrd using DMA interface.

This makes it infinitely faster.

Also, I changed pc-bios/linuxboot.bin from a checked in binary to a
symlink, since that seems to be what the build uses. (Otherwise, it'll
build a linuxboot.bin in the optionrom subdirectory, but helpfully not
use it.) Possibly there's a better way to do this.

Change-Id: Ic3373c2b03e40b9cf461efc848bca8c39c7a312d
diff --git a/pc-bios/linuxboot.bin b/pc-bios/linuxboot.bin
deleted file mode 100644
index 923d179..0000000
--- a/pc-bios/linuxboot.bin
+++ /dev/null
Binary files differ
diff --git a/pc-bios/linuxboot.bin b/pc-bios/linuxboot.bin
new file mode 120000
index 0000000..2293e55
--- /dev/null
+++ b/pc-bios/linuxboot.bin
@@ -0,0 +1 @@
+optionrom/linuxboot.bin
\ No newline at end of file
diff --git a/pc-bios/optionrom/linuxboot.S b/pc-bios/optionrom/linuxboot.S
index ba821ab..fb09f79 100644
--- a/pc-bios/optionrom/linuxboot.S
+++ b/pc-bios/optionrom/linuxboot.S
@@ -165,7 +165,57 @@
 	/* We're now running in 16-bit CS, but 32-bit ES! */
 
 	/* Load kernel and initrd */
-	read_fw_blob_addr32_edi(FW_CFG_INITRD)
+
+	/* Load initrd using the "DMA" interface.
+	   TODO(vtl): Should really detect whether DMA is supported. */
+	/* Get stack space for a FWCfgDmaAccess struct:
+	     typedef struct FWCfgDmaAccess {
+	         uint32_t control;
+	         uint32_t length;
+	         uint64_t address;
+	     } FWCfgDmaAccess;
+	*/
+	mov             %esp, %ebp
+	sub             $16, %esp
+	/* Set dma.control (big-endian):
+	     high (16-bit) word is FW_CFG_INITRD_DATA
+	     low (16-bit) word is FW_CFG_DMA_CTL_READ (0x02) |
+	                          FW_CFG_DMA_CTL_SELECT (0x08)
+	*/
+	movl		$(FW_CFG_INITRD_DATA << 16 | 0x02 | 0x08), %eax
+	bswap		%eax
+	mov		%eax, -16(%bp)
+	/* Get the initrd's length and set dma.length. */
+	read_fw(FW_CFG_INITRD_SIZE)
+	bswap		%eax
+	movl		%eax, -12(%bp)
+	/* Set dma.address (the address is already in EDI). */
+	movl		$0, -8(%bp)
+	mov		%edi, %eax
+	bswap		%eax
+	mov		%eax, -4(%bp)
+	/* Kick off the DMA by outputting the physical address of dma to 0x514
+	   (as two 32-bit values). */
+	/* The top value is always 0. */
+	xor		%eax, %eax
+	mov		$0x514, %dx
+	out		%eax, (%dx)
+	/* Calculate the physical address of ESP, and send it to 0x514+4. */
+	mov		%ss, %eax
+	movzwl		%ax, %eax
+	shl		$4, %eax
+	addl		%esp, %eax
+	bswap		%eax
+	add		$4, %dx
+	out		%eax, (%dx)
+
+	mov		%ebp, %esp
+
+	/* The code for reading the initrd using an I/O port was just:
+	     read_fw_blob_addr32_edi(FW_CFG_INITRD)
+	   We could also use DMA for the kernel, but we assume it's relatively
+	   small.
+	*/
 	read_fw_blob_addr32(FW_CFG_KERNEL)
 	read_fw_blob_addr32(FW_CFG_CMDLINE)