mainboard/emulation/qemu-sbsa: Add qemu-sbsa board

Add coreboot support for qemu's sbsa-ref (Server Base System
Architecture) machine (-m sbsa-ref).

The qemu-sbsa coreboot port runs on EL2 and is the payload of the
EL3 firmware (Arm Trusted Firmware).

Note that, coreboot expects a pointer to the FDT in x0. Make sure
to configure TF-A to handoff the FDT pointer.

Example qemu commandline:

  qemu-system-aarch64 -nographic -m 2048 -M sbsa-ref \
                      -pflash <path/to/TFA.fd> \
                      -pflash <path/to/coreboot.rom>

The Documentation can be found here:
Documentation/mainboard/emulation/qemu-sbsa.md

Change-Id: Iacc9aaf065e0d153336cbef9a9b5b46a9eb24a53
Signed-off-by: David Milosevic <David.Milosevic@9elements.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/79086
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Lean Sheng Tan <sheng.tan@9elements.com>
diff --git a/Documentation/mainboard/emulation/qemu-sbsa.md b/Documentation/mainboard/emulation/qemu-sbsa.md
new file mode 100644
index 0000000..abbd189
--- /dev/null
+++ b/Documentation/mainboard/emulation/qemu-sbsa.md
@@ -0,0 +1,42 @@
+# QEMU SBSA emulator
+This page describes how to build and run ```coreboot``` for QEMU's sbsa-ref machine.
+The qemu-sbsa ```coreboot``` image acts as BL-3.3 for Arm Trusted Firmware (```TF-A```) and
+mainly takes care of setting up SMBIOS and ACPI tables, hence, in order to boot,
+you also need to supply a ```TF-A``` image.
+
+## Building TF-A
+
+You can build ```TF-A``` from source by fetching
+```
+https://github.com/ARM-software/arm-trusted-firmware
+```
+and building the qemu-sbsa platform
+```
+PLAT=qemu_sbsa
+```
+Upon entry, ```coreboot``` expects a FDT pointer in x0, so make sure to compile ```TF-A``` with
+```
+ARM_LINUX_KERNEL_AS_BL33=1
+```
+This will force ```TF-A``` to pass a pointer to the FDT in x0.
+
+## Building coreboot
+
+Simply select the qemu-sbsa board and, optionally, configure a payload. We recommend
+the ```leanefi``` payload. ```leanefi``` will setup a minimal set of UEFI services, just enough
+to boot into a linux kernel.
+
+## Running coreboot in QEMU
+
+Once you have obtained ```TF-A``` and ```coreboot``` images, launch qemu via
+
+```bash
+qemu-system-aarch64 -nographic -m 1024 -M sbsa-ref -pflash <path/to/TFA.fd> \
+                                                   -pflash <path/to/coreboot.rom>
+```
+
+## LBBR bootflow
+
+arm and 9elements worked together in order to create a LBBR compliant bootflow
+consisting of ```TF-A```, ```coreboot```, ```leanefi``` and ```LinuxBoot```. A proof of concept
+can be found here https://gitlab.arm.com/systemready/firmware-build/linuxboot/lbbr-coreboot-poc
diff --git a/Documentation/mainboard/index.md b/Documentation/mainboard/index.md
index 5a80d46..e54b01f 100644
--- a/Documentation/mainboard/index.md
+++ b/Documentation/mainboard/index.md
@@ -90,6 +90,7 @@
 Spike RISC-V emulator <emulation/spike-riscv.md>
 QEMU RISC-V emulator <emulation/qemu-riscv.md>
 QEMU AArch64 emulator <emulation/qemu-aarch64.md>
+QEMU SBSA emulator <emulation/qemu-sbsa.md>
 QEMU x86 Q35 <emulation/qemu-q35.md>
 QEMU x86 PC <emulation/qemu-i440fx.md>
 QEMU POWER9 <emulation/qemu-power9.md>
diff --git a/src/mainboard/emulation/Kconfig b/src/mainboard/emulation/Kconfig
index 5f596b8..1c2ab86 100644
--- a/src/mainboard/emulation/Kconfig
+++ b/src/mainboard/emulation/Kconfig
@@ -3,6 +3,7 @@
 # ugly to put it in here, but unavoidable
 config SEPARATE_ROMSTAGE
 	default n if BOARD_EMULATION_QEMU_RISCV
+	default n if BOARD_EMULATION_QEMU_SBSA
 
 if VENDOR_EMULATION
 
