board: emulation: Add QEMU sbsa support

Add support for Arm sbsa [1] v0.3+ that is supported by QEMU [2].

Unlike other Arm based platforms the machine only provides a minimal
FDT that contains number of CPUs, ammount of memory and machine-version.
The boot firmware has to provide ACPI tables to the OS.
Due to this design a full DTB is added here as well that allows U-Boot's
driver to properly function. The DTB is appended at the end of the U-Boot
image and will be merged with the QEMU provided DTB.

In addition provide documentation how to use, enable binman to fabricate both
ROMs that are required to boot and add ACPI tables to make it full compatible
to the EDK2 reference implementation.

The board was tested using Fedora 40 Aarch64 Workstation. It's able
to boot from USB and AHCI or network.

Tested and found working:
- serial
- PCI
- xHCI
- Bochs display
- AHCI
- network using e1000e
- CPU init
- Booting Fedora 40

1: Server Base System Architecture (SBSA)
2: https://www.qemu.org/docs/master/system/arm/sbsa.html

Signed-off-by: Patrick Rudolph <patrick.rudolph@9elements.com>
Cc: Peter Robinson <pbrobinson@gmail.com>
Cc: Simon Glass <sjg@chromium.org>
Cc: Tom Rini <trini@konsulko.com>
diff --git a/board/emulation/qemu-arm/MAINTAINERS b/board/emulation/qemu-arm/MAINTAINERS
index 5154262..7bc0ee6 100644
--- a/board/emulation/qemu-arm/MAINTAINERS
+++ b/board/emulation/qemu-arm/MAINTAINERS
@@ -4,5 +4,7 @@
 F:	board/emulation/qemu-arm/
 F:	board/emulation/common/
 F:	include/configs/qemu-arm.h
+F:	include/configs/qemu-sbsa.h
 F:	configs/qemu_arm_defconfig
 F:	configs/qemu_arm64_defconfig
