diff --git a/docs/misc/xen-command-line.pandoc b/docs/misc/xen-command-line.pandoc
index 7c72e31032..b2dfd64b48 100644
--- a/docs/misc/xen-command-line.pandoc
+++ b/docs/misc/xen-command-line.pandoc
@@ -886,7 +886,7 @@ disable it (edid=no). This option should not normally be required
 except for debugging purposes.
 
 ### efi
-    = List of [ rs=<bool>, attr=no|uc ]
+    = List of [ rs=<bool>, attr=no|uc, mapbs=<bool> ]
 
 Controls for interacting with the system Extended Firmware Interface.
 
@@ -899,6 +899,10 @@ Controls for interacting with the system Extended Firmware Interface.
     leave the memory regions unmapped, while `attr=uc` will map them as fully
     uncacheable.
 
+*   The `mapbs=` boolean controls whether EfiBootServices{Code,Data} should
+    remain mapped after Exit() BootServices call. By default those memory regions
+    will not be mapped after Exit() BootServices call.
+
 ### ept
 > `= List of [ ad=<bool>, pml=<bool> ]`
 
diff --git a/xen/arch/x86/boot/head.S b/xen/arch/x86/boot/head.S
index d78bed394a..b13f46c70e 100644
--- a/xen/arch/x86/boot/head.S
+++ b/xen/arch/x86/boot/head.S
@@ -224,9 +224,10 @@ __efi64_mb2_start:
         jmp     x86_32_switch
 
 .Lefi_multiboot2_proto:
-        /* Zero EFI SystemTable and EFI ImageHandle addresses. */
+        /* Zero EFI SystemTable, EFI ImageHandle and cmdline addresses. */
         xor     %esi,%esi
         xor     %edi,%edi
+        xor     %rdx,%rdx
 
         /* Skip Multiboot2 information fixed part. */
         lea     (MB2_fixed_sizeof+MULTIBOOT2_TAG_ALIGN-1)(%rbx),%ecx
@@ -241,7 +242,7 @@ __efi64_mb2_start:
 
         /* Are EFI boot services available? */
         cmpl    $MULTIBOOT2_TAG_TYPE_EFI_BS,MB2_tag_type(%rcx)
-        jne     .Lefi_mb2_st
+        jne     .Lefi_mb2_cmdline
 
         /* We are on EFI platform and EFI boot services are available. */
         incb    efi_platform(%rip)
@@ -253,6 +254,14 @@ __efi64_mb2_start:
         incb    skip_realmode(%rip)
         jmp     .Lefi_mb2_next_tag
 
+.Lefi_mb2_cmdline:
+        /* Get cmdline address from Multiboot2 information. */
+        cmpl    $MULTIBOOT2_TAG_TYPE_CMDLINE,MB2_tag_type(%rcx)
+        jne     .Lefi_mb2_st
+
+        lea     MB2_string(%rcx),%rdx
+        jmp     .Lefi_mb2_next_tag
+
 .Lefi_mb2_st:
         /* Get EFI SystemTable address from Multiboot2 information. */
         cmpl    $MULTIBOOT2_TAG_TYPE_EFI64,MB2_tag_type(%rcx)
@@ -264,6 +273,11 @@ __efi64_mb2_start:
         cmove   MB2_efi64_ih(%rcx),%rdi
         je      .Lefi_mb2_next_tag
 
+        /* Get cmdline address from Multiboot2 information. */
+        cmpl    $MULTIBOOT2_TAG_TYPE_CMDLINE,MB2_tag_type(%rcx)
+        cmove   MB2_efi64_ih(%rcx),%rdi
+        je      .Lefi_mb2_next_tag
+
         /* Is it the end of Multiboot2 information? */
         cmpl    $MULTIBOOT2_TAG_TYPE_END,MB2_tag_type(%rcx)
         je      .Lrun_bs
@@ -327,7 +341,8 @@ __efi64_mb2_start:
 
         /*
          * efi_multiboot2() is called according to System V AMD64 ABI:
-         *   - IN:  %rdi - EFI ImageHandle, %rsi - EFI SystemTable.
+         *   - IN:  %rdi - EFI ImageHandle, %rsi - EFI SystemTable,
+         *          %rdx - MB2 cmdline.
          */
         call    efi_multiboot2
 
