summaryrefslogtreecommitdiffstats
path: root/src/image/initrd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/image/initrd.c')
-rw-r--r--src/image/initrd.c399
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,
+};