+F:	configs/qemu-arm-sbsa_defconfig
diff --git a/board/emulation/qemu-sbsa/Kconfig b/board/emulation/qemu-sbsa/Kconfig
new file mode 100644
index 0000000..72c76b3
--- /dev/null
+++ b/board/emulation/qemu-sbsa/Kconfig
@@ -0,0 +1,59 @@
+if TARGET_QEMU_ARM_SBSA
+
+config SYS_SOC
+	default "qemu-sbsa"
+
+config TEXT_BASE
+	default 0x10000100000
+
+config SYS_LOAD_ADDR
+	default 0x10000100000
+
+config PRE_CON_BUF_ADDR
+	default 0x100000FF000
+
+config DEFAULT_DEVICE_TREE
+	default "qemu-sbsa"
+
+config BOARD_SPECIFIC_OPTIONS # dummy
+	def_bool y
+	select AHCI
+	select ACPIGEN
+	select ACPI
+	select BLOBLIST
+	select CPU
+	select CPU_ARMV8
+	select DM
+	select DM_USB
+	select DM_MTD
+	select GENERATE_ACPI_TABLE
+	select HAS_ROM
+	select MTD
+	select OF_LIBFDT_OVERLAY
+	select OF_SEPARATE
+	select PCI
+	select PCIE_ECAM_GENERIC
+	select USB
+	select GIC_V3
+	select GIC_V3_ITS
+	select SYS_FLASH_CFI_WIDTH_16BIT
+	imply AHCI_GENERIC
+	imply USB_XHCI_HCD
+	imply USB_XHCI_GENERIC
+	imply USB_STORAGE
+	imply E1000
+	imply E1000_NO_NVM
+	imply NET_RANDOM_ETHADDR
+	imply VIDEO_BOCHS
+	imply CFI_FLASH
+	imply SYS_MTDPARTS_RUNTIME
+	imply SET_DFU_ALT_INFO
+
+if DEBUG_UART
+
+config DEBUG_UART_BASE
+	default 0x60000000
+endif
+
+source "board/emulation/common/Kconfig"
+endif
diff --git a/board/emulation/qemu-sbsa/Makefile b/board/emulation/qemu-sbsa/Makefile
new file mode 100644
index 0000000..bacae32
--- /dev/null
+++ b/board/emulation/qemu-sbsa/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0+
+
+obj-y	+= qemu-sbsa.o
+obj-y	+= lowlevel_init.o
+obj-y	+= smc.o
+
+obj-$(CONFIG_GENERATE_ACPI_TABLE) += dsdt_generated.o
+obj-$(CONFIG_GENERATE_ACPI_TABLE) += acpi.o
diff --git a/board/emulation/qemu-sbsa/acpi.c b/board/emulation/qemu-sbsa/acpi.c
new file mode 100644
index 0000000..ba85e08
--- /dev/null
+++ b/board/emulation/qemu-sbsa/acpi.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2024 9elements GmbH
+ */
+
+#include <cpu.h>
+#include <tables_csum.h>
+#include <string.h>
+#include <acpi/acpi_table.h>
+#include <asm/acpi_table.h>
+#include <asm/armv8/sec_firmware.h>
+#include <configs/qemu-sbsa.h>
+#include <dm/uclass.h>
+#include <dm/device.h>
+#include "qemu-sbsa.h"
+
+#define SBSAQEMU_MADT_GIC_VBASE          0x2c020000
+#define SBSAQEMU_MADT_GIC_HBASE          0x2c010000
+#define SBSAQEMU_MADT_GIC_PMU_IRQ        23
+
+#define SBSA_PLATFORM_WATCHDOG_COUNT    1
+#define SBSA_PLATFORM_TIMER_COUNT       (SBSA_PLATFORM_WATCHDOG_COUNT)
+
+#define L2_ATTRIBUTES (ACPI_PPTT_READ_ALLOC | ACPI_PPTT_WRITE_ALLOC | \
+			(ACPI_PPTT_CACHE_TYPE_UNIFIED << \
+			 ACPI_PPTT_CACHE_TYPE_SHIFT))
+#define L2_SIZE 0x80000
+#define L2_SETS 0x400
+#define L2_WAYS 8
+
+#define L1D_ATTRIBUTES (ACPI_PPTT_READ_ALLOC | ACPI_PPTT_WRITE_ALLOC | \
+			(ACPI_PPTT_CACHE_TYPE_DATA << \
+			 ACPI_PPTT_CACHE_TYPE_SHIFT))
+#define L1D_SIZE 0x8000
+#define L1D_SETS 0x100
+#define L1D_WAYS 2
+
+#define L1I_ATTRIBUTES (ACPI_PPTT_READ_ALLOC | \
+			(ACPI_PPTT_CACHE_TYPE_INSTR << \
+			 ACPI_PPTT_CACHE_TYPE_SHIFT))
+#define L1I_SIZE 0x8000
+#define L1I_SETS 0x100
+#define L1I_WAYS 2
+
+int acpi_fill_iort(struct acpi_ctx *ctx)
+{
+	u32 its_offset, smmu_offset;
+	u64 gic_its_base = 0;
+
+	smc_get_gic_its_base(&gic_its_base);
+	if (gic_its_base == 0)
+		return 0;
+
+	u32 identifiers[] = { 0 };
+
+	its_offset = acpi_iort_add_its_group(ctx, ARRAY_SIZE(identifiers),
+					     identifiers);
+
+	struct acpi_iort_id_mapping map_smmu[] = {{
+		0, 0xffff, 0, its_offset, 0
+	}};
+
+	smmu_offset = acpi_iort_add_smmu_v3(ctx,
+					    SBSA_SMMU_BASE_ADDR, // Base address
+					    ACPI_IORT_SMMU_V3_COHACC_OVERRIDE, // Flags
+					    0,  // VATOS address
+					    0,  // SMMUv3 Model
+					    74, // Event
+					    75, // Pri
+					    77, // Gerror
+					    76, // Sync
+					    0,  // Proximity domain
+					    1,  // DevIDMappingIndex
+					    ARRAY_SIZE(map_smmu),
+					    map_smmu);
+
+	struct acpi_iort_id_mapping map_rc[] = {{
+		0, 0xffff, 0, smmu_offset, 0
+	}};
+
+	acpi_iort_add_rc(ctx,
+			 BIT(0) | BIT(56),  // CacheCoherent + CPM
+			 0,  // AtsAttribute
+			 0,  // PciSegmentNumber
+			 64, // MemoryAddressSizeLimit
+			 ARRAY_SIZE(map_rc),
+			 map_rc);
+	return 0;
+}
+
+void acpi_fill_fadt(struct acpi_fadt *fadt)
+{
+	fadt->flags = ACPI_FADT_HW_REDUCED_ACPI | ACPI_FADT_LOW_PWR_IDLE_S0;
+	fadt->preferred_pm_profile = ACPI_PM_PERFORMANCE_SERVER;
+	fadt->arm_boot_arch = ACPI_ARM_PSCI_COMPLIANT;
+}
+
+int acpi_fill_mcfg(struct acpi_ctx *ctx)
+{
+	size_t size;
+
+	/* PCI Segment Group 0, Start Bus Number 0, End Bus Number is 255 */
+	size = acpi_create_mcfg_mmconfig((void *)ctx->current,
+					 SBSA_PCIE_ECAM_BASE_ADDR, 0, 0, 255);
+	acpi_inc(ctx, size);
+
+	return 0;
+}
+
+static int sbsa_write_gtdt(struct acpi_ctx *ctx, const struct acpi_writer *entry)
+{
+	struct acpi_table_header *header;
+	struct acpi_gtdt *gtdt;
+
+	gtdt = ctx->current;
+	header = &gtdt->header;
+
+	memset(gtdt, '\0', sizeof(struct acpi_gtdt));
+
+	acpi_fill_header(header, "GTDT");
+	header->length = sizeof(struct acpi_gtdt);
+	header->revision = acpi_get_table_revision(ACPITAB_GTDT);
+
+	gtdt->cnt_ctrl_base = 0xFFFFFFFFFFFFFFFF;
+	gtdt->sec_el1_gsiv = 29;
+	gtdt->sec_el1_flags = GTDT_FLAG_INT_ACTIVE_LOW;
+	gtdt->el1_gsiv = 30;
+	gtdt->el1_flags = GTDT_FLAG_INT_ACTIVE_LOW;
+	gtdt->virt_el1_gsiv = 27;
+	gtdt->virt_el1_flags = GTDT_FLAG_INT_ACTIVE_LOW;
+	gtdt->el2_gsiv = 26;
+	gtdt->el2_flags = GTDT_FLAG_INT_ACTIVE_LOW;
+	gtdt->cnt_read_base = 0xffffffffffffffff;
+
+	// FIXME: VirtualPL2Timer
+	header->checksum = table_compute_checksum(header, header->length);
+
+	acpi_add_table(ctx, gtdt);
+
+	acpi_inc(ctx, sizeof(struct acpi_gtdt));
+
+	return 0;
+};
+
+ACPI_WRITER(5gtdt, "GTDT", sbsa_write_gtdt, 0);
+
+static int acpi_write_pptt(struct acpi_ctx *ctx, const struct acpi_writer *entry)
+{
+	struct acpi_table_header *header;
+	int cluster_offset, l2_offset;
+	u32 offsets[2];
+
+	header = ctx->current;
+	ctx->tab_start = ctx->current;
+
+	memset(header, '\0', sizeof(struct acpi_table_header));
+
+	acpi_fill_header(header, "PPTT");
+	header->revision = acpi_get_table_revision(ACPITAB_PPTT);
+	acpi_inc(ctx, sizeof(*header));
+
+	cluster_offset = acpi_pptt_add_proc(ctx, ACPI_PPTT_PHYSICAL_PACKAGE |
+					    ACPI_PPTT_CHILDREN_IDENTICAL,
+					    0, 0, 0, NULL);
+
+	l2_offset = acpi_pptt_add_cache(ctx, ACPI_PPTT_ALL_VALID, 0, L2_SIZE,
+					L2_SETS, L2_WAYS, L2_ATTRIBUTES, 64);
+
+	offsets[0] = acpi_pptt_add_cache(ctx, ACPI_PPTT_ALL_VALID, l2_offset,
+					 L1D_SIZE, L1D_SETS, L1D_WAYS,
+					 L1D_ATTRIBUTES, 64);
+
+	offsets[1] = acpi_pptt_add_cache(ctx, ACPI_PPTT_ALL_BUT_WRITE_POL,
+					 l2_offset, L1I_SIZE, L1I_SETS,
+					 L1I_WAYS, L1I_ATTRIBUTES, 64);
+
+	for (int i = 0; i < uclass_id_count(UCLASS_CPU); i++) {
+		acpi_pptt_add_proc(ctx, ACPI_PPTT_CHILDREN_IDENTICAL |
+				   ACPI_PPTT_NODE_IS_LEAF | ACPI_PPTT_PROC_ID_VALID,
+				   cluster_offset, i, 2, offsets);
+	}
+
+	header->length = ctx->current - ctx->tab_start;
+	header->checksum = table_compute_checksum(header, header->length);
+
+	acpi_inc(ctx, header->length);
+	acpi_add_table(ctx, header);
+
+	return 0;
+};
+
+ACPI_WRITER(5pptt, "PPTT", acpi_write_pptt, 0);
diff --git a/board/emulation/qemu-sbsa/dsdt.asl b/board/emulation/qemu-sbsa/dsdt.asl
new file mode 100644
index 0000000..f12cca0
--- /dev/null
+++ b/board/emulation/qemu-sbsa/dsdt.asl
@@ -0,0 +1,483 @@
+/** @file
+*  Differentiated System Description Table Fields (DSDT).
+*
+*  Copyright (c) 2020, Linaro Ltd. All rights reserved.
+*
+*  SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
+
+#include <configs/qemu-sbsa.h>
+
+#define LINK_DEVICE(Uid, LinkName, Irq)                                        \
+        Device (LinkName) {                                                    \
+            Name (_HID, EISAID("PNP0C0F"))                                     \
+            Name (_UID, Uid)                                                   \
+            Name (_PRS, ResourceTemplate() {                                   \
+                Interrupt (ResourceProducer, Level, ActiveHigh, Exclusive) { Irq } \
+            })                                                                 \
+            Method (_STA) {                                                    \
+              Return (0xF)                                                     \
+            }                                                                  \
+            Method (_CRS, 0) { Return (_PRS) }                                 \
+            Method (_SRS, 1) { }                                               \
+            Method (_DIS) { }                                                  \
+        }
+
+#define PRT_ENTRY(Address, Pin, Link)                                          \
+        Package (4) {                                                          \
+            Address, Pin, Link, Zero                                           \
+          }
+
+DefinitionBlock ("Dsdt.aml", "DSDT", 2, "U-Boot", "SBSAQEMU", 2) {
+  Scope (_SB) {
+    // UART PL011
+    Device (COM0) {
+      Name (_HID, "ARMH0011")
+      Name (_UID, Zero)
+      Name (_CRS, ResourceTemplate () {
+        Memory32Fixed (ReadWrite,
+                       SBSA_UART_BASE_ADDR,
+                       SBSA_UART_LENGTH)
+        Interrupt (ResourceConsumer, Level, ActiveHigh, Exclusive) { 33 }
+      })
+      Method (_STA) {
+        Return (0xF)
+      }
+    }
+
+    // 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_ADDR,
+                       SBSA_AHCI_LENGTH)
+        Interrupt (ResourceConsumer, Level, ActiveHigh, Exclusive) { 42 }
+      })
+      Method (_STA) {
+        Return (0xF)
+      }
+    }
+
+
+    // USB XHCI Host Controller
+    Device (USB0) {
+        Name (_HID, "PNP0D10")      // _HID: Hardware ID
+        Name (_UID, 0x00)            // _UID: Unique ID
+        Name (_CCA, 0x01)            // _CCA: Cache Coherency Attribute
+        Name (XHCI, 0xF)            // will be set using AcpiLib
+        Method (_STA) {
+          Return (XHCI)
+        }
+        Name (_CRS, ResourceTemplate() {
+            Memory32Fixed (ReadWrite,
+                           SBSA_XHCI_BASE_ADDR,
+                           SBSA_XHCI_LENGTH)
+            Interrupt (ResourceConsumer, Level, ActiveHigh, Exclusive) { 43 }
+        })
+
+        // Root Hub
+        Device (RHUB) {
+            Name (_ADR, 0x00000000)  // Address of Root Hub should be 0 as per ACPI 5.0 spec
+            Method (_STA) {
+              Return (0xF)
+            }
+
+            // 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
+                })
+                Method (_STA) {
+                  Return (0xF)
+                }
+
+                Device (PRT1) {
+                    Name (_ADR, 0x00000001)
+                    Name (_UPC, Package() {
+                        0xFF,        // Port is connectable
+                        0x00,        // Port connector is A
+                        0x00000000,
+                        0x00000000
+                    })
+                    Name (_PLD, Package() {
+                        Buffer(0x10) {
+                            0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                            0x31, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+                        }
+                    })
+                    Method (_STA) {
+                      Return (0xF)
+                    }
+                } // USB0_RHUB_HUB1_PRT1
+                Device (PRT2) {
+                    Name (_ADR, 0x00000002)
+                    Name (_UPC, Package() {
+                        0xFF,        // Port is connectable
+                        0x00,        // Port connector is A
+                        0x00000000,
+                        0x00000000
+                    })
+                    Name (_PLD, Package() {
+                        Buffer(0x10) {
+                            0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                            0x31, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+                        }
+                    })
+                    Method (_STA) {
+                      Return (0xF)
+                    }
+                } // USB0_RHUB_HUB1_PRT2
+
+                Device (PRT3) {
+                    Name (_ADR, 0x00000003)
+                    Name (_UPC, Package() {
+                        0xFF,        // Port is connectable
+                        0x09,        // Type C connector - USB2 and SS with Switch
+                        0x00000000,
+                        0x00000000
+                    })
+                    Name (_PLD, Package() {
+                        Buffer (0x10) {
+                            0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                            0x31, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+                        }
+                    })
+                    Method (_STA) {
+                      Return (0xF)
+                    }
+                } // USB0_RHUB_HUB1_PRT3
+
+                Device (PRT4) {
+                    Name (_ADR, 0x00000004)
+                    Name (_UPC, Package() {
+                        0xFF,        // Port is connectable
+                        0x09,        // Type C connector - USB2 and SS with Switch
+                        0x00000000,
+                        0x00000000
+                    })
+                    Name (_PLD, Package() {
+                        Buffer (0x10){
+                            0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                            0x31, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+                        }
+                    })
+                    Method (_STA) {
+                      Return (0xF)
+                    }
+                } // 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 (_STA) {
+        Return (0xF)
+      }
+
+      Method (_CBA, 0, NotSerialized) {
+          return (SBSA_PCIE_ECAM_BASE_ADDR)
+      }
+
+      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(0x0000FFFF, 0, GSI0),
+        PRT_ENTRY(0x0000FFFF, 0, GSI1),
+        PRT_ENTRY(0x0000FFFF, 0, GSI2),
+        PRT_ENTRY(0x0000FFFF, 0, GSI3),
+
+        PRT_ENTRY(0x0001FFFF, 0, GSI1),
+        PRT_ENTRY(0x0001FFFF, 1, GSI2),
+        PRT_ENTRY(0x0001FFFF, 2, GSI3),
+        PRT_ENTRY(0x0001FFFF, 3, GSI0),
+
+        PRT_ENTRY(0x0002FFFF, 0, GSI2),
+        PRT_ENTRY(0x0002FFFF, 1, GSI3),
+        PRT_ENTRY(0x0002FFFF, 2, GSI0),
+        PRT_ENTRY(0x0002FFFF, 3, GSI1),
+
+        PRT_ENTRY(0x0003FFFF, 0, GSI3),
+        PRT_ENTRY(0x0003FFFF, 1, GSI0),
+        PRT_ENTRY(0x0003FFFF, 2, GSI1),
+        PRT_ENTRY(0x0003FFFF, 3, GSI2),
+
+        PRT_ENTRY(0x0004FFFF, 0, GSI0),
+        PRT_ENTRY(0x0004FFFF, 1, GSI1),
+        PRT_ENTRY(0x0004FFFF, 2, GSI2),
+        PRT_ENTRY(0x0004FFFF, 3, GSI3),
+
+        PRT_ENTRY(0x0005FFFF, 0, GSI1),
+        PRT_ENTRY(0x0005FFFF, 1, GSI2),
+        PRT_ENTRY(0x0005FFFF, 2, GSI3),
+        PRT_ENTRY(0x0005FFFF, 3, GSI0),
+
+        PRT_ENTRY(0x0006FFFF, 0, GSI2),
+        PRT_ENTRY(0x0006FFFF, 1, GSI3),
+        PRT_ENTRY(0x0006FFFF, 2, GSI0),
+        PRT_ENTRY(0x0006FFFF, 3, GSI1),
+
+        PRT_ENTRY(0x0007FFFF, 0, GSI3),
+        PRT_ENTRY(0x0007FFFF, 1, GSI0),
+        PRT_ENTRY(0x0007FFFF, 2, GSI1),
+        PRT_ENTRY(0x0007FFFF, 3, GSI2),
+
+        PRT_ENTRY(0x0008FFFF, 0, GSI0),
+        PRT_ENTRY(0x0008FFFF, 1, GSI1),
+        PRT_ENTRY(0x0008FFFF, 2, GSI2),
+        PRT_ENTRY(0x0008FFFF, 3, GSI3),
+
+        PRT_ENTRY(0x0009FFFF, 0, GSI1),
+        PRT_ENTRY(0x0009FFFF, 1, GSI2),
+        PRT_ENTRY(0x0009FFFF, 2, GSI3),
+        PRT_ENTRY(0x0009FFFF, 3, GSI0),
+
+        PRT_ENTRY(0x000AFFFF, 0, GSI2),
+        PRT_ENTRY(0x000AFFFF, 1, GSI3),
+        PRT_ENTRY(0x000AFFFF, 2, GSI0),
+        PRT_ENTRY(0x000AFFFF, 3, GSI1),
+
+        PRT_ENTRY(0x000BFFFF, 0, GSI3),
+        PRT_ENTRY(0x000BFFFF, 1, GSI0),
+        PRT_ENTRY(0x000BFFFF, 2, GSI1),
+        PRT_ENTRY(0x000BFFFF, 3, GSI2),
+
+        PRT_ENTRY(0x000CFFFF, 0, GSI0),
+        PRT_ENTRY(0x000CFFFF, 1, GSI1),
+        PRT_ENTRY(0x000CFFFF, 2, GSI2),
+        PRT_ENTRY(0x000CFFFF, 3, GSI3),
+
+        PRT_ENTRY(0x000DFFFF, 0, GSI1),
+        PRT_ENTRY(0x000DFFFF, 1, GSI2),
+        PRT_ENTRY(0x000DFFFF, 2, GSI3),
+        PRT_ENTRY(0x000DFFFF, 3, GSI0),
+
+        PRT_ENTRY(0x000EFFFF, 0, GSI2),
+        PRT_ENTRY(0x000EFFFF, 1, GSI3),
+        PRT_ENTRY(0x000EFFFF, 2, GSI0),
+        PRT_ENTRY(0x000EFFFF, 3, GSI1),
+
+        PRT_ENTRY(0x000FFFFF, 0, GSI3),
+        PRT_ENTRY(0x000FFFFF, 1, GSI0),
+        PRT_ENTRY(0x000FFFFF, 2, GSI1),
+        PRT_ENTRY(0x000FFFFF, 3, GSI2),
+
+        PRT_ENTRY(0x0010FFFF, 0, GSI0),
+        PRT_ENTRY(0x0010FFFF, 1, GSI1),
+        PRT_ENTRY(0x0010FFFF, 2, GSI2),
+        PRT_ENTRY(0x0010FFFF, 3, GSI3),
+
+        PRT_ENTRY(0x0011FFFF, 0, GSI1),
+        PRT_ENTRY(0x0011FFFF, 1, GSI2),
+        PRT_ENTRY(0x0011FFFF, 2, GSI3),
+        PRT_ENTRY(0x0011FFFF, 3, GSI0),
+
+        PRT_ENTRY(0x0012FFFF, 0, GSI2),
+        PRT_ENTRY(0x0012FFFF, 1, GSI3),
+        PRT_ENTRY(0x0012FFFF, 2, GSI0),
+        PRT_ENTRY(0x0012FFFF, 3, GSI1),
+
+        PRT_ENTRY(0x0013FFFF, 0, GSI3),
+        PRT_ENTRY(0x0013FFFF, 1, GSI0),
+        PRT_ENTRY(0x0013FFFF, 2, GSI1),
+        PRT_ENTRY(0x0013FFFF, 3, GSI2),
+
+        PRT_ENTRY(0x0014FFFF, 0, GSI0),
+        PRT_ENTRY(0x0014FFFF, 1, GSI1),
+        PRT_ENTRY(0x0014FFFF, 2, GSI2),
+        PRT_ENTRY(0x0014FFFF, 3, GSI3),
+
+        PRT_ENTRY(0x0015FFFF, 0, GSI1),
+        PRT_ENTRY(0x0015FFFF, 1, GSI2),
+        PRT_ENTRY(0x0015FFFF, 2, GSI3),
+        PRT_ENTRY(0x0015FFFF, 3, GSI0),
+
+        PRT_ENTRY(0x0016FFFF, 0, GSI2),
+        PRT_ENTRY(0x0016FFFF, 1, GSI3),
+        PRT_ENTRY(0x0016FFFF, 2, GSI0),
+        PRT_ENTRY(0x0016FFFF, 3, GSI1),
+
+        PRT_ENTRY(0x0017FFFF, 0, GSI3),
+        PRT_ENTRY(0x0017FFFF, 1, GSI0),
+        PRT_ENTRY(0x0017FFFF, 2, GSI1),
+        PRT_ENTRY(0x0017FFFF, 3, GSI2),
+
+        PRT_ENTRY(0x0018FFFF, 0, GSI0),
+        PRT_ENTRY(0x0018FFFF, 1, GSI1),
+        PRT_ENTRY(0x0018FFFF, 2, GSI2),
+        PRT_ENTRY(0x0018FFFF, 3, GSI3),
+
+        PRT_ENTRY(0x0019FFFF, 0, GSI1),
+        PRT_ENTRY(0x0019FFFF, 1, GSI2),
+        PRT_ENTRY(0x0019FFFF, 2, GSI3),
+        PRT_ENTRY(0x0019FFFF, 3, GSI0),
+
+        PRT_ENTRY(0x001AFFFF, 0, GSI2),
+        PRT_ENTRY(0x001AFFFF, 1, GSI3),
+        PRT_ENTRY(0x001AFFFF, 2, GSI0),
+        PRT_ENTRY(0x001AFFFF, 3, GSI1),
+
+        PRT_ENTRY(0x001BFFFF, 0, GSI3),
+        PRT_ENTRY(0x001BFFFF, 1, GSI0),
+        PRT_ENTRY(0x001BFFFF, 2, GSI1),
+        PRT_ENTRY(0x001BFFFF, 3, GSI2),
+
+        PRT_ENTRY(0x001CFFFF, 0, GSI0),
+        PRT_ENTRY(0x001CFFFF, 1, GSI1),
+        PRT_ENTRY(0x001CFFFF, 2, GSI2),
+        PRT_ENTRY(0x001CFFFF, 3, GSI3),
+
+        PRT_ENTRY(0x001DFFFF, 0, GSI1),
+        PRT_ENTRY(0x001DFFFF, 1, GSI2),
+        PRT_ENTRY(0x001DFFFF, 2, GSI3),
+        PRT_ENTRY(0x001DFFFF, 3, GSI0),
+
+        PRT_ENTRY(0x001EFFFF, 0, GSI2),
+        PRT_ENTRY(0x001EFFFF, 1, GSI3),
+        PRT_ENTRY(0x001EFFFF, 2, GSI0),
+        PRT_ENTRY(0x001EFFFF, 3, GSI1),
+
+        PRT_ENTRY(0x001FFFFF, 0, GSI3),
+        PRT_ENTRY(0x001FFFFF, 1, GSI0),
+        PRT_ENTRY(0x001FFFFF, 2, GSI1),
+        PRT_ENTRY(0x001FFFFF, 3, GSI2),
+      })
+
+      // Root complex resources
+      Name (_CRS, ResourceTemplate () {
+        WordBusNumber ( // Bus numbers assigned to this root
+        ResourceProducer,
+        MinFixed, MaxFixed, PosDecode,
+        0,   // AddressGranularity
+        0,   // AddressMinimum - Minimum Bus Number
+        0xff,// AddressMaximum - Maximum Bus Number
+        0,   // AddressTranslation - Set to 0
+        256  // RangeLength - Number of Busses
+        )
+
+        // IO to mmio window
+        QWordIO (
+          ResourceProducer, MinFixed,
+          MaxFixed, PosDecode,
+          EntireRange,
+          0x00000000,                              // Granularity
+          0x0000,                                  // Min Base Address
+          0xffff,                                  // Max Base Address
+          SBSA_PIO_BASE_ADDR,                      // Translate
+          SBSA_PIO_LENGTH                          // Length
+        )
+
+        DWordMemory ( // 32-bit BAR Windows
+          ResourceProducer, PosDecode,
+          MinFixed, MaxFixed,
+          Cacheable, ReadWrite,
+          0x00000000,                              // Granularity
+          SBSA_PCIE_MMIO_BASE_ADDR,                // Min Base Address
+          SBSA_PCIE_MMIO_END,                      // Max Base Address
+          0,                                       // Translate
+          SBSA_PCIE_MMIO_LENGTH                    // Length
+          )
+
+        QWordMemory ( // 64-bit BAR Windows
+          ResourceProducer, PosDecode,
+          MinFixed, MaxFixed,
+          Cacheable, ReadWrite,
+          0x00000000,                              // Granularity
+          SBSA_PCIE_MMIO_HIGH_BASE_ADDR,           // Min Base Address
+          SBSA_PCIE_MMIO_HIGH_END,                 // Max Base Address
+          0,                                       // Translate
+          SBSA_PCIE_MMIO_HIGH_LENGTH               // Length
+          )
+      }) // Name(_CRS)
+
+      Device (RES0)
+      {
+        Name (_HID, "PNP0C02" /* PNP Motherboard Resources */)  // _HID: Hardware ID
+        Name (_CRS, ResourceTemplate ()  // _CRS: Current Resource Settings
+        {
+           QWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, NonCacheable, ReadWrite,
+           0x0000000000000000,                       // Granularity
+           SBSA_PCIE_ECAM_BASE_ADDR,                 // Range Minimum
+           SBSA_PCIE_ECAM_END,                       // Range Maximum
+           0x0000000000000000,                       // Translation Offset
+           SBSA_PCIE_ECAM_LENGTH,                    // Length
+           ,, , AddressRangeMemory, TypeStatic)
+        })
+        Method (_STA) {
+          Return (0xF)
+        }
+      }
+
+      // 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
+          Store (CDW2,SUPP)
+          Store (CDW3,CTRL)
+
+          // 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
+          Store (CTRL,CDW3)
+          Return (Arg3)
+        } Else {
+          CDW1 |= 4 // Unrecognized UUID
+          Return (Arg3)
+        }
+      } // End _OSC
+    }
+  } // Scope (_SB)
+}
diff --git a/board/emulation/qemu-sbsa/lowlevel_init.S b/board/emulation/qemu-sbsa/lowlevel_init.S
new file mode 100644
index 0000000..c997721
--- /dev/null
+++ b/board/emulation/qemu-sbsa/lowlevel_init.S
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * (C) Copyright 2016
+ * Cédric Schieli <cschieli@gmail.com>
+ */
+
+#include <config.h>
+
+/*
+ * Routine: save_boot_params (called after reset from start.S)
+ * Description: save ATAG/FDT address provided by the firmware at boot time
+ */
+
+.global save_boot_params
+save_boot_params:
+	/* The firmware provided ATAG/FDT address can be found in r2/x0 */
+	adr	x8, fw_dtb_pointer
+	str	x0, [x8]
+
+
+	/* Returns */
+	b	save_boot_params_ret
diff --git a/board/emulation/qemu-sbsa/qemu-sbsa.c b/board/emulation/qemu-sbsa/qemu-sbsa.c
new file mode 100644
index 0000000..3943c92
--- /dev/null
+++ b/board/emulation/qemu-sbsa/qemu-sbsa.c
@@ -0,0 +1,273 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (c) 2017 Tuomas Tynkkynen
+ */
+
+#include <cpu_func.h>
+#include <dm.h>
+#include <env.h>
+#include <fdtdec.h>
+#include <fdt_support.h>
+#include <init.h>
+#include <log.h>
+#include <usb.h>
+#include <asm/armv8/mmu.h>
+
+#include "qemu-sbsa.h"
+
+/* Assigned in lowlevel_init.S
+ * Push the variable into the .data section so that it
+ * does not get cleared later.
+ */
+unsigned long __section(".data") fw_dtb_pointer;
+
+static struct mm_region qemu_sbsa_mem_map[] = {
+	{
+		/* Secure flash */
+		.virt = SBSA_SECURE_FLASH_BASE_ADDR,
+		.phys = SBSA_SECURE_FLASH_BASE_ADDR,
+		.size = SBSA_SECURE_FLASH_LENGTH,
+		.attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
+			 PTE_BLOCK_INNER_SHARE |
+			 PTE_BLOCK_PXN | PTE_BLOCK_UXN
+	}, {
+		/* Flash */
+		.virt = SBSA_FLASH_BASE_ADDR,
+		.phys = SBSA_FLASH_BASE_ADDR,
+		.size = SBSA_FLASH_LENGTH,
+		.attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
+			 PTE_BLOCK_INNER_SHARE
+	}, {
+		/* Lowmem peripherals */
+		.virt = SBSA_PERIPH_BASE_ADDR,
+		.phys = SBSA_PERIPH_BASE_ADDR,
+		.size = SBSA_PCIE_MMIO_BASE_ADDR - SBSA_PERIPH_BASE_ADDR,
+		.attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
+			 PTE_BLOCK_NON_SHARE |
+			 PTE_BLOCK_PXN | PTE_BLOCK_UXN
+	}, {
+		/* 32-bit address PCIE MMIO space */
+		.virt = SBSA_PCIE_MMIO_BASE_ADDR,
+		.phys = SBSA_PCIE_MMIO_BASE_ADDR,
+		.size = SBSA_PCIE_MMIO_LENGTH,
+		.attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
+			 PTE_BLOCK_NON_SHARE |
+			 PTE_BLOCK_PXN | PTE_BLOCK_UXN
+	}, {
+		/* PCI-E ECAM memory area */
+		.virt = SBSA_PCIE_ECAM_BASE_ADDR,
+		.phys = SBSA_PCIE_ECAM_BASE_ADDR,
+		.size = SBSA_PCIE_ECAM_LENGTH,
+		.attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
+			 PTE_BLOCK_NON_SHARE |
+			 PTE_BLOCK_PXN | PTE_BLOCK_UXN
+	}, {
+		/* Highmem PCI-E MMIO memory area */
+		.virt = SBSA_PCIE_MMIO_HIGH_BASE_ADDR,
+		.phys = SBSA_PCIE_MMIO_HIGH_BASE_ADDR,
+		.size = SBSA_PCIE_MMIO_HIGH_LENGTH,
+		.attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
+			 PTE_BLOCK_NON_SHARE |
+			 PTE_BLOCK_PXN | PTE_BLOCK_UXN
+	}, {
+		/* DRAM */
+		.virt = SBSA_MEM_BASE_ADDR,
+		.phys = SBSA_MEM_BASE_ADDR,
+		.size = 0x800000000000ULL,
+		.attrs = PTE_BLOCK_MEMTYPE(MT_NORMAL) |
+			 PTE_BLOCK_INNER_SHARE
+	}, {
+		/* List terminator */
+		0,
+	}
+};
+
+struct mm_region *mem_map = qemu_sbsa_mem_map;
+
+int board_late_init(void)
+{
+	/* start usb so that usb keyboard can be used as input device */
+	if (CONFIG_IS_ENABLED(USB_KEYBOARD))
+		usb_init();
+
+	return 0;
+}
+
+int board_init(void)
+{
+	return 0;
+}
+
+/**
+ * dtb_dt_qemu - Return the address of the QEMU provided FDT.
+ *
+ * @return: Pointer to FDT or NULL on failure
+ */
+static void *dtb_dt_qemu(void)
+{
+	/* FDT might be at start of DRAM */
+	if (fdt_magic(SBSA_MEM_BASE_ADDR) == FDT_MAGIC)
+		return (void *)(u64)SBSA_MEM_BASE_ADDR;
+
+	/* When ARM_LINUX_KERNEL_AS_BL33 is enabled in ATF, it's passed in x0 */
+	if (fw_dtb_pointer >= SBSA_MEM_BASE_ADDR &&
+	    fdt_magic(fw_dtb_pointer) == FDT_MAGIC) {
+		return (void *)fw_dtb_pointer;
+	}
+
+	return NULL;
+}
+
+/*
+ * QEMU doesn't set compatible on cpus.
+ * Add them to make sure the U-Boot driver properly bind.
+ */
+static int fdtdec_fix_cpus(void *fdt_blob)
+{
+	int cpus_offset, off, ret;
+	u64 mpidr, i = 0;
+
+	cpus_offset = fdt_path_offset(fdt_blob, "/cpus");
+	if (cpus_offset < 0) {
+		puts("couldn't find /cpus node\n");
+		return cpus_offset;
+	}
+
+	fdt_for_each_subnode(off, fdt_blob, cpus_offset) {
+		if (strncmp(fdt_get_name(fdt_blob, off, NULL), "cpu@", 4))
+			continue;
+
+		mpidr = 0;
+		ret = smc_get_mpidr(i, &mpidr);
+		if (ret) {
+			log_warning("Failed to get MPIDR for processor %lld from SMC: %d\n",
+				    i, ret);
+			mpidr = i;
+		}
+
+		ret = fdt_setprop_string(fdt_blob, off, "compatible", "arm,armv8");
+		if (ret < 0)
+			return ret;
+
+		ret = fdt_setprop_string(fdt_blob, off, "device_type", "cpu");
+		if (ret < 0)
+			return ret;
+
+		ret = fdt_setprop_u64(fdt_blob, off, "reg", mpidr);
+		if (ret < 0)
+			return ret;
+		i++;
+	}
+	return 0;
+}
+
+/*
+ * Update the GIC node when necessary and add optional ITS when it has a
+ * non zero base-address.
+ */
+static int fdtdec_fix_gic(void *fdt)
+{
+	u64 gic_dist_base = SBSA_GIC_DIST_BASE_ADDR;
+	u64 gic_redist_base = SBSA_GIC_REDIST_BASE_ADDR;
+	u64 gic_its_base = 0;
+	int offs, ret;
+	u64 reg[10];
+
+	/* Invoke SMC to get real base-address */
+	smc_get_gic_dist_base(&gic_dist_base);
+	smc_get_gic_redist_base(&gic_redist_base);
+
+	if ((gic_dist_base != SBSA_GIC_DIST_BASE_ADDR) ||
+	    (gic_redist_base != SBSA_GIC_REDIST_BASE_ADDR)) {
+		offs = fdt_path_offset(fdt, "/interrupt-controller");
+		if (offs < 0) {
+			puts("couldn't find /interrupt-controller node\n");
+			return offs;
+		}
+
+		reg[0] = cpu_to_fdt64(gic_dist_base);
+		reg[1] = cpu_to_fdt64((u64)SBSA_GIC_DIST_LENGTH);
+		reg[2] = cpu_to_fdt64(gic_redist_base);
+		reg[3] = cpu_to_fdt64((u64)SBSA_GIC_REDIST_LENGTH);
+		reg[4] = cpu_to_fdt64(0);
+		reg[5] = cpu_to_fdt64(0);
+		reg[6] = cpu_to_fdt64(SBSA_GIC_HBASE_ADDR);
+		reg[7] = cpu_to_fdt64((u64)SBSA_GIC_HBASE_LENGTH);
+		reg[8] = cpu_to_fdt64(SBSA_GIC_VBASE_ADDR);
+		reg[9] = cpu_to_fdt64((u64)SBSA_GIC_VBASE_LENGTH);
+
+		ret = fdt_setprop_inplace(fdt, offs, "reg", reg, sizeof(reg));
+	}
+
+	smc_get_gic_its_base(&gic_its_base);
+
+	if (gic_its_base != 0) {
+		offs = fdt_path_offset(fdt, "/its");
+		if (offs < 0)
+			return offs;
+
+		ret = fdt_setprop_string(fdt, offs, "status", "okay");
+		if (ret < 0)
+			return ret;
+
+		reg[0] = cpu_to_fdt64(gic_its_base);
+		reg[1] = 0;
+
+		ret = fdt_setprop(fdt, offs, "reg", reg, sizeof(u64) * 2);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+int fdtdec_board_setup(const void *fdt_blob)
+{
+	void *qemu_fdt;
+	int ret;
+
+	/*
+	 * Locate the QEMU provided DTB that contains the CPUs and amount of DRAM.
+	 */
+	qemu_fdt = dtb_dt_qemu();
+	if (!qemu_fdt) {
+		log_err("QEMU FDT not found\n");
+		return -ENODEV;
+	}
+
+	ret = fdt_increase_size((void *)fdt_blob, 1024 + fdt_totalsize(qemu_fdt));
+	if (ret)
+		return -ENOMEM;
+
+	/*
+	 * Merge the QEMU DTB as overlay into the U-Boot provided DTB.
+	 */
+	ret = fdt_overlay_apply_node((void *)fdt_blob, 0, qemu_fdt, 0);
+	if (ret < 0)
+		log_err("Failed to apply overlay: %d\n", ret);
+
+	/* Fix QEMU nodes to make sure U-Boot drivers are properly working */
+	ret = fdtdec_fix_cpus((void *)fdt_blob);
+	if (ret < 0)
+		log_err("Failed to fix CPUs in FDT: %d\n", ret);
+
+	ret = fdtdec_fix_gic((void *)fdt_blob);
+	if (ret < 0)
+		log_err("Failed to fix INTC in FDT: %d\n", ret);
+
+	return 0;
+}
+
+int misc_init_r(void)
+{
+	return env_set_hex("fdt_addr", (uintptr_t)gd->fdt_blob);
+}
+
+void reset_cpu(void)
+{
+}
+
+int dram_init(void)
+{
+	return fdtdec_setup_mem_size_base();
+}
\ No newline at end of file
diff --git a/board/emulation/qemu-sbsa/qemu-sbsa.env b/board/emulation/qemu-sbsa/qemu-sbsa.env
new file mode 100644
index 0000000..88fdb0e
--- /dev/null
+++ b/board/emulation/qemu-sbsa/qemu-sbsa.env
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+
+/* environment for qemu-arm and qemu-arm64 */
+
+stdin=serial,usbkbd
+stdout=serial,vidconsole
+stderr=serial,vidconsole
+fdt_high=0xffffffffffffffff
+initrd_high=0xffffffffffffffff
+scriptaddr=0x100000300000
+pxefile_addr_r=0x10000400000
+kernel_addr_r=0x10000200000
+ramdisk_addr_r=0x10001000000
+boot_targets=qfw usb scsi virtio nvme dhcp
diff --git a/board/emulation/qemu-sbsa/qemu-sbsa.h b/board/emulation/qemu-sbsa/qemu-sbsa.h
new file mode 100644
index 0000000..391a70b
--- /dev/null
+++ b/board/emulation/qemu-sbsa/qemu-sbsa.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (c) 2024 9elements GmbH
+ */
+
+/**
+ * smc_get_mpidr() - Call into SMC and get the MPIDR for given CPU
+ *
+ * @id:		CPU index
+ * @mpidr:	Pointer where to place the MPIDR
+ * @return 0 if OK, other -ve on error
+ */
+int smc_get_mpidr(unsigned long id, u64 *mpidr);
+
+/**
+ * smc_get_gic_dist_base() - Call into SMC and get GIC dist base address
+ *
+ * @mpidr:	Pointer where to place the base address
+ * @return 0 if OK, other -ve on error
+ */
+int smc_get_gic_dist_base(u64 *base);
+
+/**
+ * smc_get_gic_redist_base() - Call into SMC and get the GIC redistributor
+ *                             base address
+ *
+ * @mpidr:	Pointer where to place the base address
+ * @return 0 if OK, other -ve on error
+ */
+int smc_get_gic_redist_base(u64 *base);
+
+/**
+ * smc_get_gic_its_base() - Call into SMC and get the ITS base address
+ *
+ * @mpidr:	Pointer where to place the base address
+ * @return 0 if OK, other -ve on error
+ */
+int smc_get_gic_its_base(u64 *base);
diff --git a/board/emulation/qemu-sbsa/smc.c b/board/emulation/qemu-sbsa/smc.c
new file mode 100644
index 0000000..9a2d091
--- /dev/null
+++ b/board/emulation/qemu-sbsa/smc.c
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2024 9elements GmbH
+ */
+
+#include <cpu.h>
+#include <init.h>
+#include <log.h>
+#include <linux/arm-smccc.h>
+
+#define SMC_SIP_FUNCTION_ID(n)  (0xC2000000 | (n))
+
+#define SIP_SVC_VERSION        SMC_SIP_FUNCTION_ID(1)
+#define SIP_SVC_GET_GIC        SMC_SIP_FUNCTION_ID(100)
+#define SIP_SVC_GET_GIC_ITS    SMC_SIP_FUNCTION_ID(101)
+#define SIP_SVC_GET_CPU_COUNT  SMC_SIP_FUNCTION_ID(200)
+#define SIP_SVC_GET_CPU_NODE   SMC_SIP_FUNCTION_ID(201)
+#define SIP_SVC_GET_MEMORY_NODE_COUNT SMC_SIP_FUNCTION_ID(300)
+#define SIP_SVC_GET_MEMORY_NODE SMC_SIP_FUNCTION_ID(301)
+
+int smc_get_mpidr(unsigned long id, u64 *mpidr)
+{
+	struct arm_smccc_res res;
+
+	res.a0 = ~0;
+	arm_smccc_smc(SIP_SVC_GET_CPU_NODE, id, 0, 0, 0, 0, 0, 0, &res);
+
+	if (!res.a0)
+		*mpidr = res.a2;
+
+	return res.a0;
+}
+
+int smc_get_gic_dist_base(u64 *base)
+{
+	struct arm_smccc_res res;
+
+	res.a0 = ~0;
+	arm_smccc_smc(SIP_SVC_GET_GIC, 0, 0, 0, 0, 0, 0, 0, &res);
+
+	if (!res.a0)
+		*base = res.a1;
+
+	return res.a0;
+}
+
+int smc_get_gic_redist_base(u64 *base)
+{
+	struct arm_smccc_res res;
+
+	res.a0 = ~0;
+	arm_smccc_smc(SIP_SVC_GET_GIC, 0, 0, 0, 0, 0, 0, 0, &res);
+
+	if (!res.a0)
+		*base = res.a2;
+
+	return res.a0;
+}
+
+int smc_get_gic_its_base(u64 *base)
+{
+	struct arm_smccc_res res;
+
+	res.a0 = ~0;
+	arm_smccc_smc(SIP_SVC_GET_GIC_ITS, 0, 0, 0, 0, 0, 0, 0, &res);
+
+	if (!res.a0)
+		*base = res.a1;
+
+	return res.a0;
+}