diff --git a/xen/arch/x86/efi/efi-boot.h b/xen/arch/x86/efi/efi-boot.h
index 7a13a30bc0..df5e98e6bc 100644
--- a/xen/arch/x86/efi/efi-boot.h
+++ b/xen/arch/x86/efi/efi-boot.h
@@ -315,8 +315,10 @@ static void __init efi_arch_handle_cmdline(CHAR16 *image_name,
         name.s = "xen";
     place_string(&mbi.cmdline, name.s);
 
-    if ( mbi.cmdline )
+    if ( mbi.cmdline ) {
         mbi.flags |= MBI_CMDLINE;
+        efi_early_parse_cmdline(mbi.cmdline);
+    }
     /*
      * These must not be initialized statically, since the value must
      * not get relocated when processing base relocations later.
@@ -675,7 +677,8 @@ static bool __init efi_arch_use_config_file(EFI_SYSTEM_TABLE *SystemTable)
 
 static void __init efi_arch_flush_dcache_area(const void *vaddr, UINTN size) { }
 
-void __init efi_multiboot2(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
+void __init efi_multiboot2(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable,
+                           const char *cmdline)
 {
     EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
     UINTN cols, gop_mode = ~0, rows;
@@ -685,6 +688,9 @@ void __init efi_multiboot2(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable
 
     efi_init(ImageHandle, SystemTable);
 
+    if (cmdline)
+        efi_early_parse_cmdline(cmdline);
+
     efi_console_set_mode();
 
     if ( StdOut->QueryMode(StdOut, StdOut->Mode->Mode,
diff --git a/xen/arch/x86/x86_64/asm-offsets.c b/xen/arch/x86/x86_64/asm-offsets.c
index 33930ce97c..b5be6ca30f 100644
--- a/xen/arch/x86/x86_64/asm-offsets.c
+++ b/xen/arch/x86/x86_64/asm-offsets.c
@@ -163,6 +163,7 @@ void __dummy__(void)
     OFFSET(MB2_mem_lower, multiboot2_tag_basic_meminfo_t, mem_lower);
     OFFSET(MB2_efi64_st, multiboot2_tag_efi64_t, pointer);
     OFFSET(MB2_efi64_ih, multiboot2_tag_efi64_ih_t, pointer);
+    OFFSET(MB2_string, multiboot2_tag_string_t, string);
     BLANK();
 
     DEFINE(l2_identmap_sizeof, sizeof(l2_identmap));
diff --git a/xen/common/efi/boot.c b/xen/common/efi/boot.c
index 79193784ff..a8514bbb5f 100644
--- a/xen/common/efi/boot.c
+++ b/xen/common/efi/boot.c
@@ -126,6 +126,7 @@ static bool read_file(EFI_FILE_HANDLE dir_handle, CHAR16 *name,
 static size_t wstrlen(const CHAR16 * s);
 static int set_color(u32 mask, int bpp, u8 *pos, u8 *sz);
 static bool match_guid(const EFI_GUID *guid1, const EFI_GUID *guid2);
+static void efi_early_parse_cmdline(const char *cmdline);
 
 static void efi_init(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable);
 static void efi_console_set_mode(void);
@@ -1386,6 +1387,10 @@ efi_start(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
 
 static bool __initdata efi_map_uc;
 
+/*
+ * Warning: this function must be idempotent as it may be called multiple
+ * times.
+ */
 static int __init parse_efi_param(const char *s)
 {
     const char *ss;
@@ -1412,16 +1417,32 @@ static int __init parse_efi_param(const char *s)
             else
                 rc = -EINVAL;
         }
+        else if ( (val = parse_boolean("mapbs", s, ss)) >= 0 )
+        {
+            map_bs = val;
+        }
         else
             rc = -EINVAL;
 
         s = ss + 1;
-    } while ( *ss );
+        /*
+         * End parsing on both '\0' and ' ',
+         * to make efi_early_parse_cmdline simpler.
+         */
+    } while ( *ss && *ss != ' ');
 
     return rc;
 }
 custom_param("efi", parse_efi_param);
 
+/* Extract efi= param early in the boot */
+static void __init efi_early_parse_cmdline(const char *cmdline)
+{
+    const char *efi_opt = strstr(cmdline, "efi=");
+    if ( efi_opt )
+        parse_efi_param(efi_opt + 4);
+}
+
 #ifndef USE_SET_VIRTUAL_ADDRESS_MAP
 static __init void copy_mapping(unsigned long mfn, unsigned long end,
                                 bool (*is_valid)(unsigned long smfn,
