From: Michael Brown Date: Fri, 23 May 2025 11:13:02 +0000 (+0100) Subject: [initrd] Split out initrd construction from bzimage.c X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=4a39b877dd165cc903da2e15cb1c42d1d115cc80;p=thirdparty%2Fipxe.git [initrd] Split out initrd construction from bzimage.c Provide a reusable function initrd_load_all() to load all initrds (including any constructed CPIO headers) into a contiguous memory region, and support functions to find the constructed total length and permissible post-reshuffling load address range. Signed-off-by: Michael Brown --- diff --git a/src/arch/x86/image/bzimage.c b/src/arch/x86/image/bzimage.c index 08e0d0873..764f37bbc 100644 --- a/src/arch/x86/image/bzimage.c +++ b/src/arch/x86/image/bzimage.c @@ -77,9 +77,9 @@ struct bzimage_context { /** Memory limit */ uint64_t mem_limit; /** Initrd address */ - physaddr_t ramdisk_image; + void *initrd; /** Initrd size */ - physaddr_t ramdisk_size; + physaddr_t initrd_size; }; /** @@ -226,8 +226,8 @@ static void bzimage_update_header ( struct image *image, /* Set initrd address */ if ( bzimg->version >= 0x0200 ) { - bzhdr->ramdisk_image = bzimg->ramdisk_image; - bzhdr->ramdisk_size = bzimg->ramdisk_size; + bzhdr->ramdisk_image = virt_to_phys ( bzimg->initrd ); + bzhdr->ramdisk_size = bzimg->initrd_size; } } @@ -316,67 +316,6 @@ static void bzimage_set_cmdline ( struct image *image, image->name, rm_cmdline ); } -/** - * Load initrd - * - * @v image bzImage image - * @v initrd initrd image - * @v address Address at which to load, or NULL - * @ret len Length of loaded image, excluding zero-padding - */ -static size_t bzimage_load_initrd ( struct image *image, - struct image *initrd, - void *address ) { - const char *filename = cpio_name ( initrd ); - struct cpio_header cpio; - size_t offset; - size_t cpio_len; - size_t pad_len; - size_t len; - unsigned int i; - - /* Skip hidden images */ - if ( initrd->flags & IMAGE_HIDDEN ) - return 0; - - /* Determine length of cpio headers for non-prebuilt images */ - len = 0; - for ( i = 0 ; ( cpio_len = cpio_header ( initrd, i, &cpio ) ) ; i++ ) - len += ( cpio_len + cpio_pad_len ( cpio_len ) ); - - /* Copy in initrd image body and construct any cpio headers */ - if ( address ) { - memmove ( ( address + len ), initrd->data, initrd->len ); - memset ( address, 0, len ); - offset = 0; - for ( i = 0 ; ( cpio_len = cpio_header ( initrd, i, &cpio ) ) ; - i++ ) { - memcpy ( ( address + offset ), &cpio, - sizeof ( cpio ) ); - memcpy ( ( address + offset + sizeof ( cpio ) ), - filename, ( cpio_len - sizeof ( cpio ) ) ); - offset += ( cpio_len + cpio_pad_len ( cpio_len ) ); - } - assert ( offset == len ); - DBGC ( image, "bzImage %s initrd %s [%#08lx,%#08lx,%#08lx)" - "%s%s\n", image->name, initrd->name, - virt_to_phys ( address ), - ( virt_to_phys ( address ) + offset ), - ( virt_to_phys ( address ) + offset + initrd->len ), - ( filename ? " " : "" ), ( filename ? filename : "" ) ); - DBGC2_MD5A ( image, ( virt_to_phys ( address ) + offset ), - ( address + offset ), initrd->len ); - } - len += initrd->len; - - /* Zero-pad to next INITRD_ALIGN boundary */ - pad_len = ( ( -len ) & ( INITRD_ALIGN - 1 ) ); - if ( address ) - memset ( ( address + len ), 0, pad_len ); - - return len; -} - /** * Check that initrds can be loaded * @@ -386,48 +325,52 @@ static size_t bzimage_load_initrd ( struct image *image, */ static int bzimage_check_initrds ( struct image *image, struct bzimage_context *bzimg ) { - struct image *initrd; - physaddr_t bottom; - size_t len = 0; + struct memmap_region region; + physaddr_t min; + physaddr_t max; + physaddr_t dest; int rc; /* Calculate total loaded length of initrds */ - for_each_image ( initrd ) { - - /* Calculate length */ - len += bzimage_load_initrd ( image, initrd, NULL ); - len = initrd_align ( len ); - - DBGC ( image, "bzImage %s initrd %s from [%#08lx,%#08lx)%s%s\n", - image->name, initrd->name, virt_to_phys ( initrd->data ), - ( virt_to_phys ( initrd->data ) + initrd->len ), - ( initrd->cmdline ? " " : "" ), - ( initrd->cmdline ? initrd->cmdline : "" ) ); - DBGC2_MD5A ( image, virt_to_phys ( initrd->data ), - initrd->data, initrd->len ); - } + bzimg->initrd_size = initrd_len(); - /* Calculate lowest usable address */ - bottom = virt_to_phys ( bzimg->pm_kernel + bzimg->pm_sz ); + /* Succeed if there are no initrds */ + if ( ! bzimg->initrd_size ) + return 0; - /* 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 %s failed reshuffle check: %s\n", + /* Calculate available load region after reshuffling */ + if ( ( rc = initrd_region ( bzimg->initrd_size, ®ion ) ) != 0 ) { + DBGC ( image, "bzImage %s no region for initrds: %s\n", image->name, strerror ( rc ) ); return rc; } - /* Check that total length fits within kernel's memory limit */ - if ( ( bottom + len ) > bzimg->mem_limit ) { + /* Limit region to avoiding kernel itself */ + min = virt_to_phys ( bzimg->pm_kernel + bzimg->pm_sz ); + if ( min < region.addr ) + min = region.addr; + + /* Limit region to kernel's memory limit */ + max = region.last; + if ( max > bzimg->mem_limit ) + max = bzimg->mem_limit; + + /* Calculate installation address */ + if ( max < ( bzimg->initrd_size - 1 ) ) { + DBGC ( image, "bzImage %s not enough space for initrds\n", + image->name ); + return -ENOBUFS; + } + dest = ( ( max + 1 - bzimg->initrd_size ) & ~( INITRD_ALIGN - 1 ) ); + if ( dest < min ) { DBGC ( image, "bzImage %s not enough space for initrds\n", image->name ); return -ENOBUFS; } + bzimg->initrd = phys_to_virt ( dest ); + DBGC ( image, "bzImage %s loading initrds from %#08lx downwards\n", + image->name, max ); return 0; } @@ -439,63 +382,21 @@ static int bzimage_check_initrds ( struct image *image, */ static void bzimage_load_initrds ( struct image *image, struct bzimage_context *bzimg ) { - struct image *initrd; - struct image *other; - physaddr_t bottom; - physaddr_t top; - physaddr_t dest; - size_t offset; size_t len; - /* Reshuffle initrds into desired order */ - bottom = virt_to_phys ( bzimg->pm_kernel + bzimg->pm_sz ); - initrd_reshuffle ( bottom ); - - /* Find highest usable address */ - top = 0; - for_each_image ( initrd ) { - if ( virt_to_phys ( initrd->data ) >= top ) { - top = ( virt_to_phys ( initrd->data ) + - initrd_align ( initrd->len ) ); - } - } - /* Do nothing if there are no initrds */ - if ( ! top ) + if ( ! bzimg->initrd ) return; - if ( ( top - 1UL ) > bzimg->mem_limit ) { - top = ( ( bzimg->mem_limit + 1 ) & ~( INITRD_ALIGN - 1 ) ); - } - DBGC ( image, "bzImage %s loading initrds from %#08lx downwards\n", - image->name, ( top - 1UL ) ); - /* Load initrds in order */ - for_each_image ( initrd ) { - - /* Calculate cumulative length of following - * initrds (including padding). - */ - offset = 0; - for_each_image ( other ) { - if ( other == initrd ) - offset = 0; - offset += bzimage_load_initrd ( image, other, NULL ); - offset = initrd_align ( offset ); - } - - /* Load initrd at this address */ - dest = ( top - offset ); - len = bzimage_load_initrd ( image, initrd, - phys_to_virt ( dest ) ); + /* Reshuffle initrds into desired order */ + initrd_reshuffle(); - /* Record initrd location */ - if ( ! bzimg->ramdisk_image ) - bzimg->ramdisk_image = dest; - bzimg->ramdisk_size = ( dest + len - bzimg->ramdisk_image ); - } + /* Load initrds */ DBGC ( image, "bzImage %s initrds at [%#08lx,%#08lx)\n", - image->name, bzimg->ramdisk_image, - ( bzimg->ramdisk_image + bzimg->ramdisk_size ) ); + image->name, virt_to_phys ( bzimg->initrd ), + ( virt_to_phys ( bzimg->initrd ) + bzimg->initrd_size ) ); + len = initrd_load_all ( bzimg->initrd ); + assert ( len == bzimg->initrd_size ); } /** diff --git a/src/image/initrd.c b/src/image/initrd.c index 5ba8c473d..a93f54ffb 100644 --- a/src/image/initrd.c +++ b/src/image/initrd.c @@ -220,23 +220,19 @@ static void initrd_dump ( void ) { /** * Reshuffle initrds into desired order at top of memory * - * @v bottom Lowest physical address available for initrds - * * After this function returns, the initrds have been rearranged in * memory and the external heap structures will have been corrupted. * Reshuffling must therefore take place immediately prior to jumping * to the loaded OS kernel; no further execution within iPXE is * permitted. */ -void initrd_reshuffle ( physaddr_t bottom ) { +void initrd_reshuffle ( void ) { physaddr_t top; /* Calculate limits of available space for initrds */ top = ( initrd_top ? initrd_top : uheap_end ); - assert ( bottom >= uheap_limit ); /* Debug */ - DBGC ( &images, "INITRD region [%#08lx,%#08lx)\n", bottom, top ); initrd_dump(); /* Squash initrds as high as possible in memory */ @@ -250,23 +246,128 @@ void initrd_reshuffle ( physaddr_t bottom ) { } /** - * Check that there is enough space to reshuffle initrds + * Load initrd + * + * @v initrd initrd image + * @v address Address at which to load, or NULL + * @ret len Length of loaded image, excluding zero-padding + */ +static size_t initrd_load ( struct image *initrd, void *address ) { + const char *filename = cpio_name ( initrd ); + struct cpio_header cpio; + size_t offset; + size_t cpio_len; + size_t len; + unsigned int i; + + /* Sanity check */ + assert ( ( address == NULL ) || + ( ( virt_to_phys ( address ) & ( INITRD_ALIGN - 1 ) ) == 0 )); + + /* Skip hidden images */ + if ( initrd->flags & IMAGE_HIDDEN ) + return 0; + + /* Determine length of cpio headers for non-prebuilt images */ + len = 0; + for ( i = 0 ; ( cpio_len = cpio_header ( initrd, i, &cpio ) ) ; i++ ) + len += ( cpio_len + cpio_pad_len ( cpio_len ) ); + + /* Copy in initrd image body and construct any cpio headers */ + if ( address ) { + memmove ( ( address + len ), initrd->data, initrd->len ); + memset ( address, 0, len ); + offset = 0; + for ( i = 0 ; ( cpio_len = cpio_header ( initrd, i, &cpio ) ) ; + i++ ) { + memcpy ( ( address + offset ), &cpio, + sizeof ( cpio ) ); + memcpy ( ( address + offset + sizeof ( cpio ) ), + filename, ( cpio_len - sizeof ( cpio ) ) ); + offset += ( cpio_len + cpio_pad_len ( cpio_len ) ); + } + assert ( offset == len ); + DBGC ( &images, "INITRD %s [%#08lx,%#08lx,%#08lx)%s%s\n", + initrd->name, virt_to_phys ( address ), + ( virt_to_phys ( address ) + offset ), + ( virt_to_phys ( address ) + offset + initrd->len ), + ( filename ? " " : "" ), ( filename ? filename : "" ) ); + DBGC2_MD5A ( &images, ( virt_to_phys ( address ) + offset ), + ( address + offset ), initrd->len ); + } + len += initrd->len; + + return len; +} + +/** + * Load all initrds + * + * @v address Load address, or NULL + * @ret len Length * - * @v len Total length of initrds (including padding) - * @v bottom Lowest physical address available for initrds + * This function is called after the point of no return, when the + * external heap has been corrupted by reshuffling and there is no way + * to resume normal execution. The caller must have previously + * ensured that there is no way for installation to this address to + * fail. + */ +size_t initrd_load_all ( void *address ) { + struct image *initrd; + size_t len = 0; + size_t pad_len; + void *dest; + + /* Load all initrds */ + for_each_image ( initrd ) { + + /* Zero-pad to next INITRD_ALIGN boundary */ + pad_len = ( ( -len ) & ( INITRD_ALIGN - 1 ) ); + if ( address ) + memset ( ( address + len ), 0, pad_len ); + len += pad_len; + assert ( len == initrd_align ( len ) ); + + /* Load initrd */ + dest = ( address ? ( address + len ) : NULL ); + len += initrd_load ( initrd, dest ); + } + + return len; +} + +/** + * Calculate post-reshuffle initrd load region + * + * @v len Length of initrds (from initrd_len()) + * @v region Region descriptor to fill in * @ret rc Return status code + * + * If successful, then any suitably aligned range within the region + * may be used as the load address after reshuffling. The caller does + * not need to call prep_segment() for a range in this region. + * (Calling prep_segment() would probably fail, since prior to + * reshuffling the region is still in use by the external heap.) */ -int initrd_reshuffle_check ( size_t len, physaddr_t bottom ) { - physaddr_t top; +int initrd_region ( size_t len, struct memmap_region *region ) { + physaddr_t min; size_t available; /* Calculate limits of available space for initrds */ - top = ( initrd_top ? initrd_top : uheap_end ); - assert ( bottom >= uheap_limit ); - available = ( top - bottom ); + min = uheap_limit; + available = ( ( initrd_top ? initrd_top : uheap_end ) - min ); + if ( available < len ) + return -ENOSPC; + DBGC ( &images, "INITRD post-reshuffle region is [%#08lx,%#08lx)\n", + min, ( min + available ) ); + + /* Populate region descriptor */ + region->addr = min; + region->last = ( min + available - 1 ); + region->flags = MEMMAP_FL_MEMORY; + region->name = "initrd"; - /* Check for available space */ - return ( ( len < available ) ? 0 : -ENOBUFS ); + return 0; } /** diff --git a/src/include/ipxe/initrd.h b/src/include/ipxe/initrd.h index 10533b53b..0b955a381 100644 --- a/src/include/ipxe/initrd.h +++ b/src/include/ipxe/initrd.h @@ -10,13 +10,15 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include - -extern void initrd_reshuffle ( physaddr_t bottom ); -extern int initrd_reshuffle_check ( size_t len, physaddr_t bottom ); +#include /** Initial ramdisk chunk alignment */ #define INITRD_ALIGN 4096 +extern void initrd_reshuffle ( void ); +extern int initrd_region ( size_t len, struct memmap_region *region ); +extern size_t initrd_load_all ( void *address ); + /** * Align initrd length * @@ -29,4 +31,15 @@ initrd_align ( size_t len ) { return ( ( len + INITRD_ALIGN - 1 ) & ~( INITRD_ALIGN - 1 ) ); } +/** + * Get required length for initrds + * + * @ret len Required length + */ +static inline __attribute__ (( always_inline )) size_t +initrd_len ( void ) { + + return initrd_load_all ( NULL ); +} + #endif /* _IPXE_INITRD_H */