This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

[FAQ] Linux: How to boot Sitara AM3x/AM4x/AM6x devices from initramfs (.cpio Archive, RAM Disk)

Part Number: AM335x, AM437x, AM62x, AM62Ax, AM64x, AM65x

Overview

The TI SDK Yocto build environment common for all Sitara MPUs such as AM335x, AM437x, AM62x, AM62Ax, AM64x, and AM65x can be used to generate "tiny" images (via bitbake tisdk-tiny-image) that can be used directly for purposes of booting Linux from a RAM disk and with this provide a great starting point for experimentation and custom development, specifically the .cpio archives that are being generated as part of those builds:

  • tisdk-tiny-image-am*-evm.cpio (uncompressed rootfs archive)
  • tisdk-tiny-image-am*-evm.cpio.xz (compressed archive)

Furthermore, it is also possible to generate a third type of .cpio archive that is similar to the above but packaged with an extra U-Boot header for easier use/consumption by U-Boot called tisdk-tiny-image-am*-evm.cpio.gz.u-boot by adding the following to Yocto's local.conf configuration file:

# Generate a cpio archive which is compressed and has a U-Boot header
IMAGE_FSTYPES:append = " cpio.gz.u-boot"

If you like to manually extract / modify / create existing or new .cpio archives for example to add files or programs, you can use the following commands:

# Extracting an existing cpio archive by creating a new folder and
# running the below command from within that folder
$ cpio -iv < <PATH_TO_CPIO>/tisdk-tiny-image-am62*-evm.cpio

# Creating a new cpio archive by bundling all files from the current
# folder
$ find . | sort | cpio --reproducible -o -H newc -R root:root > <PATH_TO_CPIO>/tisdk-tiny-image-am*-evm.cpio

There are (at least) 4 different methods to load and use a .cpio-formatted rootfs image to boot Linux from U-Boot:

  1. Build the cpio-formatted image directly into the Kernel (requires Kernel re-build every time the image needs any changes)
  2. Load the cpio-formatted image through U-Boot and pass it to the Kernel (keeps Kernel constant)
    1. Load a standard cpio image in U-Boot and pass it to the U-Boot booti/bootm command
    2. Load a standard cpio image in U-Boot and pass it directly to the Kernel through initrd=
    3. Load a cpio image with an U-Boot header and pass it to the U-Boot booti/bootm command

Let's look at those options closer...

Methods to load and use a .cpio-formatted rootfs image to boot Linux from U-Boot

Option 1: Build the cpio-formatted image directly into the Kernel

Using the CONFIG_INITRAMFS_SOURCE Kernel config option (in General Setup in menuconfig) it is possible to point to a folder location on the host system that contains either a single cpio archive with a .cpio suffix or a space-separated list of directories and files for building the initramfs image. When the Kernel is build with the built-in initramfs image it will initialize and use this initramfs directly without requiring any special U-Boot handling (simply pass in '-' as the address for the RAM disk to the booti/bootm command second parameter) or updates needed to the Kernel command line. While this method is easy to deploy / use it requires a complete Kernel re-build each time changes to the rootfs are needed. Below example show how to load such a combined Kernel image and a device tree blob over TFTP and boot it:

=> dhcp ${loadaddr} Image-with-initramfs-am62xx-evm.bin
link up on port 1, speed 1000, full duplex
BOOTP broadcast 1
DHCP client bound to address 192.168.1.67 (3 ms)
Using ethernet@8000000port@1 device
TFTP from server 192.168.1.1; our IP address is 192.168.1.67
Filename 'Image-with-initramfs-am62xx-evm.bin'.
Load address: 0x82000000
Loading: #################################################################
	 #################################################################
	 #################################################################
	 #################################################################
	 #################################################################
	 #################################################################
	 #################################################################
	 #################################################################
	 #################################################################
	 #################################################################
	 #################################################################
	 #################################################################
	 #################################################################
	 #################################################################
	 #################################################################
	 #################################################################
	 #################################################################
	 #################################################################
	 #################################################################
	 #################################################################
	 #################################################################
	 #################################################################
	 #################################################################
	 ######
	 5 MiB/s
done
Bytes transferred = 22028800 (1502200 hex)

