mb/emulation/qemu: Copy page tables to DRAM in assembly

To work around various bugs running KVM enabled, copy page tables to
DRAM in assembly before jumping to x86_64 mode.

Tested on QEMU using KVM, no more stange bugs happen:
Tested on host
 - CPU Intel(R) Core(TM) i7-7700HQ
 - Linux 5.9
 - qemu 4.2.1
 Used to crash on emulating MMX instructions and failed to translate
 some addresses using the virtual MMU when running in long mode.

Tested on host
 - CPU AMD EPYC 7401P 24-Core Processor
 - Linux 5.4
 - qemu 4.2.1
 Used to crash on jumping to long mode.

Change-Id: Ic0bdd2bef7197edd2e7488a8efdeba7eb4ab0dd4
Signed-off-by: Patrick Rudolph <patrick.rudolph@9elements.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/49228
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Angel Pons <th3fanbus@gmail.com>
Reviewed-by: Arthur Heymans <arthur@aheymans.xyz>
diff --git a/src/cpu/qemu-x86/cache_as_ram_bootblock.S b/src/cpu/qemu-x86/cache_as_ram_bootblock.S
index e3a26b0..07f848a 100644
--- a/src/cpu/qemu-x86/cache_as_ram_bootblock.S
+++ b/src/cpu/qemu-x86/cache_as_ram_bootblock.S
@@ -2,7 +2,14 @@
 
 #include <cpu/x86/post_code.h>
 
+#define CBFS_FILE_MAGIC 0
+#define CBFS_FILE_LEN (CBFS_FILE_MAGIC + 8)
+#define CBFS_FILE_TYPE (CBFS_FILE_LEN + 4)
+#define CBFS_FILE_CHECKSUM (CBFS_FILE_TYPE + 4)
+#define CBFS_FILE_OFFSET (CBFS_FILE_CHECKSUM + 4)
+
 .section .init, "ax", @progbits
+.code32
 
 .global bootblock_pre_c_entry
 bootblock_pre_c_entry:
@@ -24,12 +31,55 @@
 
 	post_code(0x21)
 
+#if defined(__x86_64__)
+	/*
+	 * Copy page tables to final location in DRAM. This prevents some strange
+	 * bugs when running KVM enabled:
+	 * Accessing MMX instructions in long mode causes an abort
+	 * Some physical addresses aren't properly translated
+	 * Emulation fault on every instruction fetched due to page tables in ROM
+	 * Enabling or disabling paging causes a fault
+	 *
+	 * First, find page tables in CBFS:
+	 */
+	lea	pagetables_name, %esi
+	mov	$1f, %esp
+	jmp	walkcbfs_asm
+1:
+	cmpl	$0, %eax
+	je	.Lhlt
+
+	/* Test if page tables are memory-mapped and skip relocation */
+	cmpl	$(CONFIG_ARCH_X86_64_PGTBL_LOC), %eax
+	je	pages_done
+
+	movl	CBFS_FILE_OFFSET(%eax), %ebx
+	bswap	%ebx
+	addl	%eax, %ebx
+	movl	%ebx, %esi
+
+	movl	CBFS_FILE_LEN(%eax), %ecx
+	bswap	%ecx
+	shr	$2, %ecx
+
+	movl	$(CONFIG_ARCH_X86_64_PGTBL_LOC), %edi
+
+loop:
+	movl	(%esi), %eax
+	movl	%eax, (%edi)
+	addl	$4, %esi
+	addl	$4, %edi
+	decl	%ecx
+	jnz	loop
+pages_done:
+#endif
+
 	movl	$_ecar_stack, %esp
 
 	/* Align the stack and keep aligned for call to bootblock_c_entry() */
 	and	$0xfffffff0, %esp
 
-        /* entry64.inc preserves ebx. */
+	/* entry64.inc preserves ebx. */
 #include <cpu/x86/64bit/entry64.inc>
 
 	/* Restore the BIST result and timestamps. */
@@ -60,3 +110,6 @@
 	post_code(POST_DEAD_CODE)
 	hlt
 	jmp	.Lhlt
+
+pagetables_name:
+        .string "pagetables"