]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[initrd] Split out initrd construction from bzimage.c
authorMichael Brown <mcb30@ipxe.org>
Fri, 23 May 2025 11:13:02 +0000 (12:13 +0100)
committerMichael Brown <mcb30@ipxe.org>
Fri, 23 May 2025 11:31:46 +0000 (12:31 +0100)
Provide a reusable function initrd_load_all() to load all initrds
(including any constructed CPIO headers) into a contiguous memory
region, and support functions to find the constructed total length and
permissible post-reshuffling load address range.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/arch/x86/image/bzimage.c
src/image/initrd.c
src/include/ipxe/initrd.h

index 08e0d0873be3befe03487a09d8b4f4cfacdf538e..764f37bbc57523ade146d549e504e0169b00d7af 100644 (file)
@@ -77,9 +77,9 @@ struct bzimage_context {
        /** Memory limit */
        uint64_t mem_limit;
        /** Initrd address */
-       physaddr_t ramdisk_image;
+       void *initrd;
        /** Initrd size */
-       physaddr_t ramdisk_size;
+       physaddr_t initrd_size;
 };
 
 /**
@@ -226,8 +226,8 @@ static void bzimage_update_header ( struct image *image,
 
        /* Set initrd address */
        if ( bzimg->version >= 0x0200 ) {
-               bzhdr->ramdisk_image = bzimg->ramdisk_image;
-               bzhdr->ramdisk_size = bzimg->ramdisk_size;
+               bzhdr->ramdisk_image = virt_to_phys ( bzimg->initrd );
+               bzhdr->ramdisk_size = bzimg->initrd_size;
        }
 }
 
@@ -316,67 +316,6 @@ static void bzimage_set_cmdline ( struct image *image,
               image->name, rm_cmdline );
 }
 