=> dhcp ${fdtaddr} k3-am625-sk.dtb 
link up on port 1, speed 1000, full duplex
BOOTP broadcast 1
DHCP client bound to address 192.168.1.67 (3 ms)
Using ethernet@8000000port@1 device
TFTP from server 192.168.1.1; our IP address is 192.168.1.67
Filename 'k3-am625-sk.dtb'.
Load address: 0x88000000
Loading: ####
	 4.1 MiB/s
done
Bytes transferred = 55666 (d972 hex)

# Do the minimum bootargs setup for consistency sake with other flows shown here
# (otherwise the dtb 'bootargs' property will take effect)
=> setenv args_initramfs 'setenv bootargs console=${console} ${optargs}'
=> run args_all args_initramfs

# Now boot the Kernel without explicitly specifying the initramfs address
# as it is fully baked into the Kernel
=> booti ${loadaddr} - ${fdtaddr}
## Flattened Device Tree blob at 88000000
   Booting using the fdt blob at 0x88000000
   Loading Device Tree to 000000008ffef000, end 000000008ffff971 ... OK

Starting kernel ...

Option 2a: Load a standard cpio image in U-Boot and pass it to the U-Boot booti/bootm command 

This is the recommended method when working with current TI SDKs as the Yocto build artifacts can be used directly to achieve a successful boot from initramfs without any further changes needed. Here, we store the cpio image (either compressed, or uncompressed - both work) on the boot media and load it from there using U-Boot, or load it into RAM through other means (TFTP, for example). We then pass its location _and_ size (important!) directly to the U-Boot booti/bootm command second parameter. U-Boot will then use this information to setup the Kernel boot accordingly. Conceptionally this approach works as follows (example using an AM62 device variant):

=> load mmc 1:1 ${loadaddr} Image-am62axx-evm.bin
18526720 bytes read in 1547 ms (11.4 MiB/s)
=> load mmc 1:1 ${fdtaddr} k3-am62a7-sk-am62axx-evm.dtb
50483 bytes read in 19 ms (2.5 MiB/s)

# Load uncompressed cpio archive
=> load mmc 1:1 ${rdaddr} tisdk-tiny-image-am62axx-evm.cpio
6619136 bytes read in 562 ms (11.2 MiB/s)

...or....

# Load compressed cpio archive (Kernel will automatically extract)
=> load mmc 1:1 ${rdaddr} tisdk-tiny-image-am62axx-evm.cpio.xz
1802312 bytes read in 164 ms (10.5 MiB/s)

# Do the minimum bootargs setup for consistency sake with other flows shown here
# (otherwise the dtb 'bootargs' property will take effect)
=> setenv args_initramfs 'setenv bootargs console=${console} ${optargs}'
=> run args_all args_initramfs

# Now boot the Kernel with explicitly specifying the initramfs address
# and size as the second parameter. Do this step right after loading the
# initramfs image as we need ${filesize} to be initialized from that
# loading process.
=> booti ${loadaddr} ${rdaddr}:0x${filesize} ${fdtaddr}

Option 2b: Load a standard cpio image in U-Boot and pass it directly to the Kernel through initrd=

This is another method when working with current TI SDKs as the Yocto build artifacts can be used directly to achieve a successful boot from initramfs without any further changes needed. Here, we store the cpio image (either compressed, or uncompressed - both work) on the boot media and load it from there using U-Boot, or load it into RAM through other means (TFTP, for example). We pass it to the Kernel by updating the Kernel command line initrd= parameter to point to the load address _and_ the size (important!) of that initramfs image while on the U-Boot the booti/bootm command we still pass in '-' as the address for the RAM disk (second parameter) image. Conceptionally this approach works as follows (example using an AM62 device variant):

=> load mmc 1:1 ${loadaddr} Image-am62axx-evm.bin
18526720 bytes read in 1547 ms (11.4 MiB/s)
=> load mmc 1:1 ${fdtaddr} k3-am62a7-sk-am62axx-evm.dtb
50483 bytes read in 19 ms (2.5 MiB/s)

# Load uncompressed cpio archive
=> load mmc 1:1 ${rdaddr} tisdk-tiny-image-am62axx-evm.cpio
6619136 bytes read in 562 ms (11.2 MiB/s)

