| /* |
| * 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 <errno.h> |
| #include <initrd.h> |
| #include <ipxe/image.h> |
| #include <ipxe/uaccess.h> |
| #include <ipxe/init.h> |
| #include <ipxe/memblock.h> |
| #include <ipxe/cpio.h> |
| |
| /** @file |
| * |
| * Initial ramdisk (initrd) reshuffling |
| * |
| */ |
| |
| /** Maximum address available for initrd */ |
| userptr_t initrd_top; |
| |
| /** Minimum address available for initrd */ |
| userptr_t initrd_bottom; |
| |
| /** |
| * Squash initrds as high as possible in memory |
| * |
| * @v top Highest possible address |
| * @ret used Lowest address used by initrds |
| */ |
| static userptr_t initrd_squash_high ( userptr_t top ) { |
| userptr_t current = top; |
| struct image *initrd; |
| struct image *highest; |
| size_t len; |
| |
| /* Squash up any initrds already within or below the region */ |
| while ( 1 ) { |
| |
| /* Find the highest image not yet in its final position */ |
| highest = NULL; |
| for_each_image ( initrd ) { |
| if ( ( userptr_sub ( initrd->data, current ) < 0 ) && |
| ( ( highest == NULL ) || |
| ( userptr_sub ( initrd->data, |
| highest->data ) > 0 ) ) ) { |
| highest = initrd; |
| } |
| } |
| if ( ! highest ) |
| break; |
| |
| /* Move this image to its final position */ |
| len = ( ( highest->len + INITRD_ALIGN - 1 ) & |
| ~( INITRD_ALIGN - 1 ) ); |
| current = userptr_sub ( current, len ); |
| DBGC ( &images, "INITRD squashing %s [%#08lx,%#08lx)->" |
| "[%#08lx,%#08lx)\n", highest->name, |
| user_to_phys ( highest->data, 0 ), |
| user_to_phys ( highest->data, highest->len ), |
| user_to_phys ( current, 0 ), |
| user_to_phys ( current, highest->len ) ); |
| memmove_user ( current, 0, highest->data, 0, highest->len ); |
| highest->data = current; |
| } |
| |
| /* Copy any remaining initrds (e.g. embedded images) to the region */ |
| for_each_image ( initrd ) { |
| if ( userptr_sub ( initrd->data, top ) >= 0 ) { |
| len = ( ( initrd->len + INITRD_ALIGN - 1 ) & |
| ~( INITRD_ALIGN - 1 ) ); |
| current = userptr_sub ( current, len ); |
| DBGC ( &images, "INITRD copying %s [%#08lx,%#08lx)->" |
| "[%#08lx,%#08lx)\n", initrd->name, |
| user_to_phys ( initrd->data, 0 ), |
| user_to_phys ( initrd->data, initrd->len ), |
| user_to_phys ( current, 0 ), |
| user_to_phys ( current, initrd->len ) ); |
| memcpy_user ( current, 0, initrd->data, 0, |
| initrd->len ); |
| initrd->data = current; |
| } |
| } |
| |
| return current; |
| } |
| |
| /** |
| * Swap position of two adjacent initrds |
| * |
| * @v low Lower initrd |
| * @v high Higher initrd |
| * @v free Free space |
| * @v free_len Length of free space |
| */ |
| static void initrd_swap ( struct image *low, struct image *high, |
| userptr_t free, size_t free_len ) { |
| size_t len = 0; |
| size_t frag_len; |
| size_t new_len; |
| |
| DBGC ( &images, "INITRD swapping %s [%#08lx,%#08lx)<->[%#08lx,%#08lx) " |
| "%s\n", low->name, user_to_phys ( low->data, 0 ), |
| user_to_phys ( low->data, low->len ), |
| user_to_phys ( high->data, 0 ), |
| user_to_phys ( high->data, high->len ), high->name ); |
| |
| /* Round down length of free space */ |
| free_len &= ~( INITRD_ALIGN - 1 ); |
| assert ( free_len > 0 ); |
| |
| /* Swap image data */ |
| while ( len < high->len ) { |
| |
| /* Calculate maximum fragment length */ |
| frag_len = ( high->len - len ); |
| if ( frag_len > free_len ) |
| frag_len = free_len; |
| new_len = ( ( len + frag_len + INITRD_ALIGN - 1 ) & |
| ~( INITRD_ALIGN - 1 ) ); |
| |
| /* Swap fragments */ |
| memcpy_user ( free, 0, high->data, len, frag_len ); |
| memmove_user ( low->data, new_len, low->data, len, low->len ); |
| memcpy_user ( low->data, len, free, 0, frag_len ); |
| len = new_len; |
| } |
| |
| /* Adjust data pointers */ |
| high->data = low->data; |
| low->data = userptr_add ( low->data, len ); |
| } |
| |
| /** |
| * Swap position of any two adjacent initrds not currently in the correct order |
| * |
| * @v free Free space |
| * @v free_len Length of free space |
| * @ret swapped A pair of initrds was swapped |
| */ |
| static int initrd_swap_any ( userptr_t free, size_t free_len ) { |
| struct image *low; |
| struct image *high; |
| size_t padded_len; |
| userptr_t adjacent; |
| |
| /* Find any pair of initrds that can be swapped */ |
| for_each_image ( low ) { |
| |
| /* Calculate location of adjacent image (if any) */ |
| padded_len = ( ( low->len + INITRD_ALIGN - 1 ) & |
| ~( INITRD_ALIGN - 1 ) ); |
| adjacent = userptr_add ( low->data, padded_len ); |
| |
| /* Search for adjacent image */ |
| for_each_image ( high ) { |
| |
| /* 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, free, free_len ); |
| 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, user_to_phys ( initrd->data, 0 ), |
| user_to_phys ( initrd->data, initrd->len ) ); |
| DBGC2_MD5A ( &images, user_to_phys ( initrd->data, 0 ), |
| user_to_virt ( initrd->data, 0 ), initrd->len ); |
| } |
| } |
| |
| /** |
| * Reshuffle initrds into desired order at top of memory |
| * |
| * @v bottom Lowest 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 ( userptr_t bottom ) { |
| userptr_t top; |
| userptr_t used; |
| userptr_t free; |
| size_t free_len; |
| |
| /* Calculate limits of available space for initrds */ |
| top = initrd_top; |
| if ( userptr_sub ( initrd_bottom, bottom ) > 0 ) |
| bottom = initrd_bottom; |
| |
| /* Debug */ |
| DBGC ( &images, "INITRD region [%#08lx,%#08lx)\n", |
| user_to_phys ( bottom, 0 ), user_to_phys ( top, 0 ) ); |
| initrd_dump(); |
| |
| /* Squash initrds as high as possible in memory */ |
| used = initrd_squash_high ( top ); |
| |
| /* Calculate available free space */ |
| free = bottom; |
| free_len = userptr_sub ( used, free ); |
| |
| /* Bubble-sort initrds into desired order */ |
| while ( initrd_swap_any ( free, free_len ) ) {} |
| |
| /* Debug */ |
| initrd_dump(); |
| } |
| |
| /** |
| * Check that there is enough space to reshuffle initrds |
| * |
| * @v len Total length of initrds (including padding) |
| * @v bottom Lowest address available for initrds |
| * @ret rc Return status code |
| */ |
| int initrd_reshuffle_check ( size_t len, userptr_t bottom ) { |
| userptr_t top; |
| size_t available; |
| |
| /* Calculate limits of available space for initrds */ |
| top = initrd_top; |
| if ( userptr_sub ( initrd_bottom, bottom ) > 0 ) |
| bottom = initrd_bottom; |
| available = userptr_sub ( top, bottom ); |
| |
| /* Allow for a sensible minimum amount of free space */ |
| len += INITRD_MIN_FREE_LEN; |
| |
| /* Check for available space */ |
| return ( ( len < available ) ? 0 : -ENOBUFS ); |
| } |
| |
| /** |
| * initrd startup function |
| * |
| */ |
| static void initrd_startup ( void ) { |
| size_t len; |
| |
| /* Record largest memory block available. Do this after any |
| * allocations 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) |
| * but before any allocations for downloaded images (which we |
| * can safely reuse when rearranging). |
| */ |
| len = largest_memblock ( &initrd_bottom ); |
| initrd_top = userptr_add ( initrd_bottom, len ); |
| } |
| |
| /** initrd startup function */ |
| struct startup_fn startup_initrd __startup_fn ( STARTUP_LATE ) = { |
| .name = "initrd", |
| .startup = initrd_startup, |
| }; |