| /* |
| * 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, |
| }; |