diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig
index c981be17d3c0..1933e186a9c2 100644
--- a/drivers/firmware/efi/Kconfig
+++ b/drivers/firmware/efi/Kconfig
@@ -129,6 +129,15 @@ config EFI_TEST
 	  Say Y here to enable the runtime services support via /dev/efi_test.
 	  If unsure, say N.
 
+config EFI_MMC_PROXY
+	bool "Expose Embedded MMC host protocol to the firmware"
+	depends on EFI && (ARM || ARM64)
+	help
+	  This driver exposes the MMC host whose DT node is annotated with the
+	  "linux,uefi-varstore" property to the firmware via a UEFI compatible
+	  protocol. This allows the EFI variable store to reside on an MMC
+	  volume that is owned by the OS at runtime.
+
 endmenu
 
 config UEFI_CPER
diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile
index c8a439f6d715..88fdf0e13d6b 100644
--- a/drivers/firmware/efi/Makefile
+++ b/drivers/firmware/efi/Makefile
@@ -26,3 +26,4 @@ arm-obj-$(CONFIG_EFI)			:= arm-init.o arm-runtime.o
 obj-$(CONFIG_ARM)			+= $(arm-obj-y)
 obj-$(CONFIG_ARM64)			+= $(arm-obj-y)
 obj-$(CONFIG_EFI_CAPSULE_LOADER)	+= capsule-loader.o
+obj-$(CONFIG_EFI_MMC_PROXY)		+= mmc-proxy.o
diff --git a/drivers/firmware/efi/arm-init.c b/drivers/firmware/efi/arm-init.c
index 8efe13075c92..7a57253975a5 100644
--- a/drivers/firmware/efi/arm-init.c
+++ b/drivers/firmware/efi/arm-init.c
@@ -56,9 +56,11 @@ static phys_addr_t efi_to_phys(unsigned long addr)
 }
 
 static __initdata unsigned long screen_info_table = EFI_INVALID_TABLE_ADDR;
+unsigned long __initdata mmc_fv_emulation_table = EFI_INVALID_TABLE_ADDR;
 
 static __initdata efi_config_table_type_t arch_tables[] = {
 	{LINUX_EFI_ARM_SCREEN_INFO_TABLE_GUID, NULL, &screen_info_table},
+	{LINUX_MMC_FV_EMULATION_TABLE_GUID, NULL, &mmc_fv_emulation_table},
 	{NULL_GUID, NULL, NULL}
 };
 
