david/ipxe
Archived
1
0

[bzimage] Allow initrds to be rearranged in place

At present, loading a bzImage via iPXE requires enough RAM to hold two
copies of each initrd file.  Remove this constraint by rearranging the
initrds in place.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Michael Brown 2012-11-12 16:16:31 +00:00
parent 4ca98693b9
commit d6b0b76a05

View File

@ -33,6 +33,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
#include <assert.h> #include <assert.h>
#include <realmode.h> #include <realmode.h>
#include <bzimage.h> #include <bzimage.h>
#include <initrd.h>
#include <ipxe/uaccess.h> #include <ipxe/uaccess.h>
#include <ipxe/image.h> #include <ipxe/image.h>
#include <ipxe/segment.h> #include <ipxe/segment.h>
@ -304,9 +305,8 @@ static int bzimage_parse_cmdline ( struct image *image,
* @v image bzImage image * @v image bzImage image
* @v bzimg bzImage context * @v bzimg bzImage context
* @v cmdline Kernel command line * @v cmdline Kernel command line
* @ret rc Return status code
*/ */
static int bzimage_set_cmdline ( struct image *image, static void bzimage_set_cmdline ( struct image *image,
struct bzimage_context *bzimg, struct bzimage_context *bzimg,
const char *cmdline ) { const char *cmdline ) {
size_t cmdline_len; size_t cmdline_len;
@ -318,8 +318,6 @@ static int bzimage_set_cmdline ( struct image *image,
copy_to_user ( bzimg->rm_kernel, bzimg->rm_cmdline, copy_to_user ( bzimg->rm_kernel, bzimg->rm_cmdline,
cmdline, cmdline_len ); cmdline, cmdline_len );
DBGC ( image, "bzImage %p command line \"%s\"\n", image, cmdline ); DBGC ( image, "bzImage %p command line \"%s\"\n", image, cmdline );
return 0;
} }
/** /**
@ -354,7 +352,7 @@ static void bzimage_parse_cpio_cmdline ( struct image *image,
* @v image bzImage image * @v image bzImage image
* @v initrd initrd image * @v initrd initrd image
* @v address Address at which to load, or UNULL * @v address Address at which to load, or UNULL
* @ret len Length of loaded image, rounded up to 4 bytes * @ret len Length of loaded image, rounded up to INITRD_ALIGN
*/ */
static size_t bzimage_load_initrd ( struct image *image, static size_t bzimage_load_initrd ( struct image *image,
struct image *initrd, struct image *initrd,
@ -364,6 +362,7 @@ static size_t bzimage_load_initrd ( struct image *image,
struct cpio_header cpio; struct cpio_header cpio;
size_t offset = 0; size_t offset = 0;
size_t name_len; size_t name_len;
size_t pad_len;
/* Do not include kernel image itself as an initrd */ /* Do not include kernel image itself as an initrd */
if ( initrd == image ) if ( initrd == image )
@ -371,8 +370,6 @@ static size_t bzimage_load_initrd ( struct image *image,
/* Create cpio header before non-prebuilt images */ /* Create cpio header before non-prebuilt images */
if ( filename && filename[0] ) { if ( filename && filename[0] ) {
DBGC ( image, "bzImage %p inserting initrd %p as %s\n",
image, initrd, filename );
cmdline = strchr ( filename, ' ' ); cmdline = strchr ( filename, ' ' );
name_len = ( ( cmdline ? ( ( size_t ) ( cmdline - filename ) ) name_len = ( ( cmdline ? ( ( size_t ) ( cmdline - filename ) )
: strlen ( filename ) ) + 1 /* NUL */ ); : strlen ( filename ) ) + 1 /* NUL */ );
@ -402,17 +399,82 @@ static size_t bzimage_load_initrd ( struct image *image,
/* Copy in initrd image body */ /* Copy in initrd image body */
if ( address ) if ( address )
memcpy_user ( address, offset, initrd->data, 0, initrd->len ); memmove_user ( address, offset, initrd->data, 0, initrd->len );
offset += initrd->len;
if ( address ) { if ( address ) {
DBGC ( image, "bzImage %p has initrd %p at [%lx,%lx)\n", DBGC ( image, "bzImage %p initrd %p [%#08lx,%#08lx,%#08lx)"
image, initrd, user_to_phys ( address, 0 ), "%s%s\n", image, initrd, user_to_phys ( address, 0 ),
user_to_phys ( address, offset ) ); user_to_phys ( address, offset ),
user_to_phys ( address, ( offset + initrd->len ) ),
( filename ? " " : "" ), ( filename ? filename : "" ) );
DBGC2_MD5A ( image, user_to_phys ( address, offset ),
user_to_virt ( address, offset ), initrd->len );
}
offset += initrd->len;
/* Round up to multiple of INITRD_ALIGN and zero-pad */
pad_len = ( ( -offset ) & ( INITRD_ALIGN - 1 ) );
if ( address )
memset_user ( address, offset, 0, pad_len );
offset += pad_len;
return offset;
} }
/* Round up to 4-byte boundary */ /**
offset = ( ( offset + 0x03 ) & ~0x03 ); * Check that initrds can be loaded
return offset; *
* @v image bzImage image
* @v bzimg bzImage context
* @ret rc Return status code
*/
static int bzimage_check_initrds ( struct image *image,
struct bzimage_context *bzimg ) {
struct image *initrd;
userptr_t bottom;
size_t len = 0;
int rc;
/* Calculate total loaded length of initrds */
for_each_image ( initrd ) {
/* Skip kernel */
if ( initrd == image )
continue;
/* Calculate length */
len += bzimage_load_initrd ( image, initrd, UNULL );
DBGC ( image, "bzImage %p initrd %p from [%#08lx,%#08lx)%s%s\n",
image, initrd, user_to_phys ( initrd->data, 0 ),
user_to_phys ( initrd->data, initrd->len ),
( initrd->cmdline ? " " : "" ),
( initrd->cmdline ? initrd->cmdline : "" ) );
DBGC2_MD5A ( image, user_to_phys ( initrd->data, 0 ),
user_to_virt ( initrd->data, 0 ), initrd->len );
}
/* Calculate lowest usable address */
bottom = userptr_add ( bzimg->pm_kernel, bzimg->pm_sz );
/* Check that total length fits within space available for
* reshuffling. This is a conservative check, since CPIO
* headers are not present during reshuffling, but this
* doesn't hurt and keeps the code simple.
*/
if ( ( rc = initrd_reshuffle_check ( len, bottom ) ) != 0 ) {
DBGC ( image, "bzImage %p failed reshuffle check: %s\n",
image, strerror ( rc ) );
return rc;
}
/* Check that total length fits within kernel's memory limit */
if ( user_to_phys ( bottom, len ) > bzimg->mem_limit ) {
DBGC ( image, "bzImage %p not enough space for initrds\n",
image );
return -ENOBUFS;
}
return 0;
} }
/** /**
@ -420,62 +482,63 @@ static size_t bzimage_load_initrd ( struct image *image,
* *
* @v image bzImage image * @v image bzImage image
* @v bzimg bzImage context * @v bzimg bzImage context
* @ret rc Return status code
*/ */
static int bzimage_load_initrds ( struct image *image, static void bzimage_load_initrds ( struct image *image,
struct bzimage_context *bzimg ) { struct bzimage_context *bzimg ) {
struct image *initrd; struct image *initrd;
size_t total_len = 0; struct image *highest = NULL;
physaddr_t address; struct image *other;
int rc; userptr_t top;
userptr_t dest;
size_t len;
/* Add up length of all initrd images */ /* Reshuffle initrds into desired order */
for_each_image ( initrd ) initrd_reshuffle ( userptr_add ( bzimg->pm_kernel, bzimg->pm_sz ) );
total_len += bzimage_load_initrd ( image, initrd, UNULL );
/* Give up if no initrd images found */ /* Find highest initrd */
if ( ! total_len ) for_each_image ( initrd ) {
return 0; if ( ( highest == NULL ) ||
( userptr_sub ( initrd->data, highest->data ) > 0 ) ) {
/* Find a suitable start address. Try 1MB boundaries, highest = initrd;
* starting from the downloaded kernel image itself and
* working downwards until we hit an available region.
*/
for ( address = ( user_to_phys ( image->data, 0 ) & ~0xfffff ) ; ;
address -= 0x100000 ) {
/* Check that we're not going to overwrite the
* kernel itself. This check isn't totally
* accurate, but errs on the side of caution.
*/
if ( address <= ( BZI_LOAD_HIGH_ADDR + image->len ) ) {
DBGC ( image, "bzImage %p could not find a location "
"for initrd\n", image );
return -ENOBUFS;
} }
/* Check that we are within the kernel's range */
if ( ( address + total_len - 1 ) > bzimg->mem_limit )
continue;
/* Prepare and verify segment */
if ( ( rc = prep_segment ( phys_to_user ( address ), 0,
total_len ) ) != 0 )
continue;
/* Use this address */
break;
} }
/* Do nothing if there are no initrds */
if ( ! highest )
return;
/* Find highest usable address */
top = userptr_add ( highest->data,
( ( highest->len + INITRD_ALIGN - 1 ) &
~( INITRD_ALIGN - 1 ) ) );
if ( user_to_phys ( top, 0 ) > bzimg->mem_limit )
top = phys_to_user ( bzimg->mem_limit );
DBGC ( image, "bzImage %p loading initrds from %#08lx downwards\n",
image, user_to_phys ( top, 0 ) );
/* Load initrds in order */
for_each_image ( initrd ) {
/* Calculate cumulative length of following
* initrds (including padding).
*/
len = 0;
for_each_image ( other ) {
if ( other == initrd )
len = 0;
len += bzimage_load_initrd ( image, other, UNULL );
}
/* Load initrd at this address */
dest = userptr_add ( top, -len );
bzimage_load_initrd ( image, initrd, dest );
/* Record initrd location */ /* Record initrd location */
bzimg->ramdisk_image = address; if ( ! bzimg->ramdisk_image ) {
bzimg->ramdisk_size = total_len; bzimg->ramdisk_image = user_to_phys ( dest, 0 );
bzimg->ramdisk_size = len;
/* Construct initrd */ }
DBGC ( image, "bzImage %p constructing initrd at [%lx,%lx)\n",
image, address, ( address + total_len ) );
for_each_image ( initrd ) {
address += bzimage_load_initrd ( image, initrd,
phys_to_user ( address ) );
} }
return 0;
} }
/** /**
@ -508,33 +571,37 @@ static int bzimage_exec ( struct image *image ) {
return rc; return rc;
} }
/* Parse command line for bootloader parameters */
if ( ( rc = bzimage_parse_cmdline ( image, &bzimg, cmdline ) ) != 0)
return rc;
/* Check that initrds can be loaded */
if ( ( rc = bzimage_check_initrds ( image, &bzimg ) ) != 0 )
return rc;
/* Remove kernel from image list (without invalidating image pointer) */
unregister_image ( image_get ( image ) );
/* Load segments */ /* Load segments */
memcpy_user ( bzimg.rm_kernel, 0, image->data, memcpy_user ( bzimg.rm_kernel, 0, image->data,
0, bzimg.rm_filesz ); 0, bzimg.rm_filesz );
memcpy_user ( bzimg.pm_kernel, 0, image->data, memcpy_user ( bzimg.pm_kernel, 0, image->data,
bzimg.rm_filesz, bzimg.pm_sz ); bzimg.rm_filesz, bzimg.pm_sz );
/* Update and write out header */
bzimage_update_header ( image, &bzimg, bzimg.rm_kernel );
/* Parse command line for bootloader parameters */
if ( ( rc = bzimage_parse_cmdline ( image, &bzimg, cmdline ) ) != 0)
return rc;
/* Store command line */ /* Store command line */
if ( ( rc = bzimage_set_cmdline ( image, &bzimg, cmdline ) ) != 0 ) bzimage_set_cmdline ( image, &bzimg, cmdline );
return rc;
/* Prepare for exiting. Must do this before loading initrds,
* since loading the initrds will corrupt the external heap.
*/
shutdown_boot();
/* Load any initrds */ /* Load any initrds */
if ( ( rc = bzimage_load_initrds ( image, &bzimg ) ) != 0 ) bzimage_load_initrds ( image, &bzimg );
return rc;
/* Update kernel header */ /* Update kernel header */
bzimage_update_header ( image, &bzimg, bzimg.rm_kernel ); bzimage_update_header ( image, &bzimg, bzimg.rm_kernel );
/* Prepare for exiting */
shutdown_boot();
DBGC ( image, "bzImage %p jumping to RM kernel at %04x:0000 " DBGC ( image, "bzImage %p jumping to RM kernel at %04x:0000 "
"(stack %04x:%04zx)\n", image, ( bzimg.rm_kernel_seg + 0x20 ), "(stack %04x:%04zx)\n", image, ( bzimg.rm_kernel_seg + 0x20 ),
bzimg.rm_kernel_seg, bzimg.rm_heap ); bzimg.rm_kernel_seg, bzimg.rm_heap );