-/**
- * Load initrd
- *
- * @v image            bzImage image
- * @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 bzimage_load_initrd ( struct image *image,
-                                   struct image *initrd,
-                                   void *address ) {
-       const char *filename = cpio_name ( initrd );
-       struct cpio_header cpio;
-       size_t offset;
-       size_t cpio_len;
-       size_t pad_len;
-       size_t len;
-       unsigned int i;
-
-       /* 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 ( image, "bzImage %s initrd %s [%#08lx,%#08lx,%#08lx)"
-                      "%s%s\n", image->name, initrd->name,
-                      virt_to_phys ( address ),
-                      ( virt_to_phys ( address ) + offset ),
-                      ( virt_to_phys ( address ) + offset + initrd->len ),
-                      ( filename ? " " : "" ), ( filename ? filename : "" ) );
-               DBGC2_MD5A ( image, ( virt_to_phys ( address ) + offset ),
-                            ( address + offset ), initrd->len );
-       }
-       len += initrd->len;
-
-       /* Zero-pad to next INITRD_ALIGN boundary */
-       pad_len = ( ( -len ) & ( INITRD_ALIGN - 1 ) );
-       if ( address )
-               memset ( ( address + len ), 0, pad_len );
-
-       return len;
-}
-
 /**
  * Check that initrds can be loaded
  *
@@ -386,48 +325,52 @@ static size_t bzimage_load_initrd ( struct image *image,
  */
 static int bzimage_check_initrds ( struct image *image,
                                   struct bzimage_context *bzimg ) {
-       struct image *initrd;
-       physaddr_t bottom;
-       size_t len = 0;
+       struct memmap_region region;
+       physaddr_t min;
+       physaddr_t max;
+       physaddr_t dest;
        int rc;
 
        /* Calculate total loaded length of initrds */
-       for_each_image ( initrd ) {
-
-               /* Calculate length */
-               len += bzimage_load_initrd ( image, initrd, NULL );
-               len = initrd_align ( len );
-
-               DBGC ( image, "bzImage %s initrd %s from [%#08lx,%#08lx)%s%s\n",
-                      image->name, initrd->name, virt_to_phys ( initrd->data ),
-                      ( virt_to_phys ( initrd->data ) + initrd->len ),
-                      ( initrd->cmdline ? " " : "" ),
-                      ( initrd->cmdline ? initrd->cmdline : "" ) );
-               DBGC2_MD5A ( image, virt_to_phys ( initrd->data ),
-                            initrd->data, initrd->len );
-       }
+       bzimg->initrd_size = initrd_len();
 
-       /* Calculate lowest usable address */
-       bottom = virt_to_phys ( bzimg->pm_kernel + bzimg->pm_sz );
+       /* Succeed if there are no initrds */
+       if ( ! bzimg->initrd_size )
+               return 0;
 
-       /* Check that total length fits within space available for
-        * reshuffling.  This is a conservative check, since CPIO
-        * headers are not present during reshuffling, but this
-        * doesn't hurt and keeps the code simple.
-        */
-       if ( ( rc = initrd_reshuffle_check ( len, bottom ) ) != 0 ) {
-               DBGC ( image, "bzImage %s failed reshuffle check: %s\n",
+       /* Calculate available load region after reshuffling */
+       if ( ( rc = initrd_region ( bzimg->initrd_size, &region ) ) != 0 ) {
+               DBGC ( image, "bzImage %s no region for initrds: %s\n",
                       image->name, strerror ( rc ) );
                return rc;
        }
 
-       /* Check that total length fits within kernel's memory limit */
-       if ( ( bottom + len ) > bzimg->mem_limit ) {
+       /* Limit region to avoiding kernel itself */
+       min = virt_to_phys ( bzimg->pm_kernel + bzimg->pm_sz );
+       if ( min < region.addr )
+               min = region.addr;
+
+       /* Limit region to kernel's memory limit */
+       max = region.last;
+       if ( max > bzimg->mem_limit )
+               max = bzimg->mem_limit;
+
+       /* Calculate installation address */
+       if ( max < ( bzimg->initrd_size - 1 ) ) {
+               DBGC ( image, "bzImage %s not enough space for initrds\n",
+                      image->name );
+               return -ENOBUFS;
+       }
+       dest = ( ( max + 1 - bzimg->initrd_size ) & ~( INITRD_ALIGN - 1 ) );
+       if ( dest < min ) {
                DBGC ( image, "bzImage %s not enough space for initrds\n",
                       image->name );
                return -ENOBUFS;
        }
+       bzimg->initrd = phys_to_virt ( dest );
 
+       DBGC ( image, "bzImage %s loading initrds from %#08lx downwards\n",
+              image->name, max );
        return 0;
 }
 
@@ -439,63 +382,21 @@ static int bzimage_check_initrds ( struct image *image,
  */
 static void bzimage_load_initrds ( struct image *image,
                                   struct bzimage_context *bzimg ) {
-       struct image *initrd;
-       struct image *other;
-       physaddr_t bottom;
-       physaddr_t top;
-       physaddr_t dest;
-       size_t offset;
        size_t len;
 
-       /* Reshuffle initrds into desired order */
-       bottom = virt_to_phys ( bzimg->pm_kernel + bzimg->pm_sz );
-       initrd_reshuffle ( bottom );
-
-       /* Find highest usable address */
-       top = 0;
-       for_each_image ( initrd ) {
-               if ( virt_to_phys ( initrd->data ) >= top ) {
-                       top = ( virt_to_phys ( initrd->data ) +
-                               initrd_align ( initrd->len ) );
-               }
-       }
-
        /* Do nothing if there are no initrds */
-       if ( ! top )
+       if ( ! bzimg->initrd )
                return;
-       if ( ( top - 1UL ) > bzimg->mem_limit ) {
-               top = ( ( bzimg->mem_limit + 1 ) & ~( INITRD_ALIGN - 1 ) );
-       }
-       DBGC ( image, "bzImage %s loading initrds from %#08lx downwards\n",
-              image->name, ( top - 1UL ) );
 
-       /* Load initrds in order */
-       for_each_image ( initrd ) {
-
-               /* Calculate cumulative length of following
-                * initrds (including padding).
-                */
-               offset = 0;
-               for_each_image ( other ) {
-                       if ( other == initrd )
-                               offset = 0;
-                       offset += bzimage_load_initrd ( image, other, NULL );
-                       offset = initrd_align ( offset );
-               }
-
-               /* Load initrd at this address */
-               dest = ( top - offset );
-               len = bzimage_load_initrd ( image, initrd,
-                                           phys_to_virt ( dest ) );
+       /* Reshuffle initrds into desired order */
+       initrd_reshuffle();
 
-               /* Record initrd location */
-               if ( ! bzimg->ramdisk_image )
-                       bzimg->ramdisk_image = dest;
-               bzimg->ramdisk_size = ( dest + len - bzimg->ramdisk_image );
-       }
+       /* Load initrds */
        DBGC ( image, "bzImage %s initrds at [%#08lx,%#08lx)\n",
-              image->name, bzimg->ramdisk_image,
-              ( bzimg->ramdisk_image + bzimg->ramdisk_size ) );
+              image->name, virt_to_phys ( bzimg->initrd ),
+              ( virt_to_phys ( bzimg->initrd ) + bzimg->initrd_size ) );
+       len = initrd_load_all ( bzimg->initrd );
+       assert ( len == bzimg->initrd_size );
 }
 
 /**
index 5ba8c473deacaf0bfb64a6d01c24c20c73be9441..a93f54ffb1e5608347704e40bf976a7e1c5a29a3 100644 (file)
@@ -220,23 +220,19 @@ static void initrd_dump ( void ) {
 /**
  * Reshuffle initrds into desired order at top of memory
  *
- * @v bottom           Lowest physical 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 ( physaddr_t bottom ) {
+void initrd_reshuffle ( void ) {
        physaddr_t top;
 
        /* Calculate limits of available space for initrds */
        top = ( initrd_top ? initrd_top : uheap_end );
-       assert ( bottom >= uheap_limit );
 
        /* Debug */
-       DBGC ( &images, "INITRD region [%#08lx,%#08lx)\n", bottom, top );
        initrd_dump();
 
        /* Squash initrds as high as possible in memory */
@@ -250,23 +246,128 @@ void initrd_reshuffle ( physaddr_t bottom ) {
 }
 
 /**
- * Check that there is enough space to reshuffle initrds
+ * 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
  *
- * @v len              Total length of initrds (including padding)
- * @v bottom           Lowest physical address available for initrds
+ * 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_reshuffle_check ( size_t len, physaddr_t bottom ) {
-       physaddr_t top;
+int initrd_region ( size_t len, struct memmap_region *region ) {
+       physaddr_t min;
        size_t available;
 
        /* Calculate limits of available space for initrds */
-       top = ( initrd_top ? initrd_top : uheap_end );
-       assert ( bottom >= uheap_limit );
-       available = ( top - bottom );
+       min = uheap_limit;
+       available = ( ( initrd_top ? initrd_top : 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->addr = min;
+       region->last = ( min + available - 1 );
+       region->flags = MEMMAP_FL_MEMORY;
+       region->name = "initrd";
 
-       /* Check for available space */
-       return ( ( len < available ) ? 0 : -ENOBUFS );
+       return 0;
 }
 
 /**
index 10533b53b76441ff8197b63aa68e8165fe548e34..0b955a38145c1bb4aa8334e095dae6dc76ae29a6 100644 (file)
 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 
 #include <stdint.h>
-
-extern void initrd_reshuffle ( physaddr_t bottom );
-extern int initrd_reshuffle_check ( size_t len, physaddr_t bottom );
+#include <ipxe/memmap.h>
 
 /** Initial ramdisk chunk alignment */
 #define INITRD_ALIGN 4096
 
+extern void initrd_reshuffle ( void );
+extern int initrd_region ( size_t len, struct memmap_region *region );
+extern size_t initrd_load_all ( void *address );
+
 /**
  * Align initrd length
  *
@@ -29,4 +31,15 @@ initrd_align ( size_t len ) {
        return ( ( len + INITRD_ALIGN - 1 ) & ~( INITRD_ALIGN - 1 ) );
 }
 
+/**
+ * Get required length for initrds
+ *
+ * @ret len            Required length
+ */
+static inline __attribute__ (( always_inline )) size_t
+initrd_len ( void ) {
+
+       return initrd_load_all ( NULL );
+}
+
 #endif /* _IPXE_INITRD_H */