[ipxe-devel] [PATCH 5/6] [multiboot2] implement the ability to load mb2 images

Doug Goldstein cardoe at cardoe.com
Thu Jan 26 21:35:00 UTC 2017


From: Jonathan Creekmore <jonathan at thecreekmores.org>

Add the ability to load multiboot2 images on EFI only at this time. This
has been tested with Xen 4.9 using their multiboot2 support.

Signed-off-by: Jonathan Creekmore <jonathan at thecreekmores.org>
---
 src/arch/x86/image/multiboot2.c | 558 ++++++++++++++++++++++++++++++++-
 1 file changed, 554 insertions(+), 4 deletions(-)

diff --git a/src/arch/x86/image/multiboot2.c b/src/arch/x86/image/multiboot2.c
index 503a549..851b178 100644
--- a/src/arch/x86/image/multiboot2.c
+++ b/src/arch/x86/image/multiboot2.c
@@ -41,20 +41,560 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 #include <ipxe/elf.h>
 #include <ipxe/init.h>
 #include <ipxe/features.h>
+#include <ipxe/umalloc.h>
 #include <ipxe/uri.h>
 #include <ipxe/version.h>
+#ifdef EFIAPI
+#include <ipxe/efi/efi.h>
+#endif
 
 FEATURE ( FEATURE_IMAGE, "MBOOT2", DHCP_EB_FEATURE_MULTIBOOT2, 1 );
 
 /**
+ * Maximum multiboot2 boot information size
+ */
+#define MB_MAX_BOOTINFO_SIZE 4096
+
+/** Multiboot2 boot information buffer */
+static union {
+	uint64_t align;
+	char bib[MB_MAX_BOOTINFO_SIZE];
+} mb2_bib;
+
+/** A multiboot2 header descriptor */
+struct multiboot2_header_info {
+	/** The actual multiboot2 header */
+	struct multiboot_header mb;
+	/** Offset of header within the multiboot2 image */
+	size_t offset;
+};
+
+/**
+ * Find multiboot2 header
+ *
+ * @v image		Multiboot file
+ * @v hdr		Multiboot header descriptor to fill in
+ * @ret rc		Return status code
+ */
+static int multiboot2_find_header ( struct image *image,
+				   struct multiboot2_header_info *hdr ) {
+	uint32_t buf[64];
+	size_t offset;
+	unsigned int buf_idx;
+	uint32_t checksum;
+
+	/* Scan through first MULTIBOOT_SEARCH of image file 256 bytes at a time.
+	 * (Use the buffering to avoid the overhead of a
+	 * copy_from_user() for every dword.)
+	 */
+	for ( offset = 0 ; offset < MULTIBOOT_SEARCH ; offset += sizeof ( buf[0] ) ) {
+		/* Check for end of image */
+		if ( offset > image->len )
+			break;
+		/* Refill buffer if applicable */
+		buf_idx = ( ( offset % sizeof ( buf ) ) / sizeof ( buf[0] ) );
+		if ( buf_idx == 0 ) {
+			copy_from_user ( buf, image->data, offset,
+					 sizeof ( buf ) );
+		}
+		/* Check signature */
+		if ( buf[buf_idx] != MULTIBOOT2_HEADER_MAGIC )
+			continue;
+		/* Copy header and verify checksum */
+		copy_from_user ( &hdr->mb, image->data, offset,
+				 sizeof ( hdr->mb ) );
+		checksum = ( hdr->mb.magic + hdr->mb.architecture + hdr->mb.header_length +
+				 hdr->mb.checksum );
+		if ( checksum != 0 )
+			continue;
+
+		/* Make sure that the multiboot architecture is x86 */
+		if (hdr->mb.architecture != MULTIBOOT_ARCHITECTURE_I386) {
+			return -ENOEXEC;
+		}
+
+		/* Record offset of multiboot header and return */
+		hdr->offset = offset;
+		return 0;
+	}
+
+	/* No multiboot header found */
+	return -ENOEXEC;
+}
+
+struct multiboot2_tags {
+	int module_align;
+	int boot_services;
+
+	int entry_addr_valid;
+	int entry_addr_efi32_valid;
+	int entry_addr_efi64_valid;
+	int relocatable_valid;
+
+	uint32_t entry_addr;
+	uint32_t entry_addr_efi32;
+	uint32_t entry_addr_efi64;
+	uint32_t reloc_min_addr;
+	uint32_t reloc_max_addr;
+	uint32_t reloc_align;
+	uint32_t reloc_preference;
+};
+
+static int multiboot2_validate_inforeq ( struct image *image, size_t offset, size_t num_reqs ) {
+	uint32_t inforeq;
+
+	while (num_reqs) {
+		copy_from_user ( &inforeq, image->data, offset, sizeof ( inforeq ) );
+		offset += sizeof(inforeq);
+		num_reqs--;
+
+		switch (inforeq) {
+		case MULTIBOOT_TAG_TYPE_BASIC_MEMINFO:
+		case MULTIBOOT_TAG_TYPE_MMAP:
+			continue;
+
+		default:
+			return -ENOTSUP;
+		}
+	}
+
+	return 0;
+}
+
+static int multiboot2_validate_tags ( struct image *image, struct multiboot2_header_info *hdr,
+	   struct multiboot2_tags *tags ) {
+	size_t offset = hdr->offset + sizeof(struct multiboot_header);
+	size_t end_offset = offset + hdr->mb.header_length;
+	struct multiboot_header_tag tag;
+
+	/* Clear out the multiboot2 tags structure */
+	memset(tags, 0, sizeof(*tags));
+
+	while (offset < end_offset) {
+		copy_from_user ( &tag, image->data, offset, sizeof ( tag ) );
+
+		DBGC ( image, "MULTIBOOT2 %p (offset: %d) TAG type: %x flags: %x size: %d\n", image,
+				(int)(offset - hdr->offset), tag.type, tag.flags, tag.size );
+
+		if (tag.type == MULTIBOOT_HEADER_TAG_END) {
+			DBGC ( image, "MULTIBOOT2 %p tag end\n", image );
+			return 0;
+		}
+
+		switch (tag.type) {
+		case MULTIBOOT_HEADER_TAG_INFORMATION_REQUEST:
+		{
+			size_t num_inforeqs;
+
+			DBGC ( image, "MULTIBOOT2 %p has an information request tag\n",
+				   image );
+
+			num_inforeqs = (tag.size - sizeof(tag)) / sizeof(uint32_t);
+
+			if (multiboot2_validate_inforeq ( image, offset + sizeof(tag), num_inforeqs ) != 0) {
+				DBGC ( image, "MULTIBOOT2 %p cannot support all information request tags\n",
+					   image );
+				return -ENOTSUP;
+			}
+
+			break;
+		}
+		case MULTIBOOT_HEADER_TAG_ADDRESS:
+			DBGC ( image, "MULTIBOOT2 %p has an address tag\n",
+				   image );
+
+			if ((tag.flags & MULTIBOOT_HEADER_TAG_OPTIONAL) != MULTIBOOT_HEADER_TAG_OPTIONAL)
+				return -ENOTSUP;
+
+			break;
+
+		case MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS:
+		{
+			struct multiboot_header_tag_entry_address mb_tag = { 0 };
+			copy_from_user ( &mb_tag, image->data, offset, tag.size );
+
+			DBGC ( image, "MULTIBOOT2 %p has an entry address tag\n",
+				   image );
+
+			tags->entry_addr_valid = 1;
+			tags->entry_addr = mb_tag.entry_addr;
+			break;
+		}
+		case MULTIBOOT_HEADER_TAG_CONSOLE_FLAGS:
+			DBGC ( image, "MULTIBOOT2 %p has a console flags tag\n",
+				   image );
+
+			if ((tag.flags & MULTIBOOT_HEADER_TAG_OPTIONAL) != MULTIBOOT_HEADER_TAG_OPTIONAL)
+				return -ENOTSUP;
+
+			break;
+
+		case MULTIBOOT_HEADER_TAG_FRAMEBUFFER:
+			DBGC ( image, "MULTIBOOT2 %p has a framebuffer tag\n",
+				   image );
+
+			if ((tag.flags & MULTIBOOT_HEADER_TAG_OPTIONAL) != MULTIBOOT_HEADER_TAG_OPTIONAL)
+				return -ENOTSUP;
+
+			break;
+
+		case MULTIBOOT_HEADER_TAG_MODULE_ALIGN:
+			DBGC ( image, "MULTIBOOT2 %p has a module align tag\n",
+				   image );
+			tags->module_align = 1;
+			break;
+
+		case MULTIBOOT_HEADER_TAG_EFI_BS:
+			DBGC ( image, "MULTIBOOT2 %p has a boot services tag\n",
+				   image );
+			tags->boot_services = 1;
+			break;
+
+		case MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI32:
+		{
+			struct multiboot_header_tag_entry_address mb_tag = { 0 };
+			copy_from_user ( &mb_tag, image->data, offset, tag.size );
+
+			DBGC ( image, "MULTIBOOT2 %p has an entry address EFI32 tag\n",
+				   image );
+
+			tags->entry_addr_efi32_valid = 1;
+			tags->entry_addr_efi32 = mb_tag.entry_addr;
+			break;
+		}
+		case MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI64:
+		{
+			struct multiboot_header_tag_entry_address mb_tag = { 0 };
+			copy_from_user ( &mb_tag, image->data, offset, tag.size );
+
+			DBGC ( image, "MULTIBOOT2 %p has an entry address EFI64 tag: %x\n",
+				   image, mb_tag.entry_addr );
+
+			tags->entry_addr_efi64_valid = 1;
+			tags->entry_addr_efi64 = mb_tag.entry_addr;
+			break;
+		}
+		case MULTIBOOT_HEADER_TAG_RELOCATABLE:
+		{
+			struct multiboot_header_tag_relocatable mb_tag = { 0 };
+			copy_from_user ( &mb_tag, image->data, offset, tag.size );
+
+			DBGC ( image, "MULTIBOOT2 %p has a relocatable tag\n",
+				   image );
+
+			tags->relocatable_valid = 1;
+			tags->reloc_min_addr = mb_tag.min_addr;
+			tags->reloc_max_addr = mb_tag.max_addr;
+			tags->reloc_align = mb_tag.align;
+			tags->reloc_preference = mb_tag.preference;
+			break;
+		}
+		default:
+			DBGC ( image, "MULTIBOOT2 %p unknown tag %x\n",
+				   image, tag.type );
+			return -ENOTSUP;
+		}
+
+		offset += tag.size + (MULTIBOOT_TAG_ALIGN - 1);
+		offset = offset & ~(MULTIBOOT_TAG_ALIGN - 1);
+	}
+
+	/* If we did not get a MULTIBOOT_HEADER_TAG_END, fail out */
+	DBGC ( image, "MULTIBOOT %p missing tag end\n", image );
+	return -ENOTSUP;
+}
+
+/**
+ * Add bootloader into bib
+ */
+static size_t multiboot2_add_bootloader ( struct image *image, size_t offset ) {
+	struct multiboot_tag_string *bootloader = (struct multiboot_tag_string *)&mb2_bib.bib[offset];
+	size_t remaining = MB_MAX_BOOTINFO_SIZE - offset - sizeof(*bootloader);
+	size_t len;
+	char *buf = bootloader->string;
+
+	len = ( snprintf ( buf, remaining, "iPXE %s", product_version ) + 1 /* NUL */ );
+	if ( len > remaining )
+		len = remaining;
+
+	DBGC ( image, "MULTIBOOT2 %p bootloader: %s\n", image, bootloader->string );
+
+	bootloader->type = MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME;
+	bootloader->size = len + sizeof(*bootloader);
+	return bootloader->size;
+}
+
+/**
+ * Add command line into bib
+ */
+static size_t multiboot2_add_cmdline ( struct image *image, size_t offset ) {
+	struct multiboot_tag_string *cmdline = (struct multiboot_tag_string *)&mb2_bib.bib[offset];
+	size_t remaining = MB_MAX_BOOTINFO_SIZE - offset - sizeof(*cmdline);
+	size_t len;
+	char *buf = cmdline->string;
+
+	cmdline->type = MULTIBOOT_TAG_TYPE_CMDLINE;
+	cmdline->size = sizeof(*cmdline);
+
+	/* Copy image URI to base memory buffer as start of command line */
+	len = ( format_uri ( image->uri, buf, remaining ) + 1 /* NUL */ );
+	if ( len > remaining )
+		len = remaining;
+	buf += len;
+	remaining -= len;
+	cmdline->size += len;
+
+	/* Copy command line to base memory buffer, if present */
+	if ( image->cmdline ) {
+		buf--;
+		cmdline->size--;
+		remaining++;
+		len = ( snprintf ( buf, remaining, " %s", image->cmdline ) + 1 /* NUL */ );
+		if ( len > remaining )
+			len = remaining;
+	}
+
+	DBGC ( image, "MULTIBOOT2 %p cmdline: %s\n", image, cmdline->string );
+
+	cmdline->size += len;
+	return cmdline->size;
+}
+
+/**
+ * Load multiboot2 image into memory
+ *
+ * @v image		Multiboot file
+ * @v hdr		Multiboot header descriptor
+ * @ret entry		Entry point
+ * @ret max		Maximum used address
+ * @ret rc		Return status code
+ */
+static int multiboot2_load ( struct image *image, struct multiboot2_tags *tags,
+				physaddr_t *load, physaddr_t *entry, physaddr_t *max ) {
+
+	int rc;
+
+	if ( ( rc = elf_load ( image, load, entry, max ) ) < 0 ) {
+		DBGC ( image, "MULTIBOOT2 %p could not load elf image\n", image );
+		return rc;
+	}
+	*entry = tags->entry_addr_efi64;
+
+	return rc;
+}
+
+static size_t adjust_tag_offset(size_t offset) {
+	if ((offset & 7) != 0) {
+		return ((offset + 8) & ~7);
+	}
+	return offset;
+}
+
+/**
+ * Add multiboot modules
+ */
+static size_t multiboot2_add_modules ( struct image *image, size_t offset ) {
+	struct image *module_image;
+	struct multiboot_tag_module *module;
+	char *buf;
+	size_t remaining;
+	size_t len;
+	userptr_t memory;
+
+	/* Add each image as a multiboot module */
+	for_each_image ( module_image ) {
+
+		/* Do not include kernel image itself as a module */
+		if ( module_image == image )
+			continue;
+
+		memory = umalloc ( module_image->len );
+		if ( memory == UNULL ) {
+			DBGC ( image, "MULTIBOOT2 %p could not allocate %zd bytes.\n", module_image, module_image->len );
+			return 0;
+		}
+
+		memcpy_user ( memory, 0, module_image->data, 0, module_image->len );
+
+		/* Add module to list */
+		module = (struct multiboot_tag_module *)&mb2_bib.bib[offset];
+		module->type = MULTIBOOT_TAG_TYPE_MODULE;
+		module->size = sizeof(*module);
+		module->mod_start = memory;
+		module->mod_end = ( memory + module_image->len );
+
+		buf = module->cmdline;
+		remaining = MB_MAX_BOOTINFO_SIZE - offset - sizeof(*module);
+
+		/* Copy image URI to base memory buffer as start of command line */
+		len = ( format_uri ( module_image->uri, buf, remaining ) + 1 /* NUL */ );
+		if ( len > remaining )
+			len = remaining;
+		buf += len;
+		remaining -= len;
+		module->size += len;
+
+		/* Copy command line to base memory buffer, if present */
+		if ( module_image->cmdline ) {
+			buf--;
+			module->size--;
+			remaining++;
+			len = ( snprintf ( buf, remaining, " %s", module_image->cmdline ) + 1 /* NUL */ );
+			if ( len > remaining )
+				len = remaining;
+			module->size += len;
+		}
+
+		offset += module->size;
+		offset = adjust_tag_offset(offset);
+
+		DBGC ( image, "MULTIBOOT2 %p module %s is [%x,%x): %s\n",
+			   image, module_image->name, module->mod_start,
+			   module->mod_end, module->cmdline );
+	}
+
+	return offset;
+}
+
+void multiboot2_boot(uint32_t *bib, uint32_t entry) {
+#ifdef EFIAPI
+	__asm__ __volatile__ ( "push %%rbp\n\t"
+						   "call *%%rdi\n\t"
+						   "pop %%rbp\n\t"
+					   : : "a" ( MULTIBOOT2_BOOTLOADER_MAGIC ),
+						   "b" ( bib ),
+						   "D" ( entry )
+						 : "rcx", "rdx", "rsi", "memory" );
+#else
+	(void)bib;
+	(void)entry;
+#endif
+}
+
+/**
  * Execute multiboot2 image
  *
  * @v image		Multiboot image
  * @ret rc		Return status code
  */
 static int multiboot2_exec ( struct image *image ) {
-	(void)image;
-	return -ENOEXEC;
+	struct multiboot2_header_info hdr;
+	struct multiboot2_tags mb_tags;
+	struct multiboot_tag *tag;
+	struct multiboot_tag_load_base_addr *load_base_addr_tag;
+#ifdef EFIAPI
+	struct multiboot_tag_efi64 *tag_efi64;
+#endif
+	uint32_t *total_size;
+	uint32_t *reserved;
+	physaddr_t load;
+	physaddr_t entry;
+	physaddr_t max;
+	size_t offset;
+	int rc;
+
+	/* Locate multiboot2 header, if present */
+	if ( ( rc = multiboot2_find_header ( image, &hdr ) ) != 0 ) {
+		DBGC ( image, "MULTIBOOT2 %p has no multiboot header\n",
+			   image );
+		return rc;
+	}
+
+	/* Abort if we detect tags that we cannot support */
+	if ( ( rc = multiboot2_validate_tags ( image, &hdr, &mb_tags ) ) != 0 ) {
+		DBGC ( image, "MULTIBOOT2 %p contains unsupported tags\n",
+			   image );
+		return -ENOTSUP;
+	}
+
+	/* Attempt to load the image into memory of our choosing */
+	if ( ( rc = multiboot2_load ( image, &mb_tags, &load, &entry, &max ) ) != 0) {
+		DBGC ( image, "MULTIBOOT2 %p could not load\n", image );
+		return rc;
+	}
+
+	/* Populate multiboot information structure */
+	offset = 0;
+
+	total_size = (uint32_t *)&mb2_bib.bib[offset];
+	offset += sizeof(*total_size);
+
+	reserved = (uint32_t *)&mb2_bib.bib[offset];
+	offset += sizeof(*reserved);
+
+	/* Clear out the reserved word */
+	*reserved = 0;
+
+	/* Add the load base address tag */
+	load_base_addr_tag = (struct multiboot_tag_load_base_addr *)&mb2_bib.bib[offset];
+	load_base_addr_tag->type = MULTIBOOT_TAG_TYPE_LOAD_BASE_ADDR;
+	load_base_addr_tag->size = sizeof(*load_base_addr_tag);
+	load_base_addr_tag->load_base_addr = load;
+	offset += load_base_addr_tag->size;
+	offset = adjust_tag_offset(offset);
+
+#ifdef EFIAPI
+	/* Add the EFI boot services not terminated tag */
+	tag = (struct multiboot_tag *)&mb2_bib.bib[offset];
+	tag->type = MULTIBOOT_TAG_TYPE_EFI_BS;
+	tag->size = sizeof(*tag);
+	offset += tag->size;
+	offset = adjust_tag_offset(offset);
+
+	/* Add the EFI 64-bit image handle pointer */
+	tag_efi64 = (struct multiboot_tag_efi64 *)&mb2_bib.bib[offset];
+	tag_efi64->type = MULTIBOOT_TAG_TYPE_EFI64_IH;
+	tag_efi64->size = sizeof(*tag_efi64);
+	tag_efi64->pointer = (multiboot_uint64_t)efi_image_handle;
+	offset += tag_efi64->size;
+	offset = adjust_tag_offset(offset);
+
+	/* Add the EFI 64-bit system table handle pointer */
+	tag_efi64 = (struct multiboot_tag_efi64 *)&mb2_bib.bib[offset];
+	tag_efi64->type = MULTIBOOT_TAG_TYPE_EFI64;
+	tag_efi64->size = sizeof(*tag_efi64);
+	tag_efi64->pointer = (multiboot_uint64_t)efi_systab;
+	offset += tag_efi64->size;
+	offset = adjust_tag_offset(offset);
+#endif
+
+	/* add the boot command line */
+	offset += multiboot2_add_cmdline ( image, offset );
+	offset = adjust_tag_offset(offset);
+
+	/* add the bootloader */
+	offset += multiboot2_add_bootloader ( image, offset );
+	offset = adjust_tag_offset(offset);
+
+	/* Add the modules */
+	offset = multiboot2_add_modules ( image, offset );
+	offset = adjust_tag_offset(offset);
+
+	/* Terminate the tags */
+	tag = (struct multiboot_tag *)&mb2_bib.bib[offset];
+	tag->type = 0;
+	tag->size = sizeof(*tag);
+	offset += tag->size;
+
+	*total_size = offset;
+
+	DBGC ( image, "MULTIBOOT2 %p BIB is %d bytes\n", image, *total_size );
+
+	/* Multiboot images may not return and have no callback
+	 * interface, so shut everything down prior to booting the OS.
+	 */
+	shutdown_boot();
+
+	/* Jump to OS with flat physical addressing */
+	DBGC ( image, "MULTIBOOT2 %p starting execution at %lx\n", image, entry );
+
+	multiboot2_boot ( total_size, entry );
+	DBGC ( image, "MULTIBOOT2 %p returned\n", image );
+
+	/* It isn't safe to continue after calling shutdown() */
+	while ( 1 ) {}
+
+	return -ECANCELED;  /* -EIMPOSSIBLE, anyone? */
 }
 
 /**
@@ -64,8 +604,18 @@ static int multiboot2_exec ( struct image *image ) {
  * @ret rc		Return status code
  */
 static int multiboot2_probe ( struct image *image ) {
-	(void)image;
-	return -ENOEXEC;
+	struct multiboot2_header_info hdr;
+	int rc;
+
+	/* Locate multiboot2 header, if present */
+	if ( ( rc = multiboot2_find_header ( image, &hdr ) ) != 0 ) {
+		DBGC ( image, "MULTIBOOT2 %p has no multiboot2 header\n",
+			   image );
+		return rc;
+	}
+	DBGC ( image, "MULTIBOOT2 %p found header with architecture %08x and header_length %d\n",
+		   image, hdr.mb.architecture, hdr.mb.header_length );
+	return 0;
 }
 
 /** Multiboot image type */
-- 
git-series 0.9.1



More information about the ipxe-devel mailing list