diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile
index a2d0009560d0f..41471416945eb 100644
--- a/drivers/firmware/efi/Makefile
+++ b/drivers/firmware/efi/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_EFI_VARS_PSTORE)		+= efi-pstore.o
 obj-$(CONFIG_UEFI_CPER)			+= cper.o cper_cxl.o
 obj-$(CONFIG_EFI_RUNTIME_WRAPPERS)	+= runtime-wrappers.o
 subdir-$(CONFIG_EFI_STUB)		+= libstub
+obj-$(CONFIG_ARCH_SELECTS_KEXEC_PEIMAGE)	+= efi_emulator/
 obj-$(CONFIG_EFI_BOOTLOADER_CONTROL)	+= efibc.o
 obj-$(CONFIG_EFI_TEST)			+= test/
 obj-$(CONFIG_EFI_DEV_PATH_PARSER)	+= dev-path-parser.o
diff --git a/drivers/firmware/efi/efi_emulator/Makefile b/drivers/firmware/efi/efi_emulator/Makefile
new file mode 100644
index 0000000000000..d696381d168ba
--- /dev/null
+++ b/drivers/firmware/efi/efi_emulator/Makefile
@@ -0,0 +1,99 @@
+# non-x86 reuses KBUILD_CFLAGS, x86 does not
+cflags-y			:= $(KBUILD_CFLAGS)
+
+cflags-$(CONFIG_X86_32)		:= -march=i386
+cflags-$(CONFIG_X86_64)		:= -mcmodel=small
+cflags-$(CONFIG_X86)		+= -m$(BITS) -D__KERNEL__ \
+				   -fno-strict-aliasing -mno-red-zone \
+				   -mno-mmx -mno-sse -fshort-wchar \
+				   -Wno-pointer-sign \
+				   $(call cc-disable-warning, address-of-packed-member) \
+				   $(call cc-disable-warning, gnu) \
+				   -fno-asynchronous-unwind-tables \
+				   $(CLANG_FLAGS)
+
+# arm64 uses the full KBUILD_CFLAGS so it's necessary to explicitly
+# disable the stackleak plugin
+cflags-$(CONFIG_ARM64)		+= $(DISABLE_STACKLEAK_PLUGIN) \
+				   -fno-unwind-tables -fno-asynchronous-unwind-tables
+cflags-$(CONFIG_ARM)		+= -DEFI_HAVE_STRLEN -DEFI_HAVE_STRNLEN \
+				   -DEFI_HAVE_MEMCHR -DEFI_HAVE_STRRCHR \
+				   -DEFI_HAVE_STRCMP -fno-builtin  \
+				   $(call cc-option,-mno-single-pic-base)
+cflags-$(CONFIG_RISCV)		+= -DNO_ALTERNATIVE -mno-relax
+cflags-$(CONFIG_LOONGARCH)	+=
+
+cflags-$(CONFIG_EFI_PARAMS_FROM_FDT)	+= -I$(srctree)/scripts/dtc/libfdt
+
+cflags-y	+= -I drivers/firmware/efi/libstub
+
+KBUILD_CFLAGS			:= $(subst $(CC_FLAGS_FTRACE),,$(cflags-y)) \
+				   -Os -DDISABLE_BRANCH_PROFILING \
+				   -D__NO_FORTIFY \
+				   -ffreestanding \
+				   -fno-stack-protector \
+				   $(call cc-option,-fno-addrsig) \
+				   -D__DISABLE_EXPORTS
+
+#
+# struct randomization only makes sense for Linux internal types, which the EFI
+# stub code never touches, so let's turn off struct randomization for the stub
+# altogether
+#
+KBUILD_CFLAGS := $(filter-out $(RANDSTRUCT_CFLAGS), $(KBUILD_CFLAGS))
+
+# remove SCS flags from all objects in this directory
+KBUILD_CFLAGS := $(filter-out $(CC_FLAGS_SCS), $(KBUILD_CFLAGS))
+# disable CFI
+KBUILD_CFLAGS := $(filter-out $(CC_FLAGS_CFI), $(KBUILD_CFLAGS))
+# disable LTO
+KBUILD_CFLAGS := $(filter-out $(CC_FLAGS_LTO), $(KBUILD_CFLAGS))
+
+GCOV_PROFILE			:= n
+# Sanitizer runtimes are unavailable and cannot be linked here.
+KASAN_SANITIZE			:= n
+KCSAN_SANITIZE			:= n
+KMSAN_SANITIZE			:= n
+UBSAN_SANITIZE			:= n
+OBJECT_FILES_NON_STANDARD	:= y
+
+# Prevents link failures: __sanitizer_cov_trace_pc() is not linked in.
+KCOV_INSTRUMENT			:= n
+
+OBJECT_FILES_NON_STANDARD	:= y
+emulator-y			:= head.o entry.o \
+				   core.o pe_loader.o memory.o memory_api.o config_table.o misc.o \
+				   device_handle.o protocol_simple_text_output.o protocol_device_path.o \
+				   lib.o printf.o \
+				   amba-pl011.o
+obj-y				:= efi_emulator.o
+
+
+EMULATOR_OBJS = $(addprefix $(obj)/,$(emulator-y))
+
+quiet_cmd_ar_emulator = PAD     $@
+      cmd_ar_emulator = $(AR) rcSTP $@ $^
+
+$(obj)/emulator.a: $(EMULATOR_OBJS)
+	$(call if_changed,ar_emulator)
+
+
+quiet_cmd_link_emulator = PAD     $@
+      cmd_link_emulator = ld -z norelro -z noexecstack -shared --no-undefined -X -Bsymbolic -z notext --emit-relocs --no-apply-dynamic-relocs  \
+				-T $(srctree)/drivers/firmware/efi/efi_emulator/arm64_emulator_service.lds \
+                                --whole-archive $< --no-whole-archive -o $@
+
+
+$(obj)/emulator.ro: $(obj)/emulator.a FORCE
+	$(call if_changed,link_emulator)
+
+
+$(obj)/emulator.raw: $(obj)/emulator.ro FORCE
+	@$(OBJCOPY)  -O binary -R .note -R .note.gnu.build-id -R .comment -g $< $@
+
+$(obj)/efi_emulator.o: $(obj)/emulator.raw
+
+
+targets				+= $(emulator-y)
+
+
diff --git a/drivers/firmware/efi/efi_emulator/amba-pl011.c b/drivers/firmware/efi/efi_emulator/amba-pl011.c
new file mode 100644
index 0000000000000..e81b12b2a7d21
--- /dev/null
+++ b/drivers/firmware/efi/efi_emulator/amba-pl011.c
@@ -0,0 +1,81 @@
+//SPDX-License-Identifier: GPL-2.0
+
+#include <linux/stdarg.h>
+#include <linux/amba/serial.h>
+#include "earlycon.h"
+
+#define SERIAL_IO_MEM32 3
+#define UPIO_MEM32 SERIAL_IO_MEM32
+
+struct uart_port {
+	unsigned long		iobase;			/* in/out[bwl] */
+	unsigned char __iomem	*membase;		/* read/write[bwl] */
+	unsigned char		iotype;			/* io access style */
+};
+
+static struct uart_port pl011_port;
+
+static void pl011_putc(struct uart_port *port, unsigned char c)
+{
+	while (readl(port->membase + UART01x_FR) & UART01x_FR_TXFF)
+		cpu_relax();
+	if (port->iotype == UPIO_MEM32)
+		writel(c, port->membase + UART01x_DR);
+	else
+		writeb(c, port->membase + UART01x_DR);
+	while (readl(port->membase + UART01x_FR) & UART01x_FR_BUSY)
+		cpu_relax();
+}
+
+static int pl011_put_str(const char *str, void *data)
+{
+	char *p = (char *)str;
+	struct uart_port *port = (struct uart_port *)data;
+
+	for (; *p != '\0'; p++)
+		pl011_putc(port, *p);
+
+	return (p - str);
+}
+
+static void pl011_write(struct uart_port *port, unsigned int reg, unsigned int val)
+{
+	void __iomem *addr = port->membase + reg;
+
+	if (port->iotype == UPIO_MEM32)
+		writel_relaxed(val, addr);
+	else
+		writew_relaxed(val, addr);
+}
+
+static bool pl011_match(struct efi_emulator_param *param, const char *name)
+{
+	struct uart_port *port = &pl011_port;
+
+	if (strcmp(param->earlycon_name, name))
+		return false;
+
+	port->iotype = UPIO_MEM32;
+	port->membase = (unsigned char *)param->earlycon_reg_base;
+	return true;
+}
+
+static void pl011_reset(void *data)
+{
+	struct uart_port *port = data;
+
+	/* disable DMA */
+	pl011_write(port, UART011_DMACR, 0);
+	/* disable interrupt */
+	pl011_write(port, UART011_IMSC, 0);
+	/* Skip: set clk rate */
+	/* Now, pl011 can be used in poll mode */
+}
+
+struct earlycon pl011 = {
+	.match = pl011_match,
+	.reset = pl011_reset,
+	.put_str = pl011_put_str,
+	.data = &pl011_port,
+	.name = "amba-pl011",
+};
diff --git a/drivers/firmware/efi/efi_emulator/arm64_emulator_service.lds b/drivers/firmware/efi/efi_emulator/arm64_emulator_service.lds
new file mode 100644
index 0000000000000..82d7659346af5
--- /dev/null
+++ b/drivers/firmware/efi/efi_emulator/arm64_emulator_service.lds
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+ENTRY(emulator_entry)
+EMULATOR_BASE_ADDR = 0x0;
+
+SECTIONS
+{
+	. = EMULATOR_BASE_ADDR;
+	.head : ALIGN(4096) {
+		*(.head.text)
+	}
+
+	.text : {
+		*(.text* .init.text*)
+	}
+
+	.rodata : ALIGN(8) {
+		*(.rodata* .init.rodata* .srodata*)
+
+		_etext = ALIGN(4096);
+		. = _etext;
+	}
+
+	.rela.dyn : ALIGN(4096) {
+		_rela_start = .;
+		*(.rela .rela*)
+		_rela_end = .;
+	}
+
+	.data : ALIGN(4096) {
+		*(.data* .init.data*)
+		_edata = ALIGN(512);
+		. = _edata;
+	}
+
+	.bss : {
+		*(.bss* .init.bss*)
+		_end = ALIGN(512);
+		. = _end;
+	}
+
+	/DISCARD/ : {
+		*(.modinfo .init.modinfo)
+	}
+}
diff --git a/drivers/firmware/efi/efi_emulator/config_table.c b/drivers/firmware/efi/efi_emulator/config_table.c
new file mode 100644
index 0000000000000..32b702a156ba4
--- /dev/null
+++ b/drivers/firmware/efi/efi_emulator/config_table.c
@@ -0,0 +1,25 @@
+//SPDX-License-Identifier: GPL-2.0
+
+#include "emulator.h"
+
+static efi_status_t conjoin_memreserve_table(void *table, efi_config_table_t *head)
+{
+	struct linux_efi_memreserve *new, *next;
+
+	new = (struct linux_efi_memreserve *)table;
+	new->next = 0;
+	next = (struct linux_efi_memreserve *)head->table;
+	while (next->next != 0)
+		next = (struct linux_efi_memreserve *)next->next;
+	next->next = (phys_addr_t)new;
+
+	return EFI_SUCCESS;
+}
+
+efi_status_t conjoin_table(efi_guid_t *uuid, void *table, efi_config_table_t *t)
+{
+	if (!efi_guidcmp(t->guid, LINUX_EFI_MEMRESERVE_TABLE_GUID))
+		return conjoin_memreserve_table(table, t);
+
+	return EFI_OUT_OF_RESOURCES;
+}
diff --git a/drivers/firmware/efi/efi_emulator/core.c b/drivers/firmware/efi/efi_emulator/core.c
new file mode 100644
index 0000000000000..9045f6953250c
--- /dev/null
+++ b/drivers/firmware/efi/efi_emulator/core.c
@@ -0,0 +1,376 @@
+//SPDX-License-Identifier: GPL-2.0
+
+#include <linux/efi.h>
+#include <asm/efi.h>
+
+#include "emulator.h"
+
+int emulator_initialize(void);
+
+struct efi_emulator_param *emulator_param;
+bool print_enabled;
+
+static efi_loaded_image_t loaded_image;
+
+static LIST_HEAD(image_instance_list);
+
+struct efi_pe_instance *get_instance_by_handle(efi_handle_t h)
+{
+	struct efi_pe_instance *pos;
+
+	list_for_each_entry(pos, &image_instance_list, node)
+		if (pos->handle == h)
+			return pos;
+
+	return NULL;
+}
+
+/* no free path */
+struct efi_pe_instance *allocate_pe_instance(char *file_base,
+		unsigned long file_size)
+{
+	struct efi_pe_instance *inst;
+
+	inst = aligned_alloc(8, sizeof(struct efi_pe_instance));
+	/* identity */
+	inst->handle = (efi_handle_t)inst;
+	inst->image_file_buf = file_base;
+	inst->image_file_size = file_size;
+	emulator_list_add(&inst->node, &image_instance_list);
+
+	return inst;
+}
+
+/* The 1st kernel convert cmdline to utf16 and pass to emulator */
+static efi_status_t handle_protocol_loaded_image(efi_handle_t h, void **data)
+{
+	void *base;
+
+	loaded_image.load_options = emulator_param->cmdline;
+	loaded_image.load_options_size = emulator_param->sz_in_byte;
+
+	/* loaded address */
+	base = (void *)find_image_base_for_handle(h);
+	loaded_image.image_base = base;
+
+	*data = &loaded_image;
+	return EFI_SUCCESS;
+
+}
+
+
+static efi_status_t __efiapi emulator_handle_protocol(efi_handle_t h,
+				efi_guid_t *uuid, void **data)
+{
+	if (!efi_guidcmp(*uuid, LOADED_IMAGE_PROTOCOL_GUID))
+		return handle_protocol_loaded_image(h, data);
+	
+	if (!efi_guidcmp(*uuid, EFI_LOAD_FILE2_PROTOCOL_GUID))
+		return device_handle_protocol(h, uuid, data);
+
+	return EFI_UNSUPPORTED;
+}
+
+/*
+ * LocateProtocol() finds the first device handle that support Protocol, and
+ * returns a pointer to the protocol interface from that handle in Interface.
+ * If no protocol instances are found, then Interface is set to NULL
+ */
+static efi_status_t __efiapi emulator_locate_protocol(efi_guid_t *uuid,
+				void *registration, void **interface)
+{
+	if (!efi_guidcmp(*uuid, EFI_TCG2_PROTOCOL_GUID)) {
+		return EFI_UNSUPPORTED;
+	} else if (!efi_guidcmp(*uuid, EFI_CC_MEASUREMENT_PROTOCOL_GUID)) {
+		return EFI_UNSUPPORTED;
+	} else if (!efi_guidcmp(*uuid, EFI_RNG_PROTOCOL_GUID)) {
+		*interface = &emulator_rng;
+		return EFI_SUCCESS;
+	}
+
+	return EFI_UNSUPPORTED;
+}
+
+/* each pair is {efi_guid_t *, void *} */
+static efi_status_t __efiapi emulator_install_multiple_protocol_interfaces(efi_handle_t *handle, ...)
+{
+	efi_status_t ret = EFI_SUCCESS;
+	efi_guid_t *guid;
+	void *proto;
+	va_list args;
+	int i;
+
+	if (*handle == 0)
+		ret = device_create_handle(handle);
+
+	va_start(args, handle);
+	for (i = 0; ret == EFI_SUCCESS; i++) {
+		/* If protocol is NULL, then it's the end of the list */
+		guid = va_arg(args, efi_guid_t *);
+		if (guid == NULL)
+			break;
+		proto = va_arg(args, void *);
+	
+		if (!efi_guidcmp(*guid, EFI_DEVICE_PATH_PROTOCOL_GUID)) {
+			ret = device_attach_dev_path(*handle, proto);
+			continue;
+		}
+		
+		/* install one protocol on the device */
+		ret = device_register_protocol(*handle, *guid, proto);
+	}
+	va_end(args);
+
+	return ret;
+}
+
+static efi_status_t __efiapi emulator_uninstall_multiple_protocol_interfaces(efi_handle_t, ...)
+{
+	return EFI_UNSUPPORTED;
+}
+
+
+// 2do
+static efi_status_t __efiapi emulator_allocate_pages(int alloc_type, int mem_type,
+			unsigned long nr_pages, efi_physical_addr_t *addr)
+{
+	return __emulator_allocate_pages(alloc_type, mem_type, nr_pages, addr);
+}
+
+// 2do
+static efi_status_t __efiapi emulator_free_pages(efi_physical_addr_t addr,
+			unsigned long nr_4KB)
+{
+	return EFI_SUCCESS;
+
+}
+
+static efi_status_t __efiapi emulator_allocate_pool(int mem_type, unsigned long sz,
+				       void **pool)
+{
+	return __emulator_allocate_pool(mem_type, sz, pool);
+
+}
+
+static efi_status_t __efiapi emulator_free_pool(void *pool)
+{
+	return EFI_SUCCESS;
+
+}
+
+/* memmove() alias as memcpy() */
+static void __efiapi emulator_copy_mem(void *dest, const void *src, unsigned long count)
+{
+	char *tmp;
+	const char *s;
+
+	if (dest <= src) {
+		tmp = dest;
+		s = src;
+		while (count--)
+			*tmp++ = *s++;
+	} else {
+		tmp = dest;
+		tmp += count;
+		s = src;
+		s += count;
+		while (count--)
+			*--tmp = *--s;
+	}
+
+}
+
+static void __efiapi emulator_set_mem(void *dst, unsigned long cnt, unsigned char val)
+{
+	unsigned char *dst_ptr = (char *)dst;
+	unsigned long i;
+
+	for (i = 0; i < cnt; i++)
+		dst_ptr[i] = val;
+}
+
+static efi_status_t __efiapi emulator_stall(unsigned long ms)
+{
+
+	return EFI_SUCCESS;
+}
+
+static efi_status_t __efiapi emulator_locate_handle(int, efi_guid_t *,
+				       void *, unsigned long *,
+				       efi_handle_t *)
+{
+	return EFI_UNSUPPORTED;
+}
+
+/*
+ * locates all devices on DevicePath that support Protocol and returns the
+ * handle to the device that is closest to DevicePath
+ */
+static efi_status_t __efiapi emulator_locate_device_path(efi_guid_t *guid,
+		efi_device_path_protocol_t **dp, efi_handle_t *handle)
+{
+	efi_status_t ret;
+		/* Only one device implements this protocol, so dp can be ignored */
+	if (!efi_guidcmp(*guid, EFI_LOAD_FILE2_PROTOCOL_GUID)) {
+		ret = device_find_handle_by_path(dp, handle);
+		return ret;
+	}
+
+	return EFI_NOT_FOUND;
+}
+
+static efi_status_t __efiapi emulator_install_configuration_table(efi_guid_t *uuid,
+								     void *table)
+{
+	efi_config_table_t *t = (efi_config_table_t *)systabs.tables;
+	int i;
+
+	for (i = 0; i < systabs.nr_tables; i++, t++) {
+		if (!efi_guidcmp(t->guid, *uuid))
+			return conjoin_table(uuid, table, t);
+	}
+	t->guid = *uuid;
+	t->table = table;
+	systabs.nr_tables++;
+
+	return EFI_SUCCESS;
+}
+
+/*
+ * For UKI, systemd-stub loads linux image and start image.
+ * @path: The DeviceHandle specific file path from which the image is loaded
+ */
+static efi_status_t __efiapi emulator_load_image(bool boot_policy,
+		efi_handle_t parent_image, efi_device_path_protocol_t *path,
+		void *src_buf, unsigned long src_sz,
+		efi_handle_t *handle)
+{
+	struct efi_pe_instance *inst;
+	char *dst;
+	
+	/* copy the in-memory image */
+	if (!!src_buf) {
+		dst = aligned_alloc(8, src_sz);
+		if (!dst) {
+			printf("OOM\n");
+			return EFI_OUT_OF_RESOURCES;
+		}
+		emulator_copy_mem(dst, src_buf, src_sz);
+		inst = allocate_pe_instance(dst, src_sz);
+		inst->handle = inst;
+		*handle = inst->handle;
+	/* EFI_SIMPLE_FILE_SYSTEM_PROTOCOL or EFI_LOAD_FILE_PROTOCOL */
+	} else {
+
+	}
+	return EFI_SUCCESS;
+}
+
+static	efi_status_t __efiapi emulator_start_image(efi_handle_t handle,
+		unsigned long *exit_data_sz, efi_char16_t **exit_data)
+{
+
+	struct efi_pe_instance *inst;
+
+	inst = get_instance_by_handle(handle);
+	if (unlikely(!inst)) {
+		printf("error: can not find image\n");
+		return EFI_NOT_FOUND;
+	}
+	load_kernel_pe(inst, &systabs);
+
+	return EFI_SUCCESS;
+}
+
+/*
+ * As the final stage, destroy the boottime context, e.g. release the memory
+ * occupied by some data struct.
+ */
+static efi_status_t __efiapi emulator_exit_boot_services(efi_handle_t handle,
+							    unsigned long map_key)
+{
+	return EFI_SUCCESS;
+}
+
+static efi_boot_services_t bt_services = {
+	.handle_protocol = emulator_handle_protocol,
+	.locate_protocol = emulator_locate_protocol,
+	.install_multiple_protocol_interfaces = emulator_install_multiple_protocol_interfaces,
+	.uninstall_multiple_protocol_interfaces = emulator_uninstall_multiple_protocol_interfaces,
+
+	.allocate_pool = emulator_allocate_pool,
+	.free_pool = emulator_free_pool,
+	.allocate_pages = emulator_allocate_pages,
+	.free_pages = emulator_free_pages,
+	.copy_mem = emulator_copy_mem,
+	.set_mem = emulator_set_mem,
+	.get_memory_map = emulator_get_memory_map,
+
+	.stall = emulator_stall,
+
+	.locate_handle = emulator_locate_handle,
+	.locate_device_path = emulator_locate_device_path,
+	.install_configuration_table = emulator_install_configuration_table,
+	.load_image = emulator_load_image,
+	.start_image = emulator_start_image,
+	.exit_boot_services = emulator_exit_boot_services,
+};
+
+static efi_char16_t vendor[] = u"Linux Kexec";
+
+static efi_status_t unsupported_func(void)
+{
+	return EFI_UNSUPPORTED;
+}
+
+efi_system_table_t systabs = {
+	.hdr = {
+		.signature = EFI_SYSTEM_TABLE_SIGNATURE,
+	},
+	.fw_vendor = (unsigned long)vendor,
+	.fw_revision = 0x10001,
+	.con_in_handle = 0x0,
+	.con_in = (efi_simple_text_input_protocol_t *)unsupported_func,
+	.con_out_handle = 0x0,
+	.con_out = &text_out,
+	.stderr_handle = 0x0,
+	/* Per specification, A pointer to the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL */
+	.stderr = (unsigned long)unsupported_func,
+	/* Passed in by the 1st kernel */
+	.runtime = NULL,
+	.boottime = &bt_services,
+	.nr_tables = 0,
+	.tables = 0,
+};
+
+static efi_rt_properties_table_t rt_support = {
+	.runtime_services_supported = 0,
+};
+
+int initialize_emulator_service(struct efi_emulator_param *param)
+{
+
+	efi_config_table_t *tables;
+	unsigned int i;
+
+	printf("initialize_emulator_service, dtb=0x%lx, mempool_start=0x%lx, end:0x%lx\n",
+			param->dtb, param->mempool_start, param->mempool_start + param->mempool_sz);
+	emulator_param = param;
+	print_enabled = param->print_enabled;
+	i = param->rt_info.systab_nr_tables;
+	systabs.tables = (unsigned long)&param->rt_info.systab_tables;
+	tables = param->rt_info.systab_tables;
+	tables[i].guid = DEVICE_TREE_GUID;
+	tables[i].table = (void *)param->dtb;
+	i++;
+	if (!param->noefi_boot) {
+		rt_support.runtime_services_supported = param->rt_info.runtime_supported_mask;
+	}
+	tables[i].guid = EFI_RT_PROPERTIES_TABLE_GUID;
+	tables[i].table = (void *)&rt_support;
+	i++;
+	systabs.nr_tables = i;
+
+	systabs.runtime = (efi_runtime_services_t *)param->rt_info.runtime;
+	return 0;
+}
diff --git a/drivers/firmware/efi/efi_emulator/device_handle.c b/drivers/firmware/efi/efi_emulator/device_handle.c
new file mode 100644
index 0000000000000..6e15979f62e9a
--- /dev/null
+++ b/drivers/firmware/efi/efi_emulator/device_handle.c
@@ -0,0 +1,138 @@
+//SPDX-License-Identifier: GPL-2.0
+#include <linux/efi.h>
+#include <asm/efi.h>
+#include "emulator.h"
+
+static LIST_HEAD(devices_list);
+
+struct protocol_entry {
+	efi_guid_t guid;
+	void *proto;
+};
+
+/*
+ * Drivers can implement their own version of efi_load_file_protocol_t. This represents one.
+ * For example, refer to systemd-stub initrd_load_file()
+ *
+ * BS->InstallMultipleProtocolInterfaces(handle, ...)
+ * BS->LocateDevicePath(guid, dp, handle)
+ * BS->HandleProtocol(handle, guid, interface)
+ * interface->func()
+ * This struct can be abstracted to serve EFI_BOOT_SERVICES.LocateDevicePath(guid, dp, handle)
+ */
+struct device_instance {
+	struct list_head node;
+	efi_handle_t handle;
+	/*
+	 * Quote UEFI specification:
+	 * 'It is illegal to have two handles in the handle database with identical device paths'
+	 */
+	efi_device_path_protocol_t *dp;
+	/* For simplity, keep the capacity at 8 for the time being */
+	struct protocol_entry entries[8];
+};
+
+static struct device_instance *find_device_by_handle(efi_handle_t h)
+{
+	struct device_instance *inst;
+
+	list_for_each_entry(inst, &devices_list, node) {
+		if (inst->handle == h)
+			return inst;
+	}
+
+	return NULL;
+}
+
+efi_status_t device_create_handle(efi_handle_t *handle)
+{
+	struct device_instance *inst;
+	int sz;
+
+	sz = sizeof(struct device_instance);
+	inst = aligned_alloc(4, sz);
+	memset(inst, 0, sz);
+	emulator_list_add(&inst->node, &devices_list);
+	inst->handle = (efi_handle_t)inst;
+	*handle = inst->handle;
+
+	return EFI_SUCCESS;
+}
+
+efi_status_t device_attach_dev_path(efi_handle_t h, efi_device_path_protocol_t *dp)
+{
+	struct device_instance *inst = (struct device_instance *)h;
+	int sz;
+
+	sz = efi_device_path_size(dp);
+	inst->dp = aligned_alloc(4, sz);
+	/* clone the device path */
+	efi_device_path_clone(inst->dp, dp);
+
+	return EFI_SUCCESS;
+}
+
+/*
+ * BS->InstallMultipleProtocolInterfaces() calls down to here.
+ *
+ * A driver implements its own efi_load_file_protocol_t.
+ *
+ * According to EFI_LOAD_FILE2_PROTOCOL.LoadFile(), only
+ * efi_device_path_protocol_t is required.
+ */
+efi_status_t device_register_protocol(efi_handle_t handle, efi_guid_t guid,
+		void *proto)
+{
+	struct device_instance *inst;
+
+	inst = find_device_by_handle(handle);
+	if (!inst)
+		return EFI_NOT_FOUND;
+
+	for (int i = 0; i < 8; i++) {
+		if (!efi_guidcmp(inst->entries[i].guid, NULL_GUID)) {
+			inst->entries[i].guid = guid;
+			inst->entries[i].proto = proto;
+			return EFI_SUCCESS;
+		}
+	}
+
+	return EFI_OUT_OF_RESOURCES;
+}
+
+efi_status_t device_find_handle_by_path(efi_device_path_protocol_t **dp,
+		efi_handle_t *h)
+{
+	struct device_instance *inst;
+	int ret = -1;
+
+	list_for_each_entry(inst, &devices_list, node) {
+		ret = efi_device_path_compare(*dp, inst->dp);
+		if (!ret) {
+			*h = inst->handle;
+			return EFI_SUCCESS;
+		}
+	}
+
+	return EFI_NOT_FOUND;
+}
+
+efi_status_t device_handle_protocol(efi_handle_t h, efi_guid_t *uuid, void **data)
+{
+	struct device_instance *inst;
+
+	list_for_each_entry(inst, &devices_list, node) {
+		if (inst->handle == h) {
+			for (int i = 0; i < 8; i++) {
+				if (!efi_guidcmp(inst->entries[i].guid, *uuid)) {
+					*data = inst->entries[i].proto;
+					return EFI_SUCCESS;
+				}
+			}
+			/* no need to try other handles */
+			return EFI_NOT_FOUND;
+		}
+	}
+
+	return EFI_NOT_FOUND;
+}
diff --git a/drivers/firmware/efi/efi_emulator/earlycon.h b/drivers/firmware/efi/efi_emulator/earlycon.h
new file mode 100644
index 0000000000000..189af549d5af2
--- /dev/null
+++ b/drivers/firmware/efi/efi_emulator/earlycon.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <linux/efi_emulator.h>
+#include <asm/processor.h>
+#include <asm/io.h>
+#include <asm-generic/io.h>
+
+struct earlycon {
+	bool (*match)(struct efi_emulator_param *param, const char *name);
+	int (*put_str)(const char *str, void *data);
+	void (*reset)(void *data);
+	void *data;
+	const char *name;
+};
+
+extern struct earlycon pl011;
+
+extern int pl011_puts(const char *str);
+void setup_earlycon(struct efi_emulator_param *param);
diff --git a/drivers/firmware/efi/efi_emulator/efi_emulator.S b/drivers/firmware/efi/efi_emulator/efi_emulator.S
new file mode 100644
index 0000000000000..fb52593ba3b2e
--- /dev/null
+++ b/drivers/firmware/efi/efi_emulator/efi_emulator.S
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+	.section .rodata, "a"
+
+	.align	8
+_efi_emulator_start:
+	.globl	_efi_emulator_start
+	.incbin	"drivers/firmware/efi/efi_emulator/emulator.raw"
+
+	.align	8
+_efi_emulator_end:
+	.globl	_efi_emulator_end
diff --git a/drivers/firmware/efi/efi_emulator/emulator.h b/drivers/firmware/efi/efi_emulator/emulator.h
new file mode 100644
index 0000000000000..9b38d1ee69cea
--- /dev/null
+++ b/drivers/firmware/efi/efi_emulator/emulator.h
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <linux/nls.h>
+#include <linux/efi_emulator.h>
+
+/* Included from drivers/firmware/efi/libstub */
+#include <efistub.h>
+
+#define EMULATOR_BASE_ADDR 0
+
+typedef union efi_rng_protocol efi_rng_protocol_t;
+
+union efi_rng_protocol {
+	struct {
+		efi_status_t (__efiapi *get_info)(efi_rng_protocol_t *,
+						  unsigned long *,
+						  efi_guid_t *);
+		efi_status_t (__efiapi *get_rng)(efi_rng_protocol_t *,
+						 efi_guid_t *, unsigned long,
+						 u8 *out);
+	};
+	struct {
+		u32 get_info;
+		u32 get_rng;
+	} mixed_mode;
+};
+
+typedef efi_status_t (*uefi_pe_entry)(efi_handle_t handle, efi_system_table_t *systab);
+
+struct efi_pe_instance {
+	struct list_head node;
+	efi_handle_t handle;
+	char *image_file_buf;
+	unsigned long image_file_size;
+	/* load address for the instance */
+	unsigned long image_base;
+	unsigned long image_size;
+	uefi_pe_entry entry;
+};
+
+static inline void __emulator_list_add(struct list_head *new,
+			      struct list_head *prev,
+			      struct list_head *next)
+{
+	next->prev = new;
+	new->next = next;
+	new->prev = prev;
+	WRITE_ONCE(prev->next, new);
+}
+
+static inline void emulator_list_add(struct list_head *new, struct list_head *head)
+{
+	__emulator_list_add(new, head, head->next);
+}
+
+extern bool print_enabled;
+extern struct efi_emulator_param *emulator_param;
+extern efi_tcg2_protocol_t emulator_tcg2;
+extern efi_cc_protocol_t emulator_cc;
+extern efi_rng_protocol_t emulator_rng;
+extern efi_simple_text_output_protocol_t text_out;
+extern efi_system_table_t systabs;
+extern char *heap_start, *heap_end, *heap_cur;
+
+void *aligned_alloc(size_t alignment, size_t size);
+void *memcpy(void *dest, const void *src, size_t n);
+void *memset(void *s, int c, size_t n);
+int strcmp(const char *cs, const char *ct);
+size_t wcslen(const wchar_t *str);
+int wcscmp(const wchar_t *s1, const wchar_t *s2);
+int printf(const char *format, ...);
+void print_ucs2_string(efi_char16_t* ucs2_str);
+extern unsigned long find_image_base_for_handle(efi_handle_t handle);
+
+efi_status_t device_create_handle(efi_handle_t *handle);
+efi_status_t device_attach_dev_path(efi_handle_t h, efi_device_path_protocol_t *dp);
+efi_status_t device_register_protocol(efi_handle_t handle, efi_guid_t guid,
+		void *proto);
+efi_status_t device_find_handle_by_path(efi_device_path_protocol_t **dp,
+		efi_handle_t *h);
+efi_status_t device_handle_protocol(efi_handle_t h, efi_guid_t *uuid,
+		void **data);
+
+int efi_device_path_compare(efi_device_path_protocol_t *path1,
+		efi_device_path_protocol_t *path2);
+size_t efi_device_path_size(efi_device_path_protocol_t *path);
+int efi_device_path_clone(efi_device_path_protocol_t *dst,
+		efi_device_path_protocol_t *dp);
+
+efi_status_t __emulator_allocate_pages(int alloc_type, int mem_type,
+			unsigned long nr_pages, efi_physical_addr_t *addr);
+efi_status_t __emulator_allocate_pool(int mem_type, unsigned long sz,
+				       void **pool);
+efi_status_t emulator_get_memory_map(unsigned long *map_sz,
+	void *memmap, unsigned long *map_key, unsigned long *desc_sz,
+	unsigned int *desc_version);
+
+efi_status_t conjoin_table(efi_guid_t *uuid, void *table, efi_config_table_t *t);
+
+struct efi_pe_instance *get_instance_by_handle(efi_handle_t h);
+struct efi_pe_instance *allocate_pe_instance(char *file_buf, unsigned long size);
+int initialize_emulator_service(struct efi_emulator_param *param);
+void initialize_heap(struct efi_emulator_param *param);
+void load_kernel_pe(struct efi_pe_instance *inst, efi_system_table_t *systabs);
+void emulator_main(struct efi_emulator_param *param);
+void emulator_entry(struct efi_emulator_param *param);
+
diff --git a/drivers/firmware/efi/efi_emulator/entry.c b/drivers/firmware/efi/efi_emulator/entry.c
new file mode 100644
index 0000000000000..e5a31bd303858
--- /dev/null
+++ b/drivers/firmware/efi/efi_emulator/entry.c
@@ -0,0 +1,61 @@
+//SPDX-License-Identifier: GPL-2.0
+#include <linux/types.h>
+#include <linux/efi_emulator.h>
+#include <asm/barrier.h>
+#include <asm/sysreg.h>
+#include <asm/elf.h>
+#include <uapi/linux/elf.h>
+
+#include "emulator.h"
+#include "earlycon.h"
+
+extern void enable_sctlr_el1(unsigned long scratch_reg);
+static void arch_handle_mmu(struct efi_emulator_param *param)
+{
+	if (!param->mmu_on && param->pgd_root) {
+	}
+}
+
+extern const Elf64_Rela _rela_start[], _rela_end[];
+
+static void noinline arch_reloc_fixup(long delta)
+{
+	unsigned long *apply_addr, res;
+	Elf64_Rela *rela;
+
+	/* fix rela */
+	for (rela = (Elf64_Rela *)_rela_start; rela < _rela_end; rela++) {
+		//todo counterpart of R_AARCH64_RELATIVE on riscv
+		if (ELF64_R_TYPE(rela->r_info) != R_AARCH64_RELATIVE)
+			continue;
+		apply_addr = (unsigned long *)(rela->r_offset + delta);
+		res = rela->r_addend + delta;
+		*apply_addr = res;
+	}
+	// todo flush cache
+
+}
+
+/* 
+ * Ensure this entry and @param is in the mapping before jump to it.
+ * It should be PIC and at the beginning of emulator.
+ * It should be memory aligned
+ */
+void emulator_main(struct efi_emulator_param *param)
+{
+	long delta = param->load_address - EMULATOR_BASE_ADDR;
+	struct efi_pe_instance *inst;
+
+	arch_handle_mmu(param);
+	arch_reloc_fixup(delta);
+	setup_earlycon(param);
+	printf("param:0x%lx, delta=0x%lx\n", (unsigned long)param, delta);
+	printf("kernel_img_start:0x%lx, sz:0x%lx\n", (unsigned long)param->kernel_img_start, (unsigned long)param->kernel_img_sz);
+	initialize_emulator_service(param);
+	initialize_heap(param);
+	printf(" load_kernel_pe\n");
+
+	inst = allocate_pe_instance((char *)param->kernel_img_start,
+					param->kernel_img_sz);
+	load_kernel_pe(inst, &systabs);
+}
diff --git a/drivers/firmware/efi/efi_emulator/head.S b/drivers/firmware/efi/efi_emulator/head.S
new file mode 100644
index 0000000000000..705ff131e5fab
--- /dev/null
+++ b/drivers/firmware/efi/efi_emulator/head.S
@@ -0,0 +1,10 @@
+//SPDX-License-Identifier: GPL-2.0
+.section        ".head.text","ax"
+
+/* x0 holds the physical address of emulator_param */
+emulator_entry:
+	ldr	x1, [x0]
+	mov	sp, x1
+	adrp	x2, emulator_main
+	add	x2, x2, #:lo12:emulator_main
+	br	x2
diff --git a/drivers/firmware/efi/efi_emulator/lib.c b/drivers/firmware/efi/efi_emulator/lib.c
new file mode 100644
index 0000000000000..f12aa40405650
--- /dev/null
+++ b/drivers/firmware/efi/efi_emulator/lib.c
@@ -0,0 +1,73 @@
+//SPDX-License-Identifier: GPL-2.0
+#include "emulator.h"
+
+void *memcpy(void *dest, const void *src, size_t count)
+{
+	char *tmp = dest;
+	const char *s = src;
+
+	while (count--)
+		*tmp++ = *s++;
+	return dest;
+}
+
+void *memset(void *s, int c, size_t count)
+{
+	char *xs = s;
+
+	while (count--)
+		*xs++ = c;
+	return s;
+}
+
+int memcmp(const void *cs, const void *ct, size_t count)
+{
+	const unsigned char *su1, *su2;
+	int res = 0;
+
+	for (su1 = cs, su2 = ct; 0 < count; ++su1, ++su2, count--)
+		if ((res = *su1 - *su2) != 0)
+			break;
+	return res;
+}
+
+size_t strlen(const char *s)
+{
+	const char *sc;
+
+	for (sc = s; *sc != '\0'; ++sc)
+		/* nothing */;
+	return sc - s;
+}
+
+int strcmp(const char *cs, const char *ct)
+{
+	unsigned char c1, c2;
+
+	while (1) {
+		c1 = *cs++;
+		c2 = *ct++;
+		if (c1 != c2)
+			return c1 < c2 ? -1 : 1;
+		if (!c1)
+			break;
+	}
+	return 0;
+}
+
+int wcscmp(const wchar_t *s1, const wchar_t *s2)
+{
+    while (*s1 && (*s1 == *s2)) {
+        s1++;
+        s2++;
+    }
+    return (int)(*s1 - *s2);
+}
+
+size_t wcslen(const wchar_t *str)
+{
+    const wchar_t *s;
+
+    for (s = str; *s; ++s);
+    return (s - str);
+}
diff --git a/drivers/firmware/efi/efi_emulator/memory.c b/drivers/firmware/efi/efi_emulator/memory.c
new file mode 100644
index 0000000000000..518ab7f30020e
--- /dev/null
+++ b/drivers/firmware/efi/efi_emulator/memory.c
@@ -0,0 +1,27 @@
+//SPDX-License-Identifier: GPL-2.0
+#include "emulator.h"
+
+char *heap_start, *heap_end, *heap_cur;
+
+void initialize_heap(struct efi_emulator_param *param)
+{
+	heap_start = (char *)param->mempool_start;
+	heap_end = heap_start + param->mempool_sz;
+	heap_cur = heap_start;
+}
+
+//2do, the memory management is more complicated since we need to distinguish EFI_BOOT_SERVICE, RUNTIME, LOADER memory descr
+
+void *aligned_alloc(size_t alignment, size_t size)
+{
+	char *p;
+
+	p = (char *)ALIGN((unsigned long)heap_cur, alignment);
+	heap_cur = p + size;
+
+	//todo, update the efi_memory_desc to include this page, if it crosses the PAGE boundary
+	//as EFI_BOOT_SERVICE,
+	return p;
+}
+
+
diff --git a/drivers/firmware/efi/efi_emulator/memory_api.c b/drivers/firmware/efi/efi_emulator/memory_api.c
new file mode 100644
index 0000000000000..bd57942cb0410
--- /dev/null
+++ b/drivers/firmware/efi/efi_emulator/memory_api.c
@@ -0,0 +1,74 @@
+//SPDX-License-Identifier: GPL-2.0
+#include <linux/efi.h>
+#include <asm/efi.h>
+
+#include "emulator.h"
+
+/*
+ * mem_type affects the allocated chunk in efi_memory_desc_t's type. Later,
+ * kernel can know whether to reclaim them.
+ */
+efi_status_t __emulator_allocate_pages(int alloc_type, int mem_type,
+			unsigned long nr_pages, efi_physical_addr_t *addr)
+{
+	efi_physical_addr_t res;
+	efi_status_t status;
+
+	if (alloc_type == EFI_ALLOCATE_ANY_PAGES) {
+		res = (efi_physical_addr_t)aligned_alloc(PAGE_SIZE, nr_pages << PAGE_SHIFT);
+		*addr = res;
+		status = EFI_SUCCESS;
+	} else if (alloc_type == EFI_ALLOCATE_MAX_ADDRESS) {
+		//tmp
+		res = (efi_physical_addr_t)aligned_alloc(PAGE_SIZE, nr_pages << PAGE_SHIFT);
+		*addr = res;
+		status = EFI_SUCCESS;
+	/* e.g. aarch64 kimage loaded alignment */
+	} else if (alloc_type == EFI_ALLOCATE_ADDRESS) {
+		//tmp, just aligned on 2MB as aarch64 boot protocol
+		res = (efi_physical_addr_t)aligned_alloc(1<<21, nr_pages << PAGE_SHIFT);
+		*addr = res;
+		status = EFI_SUCCESS;
+	}
+
+	return status;
+}
+
+//todo
+efi_status_t __emulator_allocate_pool(int mem_type, unsigned long sz,
+				       void **pool)
+{
+	void *res;
+
+	res = aligned_alloc(sizeof(unsigned long), sz);
+	*pool = res;
+	return EFI_SUCCESS;
+}
+
+/* @memmap: only holds efi_memory_desc */
+efi_status_t emulator_get_memory_map(unsigned long *map_sz,
+	void *memmap, unsigned long *map_key, unsigned long *desc_sz,
+	unsigned int *desc_version)
+{
+	//todo rt_info.memmap will be accessed by kernel, so it should be marked as reserved
+	struct efi_boot_memmap *p = &emulator_param->rt_info.memmap;
+	//efi_memory_desc_t *desc = p->map;
+
+	if (!map_sz || !desc_sz)
+		return EFI_INVALID_PARAMETER;
+	if (*map_sz < p->map_size || !memmap) {
+		*map_sz = p->map_size;
+		*desc_sz = p->desc_size;
+		return EFI_BUFFER_TOO_SMALL;
+	}
+
+	/* desc range size*/
+	*map_sz = p->map_size;
+	memcpy(memmap, p->map, p->map_size);
+	if (!!desc_sz)
+		*desc_sz = p->desc_size;
+	if (!!desc_version)
+		*desc_version = p->desc_ver;
+
+	return EFI_SUCCESS;
+}
diff --git a/drivers/firmware/efi/efi_emulator/misc.c b/drivers/firmware/efi/efi_emulator/misc.c
new file mode 100644
index 0000000000000..0d6e92bc539cf
--- /dev/null
+++ b/drivers/firmware/efi/efi_emulator/misc.c
@@ -0,0 +1,43 @@
+//SPDX-License-Identifier: GPL-2.0
+
+#include <linux/efi.h>
+#include <asm/efi.h>
+
+#include "emulator.h"
+
+static efi_status_t __efiapi emulator_get_rng(efi_rng_protocol_t * this,
+						 efi_guid_t *uuid, unsigned long size,
+						 u8 *out)
+{
+	/* in fact, disable aslr */
+	*out = 0;
+	return EFI_SUCCESS;
+}
+
+efi_rng_protocol_t emulator_rng = {
+	.get_rng = emulator_get_rng,
+};
+
+static efi_status_t __efiapi emulator_get_memory_attributes(
+	efi_memory_attribute_protocol_t *, efi_physical_addr_t, u64, u64 *)
+{
+	return EFI_SUCCESS;
+}
+
+static efi_status_t __efiapi emulator_set_memory_attributes(
+	efi_memory_attribute_protocol_t *, efi_physical_addr_t, u64, u64)
+{
+	return EFI_SUCCESS;
+}
+
+static efi_status_t __efiapi emulator_clear_memory_attributes(
+	efi_memory_attribute_protocol_t *, efi_physical_addr_t, u64, u64)
+{
+	return EFI_SUCCESS;
+}
+
+efi_memory_attribute_protocol_t emulator_memory_attribute = {
+	.get_memory_attributes = emulator_get_memory_attributes,
+	.set_memory_attributes = emulator_set_memory_attributes,
+	.clear_memory_attributes = emulator_clear_memory_attributes,
+};
diff --git a/drivers/firmware/efi/efi_emulator/pe_loader.c b/drivers/firmware/efi/efi_emulator/pe_loader.c
new file mode 100644
index 0000000000000..9113827fd7c9a
--- /dev/null
+++ b/drivers/firmware/efi/efi_emulator/pe_loader.c
@@ -0,0 +1,173 @@
+//SPDX-License-Identifier: GPL-2.0
+#include <linux/pe.h>
+#include <linux/efi.h>
+#include <asm/efi.h>
+#include "emulator.h"
+
+#define VALID_PAYLOAD (IMAGE_SCN_CNT_CODE |IMAGE_SCN_CNT_INITIALIZED_DATA |IMAGE_SCN_CNT_UNINITIALIZED_DATA)
+
+/* Refer to PECOFF spec, 'Base Relocation Types' */
+#define IMAGE_REL_BASED_ABSOLUTE	0
+#define IMAGE_REL_BASED_DIR64	10
+
+unsigned long find_image_base_for_handle(efi_handle_t handle)
+{
+	struct efi_pe_instance *inst;
+
+	inst = get_instance_by_handle(handle);
+	if (!inst)
+		return (unsigned long)-1;
+	return inst->image_base;
+}
+
+typedef struct __packed base_relocation_block {
+	uint32_t page_rva;
+	uint32_t block_size;
+	struct {
+	        uint16_t offset : 12;
+	        uint16_t type : 4;	/* higher 4-bits in Word */
+	} entries[];
+} base_relocation_block_t;
+
+static int pe_image_handle_base_relocation(unsigned long base_reloc_table,
+		unsigned long sz, unsigned long load_addr, unsigned long delta)
+{
+	base_relocation_block_t *blk = (base_relocation_block_t *)base_reloc_table;	
+	unsigned long page_addr, *target_addr, value;
+	uint32_t i, array_num;
+
+	for (; (unsigned long)blk < (base_reloc_table + sz);
+	     blk = (base_relocation_block_t *)((unsigned char*)blk + blk->block_size)) {
+
+		/* block_size includes the total base_relocation_block structure */
+		array_num = (blk->block_size - sizeof(base_relocation_block_t)) >> 1;
+		page_addr = blk->page_rva + load_addr;
+		for (i = 0; i < array_num; i++) {
+			switch (blk->entries[i].type) {
+				case IMAGE_REL_BASED_ABSOLUTE:
+					continue;
+				case IMAGE_REL_BASED_DIR64:
+					target_addr = (unsigned long *)(page_addr + blk->entries[i].offset);
+					value = *target_addr + delta;
+					*target_addr = value;
+					continue;
+				default:
+					printf("ERR: unhandled reloc type: %d\n");
+					return -1; 
+			}
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * @pe_hdr_offset supplies the size of Dos Header and Stub.
+ */ 
+static int load_pe(char *file_buf, unsigned long buf_sz, unsigned long pe_hdr_offset,
+			struct efi_pe_instance *inst)
+{
+	unsigned long exec_sz, load_addr, delta;
+	unsigned long base_reloc_table, sz;
+	struct pe_hdr *pe_hdr;
+	struct pe32plus_opt_hdr *opt_hdr;
+	struct data_directory *dir;
+	struct data_dirent *dirent;
+	struct section_header *sect_hdr;
+	int section_nr, i;
+	char *pe_part_buf, *src, *dst;
+	printf("load_pe\n");
+	pe_part_buf = file_buf + pe_hdr_offset;
+	pe_hdr = (struct pe_hdr *)pe_part_buf;
+	if (pe_hdr->opt_hdr_size == 0) {
+		printf("ERR: optional header is missing\n");
+		return -1;
+	}
+	section_nr = pe_hdr->sections;
+	opt_hdr = (struct pe32plus_opt_hdr *)(pe_part_buf + sizeof(struct pe_hdr));
+	sect_hdr = (struct section_header *)((char *)opt_hdr + pe_hdr->opt_hdr_size);
+	exec_sz = opt_hdr->image_size;
+
+	/*
+	 * PE header must be loaded since some efi stubs parse them e.g. systemd-stub
+	 */
+	load_addr = (unsigned long)aligned_alloc(opt_hdr->section_align, exec_sz);
+
+	/*
+	 * Each section has the same delta. Got the delta based on the first
+	 * section's RVA.
+	 */
+	delta = load_addr - opt_hdr->image_base;
+	/* copy PE headers */
+	memcpy((void *)load_addr, file_buf, opt_hdr->header_size);
+
+ 	/* copy section to segment */
+	for (i = 0; i < section_nr; i++) {
+		printf("section: %s, relocs: %u\n", sect_hdr->name, sect_hdr->num_relocs);
+		if (!(sect_hdr->flags & VALID_PAYLOAD)) {
+			sect_hdr++;
+			continue;
+		}
+		/* data_addr is relative to the whole file */
+		src = file_buf + sect_hdr->data_addr;
+		dst = (char *)(sect_hdr->virtual_address + load_addr);
+		memcpy(dst, src, sect_hdr->raw_data_size);
+		printf("virtual_address: 0x%u, src: %u, dst: %u\n", sect_hdr->virtual_address, src, dst);
+		/*
+		 * The SizeOfRawData is rounded but the VirtualSize is not, hence
+		 * the former can be greater than latter.
+		 */
+		if (sect_hdr->virtual_size > sect_hdr->raw_data_size)
+			memset(dst + sect_hdr->raw_data_size, 0, sect_hdr->virtual_size - sect_hdr->raw_data_size);
+		sect_hdr++;
+	}
+
+	/* If there are relocs */
+	if (pe_hdr->opt_hdr_size >
+	    (offsetof(struct data_directory, base_relocations) + sizeof(struct pe32plus_opt_hdr))) {
+		dir = (void *)pe_hdr + sizeof(struct pe_hdr) + sizeof(struct pe32plus_opt_hdr);
+		dirent = &dir->base_relocations;
+		base_reloc_table = dirent->virtual_address + load_addr;
+		sz = dirent->size;
+		pe_image_handle_base_relocation(base_reloc_table, sz, load_addr, delta);
+	}
+
+	/* Since gcc adheres to ABI, using the current SP is fine for new image instance */
+
+	inst->entry = (uefi_pe_entry)(opt_hdr->entry_point + load_addr);
+	inst->image_base = load_addr;
+	inst->image_size = opt_hdr->image_size;
+
+	printf("entry_point:0x%lx, delta:0x%lx, final inst's entry at:0x%lx\n",
+		opt_hdr->entry_point, delta, inst->entry);
+	return 0;
+}
+
+static int parse_kernel_pe(struct efi_pe_instance *inst)
+{
+	char *buf = (char *)inst->image_file_buf;
+	u32 pe_hdr_offset;
+
+	pe_hdr_offset = *((u32 *)(buf + 0x3c));
+	buf += pe_hdr_offset;
+	if (!!memcmp(buf, "PE\0\0", 4)) {
+		printf("Not a PE file\n");
+		return -1;
+	}
+
+	load_pe((char *)inst->image_file_buf, inst->image_file_size,
+			pe_hdr_offset, inst);
+
+	return 0;
+}
+
+void load_kernel_pe(struct efi_pe_instance *inst, efi_system_table_t *systabs)
+{
+	int ret;
+
+	ret = parse_kernel_pe(inst);
+	if (ret < 0)
+		return;
+	(*(inst->entry))(inst->handle, systabs);
+
+}
diff --git a/drivers/firmware/efi/efi_emulator/printf.c b/drivers/firmware/efi/efi_emulator/printf.c
new file mode 100644
index 0000000000000..4d8daf91b426b
--- /dev/null
+++ b/drivers/firmware/efi/efi_emulator/printf.c
@@ -0,0 +1,373 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* The most of this file is copied from arch/x86/boot/printf.c */
+
+#include <linux/ctype.h>
+#include <linux/efi.h>
+
+#include "earlycon.h"
+#include "emulator.h"
+
+static int skip_atoi(const char **s)
+{
+	int i = 0;
+
+	while (isdigit(**s))
+		i = i * 10 + *((*s)++) - '0';
+	return i;
+}
+
+#define ZEROPAD	1		/* pad with zero */
+#define SIGN	2		/* unsigned/signed long */
+#define PLUS	4		/* show plus */
+#define SPACE	8		/* space if plus */
+#define LEFT	16		/* left justified */
+#define SMALL	32		/* Must be 32 == 0x20 */
+#define SPECIAL	64		/* 0x */
+
+#define __do_div(n, base) ({ \
+int __res; \
+__res = ((unsigned long) n) % (unsigned) base; \
+n = ((unsigned long) n) / (unsigned) base; \
+__res; })
+
+static char *number(char *str, long num, int base, int size, int precision,
+		    int type)
+{
+	/* we are called with base 8, 10 or 16, only, thus don't need "G..."  */
+	static const char digits[16] = "0123456789ABCDEF"; /* "GHIJKLMNOPQRSTUVWXYZ"; */
+
+	char tmp[66];
+	char c, sign, locase;
+	int i;
+
+	/* locase = 0 or 0x20. ORing digits or letters with 'locase'
+	 * produces same digits or (maybe lowercased) letters */
+	locase = (type & SMALL);
+	if (type & LEFT)
+		type &= ~ZEROPAD;
+	if (base < 2 || base > 16)
+		return NULL;
+	c = (type & ZEROPAD) ? '0' : ' ';
+	sign = 0;
+	if (type & SIGN) {
+		if (num < 0) {
+			sign = '-';
+			num = -num;
+			size--;
+		} else if (type & PLUS) {
+			sign = '+';
+			size--;
+		} else if (type & SPACE) {
+			sign = ' ';
+			size--;
+		}
+	}
+	if (type & SPECIAL) {
+		if (base == 16)
+			size -= 2;
+		else if (base == 8)
+			size--;
+	}
+	i = 0;
+	if (num == 0)
+		tmp[i++] = '0';
+	else
+		while (num != 0)
+			tmp[i++] = (digits[__do_div(num, base)] | locase);
+	if (i > precision)
+		precision = i;
+	size -= precision;
+	if (!(type & (ZEROPAD + LEFT)))
+		while (size-- > 0)
+			*str++ = ' ';
+	if (sign)
+		*str++ = sign;
+	if (type & SPECIAL) {
+		if (base == 8)
+			*str++ = '0';
+		else if (base == 16) {
+			*str++ = '0';
+			*str++ = ('X' | locase);
+		}
+	}
+	if (!(type & LEFT))
+		while (size-- > 0)
+			*str++ = c;
+	while (i < precision--)
+		*str++ = '0';
+	while (i-- > 0)
+		*str++ = tmp[i];
+	while (size-- > 0)
+		*str++ = ' ';
+	return str;
+}
+
+size_t strnlen(const char *s, size_t count)
+{
+	const char *sc;
+
+	for (sc = s; count-- && *sc != '\0'; ++sc)
+		/* nothing */;
+	return sc - s;
+}
+
+int vsprintf(char *buf, const char *fmt, va_list args)
+{
+	int len;
+	unsigned long num;
+	int i, base;
+	char *str;
+	const char *s;
+
+	int flags;		/* flags to number() */
+
+	int field_width;	/* width of output field */
+	int precision;		/* min. # of digits for integers; max
+				   number of chars for from string */
+	int qualifier;		/* 'h', 'l', or 'L' for integer fields */
+
+	for (str = buf; *fmt; ++fmt) {
+		if (*fmt != '%') {
+			*str++ = *fmt;
+			continue;
+		}
+
+		/* process flags */
+		flags = 0;
+	      repeat:
+		++fmt;		/* this also skips first '%' */
+		switch (*fmt) {
+		case '-':
+			flags |= LEFT;
+			goto repeat;
+		case '+':
+			flags |= PLUS;
+			goto repeat;
+		case ' ':
+			flags |= SPACE;
+			goto repeat;
+		case '#':
+			flags |= SPECIAL;
+			goto repeat;
+		case '0':
+			flags |= ZEROPAD;
+			goto repeat;
+		}
+
+		/* get field width */
+		field_width = -1;
+		if (isdigit(*fmt))
+			field_width = skip_atoi(&fmt);
+		else if (*fmt == '*') {
+			++fmt;
+			/* it's the next argument */
+			field_width = va_arg(args, int);
+			if (field_width < 0) {
+				field_width = -field_width;
+				flags |= LEFT;
+			}
+		}
+
+		/* get the precision */
+		precision = -1;
+		if (*fmt == '.') {
+			++fmt;
+			if (isdigit(*fmt))
+				precision = skip_atoi(&fmt);
+			else if (*fmt == '*') {
+				++fmt;
+				/* it's the next argument */
+				precision = va_arg(args, int);
+			}
+			if (precision < 0)
+				precision = 0;
+		}
+
+		/* get the conversion qualifier */
+		qualifier = -1;
+		if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L') {
+			qualifier = *fmt;
+			++fmt;
+		}
+
+		/* default base */
+		base = 10;
+
+		switch (*fmt) {
+		case 'c':
+			if (!(flags & LEFT))
+				while (--field_width > 0)
+					*str++ = ' ';
+			*str++ = (unsigned char)va_arg(args, int);
+			while (--field_width > 0)
+				*str++ = ' ';
+			continue;
+
+		case 's':
+			s = va_arg(args, char *);
+			len = strnlen(s, precision);
+
+			if (!(flags & LEFT))
+				while (len < field_width--)
+					*str++ = ' ';
+			for (i = 0; i < len; ++i)
+				*str++ = *s++;
+			while (len < field_width--)
+				*str++ = ' ';
+			continue;
+
+		case 'p':
+			if (field_width == -1) {
+				field_width = 2 * sizeof(void *);
+				flags |= ZEROPAD;
+			}
+			str = number(str,
+				     (unsigned long)va_arg(args, void *), 16,
+				     field_width, precision, flags);
+			continue;
+
+		case 'n':
+			if (qualifier == 'l') {
+				long *ip = va_arg(args, long *);
+				*ip = (str - buf);
+			} else {
+				int *ip = va_arg(args, int *);
+				*ip = (str - buf);
+			}
+			continue;
+
+		case '%':
+			*str++ = '%';
+			continue;
+
+			/* integer number formats - set up the flags and "break" */
+		case 'o':
+			base = 8;
+			break;
+
+		case 'x':
+			flags |= SMALL;
+			fallthrough;
+		case 'X':
+			base = 16;
+			break;
+
+		case 'd':
+		case 'i':
+			flags |= SIGN;
+		case 'u':
+			break;
+
+		default:
+			*str++ = '%';
+			if (*fmt)
+				*str++ = *fmt;
+			else
+				--fmt;
+			continue;
+		}
+		if (qualifier == 'l')
+			num = va_arg(args, unsigned long);
+		else if (qualifier == 'h') {
+			num = (unsigned short)va_arg(args, int);
+			if (flags & SIGN)
+				num = (short)num;
+		} else if (flags & SIGN)
+			num = va_arg(args, int);
+		else
+			num = va_arg(args, unsigned int);
+		str = number(str, num, base, field_width, precision, flags);
+	}
+	*str = '\0';
+	return str - buf;
+}
+
+int sprintf(char *buf, const char *fmt, ...)
+{
+	va_list args;
+	int i;
+
+	va_start(args, fmt);
+	i = vsprintf(buf, fmt, args);
+	va_end(args);
+	return i;
+}
+
+static struct earlycon *con;
+
+static int puts(const char *s)
+{
+	if (con)
+		return con->put_str(s, con->data);
+	else
+		return 0;
+}
+
+int printf(const char *fmt, ...)
+{
+	char printf_buf[1024];
+	va_list args;
+	int printed;
+
+	va_start(args, fmt);
+	printed = vsprintf(printf_buf, fmt, args);
+	va_end(args);
+
+	puts(printf_buf);
+
+	return printed;
+}
+
+static char *ucs2_to_ascii(char *ascii_str, efi_char16_t *ucs2_str)
+{
+	size_t len = 0;
+
+	while (ucs2_str[len] != 0) {
+		len++;
+	}
+
+	/* Convert each UCS-2 character to ASCII */
+	for (size_t i = 0; i < len; i++) {
+		if (ucs2_str[i] <= 127) {
+			/* Character is in ASCII range */
+			ascii_str[i] = (char)ucs2_str[i];
+		} else {
+			/* Character is outside ASCII range */
+			ascii_str[i] = '?';
+		}
+	}
+	/* Enforce a null-terminate the ASCII string */
+	ascii_str[len] = '\0';
+
+	return ascii_str;
+}
+
+
+/* Convert the UCS-2 string to a ascii string */
+void print_ucs2_string(efi_char16_t *ucs2_str)
+{
+	char ascii_str[1024];
+	char* p = ascii_str;
+
+	ucs2_to_ascii(p, ucs2_str);
+	/* Print the ascii string */
+	printf("%s\n", ascii_str);
+}
+
+static struct earlycon *all_con_types[] = { &pl011, };
+
+void setup_earlycon(struct efi_emulator_param *param)
+{
+	struct earlycon *p;
+	int i;
+
+	for (i = 0; i < sizeof(all_con_types) / sizeof(struct earlycon *); i++) {
+
+		p = all_con_types[i];
+		if (p->match(param, p->name)) {
+			con = p;
+			p->reset(p->data);
+			break;
+		}
+	}
+}
diff --git a/drivers/firmware/efi/efi_emulator/protocol_device_path.c b/drivers/firmware/efi/efi_emulator/protocol_device_path.c
new file mode 100644
index 0000000000000..c31dcb8d0016b
--- /dev/null
+++ b/drivers/firmware/efi/efi_emulator/protocol_device_path.c
@@ -0,0 +1,75 @@
+//SPDX-License-Identifier: GPL-2.0
+#include <linux/efi.h>
+#include <asm/efi.h>
+#include "emulator.h"
+
+static inline bool is_end_node(efi_device_path_protocol_t *node)
+{
+	return node->type == EFI_DEV_END_PATH &&
+		node->sub_type == EFI_DEV_END_ENTIRE;
+}
+
+static inline efi_device_path_protocol_t *
+	next_device_path_node(efi_device_path_protocol_t *node)
+{
+    return (efi_device_path_protocol_t *)((u8 *)node + node->length);
+}
+
+/*
+ * efi_device_path_compare - Compare two EFI device paths
+ *
+ * Return: 0 if equal, otherwise non-zero
+ */
+int efi_device_path_compare(efi_device_path_protocol_t *path1,
+		efi_device_path_protocol_t *path2)
+{
+	efi_device_path_protocol_t *node1 = path1;
+	efi_device_path_protocol_t *node2 = path2;
+
+	while (!is_end_node(node1) && !is_end_node(node2)) {
+		if (node1->type != node2->type ||
+		    node1->sub_type != node2->sub_type ||
+		    node1->length != node2->length)
+			return 1;
+
+		node1 = next_device_path_node(node1);
+		node2 = next_device_path_node(node2);
+	}
+
+	/* Check if both reached the end */
+	if (is_end_node(node1) && is_end_node(node2))
+		return 0;
+
+	return 1;
+}
+
+/*
+ * efi_device_path_size - Calculate the total size of an EFI device path
+ * @path: Pointer to the first EFI_DEVICE_PATH structure
+ *
+ * Return: Total size of the EFI device path
+ */
+size_t efi_device_path_size(efi_device_path_protocol_t *path)
+{
+	efi_device_path_protocol_t *node = path;
+	size_t total_size = 0;
+
+	while (!is_end_node(node)) {
+		total_size += node->length;
+		node = next_device_path_node(node);
+	}
+
+	/* Include the size of the end node */
+	total_size += node->length;
+
+	return total_size;
+}
+
+int efi_device_path_clone(efi_device_path_protocol_t *dst,
+		efi_device_path_protocol_t *dp)
+{
+	size_t sz = efi_device_path_size(dp);
+
+	memcpy((void *)dst, (void *)dp, sz);
+	return 0;
+}
diff --git a/drivers/firmware/efi/efi_emulator/protocol_simple_text_output.c b/drivers/firmware/efi/efi_emulator/protocol_simple_text_output.c
new file mode 100644
index 0000000000000..6544bfc7f43dc
--- /dev/null
+++ b/drivers/firmware/efi/efi_emulator/protocol_simple_text_output.c
@@ -0,0 +1,50 @@
+//SPDX-License-Identifier: GPL-2.0
+
+#include "emulator.h"
+
+/* UCS-2 (Universal Coded Character Set) */
+static efi_status_t __efiapi output_string(efi_simple_text_output_protocol_t *simple,
+						       efi_char16_t *str)
+{
+	if (print_enabled)
+		print_ucs2_string(str);
+	return EFI_SUCCESS;
+}
+
+struct simple_text_output_mode {
+	int32_t	max_mode;
+	int32_t	mode;
+	int32_t	attribute;
+	int32_t	cursor_column;
+	int32_t	cursor_row;
+	bool	cursor_visible;
+};
+
+struct simple_text_output_mode output_mode;
+
+static efi_status_t __efiapi  text_reset(
+		efi_simple_text_output_protocol_t *this,
+		bool extended_verification)
+{
+	return EFI_UNSUPPORTED;
+}
+
+static efi_status_t __efiapi text_set_attribute(
+		efi_simple_text_output_protocol_t *this,
+		unsigned int attribute)
+{
+	return EFI_UNSUPPORTED;
+}
+
+efi_simple_text_output_protocol_t text_out = {
+	
+	.reset = text_reset,
+	.output_string = output_string,
+	.query_mode = NULL,
+	.set_mode = NULL,
+	.set_attribute = text_set_attribute,
+	.clean_screen = NULL,
+	.set_cursor_pos = NULL,
+	.enable_cursor = NULL,
+	.mode	= &output_mode,
+};
diff --git a/include/linux/efi_emulator.h b/include/linux/efi_emulator.h
new file mode 100644
index 0000000000000..ef235861f3a85
--- /dev/null
+++ b/include/linux/efi_emulator.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_EFI_EMULATOR_H
+#define _LINUX_EFI_EMULATOR_H
+
+#include <linux/types.h>
+#include <linux/nls.h>
+#include <linux/efi.h>
+
+//todo, arch abstraction, for x86, it is efi_info
+struct efi_rt_info {
+	const efi_runtime_services_t	*runtime;	/* EFI runtime services table */
+	unsigned int runtime_version;	/* Runtime services version */
+	u32 runtime_supported_mask;
+	/* Build systab tables from the following */
+	unsigned int systab_nr_tables;
+	efi_config_table_t systab_tables[20];
+	struct efi_boot_memmap	memmap;
+};
+
+/* 1st kernel passes information through this struct */
+struct efi_emulator_param {
+	unsigned long sp;
+	/* Should be page-aligned */
+	unsigned long load_address;
+	unsigned int sz_in_byte;
+	wchar_t cmdline[512];
+	bool noefi_boot;
+	bool print_enabled;
+	char earlycon_name[16];
+	phys_addr_t earlycon_reg_base;
+	unsigned long earlycon_reg_sz;
+
+	bool mmu_on;
+	/* root of pgtable */
+	phys_addr_t pgd_root;
+	phys_addr_t kernel_img_start;
+	unsigned long kernel_img_sz;
+	phys_addr_t dtb;
+	phys_addr_t mempool_start;
+	unsigned long mempool_sz;
+	/* The last struct */
+	struct efi_rt_info rt_info;
+};
+
+extern unsigned char _efi_emulator_start[], _efi_emulator_end[];
+#endif
diff --git a/include/linux/kexec.h b/include/linux/kexec.h
index f0e9f8eda7a3c..cff6b6869498b 100644
--- a/include/linux/kexec.h
+++ b/include/linux/kexec.h
@@ -325,6 +325,7 @@ struct kimage {
 	unsigned int hotplug_support:1;
 #endif
 
+	bool is_pe;
 #ifdef ARCH_HAS_KIMAGE_ARCH
 	struct kimage_arch arch;
 #endif
@@ -462,6 +463,7 @@ static inline int arch_kexec_post_alloc_pages(void *vaddr, unsigned int pages, g
 static inline void arch_kexec_pre_free_pages(void *vaddr, unsigned int pages) { }
 #endif
 
+extern phys_addr_t arch_emulator_prepare_pgtable(struct kimage *kimage);
 extern bool kexec_file_dbg_print;
 
 #define kexec_dprintk(fmt, arg...) \
