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