...or....

# Load compressed cpio archive (Kernel will automatically extract)
=> load mmc 1:1 ${rdaddr} tisdk-tiny-image-am62axx-evm.cpio.xz
1802312 bytes read in 164 ms (10.5 MiB/s)

# Do this step right after loading the initramfs image as we need its ${filesize}
=> setenv args_initramfs 'setenv bootargs console=${console} ${optargs} initrd=${rdaddr},0x${filesize}'
=> run args_all args_initramfs

# Now boot the Kernel without explicitly specifying the initramfs address
# as this is done via the bootargs Kernel command line parameter
=> booti ${loadaddr} - ${fdtaddr}

Option 2c: Load a cpio image with an U-Boot header and pass it to the U-Boot booti/bootm boot command

This method is somewhat of a legacy method but perhaps the most user-friendly way from a U-Boot usage point of view as it does not require updating the Kernel command line or otherwise having to keep track of the initramfs file size, but instead lets U-Boot's booti/bootm commands do the work of extracting and passing the initramfs image size on to the Kernel by using the second parameter of booti/bootm to specify only the load address. It does require however an update to the Yocto build config to generate an *.gz.u-boot type of image that contains the required U-Boot header. Conceptionally this approach works as follows (example using an AM62 device variant):

=> load mmc 1:1 ${loadaddr} Image-am62axx-evm.bin
18526720 bytes read in 1546 ms (11.4 MiB/s)
=> load mmc 1:1 ${fdtaddr} k3-am62a7-sk-am62axx-evm.dtb
50483 bytes read in 19 ms (2.5 MiB/s)
=> load mmc 1:1 ${rdaddr} tisdk-tiny-image-am62axx-evm.cpio.gz.u-boot
2958209 bytes read in 259 ms (10.9 MiB/s)

# Do the minimum bootargs setup for consistency sake with other flows shown here
# (otherwise the dtb 'bootargs' property will take effect)
=> setenv args_initramfs 'setenv bootargs console=${console} ${optargs}'
=> run args_all args_initramfs

# Now boot the Kernel with explicitly specifying the initramfs address
=> booti ${loadaddr} ${rdaddr} ${fdtaddr} 
## Loading init Ramdisk from Legacy Image at 88080000 ...
   Image Name:   tisdk-tiny-image-am62axx-evm-202
   Image Type:   AArch64 Linux RAMDisk Image (uncompressed)
   Data Size:    2958145 Bytes = 2.8 MiB
   Load Address: 00000000
   Entry Point:  00000000
   Verifying Checksum ... OK
## Flattened Device Tree blob at 88000000
   Booting using the fdt blob at 0x88000000
   Loading Ramdisk to 8fd2d000, end 8ffff341 ... OK
   Loading Device Tree to 000000008fd1d000, end 000000008fd2c532 ... OK

Starting kernel ...

Notes and Solutions to Common Issues

  • Using initramfs via cpio-formatted archives is the more modern/efficient way to do what was traditionally done booting from an actual RAM disk image (initrd), here's some good background on this: https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt
  • Make sure the Kernel is configured for CONFIG_BLK_DEV_INITRD=y. Despite what the name may suggest this is needed also for initramfs / cpio-based boot.
  • For the Kernel to be able to boot from an initramfs it needs to have an init file at its root directory. The tisdk-tiny-image based cpio archives have this file as a symlink pointing to /sbin/init (e.g., ln -s /sbin/init init). It is also possible to explicitly specify the init file location via the rdinit= Kernel command line parameter, for example rdinit=/sbin/init. Otherwise the Kernel will throw an error like this: "Kernel panic - not syncing: No working init found."
  • When using option 2a, make sure to pass initramfs location _and_ size to the U-Boot booti/bootm command second parameter. Both location and size need to be separated by a colon. If the size is not specified U-Boot will not boot the Kernel and put an error like this"Wrong Ramdisk Image Format. Ramdisk image is corrupt or invalid"
  • When using option 2b, make sure to pass initramfs location _and_ size to the Kernel command line initrd= parameter, otherwise the Kernel will not recognize the initramfs at all and will complain about missing boot media with an error like this: "Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)"