diff --git a/src/mainboard/emulation/qemu-sbsa/Kconfig b/src/mainboard/emulation/qemu-sbsa/Kconfig
new file mode 100644
index 0000000..a28ae62
--- /dev/null
+++ b/src/mainboard/emulation/qemu-sbsa/Kconfig
@@ -0,0 +1,55 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+if BOARD_EMULATION_QEMU_SBSA
+
+config BOARD_SPECIFIC_OPTIONS
+	def_bool y
+	select ARCH_BOOTBLOCK_ARMV8_64
+	select ARCH_VERSTAGE_ARMV8_64
+	select ARCH_ROMSTAGE_ARMV8_64
+	select ARCH_RAMSTAGE_ARMV8_64
+	select ARM64_USE_ARCH_TIMER
+	select BOARD_ROMSIZE_KB_32768
+	select BOOTBLOCK_CUSTOM
+	select BOOT_DEVICE_NOT_SPI_FLASH
+	select DRIVERS_UART_PL011
+	select FLATTENED_DEVICE_TREE
+	select HAVE_LINEAR_FRAMEBUFFER
+	select MAINBOARD_FORCE_NATIVE_VGA_INIT
+	select MAINBOARD_HAS_NATIVE_VGA_INIT
+	select MISSING_BOARD_RESET
+	select PCI
+	select HAVE_ACPI_TABLES
+	select ACPI_GTDT
+	select ACPI_COMMON_MADT_GICC_V3
+	select GENERATE_SMBIOS_TABLES
+
+config ARM64_CURRENT_EL
+	default 2
+
+config ECAM_MMCONF_BASE_ADDRESS
+	default 0xf0000000
+
+config ECAM_MMCONF_BUS_NUMBER
+	default 256
+
+config FMDFILE
+	default "src/mainboard/emulation/qemu-sbsa/flash.fmd"
+
+config MAINBOARD_DIR
+	default "emulation/qemu-sbsa"
+
+config MAINBOARD_PART_NUMBER
+	default "QEMU sbsa"
+
+config MAX_CPUS
+	default 128
+
+config MAINBOARD_VENDOR
+	default "QEMU"
+
+config DRAM_SIZE_MB
+	int
+	default 8388608 # The maximum dram size is 8192GiB.
+
+endif #  BOARD_EMULATION_QEMU_SBSA
diff --git a/src/mainboard/emulation/qemu-sbsa/Kconfig.name b/src/mainboard/emulation/qemu-sbsa/Kconfig.name
new file mode 100644
index 0000000..6fefa0c
--- /dev/null
+++ b/src/mainboard/emulation/qemu-sbsa/Kconfig.name
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+config BOARD_EMULATION_QEMU_SBSA
+	bool "QEMU sbsa"
diff --git a/src/mainboard/emulation/qemu-sbsa/Makefile.mk b/src/mainboard/emulation/qemu-sbsa/Makefile.mk
new file mode 100644
index 0000000..e3855ba
--- /dev/null
+++ b/src/mainboard/emulation/qemu-sbsa/Makefile.mk
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+bootblock-y += bootblock.c
+
+romstage-y += cbmem.c
+
+bootblock-y += media.c
+romstage-y += media.c
+ramstage-y += media.c
+
+bootblock-y += mmio.c
+romstage-y += mmio.c
+ramstage-y += mmio.c
+
+ramstage-y += acpi.c
+
+bootblock-y += bootblock_custom.S
+
+CPPFLAGS_common += -mcmodel=large -I$(src)/mainboard/$(MAINBOARDDIR)/include
+
+build_complete::
+	@printf "Truncating coreboot.rom to 256M\n"
+	truncate -s 256M $(obj)/coreboot.rom
diff --git a/src/mainboard/emulation/qemu-sbsa/acpi.c b/src/mainboard/emulation/qemu-sbsa/acpi.c
new file mode 100644
index 0000000..1dae242
--- /dev/null
+++ b/src/mainboard/emulation/qemu-sbsa/acpi.c
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <acpi/acpi.h>
+#include <mainboard/addressmap.h>
+
+
+void acpi_fill_fadt(acpi_fadt_t *fadt)
+{
+	fadt->ARM_boot_arch |= ACPI_FADT_ARM_PSCI_COMPLIANT;
+}
+
+unsigned long acpi_fill_madt(unsigned long current)
+{
+	return current;
+}
+
+uintptr_t platform_get_gicd_base(void)
+{
+	return SBSA_GIC_DIST;
+}
+
+uintptr_t platform_get_gicr_base(void)
+{
+	return SBSA_GIC_REDIST;
+}
+
+#define SEC_EL1_TIMER_GISV 0x1d
+#define NONSEC_EL1_TIMER_GSIV 0x1e
+#define VIRTUAL_TIMER_GSIV 0x1b
+#define NONSEC_EL2_TIMER_GSIV 0x1a
+
+#define SBSA_TIMER_FLAGS (ACPI_GTDT_INTERRUPT_POLARITY | ACPI_GTDT_ALWAYS_ON)
+
+void acpi_soc_fill_gtdt(acpi_gtdt_t *gtdt)
+{
+	/* This value is optional if the system implements EL3 (Security
+	   Extensions). If not provided, this field must be 0xFFFFFFFFFFFFFFFF. */
+	gtdt->counter_block_address = UINT64_MAX;
+	gtdt->secure_el1_interrupt = SEC_EL1_TIMER_GISV;
+	gtdt->secure_el1_flags = SBSA_TIMER_FLAGS;
+	gtdt->non_secure_el1_interrupt = NONSEC_EL1_TIMER_GSIV;
+	gtdt->non_secure_el1_flags = SBSA_TIMER_FLAGS;
+	gtdt->virtual_timer_interrupt = VIRTUAL_TIMER_GSIV;
+	gtdt->virtual_timer_flags = SBSA_TIMER_FLAGS;
+	gtdt->non_secure_el2_interrupt = NONSEC_EL2_TIMER_GSIV;
+	gtdt->non_secure_el2_flags = SBSA_TIMER_FLAGS;
+	/* This value is optional if the system implements EL3
+	   (Security Extensions). If not provided, this field must be
+	   0xFFFFFFFFFFFFFFF. */
+	gtdt->counter_read_block_address = UINT64_MAX;
+}
+
+#define WD_TIMER_GSIV 0x30
+
+unsigned long acpi_soc_gtdt_add_timers(uint32_t *count, unsigned long current)
+{
+	(*count)++;
+	return acpi_gtdt_add_watchdog(current, SBSA_GWDT_REFRESH, SBSA_GWDT_CONTROL,
+				      WD_TIMER_GSIV, 0);
+}
diff --git a/src/mainboard/emulation/qemu-sbsa/board_info.txt b/src/mainboard/emulation/qemu-sbsa/board_info.txt
new file mode 100644
index 0000000..16a92be
--- /dev/null
+++ b/src/mainboard/emulation/qemu-sbsa/board_info.txt
@@ -0,0 +1,3 @@
+Board name: QEMU sbsa
+Category: emulation
+Board URL: https://wiki.qemu.org/Main_Page
diff --git a/src/mainboard/emulation/qemu-sbsa/bootblock.c b/src/mainboard/emulation/qemu-sbsa/bootblock.c
new file mode 100644
index 0000000..b38df6e
--- /dev/null
+++ b/src/mainboard/emulation/qemu-sbsa/bootblock.c
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <arch/mmu.h>
+#include <bootblock_common.h>
+#include <symbols.h>
+
+void bootblock_mainboard_init(void)
+{
+	mmu_init();
+
+	/* Everything below DRAM is device memory */
+	mmu_config_range((void *)0, (uintptr_t)_dram, MA_DEV | MA_RW);
+	/* Set a dummy value for DRAM. ramstage should update the mapping. */
+	mmu_config_range(_dram, ((size_t) CONFIG_DRAM_SIZE_MB) * MiB, MA_MEM | MA_RW);
+
+	mmu_config_range(_ttb, REGION_SIZE(ttb), MA_MEM | MA_S | MA_RW);
+	mmu_config_range(_bootblock, REGION_SIZE(bootblock), MA_MEM | MA_S | MA_RW);
+	mmu_config_range(_ramstage, REGION_SIZE(ramstage), MA_MEM | MA_S | MA_RW);
+	mmu_config_range((void *)CONFIG_ECAM_MMCONF_BASE_ADDRESS, CONFIG_ECAM_MMCONF_LENGTH,
+			 MA_DEV | MA_RW);
+	mmu_enable();
+}
diff --git a/src/mainboard/emulation/qemu-sbsa/bootblock_custom.S b/src/mainboard/emulation/qemu-sbsa/bootblock_custom.S
new file mode 100644
index 0000000..c05afed
--- /dev/null
+++ b/src/mainboard/emulation/qemu-sbsa/bootblock_custom.S
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/*
+ * Early initialization code for sbsa-ref machine
+ */
+
+#include <arch/asm.h>
+
+ENTRY(_start)
+
+	/* TF-A arg which contains a pointer to fdt */
+	ldr	x1, =_fdt_pointer
+	str	x0, [x1]
+
+	/* Setup CPU. */
+	/* bl      arm64_init_cpu */
+
+	/* ==== stack init from arm64_init_cpu ==== */
+
+	msr SPSel, #0 /* use SP_EL0 */
+
+	ldr	x2, =0xdeadbeefdeadbeef
+	ldr	x0, =_stack
+	ldr	x1, =_estack
+1:
+	stp	x2, x2, [x0], #16
+	cmp	x0, x1
+	bne	1b
+
+	sub	sp, x0, #16
+
+	/* ==== END ==== */
+
+	/* Get code positions. */
+	ldr	x1, =_flash
+	ldr	x0, =_bootblock
+
+	/* Calculate bootblock size. */
+	ldr     x2, =_ebootblock
+	sub     x2, x2, x0
+
+	/* Call memcpy in arch/arm64/memcpy.S */
+	bl	memcpy
+	dmb     sy
+
+	/* Calculate relocation offset between bootblock in flash and in DRAM. */
+	ldr	x0, =_flash
+	ldr	x1, =_bootblock
+	sub	x1, x1, x0
+
+	/* Jump to main() in DRAM. */
+	adr	x0, main
+	add	x0, x0, x1
+	blr	x0
+ENDPROC(_start)
diff --git a/src/mainboard/emulation/qemu-sbsa/cbmem.c b/src/mainboard/emulation/qemu-sbsa/cbmem.c
new file mode 100644
index 0000000..ebc8a78
--- /dev/null
+++ b/src/mainboard/emulation/qemu-sbsa/cbmem.c
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <cbmem.h>
+#include <symbols.h>
+#include <device_tree.h>
+#include <console/console.h>
+
+DECLARE_REGION(fdt_pointer)
+uintptr_t cbmem_top_chipset(void)
+{
+	const uint64_t top = fdt_get_memory_top((void *) *((uintptr_t *)_fdt_pointer));
+
+	if (top == 0) {
+		/* corrupted FDT? */
+		die("Could not find top of memory in FDT!");
+	}
+
+	printk(BIOS_DEBUG, "%s: 0x%llx\n", __func__, top);
+	return (uintptr_t)top;
+}
diff --git a/src/mainboard/emulation/qemu-sbsa/chip.h b/src/mainboard/emulation/qemu-sbsa/chip.h
new file mode 100644
index 0000000..98c6c71
--- /dev/null
+++ b/src/mainboard/emulation/qemu-sbsa/chip.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef MAINBOARD_EMULATION_QEMU_SBSA_CHIP_H
+#define MAINBOARD_EMULATION_QEMU_SBSA_CHIP_H
+
+#include <types.h>
+
+struct mainboard_emulation_qemu_sbsa_config {
+	uint32_t vgic_maintenance_interrupt;
+	uint32_t performance_interrupt_gsiv;
+};
+
+#endif
diff --git a/src/mainboard/emulation/qemu-sbsa/devicetree.cb b/src/mainboard/emulation/qemu-sbsa/devicetree.cb
new file mode 100644
index 0000000..2e2cf3d
--- /dev/null
+++ b/src/mainboard/emulation/qemu-sbsa/devicetree.cb
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+chip mainboard/emulation/qemu-sbsa
+	register "vgic_maintenance_interrupt" = "0x19"
+	register "performance_interrupt_gsiv" = "0x17"
+
+	device cpu_cluster 0 on ops qemu_aarch64_cpu_ops end
+
+	device domain 0 on ops qemu_aarch64_pci_domain_ops
+		device pci 00.0 on end
+	end
+end
diff --git a/src/mainboard/emulation/qemu-sbsa/dsdt.asl b/src/mainboard/emulation/qemu-sbsa/dsdt.asl
new file mode 100644
index 0000000..8280cb7
--- /dev/null
+++ b/src/mainboard/emulation/qemu-sbsa/dsdt.asl
@@ -0,0 +1,271 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#define LINK_DEVICE(Uid, LinkName, Irq)                                                    \
+	Device (LinkName) {                                                                \
+		Name (_HID, EISAID("PNP0C0F"))                                             \
+		Name (_UID, Uid)                                                           \
+		Name (_CRS, ResourceTemplate() {                                           \
+			Interrupt (ResourceProducer, Level, ActiveHigh, Exclusive) { Irq } \
+		})                                                                         \
+	}
+
+#define USB_PORT(PortName, Adr)                                                 \
+	Device (PortName) {                                                     \
+		Name (_ADR, Adr)                                                \
+		Name (_UPC, Package() {                                         \
+			0xFF,		                                        \
+			0x00,		                                        \
+			0x00000000,                                             \
+			0x00000000                                              \
+		})                                                              \
+		Name (_PLD, Package() {                                         \
+			Buffer(0x10) {                                          \
+				0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+				0x31, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  \
+			}                                                       \
+		})                                                              \
+	}
+
+#define PRT_ENTRY(Address, Pin, Link)    \
+	Package (4) {                    \
+		Address, Pin, Link, Zero \
+	}
+
+#define PRT_ENTRY_GROUP(Address, Link0, Link1, Link2, Link3) \
+	PRT_ENTRY (Address, 0, Link0),                       \
+	PRT_ENTRY (Address, 1, Link1),                       \
+	PRT_ENTRY (Address, 2, Link2),                       \
+	PRT_ENTRY (Address, 3, Link3)
+
+#include <acpi/acpi.h>
+#include <mainboard/addressmap.h>
+
+DefinitionBlock(
+	"dsdt.aml",
+	"DSDT",
+	ACPI_DSDT_REV_2,
+	OEM_ID,
+	ACPI_TABLE_CREATOR,
+	0x20230621
+)
+{
+	#include <acpi/dsdt_top.asl>
+
+	Scope (_SB) {
+		// UART PL011
+		Device (COM0) {
+			Name (_HID, "ARMH0011")
+			Name (_UID, Zero)
+			Name (_CRS, ResourceTemplate () {
+				Memory32Fixed (ReadWrite, SBSA_UART_BASE, 0x00001000)
+				Interrupt (ResourceConsumer, Level, ActiveHigh, Exclusive) { 33 }
+			})
+		}
+
+		// AHCI Host Controller
+		Device (AHC0) {
+			Name (_HID, "LNRO001E")
+			Name (_CLS, Package (3) {
+				0x01,
+				0x06,
+				0x01,
+			})
+			Name (_CCA, 1)
+			Name (_CRS, ResourceTemplate() {
+				Memory32Fixed (ReadWrite, SBSA_AHCI_BASE, 0x00010000)
+				Interrupt (ResourceConsumer, Level, ActiveHigh, Exclusive) { 42 }
+			})
+		}
+
+		// USB EHCI Host Controller
+		Device (USB0) {
+			Name (_HID, "LNRO0D20")
+			Name (_CID, "PNP0D20")
+			Method (_CRS, 0x0, Serialized) {
+				Name (RBUF, ResourceTemplate() {
+					Memory32Fixed (ReadWrite, SBSA_EHCI_BASE, 0x00010000)
+					Interrupt (ResourceConsumer, Level, ActiveHigh, Exclusive) { 43 }
+				})
+				Return (RBUF)
+			}
+
+			// Root Hub
+			Device (RHUB) {
+				Name (_ADR, 0x00000000)	// Address of Root Hub should be 0 as per ACPI 5.0 spec
+
+				// Ports connected to Root Hub
+				Device (HUB1) {
+					Name (_ADR, 0x00000001)
+					Name (_UPC, Package() {
+						0x00,		// Port is NOT connectable
+						0xFF,		// Don't care
+						0x00000000,	// Reserved 0 must be zero
+						0x00000000	// Reserved 1 must be zero
+					})
+					USB_PORT (PRT1, 0x00000001) // USB0_RHUB_HUB1_PRT1
+					USB_PORT (PRT2, 0x00000002) // USB0_RHUB_HUB1_PRT2
+					USB_PORT (PRT3, 0x00000003) // USB0_RHUB_HUB1_PRT3
+					USB_PORT (PRT4, 0x00000004) // USB0_RHUB_HUB1_PRT4
+				} // USB0_RHUB_HUB1
+			} // USB0_RHUB
+		} // USB0
+
+		Device (PCI0)
+		{
+			Name (_HID, EISAID ("PNP0A08")) // PCI Express Root Bridge
+			Name (_CID, EISAID ("PNP0A03")) // Compatible PCI Root Bridge
+			Name (_SEG, Zero)		// PCI Segment Group number
+			Name (_BBN, Zero)		// PCI Base Bus Number
+			Name (_UID, "PCI0")
+			Name (_CCA, One)		// Initially mark the PCI coherent (for JunoR1)
+
+			Method (_CBA, 0, NotSerialized) {
+				return (SBSA_PCIE_ECAM_BASE)
+			}
+
+			LINK_DEVICE (0, GSI0, 0x23)
+			LINK_DEVICE (1, GSI1, 0x24)
+			LINK_DEVICE (2, GSI2, 0x25)
+			LINK_DEVICE (3, GSI3, 0x26)
+
+			Name (_PRT, Package () {
+
+				// _PRT: PCI Routing Table
+				PRT_ENTRY_GROUP (0x0000FFFF, GSI0, GSI1, GSI2, GSI3),
+				PRT_ENTRY_GROUP (0x0001FFFF, GSI1, GSI2, GSI3, GSI0),
+				PRT_ENTRY_GROUP (0x0002FFFF, GSI2, GSI3, GSI0, GSI1),
+				PRT_ENTRY_GROUP (0x0003FFFF, GSI3, GSI0, GSI1, GSI2),
+				PRT_ENTRY_GROUP (0x0004FFFF, GSI0, GSI1, GSI2, GSI3),
+				PRT_ENTRY_GROUP (0x0005FFFF, GSI1, GSI2, GSI3, GSI0),
+				PRT_ENTRY_GROUP (0x0006FFFF, GSI2, GSI3, GSI0, GSI1),
+				PRT_ENTRY_GROUP (0x0007FFFF, GSI3, GSI0, GSI1, GSI2),
+				PRT_ENTRY_GROUP (0x0008FFFF, GSI0, GSI1, GSI2, GSI3),
+				PRT_ENTRY_GROUP (0x0009FFFF, GSI1, GSI2, GSI3, GSI0),
+				PRT_ENTRY_GROUP (0x000AFFFF, GSI2, GSI3, GSI0, GSI1),
+				PRT_ENTRY_GROUP (0x000BFFFF, GSI3, GSI0, GSI1, GSI2),
+				PRT_ENTRY_GROUP (0x000CFFFF, GSI0, GSI1, GSI2, GSI3),
+				PRT_ENTRY_GROUP (0x000DFFFF, GSI1, GSI2, GSI3, GSI0),
+				PRT_ENTRY_GROUP (0x000EFFFF, GSI2, GSI3, GSI0, GSI1),
+				PRT_ENTRY_GROUP (0x000FFFFF, GSI3, GSI0, GSI1, GSI2),
+				PRT_ENTRY_GROUP (0x0010FFFF, GSI0, GSI1, GSI2, GSI3),
+				PRT_ENTRY_GROUP (0x0011FFFF, GSI1, GSI2, GSI3, GSI0),
+				PRT_ENTRY_GROUP (0x0012FFFF, GSI2, GSI3, GSI0, GSI1),
+				PRT_ENTRY_GROUP (0x0013FFFF, GSI3, GSI0, GSI1, GSI2),
+				PRT_ENTRY_GROUP (0x0014FFFF, GSI0, GSI1, GSI2, GSI3),
+				PRT_ENTRY_GROUP (0x0015FFFF, GSI1, GSI2, GSI3, GSI0),
+				PRT_ENTRY_GROUP (0x0016FFFF, GSI2, GSI3, GSI0, GSI1),
+				PRT_ENTRY_GROUP (0x0017FFFF, GSI3, GSI0, GSI1, GSI2),
+				PRT_ENTRY_GROUP (0x0018FFFF, GSI0, GSI1, GSI2, GSI3),
+				PRT_ENTRY_GROUP (0x0019FFFF, GSI1, GSI2, GSI3, GSI0),
+				PRT_ENTRY_GROUP (0x001AFFFF, GSI2, GSI3, GSI0, GSI1),
+				PRT_ENTRY_GROUP (0x001BFFFF, GSI3, GSI0, GSI1, GSI2),
+				PRT_ENTRY_GROUP (0x001CFFFF, GSI0, GSI1, GSI2, GSI3),
+				PRT_ENTRY_GROUP (0x001DFFFF, GSI1, GSI2, GSI3, GSI0),
+				PRT_ENTRY_GROUP (0x001EFFFF, GSI2, GSI3, GSI0, GSI1),
+				PRT_ENTRY_GROUP (0x001FFFFF, GSI3, GSI0, GSI1, GSI2),
+			})
+
+			// Root complex resources
+			Method (_CRS, 0, Serialized) {
+			Name (RBUF, ResourceTemplate () {
+				WordBusNumber ( // Bus numbers assigned to this root
+				ResourceProducer,
+				MinFixed, MaxFixed, PosDecode,
+				0,	 // AddressGranularity
+				0,	 // AddressMinimum - Minimum Bus Number
+				255,	 // AddressMaximum - Maximum Bus Number
+				0,	 // AddressTranslation - Set to 0
+				256	// RangeLength - Number of Busses
+				)
+
+				DWordMemory ( // 32-bit BAR Windows
+					ResourceProducer, PosDecode,
+					MinFixed, MaxFixed,
+					Cacheable, ReadWrite,
+					0x00000000,           // Granularity
+					SBSA_PCIE_MMIO_BASE,  // Min Base Address
+					SBSA_PCIE_MMIO_LIMIT, // Max Base Address
+					0,                    // Translate
+					SBSA_PCIE_MMIO_SIZE   // Length
+					)
+
+				QWordMemory ( // 64-bit BAR Windows
+					ResourceProducer, PosDecode,
+					MinFixed, MaxFixed,
+					Cacheable, ReadWrite,
+					0x00000000,                // Granularity
+					SBSA_PCIE_MMIO_HIGH_BASE,  // Min Base Address
+					SBSA_PCIE_MMIO_HIGH_LIMIT, // Max Base Address
+					0,                         // Translate
+					SBSA_PCIE_MMIO_HIGH_SIZE   // Length
+					)
+
+				DWordIo ( // IO window
+					ResourceProducer,
+					MinFixed,
+					MaxFixed,
+					PosDecode,
+					EntireRange,
+					0x00000000,         // Granularity
+					0,                  // Min Base Address
+					0xffff,             // Max Base Address
+					SBSA_PCIE_PIO_BASE, // Translate
+					0x10000,            // Length
+					,,,TypeTranslation
+					)
+				}) // Name(RBUF)
+
+				Return (RBUF)
+			} // Method(_CRS)
+
+			// OS Control Handoff
+			Name (SUPP, Zero) // PCI _OSC Support Field value
+			Name (CTRL, Zero) // PCI _OSC Control Field value
+
+			/*
+			 * See [1] 6.2.10, [2] 4.5
+			 */
+			Method (_OSC,4) {
+				// Check for proper UUID
+				If (Arg0 == ToUUID("33DB4D5B-1FF7-401C-9657-7441C03DD766")) {
+					// Create DWord-adressable fields from the Capabilities Buffer
+					CreateDWordField (Arg3,0,CDW1)
+					CreateDWordField (Arg3,4,CDW2)
+					CreateDWordField (Arg3,8,CDW3)
+
+					// Save Capabilities DWord2 & 3
+					SUPP = CDW2
+					CTRL = CDW3
+
+					// Only allow native hot plug control if OS supports:
+					// * ASPM
+					// * Clock PM
+					// * MSI/MSI-X
+					If ((SUPP & 0x16) != 0x16) {
+						CTRL &= 0x1E // Mask bit 0 (and undefined bits)
+					}
+
+					// Always allow native PME, AER (no dependencies)
+
+					// Never allow SHPC (no SHPC controller in this system)
+					CTRL &= 0x1D
+
+					If (Arg1 != One) { // Unknown revision
+						CDW1 |= 0x08
+					}
+
+					If (CDW3 != CTRL) { // Capabilities bits were masked
+						CDW1 |= 0x10
+					}
+
+					// Update DWORD3 in the buffer
+					CDW3 = CTRL
+					Return (Arg3)
+				} Else {
+					CDW1 |= 4 // Unrecognized UUID
+					Return (Arg3)
+				}
+			} // End _OSC
+		}
+	} // Scope (_SB)
+}
diff --git a/src/mainboard/emulation/qemu-sbsa/flash.fmd b/src/mainboard/emulation/qemu-sbsa/flash.fmd
new file mode 100644
index 0000000..fcc6e19
--- /dev/null
+++ b/src/mainboard/emulation/qemu-sbsa/flash.fmd
@@ -0,0 +1,23 @@
+#
+# custom fmap which takes the additional TF-A region into account
+#
+# +-------------+ <-- 0x0
+# |    TF-A     |
+# +-------------+ <-- BIOS_BASE
+# |  bootblock  |
+# +-------------+ <-- BIOS_BASE + 128K
+# |    FMAP     |
+# +-------------+ <-- BIOS_BASE + 128K + FMAP_SIZE
+# |    CBFS     |
+# +-------------+ <-- ROM_SIZE
+#
+
+FLASH@0x10000000 CONFIG_ROM_SIZE {
+
+	BIOS {
+
+		BOOTBLOCK 128K
+		FMAP 0x200
+		COREBOOT(CBFS)
+	}
+}
diff --git a/src/mainboard/emulation/qemu-sbsa/include/mainboard/addressmap.h b/src/mainboard/emulation/qemu-sbsa/include/mainboard/addressmap.h
new file mode 100644
index 0000000..84c2d05
--- /dev/null
+++ b/src/mainboard/emulation/qemu-sbsa/include/mainboard/addressmap.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/*
+ * Base addresses for QEMU sbsa-ref machine
+ * [hw/arm/sbsa-ref.c, c6f3cbca32bde9ee94d9949aa63e8a7ef2d7bc5b]
+ */
+
+#define SBSA_FLASH_BASE           0x00000000
+#define SBSA_FLASH_SIZE           0x20000000
+#define SBSA_GIC_DIST             0x40060000
+#define SBSA_GIC_REDIST           0x40080000
+#define SBSA_GWDT_REFRESH         0x50010000
+#define SBSA_GWDT_CONTROL         0x50011000
+#define SBSA_UART_BASE            0x60000000
+#define SBSA_RTC_BASE             0x60010000
+#define SBSA_GPIO_BASE            0x60020000
+#define SBSA_SECURE_UART_BASE     0x60030000
+#define SBSA_SMMU_BASE            0x60050000
+#define SBSA_AHCI_BASE            0x60100000
+#define SBSA_EHCI_BASE            0x60110000
+#define SBSA_SECMEM_BASE          0x20000000
+#define SBSA_SECMEM_SIZE          0x20000000
+#define SBSA_PCIE_MMIO_BASE       0x80000000
+#define SBSA_PCIE_MMIO_LIMIT      0xefffffff
+#define SBSA_PCIE_MMIO_SIZE       0x70000000
+#define SBSA_PCIE_PIO_BASE        0x7fff0000
+#define SBSA_PCIE_ECAM_BASE       0xf0000000
+#define SBSA_PCIE_ECAM_LIMIT      0xffffffff
+#define SBSA_PCIE_ECAM_SIZE       0x10000000
+#define SBSA_PCIE_MMIO_HIGH_BASE  0x100000000
+#define SBSA_PCIE_MMIO_HIGH_LIMIT 0xffffffffff
+#define SBSA_PCIE_MMIO_HIGH_SIZE  0xff00000000
diff --git a/src/mainboard/emulation/qemu-sbsa/mainboard.c b/src/mainboard/emulation/qemu-sbsa/mainboard.c
new file mode 100644
index 0000000..112e118
--- /dev/null
+++ b/src/mainboard/emulation/qemu-sbsa/mainboard.c
@@ -0,0 +1,170 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "chip.h"
+#include <acpi/acpigen.h>
+#include <arch/mmu.h>
+#include <bootmem.h>
+#include <cbfs.h>
+#include <device/device.h>
+#include <device_tree.h>
+#include <bootmem.h>
+#include <arch/mmu.h>
+#include <mainboard/addressmap.h>
+#include <stdint.h>
+#include <symbols.h>
+
+static size_t ram_size(void)
+{
+	return (size_t)cbmem_top() - (uintptr_t)_dram;
+}
+
+static void mainboard_init(void *chip_info)
+{
+	mmu_config_range(_dram, ram_size(), MA_MEM | MA_RW);
+}
+
+void smbios_cpu_get_core_counts(u16 *core_count, u16 *thread_count)
+{
+	*core_count = 0;
+	struct device *dev = NULL;
+	while ((dev = dev_find_path(dev, DEVICE_PATH_GICC_V3)))
+		*core_count += 1;
+
+	*thread_count = 1;
+}
+
+static void qemu_aarch64_init(struct device *dev)
+{
+	struct memory_info *mem_info;
+
+	mem_info = cbmem_add(CBMEM_ID_MEMINFO, sizeof(*mem_info));
+	if (mem_info == NULL)
+		return;
+
+	memset(mem_info, 0, sizeof(*mem_info));
+
+	mem_info->ecc_type = MEMORY_ARRAY_ECC_UNKNOWN;
+	mem_info->max_capacity_mib = 0x800000;	// Fixed at 8 TiB for qemu-sbsa
+	mem_info->number_of_devices = mem_info->dimm_cnt = 1;
+
+	mem_info->dimm[0].dimm_size = ram_size() / MiB;
+	mem_info->dimm[0].ddr_type = MEMORY_TYPE_DRAM;
+	mem_info->dimm[0].ddr_frequency = 0;
+	mem_info->dimm[0].channel_num = mem_info->dimm[0].dimm_num = 0;
+	mem_info->dimm[0].bank_locator = 0;
+
+	mem_info->dimm[0].bus_width = 0x03;	// 64-bit, no parity
+	mem_info->dimm[0].vdd_voltage = 0;
+	mem_info->dimm[0].max_speed_mts = mem_info->dimm[0].configured_speed_mts = 0;
+}
+
+static unsigned long mb_write_acpi_tables(const struct device *dev, unsigned long current,
+					  acpi_rsdp_t *rsdp)
+{
+	printk(BIOS_DEBUG, "ACPI:    * DBG2\n");
+	return acpi_pl011_write_dbg2_uart(rsdp, current, SBSA_UART_BASE, "\\_SB.COM0");
+}
+
+
+static void mainboard_enable(struct device *dev)
+{
+	dev->ops->init = qemu_aarch64_init;
+	dev->ops->write_acpi_tables = mb_write_acpi_tables;
+}
+
+
+struct chip_operations mainboard_ops = {
+	.enable_dev = mainboard_enable,
+	.init = mainboard_init,
+};
+
+struct chip_operations mainboard_emulation_qemu_sbsa_ops = { };
+
+static void qemu_aarch64_domain_read_resources(struct device *dev)
+{
+	struct resource *res;
+	int index = 0;
+
+	/* Initialize the system-wide I/O space constraints. */
+	res = new_resource(dev, index++);
+	res->limit = 0xffffUL;
+	res->flags = IORESOURCE_IO | IORESOURCE_ASSIGNED;
+
+	/* Initialize the system-wide memory resources constraints. */
+	res = new_resource(dev, index++);
+	res->base = SBSA_PCIE_MMIO_BASE;
+	res->limit = SBSA_PCIE_MMIO_LIMIT;
+	res->flags = IORESOURCE_MEM | IORESOURCE_ASSIGNED;
+
+	res = new_resource(dev, index++);
+	res->base = SBSA_PCIE_MMIO_HIGH_BASE;
+	res->limit = SBSA_PCIE_MMIO_HIGH_LIMIT;
+	res->flags = IORESOURCE_MEM | IORESOURCE_ASSIGNED;
+
+	mmio_range(dev, index++, SBSA_PCIE_ECAM_BASE, SBSA_PCIE_ECAM_SIZE);
+
+	ram_range(dev, index++, (uintptr_t)_dram, ram_size());
+
+	mmio_range(dev, index++, SBSA_FLASH_BASE, SBSA_FLASH_SIZE);
+	reserved_ram_range(dev, index++, SBSA_SECMEM_BASE, SBSA_SECMEM_SIZE);
+}
+
+struct device_operations qemu_aarch64_pci_domain_ops = {
+	.read_resources    = qemu_aarch64_domain_read_resources,
+	.set_resources     = pci_domain_set_resources,
+	.scan_bus          = pci_host_bridge_scan_bus,
+};
+
+static void qemu_sbsa_fill_cpu_ssdt(const struct device *dev)
+{
+	acpigen_write_processor_device(dev->path.gicc_v3.mpidr);
+	acpigen_write_processor_device_end();
+}
+
+struct device_operations qemu_sbsa_cpu_ops = {
+	.acpi_fill_ssdt = qemu_sbsa_fill_cpu_ssdt,
+};
+
+DECLARE_REGION(fdt_pointer)
+static void qemu_aarch64_scan_bus(struct device *dev)
+{
+	struct bus *bus = alloc_bus(dev);
+	uintptr_t fdt_blob = *(uintptr_t *)_fdt_pointer;
+	struct device_tree *tree;
+	struct device_tree_node *node;
+	char path[14];
+	u16 fdt_cpu_count = 0;
+	struct mainboard_emulation_qemu_sbsa_config *config = dev->chip_info;
+
+	tree = fdt_unflatten((void *)fdt_blob);
+	if (tree == NULL)
+		return;
+
+	snprintf(path, sizeof(path), "/cpus/cpu@%d", fdt_cpu_count);
+	while ((node = dt_find_node_by_path(tree, path, NULL, NULL, 0)) != NULL) {
+		struct device_tree_property *prop;
+		int64_t mpidr = -1;
+		list_for_each(prop, node->properties, list_node) {
+			if (!strcmp("reg", prop->prop.name)) {
+				mpidr = be64toh(*(uint64_t *)prop->prop.data);
+				break;
+			}
+		}
+		if (mpidr >= 0) {
+			struct device_path devpath = { .type = DEVICE_PATH_GICC_V3,
+				.gicc_v3 = { .mpidr = mpidr,
+					     .vgic_mi = config->vgic_maintenance_interrupt,
+					     .pi_gsiv = config->performance_interrupt_gsiv, },
+
+			};
+			struct device *cpu = alloc_dev(bus, &devpath);
+			assert(cpu);
+			cpu->ops = &qemu_sbsa_cpu_ops;
+		}
+		snprintf(path, sizeof(path), "/cpus/cpu@%d", ++fdt_cpu_count);
+	}
+}
+
+struct device_operations qemu_aarch64_cpu_ops = {
+	.scan_bus = qemu_aarch64_scan_bus,
+};
diff --git a/src/mainboard/emulation/qemu-sbsa/media.c b/src/mainboard/emulation/qemu-sbsa/media.c
new file mode 100644
index 0000000..00ed54c
--- /dev/null
+++ b/src/mainboard/emulation/qemu-sbsa/media.c
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <boot_device.h>
+
+/* Maps directly to NOR flash up to ROM size. */
+static const struct mem_region_device boot_dev =
+	MEM_REGION_DEV_RO_INIT((void *)0x10000000, CONFIG_ROM_SIZE);
+
+const struct region_device *boot_device_ro(void)
+{
+	return &boot_dev.rdev;
+}
diff --git a/src/mainboard/emulation/qemu-sbsa/memlayout.ld b/src/mainboard/emulation/qemu-sbsa/memlayout.ld
new file mode 100644
index 0000000..d178521
--- /dev/null
+++ b/src/mainboard/emulation/qemu-sbsa/memlayout.ld
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <memlayout.h>
+#include <arch/header.ld>
+
+/*
+ * Memory map for QEMU sbsa-ref machine since
+ * [hw/arm/sbsa-ref.c, c6f3cbca32bde9ee94d9949aa63e8a7ef2d7bc5b]
+ */
+SECTIONS
+{
+	REGION(flash, 0x10000000, CONFIG_ROM_SIZE, 8)
+
+	DRAM_START(0x10000000000)
+	BOOTBLOCK(0x10020010000, 64K)
+	STACK(0x10020020000, 54K)
+	CBFS_MCACHE(0x1002002D800, 8K)
+	FMAP_CACHE(0x1002002F800, 2K)
+	TIMESTAMP(0x10020030000, 1K)
+	TTB(0x10020070000, 128K)
+	RAMSTAGE(0x100200b0000, 16M)
+	REGION(fdt_pointer, 0x100210b0000, ARCH_POINTER_ALIGN_SIZE, ARCH_POINTER_ALIGN_SIZE)
+
+	POSTRAM_CBFS_CACHE(0x10021200000, 1M)
+}
diff --git a/src/mainboard/emulation/qemu-sbsa/mmio.c b/src/mainboard/emulation/qemu-sbsa/mmio.c
new file mode 100644
index 0000000..6b50856
--- /dev/null
+++ b/src/mainboard/emulation/qemu-sbsa/mmio.c
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <console/uart.h>
+#include <mainboard/addressmap.h>
+
+uintptr_t uart_platform_base(unsigned int idx)
+{
+	return SBSA_UART_BASE;
+}