From: Jonathan Creekmore <jonat...@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 <jonat...@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
_______________________________________________
ipxe-devel mailing list
ipxe-devel@lists.ipxe.org
https://lists.ipxe.org/mailman/listinfo.cgi/ipxe-devel

Reply via email to