Skip to contentSkip to conversation

efi/x86: add support for generic EFI mixed mode boot

RFC PATCH Superseded Ard Biesheuvel 2 participants 16 messages Feb 13, 2020 – Feb 14, 2020

Subsystems (2)

v1 of 3 Superseded Latest is v3 →

lore
Share & tools
lore

Ard Biesheuvel

author maintainer
#0 lore raw

cover efi/x86: add support for generic EFI mixed mode boot

This series is another part of my effort to reduce the level of knowledge on the part of the bootloader or firmware of internal per-architecture details regarding where/how the kernel is loaded and where its initrd and other context data are passed.

The x86 architecture has a so-called 'EFI handover protocol', which defines how the bootparams struct should be populated, and how it should be interpreted to figure out where to load the kernel, and at which offset in the binary the entrypoint is located. This scheme allows the initrd to be loaded beforehand, and allows 32-bit firmware to invoke a 64-bit kernel via a special entrypoint that manages the state transitions between the two execution modes.

Due to this, x86 loaders currently do not rely on LoadImage and StartImage, and therefore, are forced to re-implement things like image authentication for secure boot and taking the measurements for measured boot in their open coded clones of these routines.

My previous series on this topic [0] implements a generic way to load the initrd from any source supported by the loader without relying on something like device trees or bootparams structures, and so native boot should not need the EFI handover protocol anymore after those change are merged.

What remains is mixed mode boot, which also needs the EFI handover protocol regardless of whether an initrd is loaded or not. So let's get rid of that requirement, and take advantage of the fact that EDK2 based firmware does support LoadImage() for X64 binaries on IA32 firmware, which means we can rely on the secure boot and measured boot checks being performed by the firmware. The only thing we need to put on top is a way to discover the non-native entrypoint into the binary in a way that does not rely on x86 specific headers and data structures.

So let's introduce a new .compat header in the PE/COFF metadata of the bzImage, and populate it with a <machine type, entrypoint> tuple, allowing a generic EFI loader to decide whether the entrypoint supports its native machine type, and invoke it as an ordinary EFI application entrypoint. Since we will not be passing a bootparams structure, we need to discover the base of the image (which contains the setup header) via the loaded image protocol before we can enter the kernel in 32-bit mode at startup_32()

A loader implementation for OVMF can be found at [1]. Note that this loader code is fully generic, and could be used without modifications if other architectures ever emerge that support kernels that can be invoked from a non-native (but cross-type supported) loader.

[0] https://lore.kernel.org/linux-arm-kernel/[email protected]/
[1] https://github.com/ardbiesheuvel/edk2/commits/linux-efi-generic

Ard Biesheuvel (3):
efi/x86: drop redundant .bss section
efi/x86: add true mixed mode entry point into .compat section efi/x86: implement mixed mode boot without the handover protocol

 arch/x86/boot/Makefile             |  2 +-
 arch/x86/boot/compressed/head_64.S | 61 +++++++++++++++++-
 arch/x86/boot/header.S             | 23 ++++---
 arch/x86/boot/tools/build.c        | 67 +++++++++++++-------
 4 files changed, 115 insertions(+), 38 deletions(-)
Signature · 1 line
2.17.1
author maintainer
#1 lore raw

RFC PATCH 1/3 efi/x86: drop redundant .bss section

In commit c7fb93ec51d462ec ("x86/efi: Include a .bss section within the PE/COFF headers"), we added a separate .bss section to the PE/COFF header of the compressed kernel describing the static memory footprint of the decompressor, to ensure that it has enough headroom to decompress itself.

We can achieve the exact same result by increasing the virtual size of the .text section, without changing the raw size, which, as per the PE/COFF specification, requires the loader to zero initialize the delta.

Doing so frees up a slot in the section table, which we will use later to describe the mixed mode entrypoint.

---
 arch/x86/boot/header.S      | 21 +-----------
 arch/x86/boot/tools/build.c | 35 ++++++++------------
 2 files changed, 14 insertions(+), 42 deletions(-)
arch/x86/boot/header.S +1 -20
index 97d9b6d6c1af..d59f6604bb42 100644
@@ -106,7 +106,7 @@ coff_header:
 #else
 	.word	0x8664				# x86-64
 #endif
-	.word	4				# nr_sections
+	.word	3				# nr_sections
 	.long	0 				# TimeDateStamp
 	.long	0				# PointerToSymbolTable
 	.long	1				# NumberOfSymbols
@@ -248,25 +248,6 @@ section_table:
 	.word	0				# NumberOfLineNumbers
 	.long	0x60500020			# Characteristics (section flags)
 
-	#
-	# The offset & size fields are filled in by build.c.
-	#
-	.ascii	".bss"
-	.byte	0
-	.byte	0
-	.byte	0
-	.byte	0
-	.long	0
-	.long	0x0
-	.long	0				# Size of initialized data
-						# on disk
-	.long	0x0
-	.long	0				# PointerToRelocations
-	.long	0				# PointerToLineNumbers
-	.word	0				# NumberOfRelocations
-	.word	0				# NumberOfLineNumbers
-	.long	0xc8000080			# Characteristics (section flags)
-
 #endif /* CONFIG_EFI_STUB */
 
 	# Kernel attributes; used by setup.  This is part 1 of the
arch/x86/boot/tools/build.c +13 -22
index 55e669d29e54..0c8c5a52f1f0 100644
@@ -203,10 +203,12 @@ static void update_pecoff_setup_and_reloc(unsigned int size)
 	put_unaligned_le32(10, &buf[reloc_offset + 4]);
 }
 
