/* * Copyright (C) 2012 Michael Brown . * * 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 #include #include #include #include #include #include #include /** @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, };