iPXE
multiboot.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public License as
00006  * published by the Free Software Foundation; either version 2 of the
00007  * License, or any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful, but
00010  * WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00017  * 02110-1301, USA.
00018  *
00019  * You can also choose to distribute this program under the terms of
00020  * the Unmodified Binary Distribution Licence (as given in the file
00021  * COPYING.UBDL), provided that you have satisfied its requirements.
00022  */
00023 
00024 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
00025 
00026 /**
00027  * @file
00028  *
00029  * Multiboot image format
00030  *
00031  */
00032 
00033 #include <stdio.h>
00034 #include <errno.h>
00035 #include <assert.h>
00036 #include <realmode.h>
00037 #include <multiboot.h>
00038 #include <ipxe/uaccess.h>
00039 #include <ipxe/image.h>
00040 #include <ipxe/segment.h>
00041 #include <ipxe/io.h>
00042 #include <ipxe/elf.h>
00043 #include <ipxe/init.h>
00044 #include <ipxe/features.h>
00045 #include <ipxe/uri.h>
00046 #include <ipxe/version.h>
00047 
00048 FEATURE ( FEATURE_IMAGE, "MBOOT", DHCP_EB_FEATURE_MULTIBOOT, 1 );
00049 
00050 /**
00051  * Maximum number of modules we will allow for
00052  *
00053  * If this has bitten you: sorry.  I did have a perfect scheme with a
00054  * dynamically allocated list of modules on the protected-mode stack,
00055  * but it was incompatible with some broken OSes that can only access
00056  * low memory at boot time (even though we kindly set up 4GB flat
00057  * physical addressing as per the multiboot specification.
00058  *
00059  */
00060 #define MAX_MODULES 8
00061 
00062 /**
00063  * Maximum combined length of command lines
00064  *
00065  * Again; sorry.  Some broken OSes zero out any non-base memory that
00066  * isn't part of the loaded module set, so we can't just use
00067  * virt_to_phys(cmdline) to point to the command lines, even though
00068  * this would comply with the Multiboot spec.
00069  */
00070 #define MB_MAX_CMDLINE 512
00071 
00072 /** Multiboot flags that we support */
00073 #define MB_SUPPORTED_FLAGS ( MB_FLAG_PGALIGN | MB_FLAG_MEMMAP | \
00074                              MB_FLAG_VIDMODE | MB_FLAG_RAW )
00075 
00076 /** Compulsory feature multiboot flags */
00077 #define MB_COMPULSORY_FLAGS 0x0000ffff
00078 
00079 /** Optional feature multiboot flags */
00080 #define MB_OPTIONAL_FLAGS 0xffff0000
00081 
00082 /**
00083  * Multiboot flags that we don't support
00084  *
00085  * We only care about the compulsory feature flags (bits 0-15); we are
00086  * allowed to ignore the optional feature flags.
00087  */
00088 #define MB_UNSUPPORTED_FLAGS ( MB_COMPULSORY_FLAGS & ~MB_SUPPORTED_FLAGS )
00089 
00090 /** A multiboot header descriptor */
00091 struct multiboot_header_info {
00092         /** The actual multiboot header */
00093         struct multiboot_header mb;
00094         /** Offset of header within the multiboot image */
00095         size_t offset;
00096 };
00097 
00098 /** Multiboot module command lines */
00099 static char __bss16_array ( mb_cmdlines, [MB_MAX_CMDLINE] );
00100 #define mb_cmdlines __use_data16 ( mb_cmdlines )
00101 
00102 /** Offset within module command lines */
00103 static unsigned int mb_cmdline_offset;
00104 
00105 /**
00106  * Build multiboot memory map
00107  *
00108  * @v image             Multiboot image
00109  * @v mbinfo            Multiboot information structure
00110  * @v mbmemmap          Multiboot memory map
00111  * @v limit             Maxmimum number of memory map entries
00112  */
00113 static void multiboot_build_memmap ( struct image *image,
00114                                      struct multiboot_info *mbinfo,
00115                                      struct multiboot_memory_map *mbmemmap,
00116                                      unsigned int limit ) {
00117         struct memory_map memmap;
00118         unsigned int i;
00119 
00120         /* Get memory map */
00121         get_memmap ( &memmap );
00122 
00123         /* Translate into multiboot format */
00124         memset ( mbmemmap, 0, sizeof ( *mbmemmap ) );
00125         for ( i = 0 ; i < memmap.count ; i++ ) {
00126                 if ( i >= limit ) {
00127                         DBGC ( image, "MULTIBOOT %p limit of %d memmap "
00128                                "entries reached\n", image, limit );
00129                         break;
00130                 }
00131                 mbmemmap[i].size = ( sizeof ( mbmemmap[i] ) -
00132                                      sizeof ( mbmemmap[i].size ) );
00133                 mbmemmap[i].base_addr = memmap.regions[i].start;
00134                 mbmemmap[i].length = ( memmap.regions[i].end -
00135                                        memmap.regions[i].start );
00136                 mbmemmap[i].type = MBMEM_RAM;
00137                 mbinfo->mmap_length += sizeof ( mbmemmap[i] );
00138                 if ( memmap.regions[i].start == 0 )
00139                         mbinfo->mem_lower = ( memmap.regions[i].end / 1024 );
00140                 if ( memmap.regions[i].start == 0x100000 )
00141                         mbinfo->mem_upper = ( ( memmap.regions[i].end -
00142                                                 0x100000 ) / 1024 );
00143         }
00144 }
00145 
00146 /**
00147  * Add command line in base memory
00148  *
00149  * @v image             Image
00150  * @ret physaddr        Physical address of command line
00151  */
00152 static physaddr_t multiboot_add_cmdline ( struct image *image ) {
00153         char *mb_cmdline = ( mb_cmdlines + mb_cmdline_offset );
00154         size_t remaining = ( sizeof ( mb_cmdlines ) - mb_cmdline_offset );
00155         char *buf = mb_cmdline;
00156         size_t len;
00157 
00158         /* Copy image URI to base memory buffer as start of command line */
00159         len = ( format_uri ( image->uri, buf, remaining ) + 1 /* NUL */ );
00160         if ( len > remaining )
00161                 len = remaining;
00162         mb_cmdline_offset += len;
00163         buf += len;
00164         remaining -= len;
00165 
00166         /* Copy command line to base memory buffer, if present */
00167         if ( image->cmdline ) {
00168                 mb_cmdline_offset--; /* Strip NUL */
00169                 buf--;
00170                 remaining++;
00171                 len = ( snprintf ( buf, remaining, " %s",
00172                                    image->cmdline ) + 1 /* NUL */ );
00173                 if ( len > remaining )
00174                         len = remaining;
00175                 mb_cmdline_offset += len;
00176         }
00177 
00178         return virt_to_phys ( mb_cmdline );
00179 }
00180 
00181 /**
00182  * Add multiboot modules
00183  *
00184  * @v image             Multiboot image
00185  * @v start             Start address for modules
00186  * @v mbinfo            Multiboot information structure
00187  * @v modules           Multiboot module list
00188  * @ret rc              Return status code
00189  */
00190 static int multiboot_add_modules ( struct image *image, physaddr_t start,
00191                                    struct multiboot_info *mbinfo,
00192                                    struct multiboot_module *modules,
00193                                    unsigned int limit ) {
00194         struct image *module_image;
00195         struct multiboot_module *module;
00196         int rc;
00197 
00198         /* Add each image as a multiboot module */
00199         for_each_image ( module_image ) {
00200 
00201                 if ( mbinfo->mods_count >= limit ) {
00202                         DBGC ( image, "MULTIBOOT %p limit of %d modules "
00203                                "reached\n", image, limit );
00204                         break;
00205                 }
00206 
00207                 /* Do not include kernel image itself as a module */
00208                 if ( module_image == image )
00209                         continue;
00210 
00211                 /* Page-align the module */
00212                 start = ( ( start + 0xfff ) & ~0xfff );
00213 
00214                 /* Prepare segment */
00215                 if ( ( rc = prep_segment ( phys_to_user ( start ),
00216                                            module_image->len,
00217                                            module_image->len ) ) != 0 ) {
00218                         DBGC ( image, "MULTIBOOT %p could not prepare module "
00219                                "%s: %s\n", image, module_image->name,
00220                                strerror ( rc ) );
00221                         return rc;
00222                 }
00223 
00224                 /* Copy module */
00225                 memcpy_user ( phys_to_user ( start ), 0,
00226                               module_image->data, 0, module_image->len );
00227 
00228                 /* Add module to list */
00229                 module = &modules[mbinfo->mods_count++];
00230                 module->mod_start = start;
00231                 module->mod_end = ( start + module_image->len );
00232                 module->string = multiboot_add_cmdline ( module_image );
00233                 module->reserved = 0;
00234                 DBGC ( image, "MULTIBOOT %p module %s is [%x,%x)\n",
00235                        image, module_image->name, module->mod_start,
00236                        module->mod_end );
00237                 start += module_image->len;
00238         }
00239 
00240         return 0;
00241 }
00242 
00243 /**
00244  * The multiboot information structure
00245  *
00246  * Kept in base memory because some OSes won't find it elsewhere,
00247  * along with the other structures belonging to the Multiboot
00248  * information table.
00249  */
00250 static struct multiboot_info __bss16 ( mbinfo );
00251 #define mbinfo __use_data16 ( mbinfo )
00252 
00253 /** The multiboot bootloader name */
00254 static char __bss16_array ( mb_bootloader_name, [32] );
00255 #define mb_bootloader_name __use_data16 ( mb_bootloader_name )
00256 
00257 /** The multiboot memory map */
00258 static struct multiboot_memory_map
00259         __bss16_array ( mbmemmap, [MAX_MEMORY_REGIONS] );
00260 #define mbmemmap __use_data16 ( mbmemmap )
00261 
00262 /** The multiboot module list */
00263 static struct multiboot_module __bss16_array ( mbmodules, [MAX_MODULES] );
00264 #define mbmodules __use_data16 ( mbmodules )
00265 
00266 /**
00267  * Find multiboot header
00268  *
00269  * @v image             Multiboot file
00270  * @v hdr               Multiboot header descriptor to fill in
00271  * @ret rc              Return status code
00272  */
00273 static int multiboot_find_header ( struct image *image,
00274                                    struct multiboot_header_info *hdr ) {
00275         uint32_t buf[64];
00276         size_t offset;
00277         unsigned int buf_idx;
00278         uint32_t checksum;
00279 
00280         /* Scan through first 8kB of image file 256 bytes at a time.
00281          * (Use the buffering to avoid the overhead of a
00282          * copy_from_user() for every dword.)
00283          */
00284         for ( offset = 0 ; offset < 8192 ; offset += sizeof ( buf[0] ) ) {
00285                 /* Check for end of image */
00286                 if ( offset > image->len )
00287                         break;
00288                 /* Refill buffer if applicable */
00289                 buf_idx = ( ( offset % sizeof ( buf ) ) / sizeof ( buf[0] ) );
00290                 if ( buf_idx == 0 ) {
00291                         copy_from_user ( buf, image->data, offset,
00292                                          sizeof ( buf ) );
00293                 }
00294                 /* Check signature */
00295                 if ( buf[buf_idx] != MULTIBOOT_HEADER_MAGIC )
00296                         continue;
00297                 /* Copy header and verify checksum */
00298                 copy_from_user ( &hdr->mb, image->data, offset,
00299                                  sizeof ( hdr->mb ) );
00300                 checksum = ( hdr->mb.magic + hdr->mb.flags +
00301                              hdr->mb.checksum );
00302                 if ( checksum != 0 )
00303                         continue;
00304                 /* Record offset of multiboot header and return */
00305                 hdr->offset = offset;
00306                 return 0;
00307         }
00308 
00309         /* No multiboot header found */
00310         return -ENOEXEC;
00311 }
00312 
00313 /**
00314  * Load raw multiboot image into memory
00315  *
00316  * @v image             Multiboot file
00317  * @v hdr               Multiboot header descriptor
00318  * @ret entry           Entry point
00319  * @ret max             Maximum used address
00320  * @ret rc              Return status code
00321  */
00322 static int multiboot_load_raw ( struct image *image,
00323                                 struct multiboot_header_info *hdr,
00324                                 physaddr_t *entry, physaddr_t *max ) {
00325         size_t offset;
00326         size_t filesz;
00327         size_t memsz;
00328         userptr_t buffer;
00329         int rc;
00330 
00331         /* Sanity check */
00332         if ( ! ( hdr->mb.flags & MB_FLAG_RAW ) ) {
00333                 DBGC ( image, "MULTIBOOT %p is not flagged as a raw image\n",
00334                        image );
00335                 return -EINVAL;
00336         }
00337 
00338         /* Verify and prepare segment */
00339         offset = ( hdr->offset - hdr->mb.header_addr + hdr->mb.load_addr );
00340         filesz = ( hdr->mb.load_end_addr ?
00341                    ( hdr->mb.load_end_addr - hdr->mb.load_addr ) :
00342                    ( image->len - offset ) );
00343         memsz = ( hdr->mb.bss_end_addr ?
00344                   ( hdr->mb.bss_end_addr - hdr->mb.load_addr ) : filesz );
00345         buffer = phys_to_user ( hdr->mb.load_addr );
00346         if ( ( rc = prep_segment ( buffer, filesz, memsz ) ) != 0 ) {
00347                 DBGC ( image, "MULTIBOOT %p could not prepare segment: %s\n",
00348                        image, strerror ( rc ) );
00349                 return rc;
00350         }
00351 
00352         /* Copy image to segment */
00353         memcpy_user ( buffer, 0, image->data, offset, filesz );
00354 
00355         /* Record execution entry point and maximum used address */
00356         *entry = hdr->mb.entry_addr;
00357         *max = ( hdr->mb.load_addr + memsz );
00358 
00359         return 0;
00360 }
00361 
00362 /**
00363  * Load ELF multiboot image into memory
00364  *
00365  * @v image             Multiboot file
00366  * @ret entry           Entry point
00367  * @ret max             Maximum used address
00368  * @ret rc              Return status code
00369  */
00370 static int multiboot_load_elf ( struct image *image, physaddr_t *entry,
00371                                 physaddr_t *max ) {
00372         int rc;
00373 
00374         /* Load ELF image*/
00375         if ( ( rc = elf_load ( image, entry, max ) ) != 0 ) {
00376                 DBGC ( image, "MULTIBOOT %p ELF image failed to load: %s\n",
00377                        image, strerror ( rc ) );
00378                 return rc;
00379         }
00380 
00381         return 0;
00382 }
00383 
00384 /**
00385  * Execute multiboot image
00386  *
00387  * @v image             Multiboot image
00388  * @ret rc              Return status code
00389  */
00390 static int multiboot_exec ( struct image *image ) {
00391         struct multiboot_header_info hdr;
00392         physaddr_t entry;
00393         physaddr_t max;
00394         int rc;
00395 
00396         /* Locate multiboot header, if present */
00397         if ( ( rc = multiboot_find_header ( image, &hdr ) ) != 0 ) {
00398                 DBGC ( image, "MULTIBOOT %p has no multiboot header\n",
00399                        image );
00400                 return rc;
00401         }
00402 
00403         /* Abort if we detect flags that we cannot support */
00404         if ( hdr.mb.flags & MB_UNSUPPORTED_FLAGS ) {
00405                 DBGC ( image, "MULTIBOOT %p flags %08x not supported\n",
00406                        image, ( hdr.mb.flags & MB_UNSUPPORTED_FLAGS ) );
00407                 return -ENOTSUP;
00408         }
00409 
00410         /* There is technically a bit MB_FLAG_RAW to indicate whether
00411          * this is an ELF or a raw image.  In practice, grub will use
00412          * the ELF header if present, and Solaris relies on this
00413          * behaviour.
00414          */
00415         if ( ( ( rc = multiboot_load_elf ( image, &entry, &max ) ) != 0 ) &&
00416              ( ( rc = multiboot_load_raw ( image, &hdr, &entry, &max ) ) != 0 ))
00417                 return rc;
00418 
00419         /* Populate multiboot information structure */
00420         memset ( &mbinfo, 0, sizeof ( mbinfo ) );
00421         mbinfo.flags = ( MBI_FLAG_LOADER | MBI_FLAG_MEM | MBI_FLAG_MMAP |
00422                          MBI_FLAG_CMDLINE | MBI_FLAG_MODS );
00423         mb_cmdline_offset = 0;
00424         mbinfo.cmdline = multiboot_add_cmdline ( image );
00425         mbinfo.mods_addr = virt_to_phys ( mbmodules );
00426         mbinfo.mmap_addr = virt_to_phys ( mbmemmap );
00427         snprintf ( mb_bootloader_name, sizeof ( mb_bootloader_name ),
00428                    "iPXE %s", product_version );
00429         mbinfo.boot_loader_name = virt_to_phys ( mb_bootloader_name );
00430         if ( ( rc = multiboot_add_modules ( image, max, &mbinfo, mbmodules,
00431                                             ( sizeof ( mbmodules ) /
00432                                               sizeof ( mbmodules[0] ) ) ) ) !=0)
00433                 return rc;
00434 
00435         /* Multiboot images may not return and have no callback
00436          * interface, so shut everything down prior to booting the OS.
00437          */
00438         shutdown_boot();
00439 
00440         /* Build memory map after unhiding bootloader memory regions as part of
00441          * shutting everything down.
00442          */
00443         multiboot_build_memmap ( image, &mbinfo, mbmemmap,
00444                                  ( sizeof(mbmemmap) / sizeof(mbmemmap[0]) ) );
00445 
00446         /* Jump to OS with flat physical addressing */
00447         DBGC ( image, "MULTIBOOT %p starting execution at %lx\n",
00448                image, entry );
00449         __asm__ __volatile__ ( PHYS_CODE ( "pushl %%ebp\n\t"
00450                                            "call *%%edi\n\t"
00451                                            "popl %%ebp\n\t" )
00452                                : : "a" ( MULTIBOOT_BOOTLOADER_MAGIC ),
00453                                    "b" ( virt_to_phys ( &mbinfo ) ),
00454                                    "D" ( entry )
00455                                : "ecx", "edx", "esi", "memory" );
00456 
00457         DBGC ( image, "MULTIBOOT %p returned\n", image );
00458 
00459         /* It isn't safe to continue after calling shutdown() */
00460         while ( 1 ) {}
00461 
00462         return -ECANCELED;  /* -EIMPOSSIBLE, anyone? */
00463 }
00464 
00465 /**
00466  * Probe multiboot image
00467  *
00468  * @v image             Multiboot file
00469  * @ret rc              Return status code
00470  */
00471 static int multiboot_probe ( struct image *image ) {
00472         struct multiboot_header_info hdr;
00473         int rc;
00474 
00475         /* Locate multiboot header, if present */
00476         if ( ( rc = multiboot_find_header ( image, &hdr ) ) != 0 ) {
00477                 DBGC ( image, "MULTIBOOT %p has no multiboot header\n",
00478                        image );
00479                 return rc;
00480         }
00481         DBGC ( image, "MULTIBOOT %p found header with flags %08x\n",
00482                image, hdr.mb.flags );
00483 
00484         return 0;
00485 }
00486 
00487 /** Multiboot image type */
00488 struct image_type multiboot_image_type __image_type ( PROBE_MULTIBOOT ) = {
00489         .name = "Multiboot",
00490         .probe = multiboot_probe,
00491         .exec = multiboot_exec,
00492 };