-static void update_pecoff_text(unsigned int text_start, unsigned int file_sz)
+static void update_pecoff_text(unsigned int text_start, unsigned int file_sz,
+			       unsigned int init_sz)
 {
 	unsigned int pe_header;
 	unsigned int text_sz = file_sz - text_start;
+	unsigned int bss_sz = init_sz - file_sz;
 
 	pe_header = get_unaligned_le32(&buf[0x3c]);
 
@@ -216,28 +218,19 @@ static void update_pecoff_text(unsigned int text_start, unsigned int file_sz)
 	 */
 	put_unaligned_le32(file_sz - 512, &buf[pe_header + 0x1c]);
 
-	/*
-	 * Address of entry point for PE/COFF executable
-	 */
-	put_unaligned_le32(text_start + efi_pe_entry, &buf[pe_header + 0x28]);
-
-	update_pecoff_section_header(".text", text_start, text_sz);
-}
-
-static void update_pecoff_bss(unsigned int file_sz, unsigned int init_sz)
-{
-	unsigned int pe_header;
-	unsigned int bss_sz = init_sz - file_sz;
-
-	pe_header = get_unaligned_le32(&buf[0x3c]);
-
 	/* Size of uninitialized data */
 	put_unaligned_le32(bss_sz, &buf[pe_header + 0x24]);
 
 	/* Size of image */
 	put_unaligned_le32(init_sz, &buf[pe_header + 0x50]);
 
-	update_pecoff_section_header_fields(".bss", file_sz, bss_sz, 0, 0);
+	/*
+	 * Address of entry point for PE/COFF executable
+	 */
+	put_unaligned_le32(text_start + efi_pe_entry, &buf[pe_header + 0x28]);
+
+	update_pecoff_section_header_fields(".text", text_start, text_sz + bss_sz,
+					    text_sz, text_start);
 }
 
 static int reserve_pecoff_reloc_section(int c)
@@ -278,9 +271,8 @@ static void efi_stub_entry_update(void)
 
 static inline void update_pecoff_setup_and_reloc(unsigned int size) {}
 static inline void update_pecoff_text(unsigned int text_start,
-				      unsigned int file_sz) {}
-static inline void update_pecoff_bss(unsigned int file_sz,
-				     unsigned int init_sz) {}
+				      unsigned int file_sz,
+				      unsigned int init_sz) {}
 static inline void efi_stub_defaults(void) {}
 static inline void efi_stub_entry_update(void) {}
 
@@ -406,9 +398,8 @@ int main(int argc, char ** argv)
 	buf[0x1f1] = setup_sectors-1;
 	put_unaligned_le32(sys_size, &buf[0x1f4]);
 
-	update_pecoff_text(setup_sectors * 512, i + (sys_size * 16));
 	init_sz = get_unaligned_le32(&buf[0x260]);
-	update_pecoff_bss(i + (sys_size * 16), init_sz);
+	update_pecoff_text(setup_sectors * 512, i + (sys_size * 16), init_sz);
 
 	efi_stub_entry_update();
 
Signature · 1 line
2.17.1
author maintainer
#2 lore raw

RFC PATCH 2/3 efi/x86: add true mixed mode entry point into .compat section

In reply to Ard Biesheuvel

Currently, mixed mode is closely tied to the EFI handover protocol and relies on intimate knowledge of the bootparams structure, setup header etc, all of which are rather byzantine and entirely specific to x86.

Even though no other EFI supported architectures are currently known that could support something like mixed mode, it makes sense to abstract a bit from this, and make it part of a generic Linux on EFI boot protocol.

To that end, add a .compat section to the mixed mode binary, and populate it with the PE machine type and entry point address, allowing firmware implementations to match it to their native machine type, and invoke non-native binaries using a secondary entry point.

---
 arch/x86/boot/Makefile      |  2 +-
 arch/x86/boot/header.S      | 20 +++++++++++-
 arch/x86/boot/tools/build.c | 32 +++++++++++++++++++-
 3 files changed, 51 insertions(+), 3 deletions(-)
arch/x86/boot/Makefile +1 -1
index 012b82fc8617..ef9e1f2c836c 100644
@@ -88,7 +88,7 @@ $(obj)/vmlinux.bin: $(obj)/compressed/vmlinux FORCE
 
 SETUP_OBJS = $(addprefix $(obj)/,$(setup-y))
 
-sed-zoffset := -e 's/^\([0-9a-fA-F]*\) [a-zA-Z] \(startup_32\|startup_64\|efi32_stub_entry\|efi64_stub_entry\|efi_pe_entry\|input_data\|kernel_info\|_end\|_ehead\|_text\|z_.*\)$$/\#define ZO_\2 0x\1/p'
+sed-zoffset := -e 's/^\([0-9a-fA-F]*\) [a-zA-Z] \(startup_32\|startup_64\|efi32_stub_entry\|efi64_stub_entry\|efi_pe_entry\|efi32_pe_entry\|input_data\|kernel_info\|_end\|_ehead\|_text\|z_.*\)$$/\#define ZO_\2 0x\1/p'
 
 quiet_cmd_zoffset = ZOFFSET $@
       cmd_zoffset = $(NM) $< | sed -n $(sed-zoffset) > $@
arch/x86/boot/header.S +19 -1
index d59f6604bb42..76a485013771 100644
@@ -106,7 +106,7 @@ coff_header:
 #else
 	.word	0x8664				# x86-64
 #endif
-	.word	3				# nr_sections
+	.word	section_count			# nr_sections
 	.long	0 				# TimeDateStamp
 	.long	0				# PointerToSymbolTable
 	.long	1				# NumberOfSymbols
@@ -230,6 +230,23 @@ section_table:
 	.word	0				# NumberOfLineNumbers
 	.long	0x42100040			# Characteristics (section flags)
 
+#ifdef CONFIG_EFI_MIXED
+	#
+	# The offset & size fields are filled in by build.c.
+	#
+	.asciz	".compat"
+	.long	0
+	.long	0x0
+	.long	0				# Size of initialized data
+						# on disk
+	.long	0x0
+	.long	0				# PointerToRelocations
+	.long	0				# PointerToLineNumbers
+	.word	0				# NumberOfRelocations
+	.word	0				# NumberOfLineNumbers
+	.long	0x60500020			# Characteristics (section flags)
+#endif
+
 	#
 	# The offset & size fields are filled in by build.c.
 	#
@@ -248,6 +265,7 @@ section_table:
 	.word	0				# NumberOfLineNumbers
 	.long	0x60500020			# Characteristics (section flags)
 
+	.set	section_count, (. - section_table) / 40
 #endif /* CONFIG_EFI_STUB */
 
 	# Kernel attributes; used by setup.  This is part 1 of the
arch/x86/boot/tools/build.c +31 -1
index 0c8c5a52f1f0..707b979566ca 100644
@@ -53,9 +53,16 @@ u8 buf[SETUP_SECT_MAX*512];
 
 #define PECOFF_RELOC_RESERVE 0x20
 
