diff options
| author | Simon Rettberg | 2026-01-28 12:53:53 +0100 |
|---|---|---|
| committer | Simon Rettberg | 2026-01-28 12:53:53 +0100 |
| commit | 8e82785c584dc13e20f9229decb95bd17bbe9cd1 (patch) | |
| tree | a8b359e59196be5b2e3862bed189107f4bc9975f /src/image/initrd.c | |
| parent | Merge branch 'master' into openslx (diff) | |
| parent | [prefix] Make unlzma.S compatible with 386 class CPUs (diff) | |
| download | ipxe-openslx.tar.gz ipxe-openslx.tar.xz ipxe-openslx.zip | |
Merge branch 'master' into openslxopenslx
Diffstat (limited to 'src/image/initrd.c')
| -rw-r--r-- | src/image/initrd.c | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/src/image/initrd.c b/src/image/initrd.c new file mode 100644 index 000000000..ba550a8f2 --- /dev/null +++ b/src/image/initrd.c @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2012 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <string.h> +#include <errno.h> +#include <ipxe/image.h> +#include <ipxe/uaccess.h> +#include <ipxe/init.h> +#include <ipxe/cpio.h> +#include <ipxe/uheap.h> +#include <ipxe/initrd.h> + +/** @file + * + * Initial ramdisk (initrd) reshuffling + * + */ + +/** End of reshuffle region */ +static physaddr_t initrd_end; + +/** + * Squash initrds as high as possible in memory + * + * @v start Start of reshuffle region + * @v end End of reshuffle region + */ +static void initrd_squash_high ( physaddr_t start, physaddr_t end ) { + physaddr_t current = end; + struct image *initrd; + struct image *highest; + void *data; + + /* Squash up any initrds already within the region */ + while ( 1 ) { + + /* Find the highest image not yet in its final position */ + highest = NULL; + for_each_image ( initrd ) { + if ( ( virt_to_phys ( initrd->data ) >= start ) && + ( virt_to_phys ( initrd->data ) < current ) && + ( ( highest == NULL ) || + ( virt_to_phys ( initrd->data ) > + virt_to_phys ( highest->data ) ) ) ) { + highest = initrd; + } + } + if ( ! highest ) + break; + + /* Calculate final position */ + current -= initrd_align ( highest->len ); + if ( current <= virt_to_phys ( highest->data ) ) { + /* Already at (or crossing) end of region */ + current = virt_to_phys ( highest->data ); + continue; + } + + /* Move this image to its final position */ + DBGC ( &images, "INITRD squashing %s [%#08lx,%#08lx)->" + "[%#08lx,%#08lx)\n", highest->name, + virt_to_phys ( highest->data ), + ( virt_to_phys ( highest->data ) + highest->len ), + current, ( current + highest->len ) ); + data = phys_to_virt ( current ); + memmove ( data, highest->data, highest->len ); + highest->data = data; + } +} + +/** + * Reverse aligned memory region + * + * @v data Memory region + * @v len Length of region + */ +static void initrd_reverse ( void *data, size_t len ) { + unsigned long *low = data; + unsigned long *high = ( data + len ); + unsigned long tmp; + + /* Reverse region */ + for ( high-- ; low < high ; low++, high-- ) { + tmp = *low; + *low = *high; + *high = tmp; + } +} + +/** + * Swap position of two adjacent initrds + * + * @v low Lower initrd + * @v high Higher initrd + */ +static void initrd_swap ( struct image *low, struct image *high ) { + size_t low_len; + size_t high_len; + size_t len; + void *data; + + DBGC ( &images, "INITRD swapping %s [%#08lx,%#08lx)<->[%#08lx,%#08lx) " + "%s\n", low->name, virt_to_phys ( low->data ), + ( virt_to_phys ( low->data ) + low->len ), + virt_to_phys ( high->data ), + ( virt_to_phys ( high->data ) + high->len ), high->name ); + + /* Calculate padded lengths */ + low_len = initrd_align ( low->len ); + high_len = initrd_align ( high->len ); + len = ( low_len + high_len ); + data = low->rwdata; + assert ( high->data == ( data + low_len ) ); + + /* Adjust data pointers */ + high->data -= low_len; + low->data += high_len; + assert ( high->data == data ); + + /* Swap content via triple reversal */ + initrd_reverse ( data, len ); + initrd_reverse ( low->rwdata, low_len ); + initrd_reverse ( high->rwdata, high_len ); +} + +/** + * Swap position of any two adjacent initrds not currently in the correct order + * + * @v start Start of reshuffle region + * @v end End of reshuffle region + * @ret swapped A pair of initrds was swapped + */ +static int initrd_swap_any ( physaddr_t start, physaddr_t end ) { + struct image *low; + struct image *high; + const void *adjacent; + physaddr_t addr; + + /* Find any pair of initrds that can be swapped */ + for_each_image ( low ) { + + /* Ignore images wholly outside the reshuffle region */ + addr = virt_to_phys ( low->data ); + if ( ( addr < start ) || ( addr >= end ) ) + continue; + + /* Calculate location of adjacent image (if any) */ + adjacent = ( low->data + initrd_align ( low->len ) ); + + /* Search for adjacent image */ + for_each_image ( high ) { + + /* Ignore images wholly outside the reshuffle region */ + addr = virt_to_phys ( high->data ); + if ( ( addr < start ) || ( addr >= end ) ) + continue; + + /* Stop search if all remaining potential + * adjacent images are already in the correct + * order. + */ + if ( high == low ) + break; + + /* If we have found the adjacent image, swap and exit */ + if ( high->data == adjacent ) { + initrd_swap ( low, high ); + return 1; + } + } + } + + /* Nothing swapped */ + return 0; +} + +/** + * Dump initrd locations (for debug) + * + */ +static void initrd_dump ( void ) { + struct image *initrd; + + /* Do nothing unless debugging is enabled */ + if ( ! DBG_LOG ) + return; + + /* Dump initrd locations */ + for_each_image ( initrd ) { + DBGC ( &images, "INITRD %s at [%#08lx,%#08lx)\n", + initrd->name, virt_to_phys ( initrd->data ), + ( virt_to_phys ( initrd->data ) + initrd->len ) ); + DBGC2_MD5A ( &images, virt_to_phys ( initrd->data ), + initrd->data, initrd->len ); + } +} + +/** + * Reshuffle initrds into desired order at top of memory + * + * 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 ( void ) { + physaddr_t start; + physaddr_t end; + + /* Calculate limits of reshuffle region */ + start = uheap_limit; + end = ( initrd_end ? initrd_end : uheap_end ); + + /* Debug */ + initrd_dump(); + + /* Squash initrds as high as possible in memory */ + initrd_squash_high ( start, end ); + + /* Bubble-sort initrds into desired order */ + while ( initrd_swap_any ( start, end ) ) {} + + /* Debug */ + initrd_dump(); +} + +/** + * 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 + * + * 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_region ( size_t len, struct memmap_region *region ) { + physaddr_t min; + size_t available; + + /* Calculate limits of available space for initrds */ + min = uheap_limit; + available = ( ( initrd_end ? initrd_end : 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->min = min; + region->max = ( min + available - 1 ); + region->flags = MEMMAP_FL_MEMORY; + region->name = "initrd"; + + return 0; +} + +/** + * initrd startup function + * + */ +static void initrd_startup ( void ) { + + /* Record address above which reshuffling cannot take place. + * If any external heap allocations have been made during + * driver startup (e.g. large host memory blocks for + * Infiniband devices, which may still be in use at the time + * of rearranging if a SAN device is hooked), then we must not + * overwrite these allocations during reshuffling. + */ + initrd_end = uheap_start; + if ( initrd_end ) { + DBGC ( &images, "INITRD limiting reshuffling to below " + "%#08lx\n", initrd_end ); + } +} + +/** initrd startup function */ +struct startup_fn startup_initrd __startup_fn ( STARTUP_LATE ) = { + .name = "initrd", + .startup = initrd_startup, +}; |