diff --git a/drivers/firmware/efi/mmc-proxy.c b/drivers/firmware/efi/mmc-proxy.c
new file mode 100644
index 000000000000..3702bb2a5af0
--- /dev/null
+++ b/drivers/firmware/efi/mmc-proxy.c
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2016 Linaro Ltd. <ard.biesheuvel@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/efi.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+
+#include <asm/efi.h>
+
+extern unsigned long mmc_fv_emulation_table;
+
+enum emmc_state {
+	mmc_invalid_state = 0,
+	mmc_hw_initialization_state,
+	mmc_idle_state,
+	mmc_ready_state,
+	mmc_identification_state,
+	mmc_standby_state,
+	mmc_transfer_state,
+	mmc_sending_data_state,
+	mmc_receive_data_state,
+	mmc_programming_state,
+	mmc_disconnect_state,
+};
+
+static char const emmc_state_string[][28] = {
+	"mmc_invalid_state",
+	"mmc_hw_initialization_state",
+	"mmc_idle_state",
+	"mmc_ready_state",
+	"mmc_identification_state",
+	"mmc_standby_state",
+	"mmc_transfer_state",
+	"mmc_sending_data_state",
+	"mmc_receive_data_state",
+	"mmc_programming_state",
+	"mmc_disconnect_state",
+};
+
+/*
+ * This MMC host protocol version is not in the UEFI spec, and lives under
+ * EmbeddedPkg/ in Tianocore/EDK2.
+ */
+struct emmc_host_protocol
+{
+	u32		revision;
+	bool		(*is_card_present)(struct emmc_host_protocol*);
+	bool		(*is_read_only)(struct emmc_host_protocol*);
+	efi_status_t	(*build_device_path)(struct emmc_host_protocol*,
+					     void*);
+	efi_status_t	(*notify_state)(struct emmc_host_protocol*,
+					enum emmc_state);
+	efi_status_t	(*send_command)(struct emmc_host_protocol*,
+					u32, u32);
+	efi_status_t	(*receive_response)(struct emmc_host_protocol*,
+					    u32, u32*);
+	efi_status_t	(*read_block_data)(struct emmc_host_protocol*,
+					   u64, unsigned long, u32*);
+	efi_status_t	(*write_block_data)(struct emmc_host_protocol*,
+					    u64, unsigned long, u32*);
+};
+
+static bool mmc_is_card_present(struct emmc_host_protocol *this)
+{
+	return true;
+}
+
+static bool mmc_is_read_only(struct emmc_host_protocol *this)
+{
+	return false;
+}
+
+static efi_status_t mmc_build_device_path(struct emmc_host_protocol *this,
+					  void *dpp)
+{
+	return EFI_UNSUPPORTED;
+}
+
+static efi_status_t mmc_notify_state(struct emmc_host_protocol *this,
+				     enum emmc_state state)
+{
+	struct efi_simd_reg_stash simd_stash;
+
+	arch_efi_stash_simd_regs(&simd_stash);
+	arch_efi_call_virt_teardown();
+
+	pr_warn("mmc-proxy: entered %s state\n", emmc_state_string[state]);
+	might_sleep();
+
+	arch_efi_call_virt_setup();
+	arch_efi_unstash_simd_regs(&simd_stash);
+
+	return EFI_SUCCESS;
+}
+
+static efi_status_t mmc_send_command(struct emmc_host_protocol *this, u32 cmd,
+				     u32 arg)
+{
+	struct efi_simd_reg_stash simd_stash;
+
+	arch_efi_stash_simd_regs(&simd_stash);
+	arch_efi_call_virt_teardown();
+
+	pr_warn("mmc-proxy: send command %d (0x%x)\n", cmd, arg);
+	might_sleep();
+
+	arch_efi_call_virt_setup();
+	arch_efi_unstash_simd_regs(&simd_stash);
+	return EFI_NOT_FOUND;
+}
+
+static efi_status_t mmc_receive_response(struct emmc_host_protocol *this,
+					 u32 mmc_response_type, u32 *buffer)
+{
+	struct efi_simd_reg_stash simd_stash;
+	u32 response[4] = {};
+
+	arch_efi_stash_simd_regs(&simd_stash);
+	arch_efi_call_virt_teardown();
+
+	pr_warn("mmc-proxy: receive response %d\n", mmc_response_type);
+	might_sleep();
+
+	arch_efi_call_virt_setup();
+	arch_efi_unstash_simd_regs(&simd_stash);
+	memcpy(buffer, response, sizeof(response));
+
+	return EFI_NOT_FOUND;
+}
+
+static efi_status_t mmc_read_block_data(struct emmc_host_protocol *this,
+					u64 lba, unsigned long len,
+					u32 *buffer)
+{
+	struct efi_simd_reg_stash simd_stash;
+	void *bounce;
+
+	arch_efi_stash_simd_regs(&simd_stash);
+	arch_efi_call_virt_teardown();
+
+	bounce = kmalloc(len, GFP_KERNEL);
+
+	pr_warn("mmc-proxy: read block data lba==%lld, len==%ld\n", lba, len);
+	might_sleep();
+
+	arch_efi_call_virt_setup();
+	arch_efi_unstash_simd_regs(&simd_stash);
+	memcpy(buffer, bounce, len);
+	kfree(bounce);
+
+	return EFI_NOT_FOUND;
+}
+
+static efi_status_t mmc_write_block_data(struct emmc_host_protocol *this,
+					 u64 lba, unsigned long len,
+					 u32 *buffer)
+{
+	struct efi_simd_reg_stash simd_stash;
+	void *bounce;
+
+	/*
+	 * 'buffer' may point to memory that is mapped via the EFI page tables,
+	 * so copy the data before switching back to the ordinary mappings.
+	 */
+	bounce = kmalloc(len, GFP_ATOMIC);
+	if (!bounce)
+		return EFI_OUT_OF_RESOURCES;
+	memcpy(bounce, buffer, len);
+
+	arch_efi_stash_simd_regs(&simd_stash);
+	arch_efi_call_virt_teardown();
+
+	pr_warn("mmc-proxy: write block data lba==%lld, len==%ld\n", lba, len);
+	might_sleep();
+
+	kfree(bounce);
+	arch_efi_call_virt_setup();
+	arch_efi_unstash_simd_regs(&simd_stash);
+	return EFI_NOT_FOUND;
+}
+
+static const struct emmc_host_protocol emmc_proxy =
+{
+	0x00010001,		// revision 1.1
+	mmc_is_card_present,
+	mmc_is_read_only,
+	mmc_build_device_path,
+	mmc_notify_state,
+	mmc_send_command,
+	mmc_receive_response,
+	mmc_read_block_data,
+	mmc_write_block_data
+};
+
+static int __init emmc_proxy_install(void)
+{
+	unsigned long *p;
+
+	if (mmc_fv_emulation_table == EFI_INVALID_TABLE_ADDR)
+		return 0;
+
+	p = memremap(mmc_fv_emulation_table, sizeof(*p), MEMREMAP_WB);
+	if (!p)
+		return -ENOMEM;
+
+	/*
+	 * We expect a NULL value here, since the firmware should have cleared
+	 * this value in its ExitBootServices() handler.
+	 */
+	if (!WARN_ON(*p != 0))
+		*p = (unsigned long)&emmc_proxy;
+
+	memunmap(p);
+	return 0;
+}
+late_initcall(emmc_proxy_install);
diff --git a/include/linux/efi.h b/include/linux/efi.h
index 2d089487d2da..25a4ae648b6f 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -599,6 +599,7 @@ void efi_native_runtime_setup(void);
  */
 #define LINUX_EFI_ARM_SCREEN_INFO_TABLE_GUID	EFI_GUID(0xe03fc20a, 0x85dc, 0x406e,  0xb9, 0x0e, 0x4a, 0xb5, 0x02, 0x37, 0x1d, 0x95)
 #define LINUX_EFI_LOADER_ENTRY_GUID		EFI_GUID(0x4a67b082, 0x0a4c, 0x41cf,  0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f)
+#define LINUX_MMC_FV_EMULATION_TABLE_GUID	EFI_GUID(0x44cd9912, 0xfda8, 0x4b7d,  0xa6, 0x13, 0x1b, 0x61, 0x34, 0xfc, 0xdc, 0x4a)
 
 typedef struct {
 	efi_guid_t guid;