+#ifdef CONFIG_EFI_MIXED
+#define PECOFF_COMPAT_RESERVE 0x20
+#else
+#define PECOFF_COMPAT_RESERVE 0x0
+#endif
+
 unsigned long efi32_stub_entry;
 unsigned long efi64_stub_entry;
 unsigned long efi_pe_entry;
+unsigned long efi32_pe_entry;
 unsigned long kernel_info;
 unsigned long startup_64;
 
@@ -189,7 +196,8 @@ static void update_pecoff_section_header(char *section_name, u32 offset, u32 siz
 static void update_pecoff_setup_and_reloc(unsigned int size)
 {
 	u32 setup_offset = 0x200;
-	u32 reloc_offset = size - PECOFF_RELOC_RESERVE;
+	u32 reloc_offset = size - PECOFF_RELOC_RESERVE - PECOFF_COMPAT_RESERVE;
+	u32 compat_offset = reloc_offset + PECOFF_RELOC_RESERVE;
 	u32 setup_size = reloc_offset - setup_offset;
 
 	update_pecoff_section_header(".setup", setup_offset, setup_size);
@@ -201,6 +209,20 @@ static void update_pecoff_setup_and_reloc(unsigned int size)
 	 */
 	put_unaligned_le32(reloc_offset + 10, &buf[reloc_offset]);
 	put_unaligned_le32(10, &buf[reloc_offset + 4]);
+
+#ifdef CONFIG_EFI_MIXED
+	update_pecoff_section_header(".compat", compat_offset, PECOFF_COMPAT_RESERVE);
+
+	/*
+	 * Put the IA-32 machine type (0x14c) and the associated entry point
+	 * address in the .compat section, so loaders can figure out which other
+	 * execution modes this image supports.
+	 */
+	buf[compat_offset] = 0x1;
+	buf[compat_offset + 1] = 0x8;
+	put_unaligned_le16(0x14c, &buf[compat_offset + 2]);
+	put_unaligned_le32(efi32_pe_entry + size, &buf[compat_offset + 4]);
+#endif
 }
 
 static void update_pecoff_text(unsigned int text_start, unsigned int file_sz,
@@ -282,6 +304,12 @@ static inline int reserve_pecoff_reloc_section(int c)
 }
 #endif /* CONFIG_EFI_STUB */
 
+static int reserve_pecoff_compat_section(int c)
+{
+	/* Reserve 0x20 bytes for .compat section */
+	memset(buf+c, 0, PECOFF_COMPAT_RESERVE);
+	return PECOFF_COMPAT_RESERVE;
+}
 
 /*
  * Parse zoffset.h and find the entry points. We could just #include zoffset.h
@@ -314,6 +342,7 @@ static void parse_zoffset(char *fname)
 		PARSE_ZOFS(p, efi32_stub_entry);
 		PARSE_ZOFS(p, efi64_stub_entry);
 		PARSE_ZOFS(p, efi_pe_entry);
+		PARSE_ZOFS(p, efi32_pe_entry);
 		PARSE_ZOFS(p, kernel_info);
 		PARSE_ZOFS(p, startup_64);
 
@@ -357,6 +386,7 @@ int main(int argc, char ** argv)
 		die("Boot block hasn't got boot flag (0xAA55)");
 	fclose(file);
 
+	c += reserve_pecoff_compat_section(c);
 	c += reserve_pecoff_reloc_section(c);
 
 	/* Pad unused space with zeros */
Signature · 1 line
2.17.1
#3 lore raw

Re: [RFC PATCH 2/3] efi/x86: add true mixed mode entry point into .compat section

Quoted 10 lines · Ard Biesheuvel

Currently, mixed mode is closely tied to the EFI handover protocol and relies on intimate knowledge of the bootparams structure, setup header etc, all of which are rather byzantine and entirely specific to x86.

Even though no other EFI supported architectures are currently known that could support something like mixed mode, it makes sense to abstract a bit from this, and make it part of a generic Linux on EFI boot protocol.

Quoted 4 lines

To that end, add a .compat section to the mixed mode binary, and populate it with the PE machine type and entry point address, allowing firmware implementations to match it to their native machine type, and invoke non-native binaries using a secondary entry point.

This patch refers to efi32_pe_entry which is only defined in the next one.

author maintainer
#4 lore raw

Re: [RFC PATCH 2/3] efi/x86: add true mixed mode entry point into .compat section

Quoted 12 lines · Arvind Sankar
Quoted 10 lines · Ard Biesheuvel

Currently, mixed mode is closely tied to the EFI handover protocol and relies on intimate knowledge of the bootparams structure, setup header etc, all of which are rather byzantine and entirely specific to x86.

Even though no other EFI supported architectures are currently known that could support something like mixed mode, it makes sense to abstract a bit from this, and make it part of a generic Linux on EFI boot protocol.

Quoted 7 lines
Quoted 4 lines

To that end, add a .compat section to the mixed mode binary, and populate it with the PE machine type and entry point address, allowing firmware implementations to match it to their native machine type, and invoke non-native binaries using a secondary entry point.

This patch refers to efi32_pe_entry which is only defined in the next one.

Indeed. So the .compat section will be emitted with a bogus entry point when applying this patch but not the next one, so perhaps I should simply reorder them .

author maintainer
#5 lore raw

RFC PATCH 3/3 efi/x86: implement mixed mode boot without the handover protocol

In reply to Ard Biesheuvel

Add support for booting 64-bit x86 kernels from 32-bit firmware running on 64-bit capable CPUs without requiring the bootloader to implement the EFI handover protocol, allocate the setup block etc etc, all of which can be done by the stub using code we have already implemented.

Instead, create an ordinary EFI application entrypoint but implemented in 32-bit code, so that it can be invoked by 32-bit firmware, and stash the address of this 32-bit entrypoint in the .compat section where the bootloader can find it.

Note that we use the setup block embedded in the binary to go through startup_32(), but it gets reallocated and copied in efi_pe_entry(), using the same code that runs when the x86 kernel is booted in EFI mode from native firmware. This requires the loaded image protocol to be installed on the kernel image's EFI handle, and point to the kernel image itself and not to its loader. This, in turn, requires the bootloader to use the LoadImage() boot services to load the 64-bit image from 32-bit firmware, which is in fact supported by firmware based on EDK2. (Only StartImage() will fail, and instead, the newly added entrypoint needs to be invoked)

---
 arch/x86/boot/compressed/head_64.S | 61 +++++++++++++++++++-
 1 file changed, 59 insertions(+), 2 deletions(-)
arch/x86/boot/compressed/head_64.S +59 -2
index a4f5561c1c0e..7baaf9c97f5a 100644
@@ -207,8 +207,8 @@ SYM_FUNC_START(startup_32)
 	cmp	$0, %edi
 	jz	1f
 	leal	efi64_stub_entry(%ebp), %eax
-	movl	%esi, %edx
 	movl	efi32_boot_args+4(%ebp), %esi
+	movl	efi32_boot_args+8(%ebp), %edx	// saved bootparams pointer
 1:
 #endif
 	pushl	%eax
@@ -233,6 +233,8 @@ SYM_FUNC_START(efi32_stub_entry)
 1:	pop	%ebp
 	subl	$1b, %ebp
 
+	movl	%esi, efi32_boot_args+8(%ebp)
+2:
 	movl	%ecx, efi32_boot_args(%ebp)
 	movl	%edx, efi32_boot_args+4(%ebp)
 	movb	$0, efi_is64(%ebp)
@@ -249,6 +251,49 @@ SYM_FUNC_START(efi32_stub_entry)
 
 	jmp	startup_32
 SYM_FUNC_END(efi32_stub_entry)
+
+#define ST32_boottime		60 // offsetof(efi_system_table_32_t, boottime)
+#define BS32_handle_protocol	88 // offsetof(efi_boot_services_32_t, handle_protocol)
+#define LI32_image_base		32 // offsetof(efi_loaded_image_32_t, image_base)
+
+	.subsection 1
+	.code32
+SYM_FUNC_START(efi32_pe_entry)
+	pushl	%ebp
+
+	call	3f
+3:	pop	%ebp
+	subl	$3b, %ebp
+
+	/* Get the loaded image protocol pointer from the image handle */
+	subl	$12, %esp			// space for the loaded image pointer
+	pushl	%esp				// pass its address
+	leal	5f(%ebp), %eax
+	pushl	%eax				// pass the GUID address
+	pushl	28(%esp)			// pass the image handle
+
+	movl	36(%esp), %eax			// sys_table
+	movl	ST32_boottime(%eax), %eax	// sys_table->boottime
+	call	*BS32_handle_protocol(%eax)	// sys_table->boottime->handle_protocol
+	cmp	$0, %eax
+	jnz	4f
+
+	movl	32(%esp), %ecx			// image_handle
+	movl	36(%esp), %edx			// sys_table
+	movl	12(%esp), %esi			// loaded_image
+	movl	LI32_image_base(%esi), %esi	// loaded_image->image_base
+	jmp	2b
+
+4:	addl	$24, %esp
+	popl	%ebp
+	ret
+SYM_FUNC_END(efi32_pe_entry)
+
+	/* EFI loaded image protocol GUID */
+5:	.long	0x5B1B31A1
+	.word	0x9562, 0x11d2
+	.byte	0x8E, 0x3F, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B
+	.previous
 #endif
 
 	.code64
@@ -465,11 +510,23 @@ SYM_CODE_END(startup_64)
 SYM_FUNC_START(efi64_stub_entry)
 SYM_FUNC_START_ALIAS(efi_stub_entry)
 	and	$~0xf, %rsp			/* realign the stack */
+#ifdef CONFIG_EFI_MIXED
+	cmpl	$0, %edx
+	jz	0f
+#endif
 	call	efi_main
 	movq	%rax,%rsi
 	movl	BP_code32_start(%esi), %eax
 	leaq	startup_64(%rax), %rax
 	jmp	*%rax
+
+#ifdef CONFIG_EFI_MIXED
+0:	movl	%edi, %ecx		// MS calling convention
+	movl	%esi, %edx
+	call	efi_pe_entry
+1:	hlt
+	jmp	1b
+#endif
 SYM_FUNC_END(efi64_stub_entry)
 SYM_FUNC_END_ALIAS(efi_stub_entry)
 #endif
@@ -641,7 +698,7 @@ SYM_DATA_START_LOCAL(gdt)
 SYM_DATA_END_LABEL(gdt, SYM_L_LOCAL, gdt_end)
 
 #ifdef CONFIG_EFI_MIXED
-SYM_DATA_LOCAL(efi32_boot_args, .long 0, 0)
+SYM_DATA_LOCAL(efi32_boot_args, .long 0, 0, 0)
 SYM_DATA(efi_is64, .byte 1)
 #endif
 
Signature · 1 line
2.17.1
#6 lore raw

Re: [RFC PATCH 3/3] efi/x86: implement mixed mode boot without the handover protocol

Quoted 41 lines · Ard Biesheuvel

Add support for booting 64-bit x86 kernels from 32-bit firmware running on 64-bit capable CPUs without requiring the bootloader to implement the EFI handover protocol, allocate the setup block etc etc, all of which can be done by the stub using code we have already implemented.

Instead, create an ordinary EFI application entrypoint but implemented in 32-bit code, so that it can be invoked by 32-bit firmware, and stash the address of this 32-bit entrypoint in the .compat section where the bootloader can find it.

Note that we use the setup block embedded in the binary to go through startup_32(), but it gets reallocated and copied in efi_pe_entry(), using the same code that runs when the x86 kernel is booted in EFI mode from native firmware. This requires the loaded image protocol to be installed on the kernel image's EFI handle, and point to the kernel image itself and not to its loader. This, in turn, requires the bootloader to use the LoadImage() boot services to load the 64-bit image from 32-bit firmware, which is in fact supported by firmware based on EDK2. (Only StartImage() will fail, and instead, the newly added entrypoint needs to be invoked)

---
 arch/x86/boot/compressed/head_64.S | 61 +++++++++++++++++++-
 1 file changed, 59 insertions(+), 2 deletions(-)
arch/x86/boot/compressed/head_64.S +1 -1
index a4f5561c1c0e..7baaf9c97f5a 100644
@@ -207,8 +207,8 @@ SYM_FUNC_START(startup_32)
 	cmp	$0, %edi
 	jz	1f
 	leal	efi64_stub_entry(%ebp), %eax
-	movl	%esi, %edx
 	movl	efi32_boot_args+4(%ebp), %esi
+	movl	efi32_boot_args+8(%ebp), %edx	// saved bootparams pointer
 1:
 #endif
 	pushl	%eax

arch/x86/boot/compressed/head_64.S SYM_FUNC_START(efi32_stub_entry) · line 237

Quoted 5 lines
arch/x86/boot/compressed/head_64.S +2 -0
@@ -233,6 +233,8 @@ SYM_FUNC_START(efi32_stub_entry)
 1:	pop	%ebp
 	subl	$1b, %ebp
 
+	movl	%esi, efi32_boot_args+8(%ebp)
+2:

I think it would be easier to read if this were turned into a proper SYM_INNER_LABEL. You could then also just put efi32_pe_entry into the .text section instead of moving it to the end of .head.text with .subsection. Another option is to use a separate flag in .data to indicate that we entered via efi32_pe_entry, then you could just jump to efi32_stub_entry.

Quoted 40 lines
arch/x86/boot/compressed/head_64.S +33 -0
 	movl	%ecx, efi32_boot_args(%ebp)
 	movl	%edx, efi32_boot_args+4(%ebp)
 	movb	$0, efi_is64(%ebp)
@@ -249,6 +251,49 @@ SYM_FUNC_START(efi32_stub_entry)
 
 	jmp	startup_32
 SYM_FUNC_END(efi32_stub_entry)
+
+#define ST32_boottime		60 // offsetof(efi_system_table_32_t, boottime)
+#define BS32_handle_protocol	88 // offsetof(efi_boot_services_32_t, handle_protocol)
+#define LI32_image_base		32 // offsetof(efi_loaded_image_32_t, image_base)
+
+	.subsection 1
+	.code32
+SYM_FUNC_START(efi32_pe_entry)
+	pushl	%ebp
+
+	call	3f
+3:	pop	%ebp
+	subl	$3b, %ebp
+
+	/* Get the loaded image protocol pointer from the image handle */
+	subl	$12, %esp			// space for the loaded image pointer
+	pushl	%esp				// pass its address
+	leal	5f(%ebp), %eax
+	pushl	%eax				// pass the GUID address
+	pushl	28(%esp)			// pass the image handle
+
+	movl	36(%esp), %eax			// sys_table
+	movl	ST32_boottime(%eax), %eax	// sys_table->boottime
+	call	*BS32_handle_protocol(%eax)	// sys_table->boottime->handle_protocol
+	cmp	$0, %eax
+	jnz	4f
+
+	movl	32(%esp), %ecx			// image_handle
+	movl	36(%esp), %edx			// sys_table
+	movl	12(%esp), %esi			// loaded_image
+	movl	LI32_image_base(%esi), %esi	// loaded_image->image_base
+	jmp	2b
+

arch/x86/boot/compressed/head_64.S SYM_FUNC_START(efi32_stub_entry) · line 296

Quoted 10 lines
arch/x86/boot/compressed/head_64.S +10 -0
+4:	addl	$24, %esp
+	popl	%ebp
+	ret
+SYM_FUNC_END(efi32_pe_entry)
+
+	/* EFI loaded image protocol GUID */
+5:	.long	0x5B1B31A1
+	.word	0x9562, 0x11d2
+	.byte	0x8E, 0x3F, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B
+	.previous

Any reason for this not to live in .data (or .rodata)?

Quoted 39 lines
arch/x86/boot/compressed/head_64.S +13 -1
 #endif
 
 	.code64
@@ -465,11 +510,23 @@ SYM_CODE_END(startup_64)
 SYM_FUNC_START(efi64_stub_entry)
 SYM_FUNC_START_ALIAS(efi_stub_entry)
 	and	$~0xf, %rsp			/* realign the stack */
+#ifdef CONFIG_EFI_MIXED
+	cmpl	$0, %edx
+	jz	0f
+#endif
 	call	efi_main
 	movq	%rax,%rsi
 	movl	BP_code32_start(%esi), %eax
 	leaq	startup_64(%rax), %rax
 	jmp	*%rax
+
+#ifdef CONFIG_EFI_MIXED
+0:	movl	%edi, %ecx		// MS calling convention
+	movl	%esi, %edx
+	call	efi_pe_entry
+1:	hlt
+	jmp	1b
+#endif
 SYM_FUNC_END(efi64_stub_entry)
 SYM_FUNC_END_ALIAS(efi_stub_entry)
 #endif
@@ -641,7 +698,7 @@ SYM_DATA_START_LOCAL(gdt)
 SYM_DATA_END_LABEL(gdt, SYM_L_LOCAL, gdt_end)
 
 #ifdef CONFIG_EFI_MIXED
-SYM_DATA_LOCAL(efi32_boot_args, .long 0, 0)
+SYM_DATA_LOCAL(efi32_boot_args, .long 0, 0, 0)
 SYM_DATA(efi_is64, .byte 1)
 #endif
 
Signature · 1 line
2.17.1
author maintainer
#7 lore raw

Re: [RFC PATCH 3/3] efi/x86: implement mixed mode boot without the handover protocol

Quoted 46 lines · Arvind Sankar
Quoted 44 lines · Ard Biesheuvel

Add support for booting 64-bit x86 kernels from 32-bit firmware running on 64-bit capable CPUs without requiring the bootloader to implement the EFI handover protocol, allocate the setup block etc etc, all of which can be done by the stub using code we have already implemented.

Instead, create an ordinary EFI application entrypoint but implemented in 32-bit code, so that it can be invoked by 32-bit firmware, and stash the address of this 32-bit entrypoint in the .compat section where the bootloader can find it.

Note that we use the setup block embedded in the binary to go through startup_32(), but it gets reallocated and copied in efi_pe_entry(), using the same code that runs when the x86 kernel is booted in EFI mode from native firmware. This requires the loaded image protocol to be installed on the kernel image's EFI handle, and point to the kernel image itself and not to its loader. This, in turn, requires the bootloader to use the LoadImage() boot services to load the 64-bit image from 32-bit firmware, which is in fact supported by firmware based on EDK2. (Only StartImage() will fail, and instead, the newly added entrypoint needs to be invoked)

---
 arch/x86/boot/compressed/head_64.S | 61 +++++++++++++++++++-
 1 file changed, 59 insertions(+), 2 deletions(-)
arch/x86/boot/compressed/head_64.S +1 -1
index a4f5561c1c0e..7baaf9c97f5a 100644
@@ -207,8 +207,8 @@ SYM_FUNC_START(startup_32)
      cmp     $0, %edi
      jz      1f
      leal    efi64_stub_entry(%ebp), %eax
-     movl    %esi, %edx
      movl    efi32_boot_args+4(%ebp), %esi
+     movl    efi32_boot_args+8(%ebp), %edx   // saved bootparams pointer
 1:
 #endif
      pushl   %eax
@@ -233,6 +233,8 @@ SYM_FUNC_START(efi32_stub_entry)
 1:   pop     %ebp
      subl    $1b, %ebp
 
Quoted 10 lines

arch/x86/boot/compressed/head_64.S SYM_FUNC_START(efi32_stub_entry) · line 237

Quoted 2 lines
arch/x86/boot/compressed/head_64.S +2 -0
+     movl    %esi, efi32_boot_args+8(%ebp)
+2:

I think it would be easier to read if this were turned into a proper SYM_INNER_LABEL. You could then also just put efi32_pe_entry into the .text section instead of moving it to the end of .head.text with .subsection. Another option is to use a separate flag in .data to indicate that we entered via efi32_pe_entry, then you could just jump to efi32_stub_entry.

Indeed.

Quoted 44 lines
Quoted 44 lines
arch/x86/boot/compressed/head_64.S +37 -0
      movl    %ecx, efi32_boot_args(%ebp)
      movl    %edx, efi32_boot_args+4(%ebp)
      movb    $0, efi_is64(%ebp)
@@ -249,6 +251,49 @@ SYM_FUNC_START(efi32_stub_entry)
 
      jmp     startup_32
 SYM_FUNC_END(efi32_stub_entry)
+
+#define ST32_boottime                60 // offsetof(efi_system_table_32_t, boottime)
+#define BS32_handle_protocol 88 // offsetof(efi_boot_services_32_t, handle_protocol)
+#define LI32_image_base              32 // offsetof(efi_loaded_image_32_t, image_base)
+
+     .subsection 1
+     .code32
+SYM_FUNC_START(efi32_pe_entry)
+     pushl   %ebp
+
+     call    3f
+3:   pop     %ebp
+     subl    $3b, %ebp
+
+     /* Get the loaded image protocol pointer from the image handle */
+     subl    $12, %esp                       // space for the loaded image pointer
+     pushl   %esp                            // pass its address
+     leal    5f(%ebp), %eax
+     pushl   %eax                            // pass the GUID address
+     pushl   28(%esp)                        // pass the image handle
+
+     movl    36(%esp), %eax                  // sys_table
+     movl    ST32_boottime(%eax), %eax       // sys_table->boottime
+     call    *BS32_handle_protocol(%eax)     // sys_table->boottime->handle_protocol
+     cmp     $0, %eax
+     jnz     4f
+
+     movl    32(%esp), %ecx                  // image_handle
+     movl    36(%esp), %edx                  // sys_table
+     movl    12(%esp), %esi                  // loaded_image
+     movl    LI32_image_base(%esi), %esi     // loaded_image->image_base
+     jmp     2b
+
+4:   addl    $24, %esp
+     popl    %ebp
+     ret
+SYM_FUNC_END(efi32_pe_entry)
Quoted 8 lines

arch/x86/boot/compressed/head_64.S SYM_FUNC_START(efi32_stub_entry) · line 296

Quoted 6 lines
arch/x86/boot/compressed/head_64.S +6 -0
+
+     /* EFI loaded image protocol GUID */
+5:   .long   0x5B1B31A1
+     .word   0x9562, 0x11d2
+     .byte   0x8E, 0x3F, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B
+     .previous

Any reason for this not to live in .data (or .rodata)?

None other than laziness ...

Quoted 39 lines
Quoted 39 lines
arch/x86/boot/compressed/head_64.S +13 -2
 #endif
 
      .code64
@@ -465,11 +510,23 @@ SYM_CODE_END(startup_64)
 SYM_FUNC_START(efi64_stub_entry)
 SYM_FUNC_START_ALIAS(efi_stub_entry)
      and     $~0xf, %rsp                     /* realign the stack */
+#ifdef CONFIG_EFI_MIXED
+     cmpl    $0, %edx
+     jz      0f
+#endif
      call    efi_main
      movq    %rax,%rsi
      movl    BP_code32_start(%esi), %eax
      leaq    startup_64(%rax), %rax
      jmp     *%rax
+
+#ifdef CONFIG_EFI_MIXED
+0:   movl    %edi, %ecx              // MS calling convention
+     movl    %esi, %edx
+     call    efi_pe_entry
+1:   hlt
+     jmp     1b
+#endif
 SYM_FUNC_END(efi64_stub_entry)
 SYM_FUNC_END_ALIAS(efi_stub_entry)
 #endif
@@ -641,7 +698,7 @@ SYM_DATA_START_LOCAL(gdt)
 SYM_DATA_END_LABEL(gdt, SYM_L_LOCAL, gdt_end)
 
 #ifdef CONFIG_EFI_MIXED
-SYM_DATA_LOCAL(efi32_boot_args, .long 0, 0)
+SYM_DATA_LOCAL(efi32_boot_args, .long 0, 0, 0)
 SYM_DATA(efi_is64, .byte 1)
 #endif
 
--

2.17.1

#8 lore raw
In reply to Ard Biesheuvel
Quoted 40 lines · Ard Biesheuvel

This series is another part of my effort to reduce the level of knowledge on the part of the bootloader or firmware of internal per-architecture details regarding where/how the kernel is loaded and where its initrd and other context data are passed.

The x86 architecture has a so-called 'EFI handover protocol', which defines how the bootparams struct should be populated, and how it should be interpreted to figure out where to load the kernel, and at which offset in the binary the entrypoint is located. This scheme allows the initrd to be loaded beforehand, and allows 32-bit firmware to invoke a 64-bit kernel via a special entrypoint that manages the state transitions between the two execution modes.

Due to this, x86 loaders currently do not rely on LoadImage and StartImage, and therefore, are forced to re-implement things like image authentication for secure boot and taking the measurements for measured boot in their open coded clones of these routines.

My previous series on this topic [0] implements a generic way to load the initrd from any source supported by the loader without relying on something like device trees or bootparams structures, and so native boot should not need the EFI handover protocol anymore after those change are merged.

What remains is mixed mode boot, which also needs the EFI handover protocol regardless of whether an initrd is loaded or not. So let's get rid of that requirement, and take advantage of the fact that EDK2 based firmware does support LoadImage() for X64 binaries on IA32 firmware, which means we can rely on the secure boot and measured boot checks being performed by the firmware. The only thing we need to put on top is a way to discover the non-native entrypoint into the binary in a way that does not rely on x86 specific headers and data structures.

So let's introduce a new .compat header in the PE/COFF metadata of the bzImage, and populate it with a <machine type, entrypoint> tuple, allowing a generic EFI loader to decide whether the entrypoint supports its native machine type, and invoke it as an ordinary EFI application entrypoint. Since we will not be passing a bootparams structure, we need to discover the base of the image (which contains the setup header) via the loaded image protocol before we can enter the kernel in 32-bit mode at startup_32()

Quoted 8 lines

A loader implementation for OVMF can be found at [1]. Note that this loader code is fully generic, and could be used without modifications if other architectures ever emerge that support kernels that can be invoked from a non-native (but cross-type supported) loader.

[0] https://lore.kernel.org/linux-arm-kernel/[email protected]/
[1] https://github.com/ardbiesheuvel/edk2/commits/linux-efi-generic

As an alternative to the new section, how about having a CONFIG option to emit the 64-bit kernel with a 32-bit PE header instead, which would point to efi32_pe_entry? In that case it could be directly loaded by existing firmware already. You could even have a tool that can mangle an existing bzImage's header from 64-bit to 32-bit, say using the newly added kernel_info structure to record the existence and location of efi32_pe_entry.

Also, the PE header can live anywhere inside the image, right? Is there any reason to struggle to shoehorn it into the "boot sector"?

author maintainer
#9 lore raw
Quoted 51 lines · Arvind Sankar
Quoted 40 lines · Ard Biesheuvel

This series is another part of my effort to reduce the level of knowledge on the part of the bootloader or firmware of internal per-architecture details regarding where/how the kernel is loaded and where its initrd and other context data are passed.

The x86 architecture has a so-called 'EFI handover protocol', which defines how the bootparams struct should be populated, and how it should be interpreted to figure out where to load the kernel, and at which offset in the binary the entrypoint is located. This scheme allows the initrd to be loaded beforehand, and allows 32-bit firmware to invoke a 64-bit kernel via a special entrypoint that manages the state transitions between the two execution modes.

Due to this, x86 loaders currently do not rely on LoadImage and StartImage, and therefore, are forced to re-implement things like image authentication for secure boot and taking the measurements for measured boot in their open coded clones of these routines.

My previous series on this topic [0] implements a generic way to load the initrd from any source supported by the loader without relying on something like device trees or bootparams structures, and so native boot should not need the EFI handover protocol anymore after those change are merged.

What remains is mixed mode boot, which also needs the EFI handover protocol regardless of whether an initrd is loaded or not. So let's get rid of that requirement, and take advantage of the fact that EDK2 based firmware does support LoadImage() for X64 binaries on IA32 firmware, which means we can rely on the secure boot and measured boot checks being performed by the firmware. The only thing we need to put on top is a way to discover the non-native entrypoint into the binary in a way that does not rely on x86 specific headers and data structures.

So let's introduce a new .compat header in the PE/COFF metadata of the bzImage, and populate it with a <machine type, entrypoint> tuple, allowing a generic EFI loader to decide whether the entrypoint supports its native machine type, and invoke it as an ordinary EFI application entrypoint. Since we will not be passing a bootparams structure, we need to discover the base of the image (which contains the setup header) via the loaded image protocol before we can enter the kernel in 32-bit mode at startup_32()

Quoted 8 lines

A loader implementation for OVMF can be found at [1]. Note that this loader code is fully generic, and could be used without modifications if other architectures ever emerge that support kernels that can be invoked from a non-native (but cross-type supported) loader.

[0] https://lore.kernel.org/linux-arm-kernel/[email protected]/
[1] https://github.com/ardbiesheuvel/edk2/commits/linux-efi-generic

Quoted 8 lines

As an alternative to the new section, how about having a CONFIG option to emit the 64-bit kernel with a 32-bit PE header instead, which would point to efi32_pe_entry? In that case it could be directly loaded by existing firmware already. You could even have a tool that can mangle an existing bzImage's header from 64-bit to 32-bit, say using the newly added kernel_info structure to record the existence and location of efi32_pe_entry.

That wouldn't work with, say, signed distro kernels.

Quoted 2 lines

Also, the PE header can live anywhere inside the image, right? Is there any reason to struggle to shoehorn it into the "boot sector"?

It cannot. It must live outside a region described by the section headers.

#10 lore raw
Quoted 11 lines · Ard Biesheuvel
Quoted 8 lines · Arvind Sankar

As an alternative to the new section, how about having a CONFIG option to emit the 64-bit kernel with a 32-bit PE header instead, which would point to efi32_pe_entry? In that case it could be directly loaded by existing firmware already. You could even have a tool that can mangle an existing bzImage's header from 64-bit to 32-bit, say using the newly added kernel_info structure to record the existence and location of efi32_pe_entry.

That wouldn't work with, say, signed distro kernels.

No, the idea would be that the distro would distribute two signed images, one 32-bit and one 64-bit, which are identical except for the header. At install time, the installer chooses based on the system's firmware bit-ness.

Quoted 5 lines
Quoted 3 lines

Also, the PE header can live anywhere inside the image, right? Is there any reason to struggle to shoehorn it into the "boot sector"?

It cannot. It must live outside a region described by the section headers.

It could still be inserted after .setup, or at the very end of the file, no?

author maintainer
#11 lore raw
Quoted 12 lines · Arvind Sankar
Quoted 10 lines · Ard Biesheuvel
Quoted 8 lines · Arvind Sankar

As an alternative to the new section, how about having a CONFIG option to emit the 64-bit kernel with a 32-bit PE header instead, which would point to efi32_pe_entry? In that case it could be directly loaded by existing firmware already. You could even have a tool that can mangle an existing bzImage's header from 64-bit to 32-bit, say using the newly added kernel_info structure to record the existence and location of efi32_pe_entry.

Quoted 7 lines
Quoted 1 line

That wouldn't work with, say, signed distro kernels.

No, the idea would be that the distro would distribute two signed images, one 32-bit and one 64-bit, which are identical except for the header. At install time, the installer chooses based on the system's firmware bit-ness.

I guess it would be possible, but then we'd need two different images while today, we can run the same image on both kinds of firmwares. The only thing I am trying to do is remove all the quirky bootparams stuff from the loader so that we can switch to LoadImage

Quoted 7 lines
Quoted 5 lines
Quoted 3 lines

Also, the PE header can live anywhere inside the image, right? Is there any reason to struggle to shoehorn it into the "boot sector"?

It cannot. It must live outside a region described by the section headers.

It could still be inserted after .setup, or at the very end of the file, no?

The PE/COFF spec mentions that the COFF header needs to follow the signature. Also, the SizeOfHeaders field would become somewhat ambiguous if the header is split up like that.

#12 lore raw
Quoted 21 lines · Ard Biesheuvel
Quoted 12 lines · Arvind Sankar
Quoted 10 lines · Ard Biesheuvel
Quoted 8 lines · Arvind Sankar

As an alternative to the new section, how about having a CONFIG option to emit the 64-bit kernel with a 32-bit PE header instead, which would point to efi32_pe_entry? In that case it could be directly loaded by existing firmware already. You could even have a tool that can mangle an existing bzImage's header from 64-bit to 32-bit, say using the newly added kernel_info structure to record the existence and location of efi32_pe_entry.

Quoted 7 lines
Quoted 1 line

That wouldn't work with, say, signed distro kernels.

No, the idea would be that the distro would distribute two signed images, one 32-bit and one 64-bit, which are identical except for the header. At install time, the installer chooses based on the system's firmware bit-ness.

Quoted 4 lines

I guess it would be possible, but then we'd need two different images while today, we can run the same image on both kinds of firmwares. The only thing I am trying to do is remove all the quirky bootparams stuff from the loader so that we can switch to LoadImage

Yeah, but doing that will allow you to boot directly from firmware on existing machines, and only one image needs to be chosen at install time, so it just adds a few MiB to the package. I guess most people will still use a boot manager or loader that can be easily enhanced to use LoadImage and the new section, but it would be nice to have the option to avoid that.

Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines

Also, the PE header can live anywhere inside the image, right? Is there any reason to struggle to shoehorn it into the "boot sector"?

Quoted 7 lines
Quoted 3 lines
Quoted 1 line

It cannot. It must live outside a region described by the section headers.

It could still be inserted after .setup, or at the very end of the file, no?

The PE/COFF spec mentions that the COFF header needs to follow the signature. Also, the SizeOfHeaders field would become somewhat ambiguous if the header is split up like that.

Ah, the definition of SizeOfHeaders doesn't make much sense if the headers weren't contiguous with the MS-DOS stub. I guess they just wanted the MS-DOS stub to potentially vary in size, but still want the header to immediately follow it, drat.

#13 lore raw
Quoted 27 lines · Arvind Sankar
Quoted 21 lines · Ard Biesheuvel
Quoted 12 lines · Arvind Sankar
Quoted 10 lines · Ard Biesheuvel
Quoted 8 lines · Arvind Sankar

As an alternative to the new section, how about having a CONFIG option to emit the 64-bit kernel with a 32-bit PE header instead, which would point to efi32_pe_entry? In that case it could be directly loaded by existing firmware already. You could even have a tool that can mangle an existing bzImage's header from 64-bit to 32-bit, say using the newly added kernel_info structure to record the existence and location of efi32_pe_entry.

Quoted 7 lines
Quoted 1 line

That wouldn't work with, say, signed distro kernels.

No, the idea would be that the distro would distribute two signed images, one 32-bit and one 64-bit, which are identical except for the header. At install time, the installer chooses based on the system's firmware bit-ness.

Quoted 4 lines

I guess it would be possible, but then we'd need two different images while today, we can run the same image on both kinds of firmwares. The only thing I am trying to do is remove all the quirky bootparams stuff from the loader so that we can switch to LoadImage

Quoted 6 lines

Yeah, but doing that will allow you to boot directly from firmware on existing machines, and only one image needs to be chosen at install time, so it just adds a few MiB to the package. I guess most people will still use a boot manager or loader that can be easily enhanced to use LoadImage and the new section, but it would be nice to have the option to avoid that.

Also not quite today, right? You still need this patchset and the modifications to bootloaders to get away with one image.

author maintainer
#14 lore raw
Quoted 29 lines · Arvind Sankar
Quoted 27 lines · Arvind Sankar
Quoted 25 lines · Ard Biesheuvel
Quoted 12 lines · Arvind Sankar
Quoted 10 lines · Ard Biesheuvel
Quoted 8 lines · Arvind Sankar

As an alternative to the new section, how about having a CONFIG option to emit the 64-bit kernel with a 32-bit PE header instead, which would point to efi32_pe_entry? In that case it could be directly loaded by existing firmware already. You could even have a tool that can mangle an existing bzImage's header from 64-bit to 32-bit, say using the newly added kernel_info structure to record the existence and location of efi32_pe_entry.

Quoted 7 lines
Quoted 1 line

That wouldn't work with, say, signed distro kernels.

No, the idea would be that the distro would distribute two signed images, one 32-bit and one 64-bit, which are identical except for the header. At install time, the installer chooses based on the system's firmware bit-ness.

I guess it would be possible, but then we'd need two different images while today, we can run the same image on both kinds of firmwares. The only thing I am trying to do is remove all the quirky bootparams stuff from the loader so that we can switch to LoadImage

Quoted 6 lines
Quoted 6 lines

Yeah, but doing that will allow you to boot directly from firmware on existing machines, and only one image needs to be chosen at install time, so it just adds a few MiB to the package. I guess most people will still use a boot manager or loader that can be easily enhanced to use LoadImage and the new section, but it would be nice to have the option to avoid that.

I see the value of having a 64-bit image that can boot natively on 32-bit firmware, but I am not expecting any buy in from the distros for this scheme.

Quoted 3 lines

Also not quite today, right? You still need this patchset and the modifications to bootloaders to get away with one image.

Sure. But we already have mixed mode support today that doesn't require this, so it's going to be a difficult sell to switch to a new scheme that requires infrastructure to distribute different kernels, and logic to choose between the two.

A generic EFI bootloader/firmware will need to implement the initrd loadfile2 protocol as well, so some Linux specific features will need to be implemented anyway. This series is intended to ensure that mixed mode doesn't get left behind, even though very few people use it today.

#15 lore raw
Quoted 6 lines · Ard Biesheuvel

I see the value of having a 64-bit image that can boot natively on 32-bit firmware, but I am not expecting any buy in from the distros for this scheme.

Ok.