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

Generated on Tue Apr 6 20:00:50 2010 for gPXE by  doxygen 1.5.7.1