[pc][multiboot] Use DMA for loading kernel and bootdata

Change-Id: I92e14c8237cb065ff77a78a3049ce70c83b692ca
diff --git a/pc-bios/multiboot.bin b/pc-bios/multiboot.bin
index e772713..a32b521 100644
--- a/pc-bios/multiboot.bin
+++ b/pc-bios/multiboot.bin
Binary files differ
diff --git a/pc-bios/optionrom/multiboot.S b/pc-bios/optionrom/multiboot.S
index b7efe4d..b40a2c1 100644
--- a/pc-bios/optionrom/multiboot.S
+++ b/pc-bios/optionrom/multiboot.S
@@ -26,7 +26,62 @@
 
 #define GS_PROT_JUMP		0
 #define GS_GDT_DESC		6
+#define GS_DMA_CTRL	        12
+#define GS_DMA_LEN	        16
+#define GS_DMA_ADDR1	        20
+#define GS_DMA_ADDR2        	24
 
+/* This macro assumes that gs:GS_DMA_* is set up as a scratch space */
+.macro do_dma data, addr, size
+	/* Struct (all fields are big-endian):
+	     typedef struct FWCfgDmaAccess {
+	         uint32_t control;
+	         uint32_t length;
+	         uint64_t address;
+	     } FWCfgDmaAccess;
+	*/
+	/* 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		$(\data << 16 | 0x02 | 0x08), %eax
+	bswap		%eax
+	mov		%eax, %gs:GS_DMA_CTRL
+	/* Get the initrd's length and set dma.length. */
+	read_fw		\size
+	bswap		%eax
+	movl		%eax, %gs:GS_DMA_LEN
+	/* Set dma.address */
+	/* read_fw clobbers edx, and puts the result in eax */
+	read_fw		\addr
+	movl		$0, %gs:GS_DMA_ADDR1
+	bswap		%eax
+	mov		%eax, %gs:GS_DMA_ADDR2
+	/* 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 the DMA request structure stored, and
+	 * send it to 0x514+4. */
+	mov		%gs, %eax
+	movzwl		%ax, %eax
+	shl		$4, %eax
+	addl		$GS_DMA_CTRL, %eax
+	bswap		%eax
+	add		$4, %dx
+	out		%eax, (%dx)
+	/* Wait for DMA to finish */
+.Lwait_for_dma\@:
+	mov 		%gs:GS_DMA_CTRL, %eax
+	bswap  		%eax
+	test 		$~1, %eax
+	jnz 		.Lwait_for_dma\@
+.endm
+
+#define read_fw_dma(var) do_dma var ## _DATA, var ## _ADDR, var ## _SIZE
 
 BOOT_ROM_START
 
@@ -67,14 +122,18 @@
 	xor		%eax, %eax
 	mov		%eax, %es
 
-	/* Read the bootinfo struct into RAM */
-	read_fw_blob(FW_CFG_INITRD)
+	/* Read the bootinfo struct into RAM using the DMA interface
+	   TODO(teisenbe): Should really detect whether DMA is supported. */
+	read_fw_dma(FW_CFG_INITRD)
 
 	/* FS = bootinfo_struct */
 	read_fw		FW_CFG_INITRD_ADDR
 	shr		$4, %eax
 	mov		%ax, %fs
 
+	/* Read the kernel and modules into RAM using the DMA interface */
+	read_fw_dma(FW_CFG_KERNEL)
+
 	/* Account for the EBDA in the multiboot structure's e801
 	 * map.
 	 */
@@ -187,9 +246,6 @@
 	movl		%eax, %fs
 	movl		%eax, %gs
 
-	/* Read the kernel and modules into RAM */
-	read_fw_blob(FW_CFG_KERNEL)
-
 	/* Jump off to the kernel */
 	read_fw		FW_CFG_KERNEL_ENTRY
 	mov		%eax, %ecx