wiki:buildroot

See also:

Buildroot

A Linux kernel without a root filesystem (aka rootfs) is useless. There are many sources for root filesystems including complete Linux distributions like Ubuntu (often too big, or limited in arch availability), pre-built root filesystems from vendors (often too limited), root filesystems built manually with Busybox (still often too limited) and more. There are Embedded Linux build systems which try to be more flexible like OpenEmbedded, Yocto, and OpenWrt but these tend to be not easy to understand or quick to setup. Buildroot tends to be a much more simplistic approach using standard makefiles, can produce a root filesystem in minutes, and has 1000+ userspace libs/apps available.

Using a buildroot rootfs is extremely useful for:

  • small fast booting self-contained systems (the default busybox rootfs is typically ~1.5MB)
  • kernel development (using initrd or initramfs options
  • using its toolchain externally

suggested tools to include for kernel development:

  • dropbear for SSH
  • benchmarksiozone, bonnie++, LTP, netperf, ramspeed, stress, lmbench, iostat, memtester, etc
  • debug tools; evtest, i2c-tools, devmem2, pciutils, usbutils, libv4l, alsa-utils, linux-firmware, mii-diag, iperf, iw
  • filesystem tools: resize2fs (BR2_PACKAGE_E2FSPROGS_RESIZE2FS) adds 1.2MB for 2.4MB cpio

Building:

git clone https://github.com/buildroot/buildroot.git
cd buildroot
make menuconfig # configure
make -j8
ls output/images
  • Note that like many build systems sources will be downloaded from the network during the build process
  • The .config file contains all the configuration options from the make menuconfig
  • see sections below on configuration tips for various platforms

For platform specific notes see below:

References:

initrd (initial ramdisk)

The initial RAM disk (initrd) is an initial root file system that is mounted prior to when the real root file system is available. The initrd is bound to the kernel and loaded as part of the kernel boot procedure.

An initrd is a 'cpio' image (an archive created with the Unix cpio tool) thus you need to have BR2_TARGET_ROOTFS_CPIO enabled and optionally one of the compression formats supported by your kernel).

If using U-Boot 'bootm' be sure to enable BR2_TARGET_ROOTFS_CPIO_UIMAGE which runs 'mkimage' on output/images/rootfs.cpio to create a 'uramdisk'.

Example booting Linux kernel with device-tree and initrd using U-Boot 'bootm':

  • Bootloader:
    setexpr fdt_addr $loadaddr
    setexpr linux_addr $fdt_addr + 0x20000 # allow 128KB for FDT
    setexpr rd_addr $linux_addr + 0x4000000 # allow 64MB for kernel
    setenv bootargs "console=${console},${baudrate}"
    setenv fsload tftpboot # for network load
    $fsload $fdt_addr $fdt_file2 && $fsload $linux_addr uImage && $fsload $rd_addr uramdisk && bootm $linux_addr $rd_addr $fdt_addr
    
    • In the above example you can modify fsload to load for your storage interface, device, and filesystem (ie "setenv fsload 'fatload mmc 0:1'" for loading from fist mmc controller first partition fatfs filesystem)

initramfs

The buildroot root filesystem can also be built statically into a kernel eliminating the need to have a separate kernel and ramdisk as in the initrd option above.

To build a rootfs suitable for use as an initramfs:

  • Select target arch
  • Configure toolchain or point to external toolchain
  • System configuration - select devtmpfs /dev management method and ensure serial port for the getty is correct
  • Filesystem images - select cpio format
  • use make to build - your rootfs will be in output/images/rootfs.cpio and will build within minutes
  • a default config using busybox will be about 1.5MiB

Make sure your kernel has the following:

  • CONFIG_DEVTMPFS=y - to get devtmpfs support, to provide a dynamic /dev
  • CONFIG_INITRAMFS_SOURCE="/path/to/buildroot/output/images/rootfs.cpio" - path to your cpio
  • CONFIG_INITRAMFS_COMPRESSION_GZIP=y - compression algorithm
  • CONFIG_INITRAMFS_ROOT_UID=0 - root user id
  • CONFIG_INITRAMFS_ROOT_GID=0 - root group id

If using buildbot to build kernel add the following to automatically build a kernel using to buildroot rootfs as an initramfs:

  • BR2_TARGET_ROOTFS_INITRAMFS=y

See also:

Using Buildroot toolchain externally

Buildroot builds its own GCC toolchain and using this externally can be useful. The toolchain generated by Buildroot is located by default in output/host/ and the simplest way to use it to to add output/host/bin/ to your PATH then and use the version of gcc tools there.

For example:

export PATH=$PWD/output/host/bin:$PATH
export CROSS_COMPILE=arm-linux-
export ARCH=arm

It is possible to relocate this toolchain making it easy for distribution using make sdk which prepares the toolchain to be relocatable and creates a tarball in the output/host/ directory. The relocate-sdk.sh script in the tarball can be used to update paths.

See the full documentation in docs/manual/using-buildroot-toolchain.txt

Busybox config

Busybox is used by default for all of the tools in the rootfs built by buildroot. If you want to alter the default configuration of busybox itself you can access it via make menuconfig:

  • Target packages -> BusyBox -> BusyBox configuration file to use
  • defaults to package/busybox/busybox.config

Newport (CN80XX)

The following details pertain to buildroot 2017.11 although newer versions will likely be similar if not the same.

To configure buildroot for the Cavium CN80XX/CN81XX SoC found on the Newport product family:

  • Target options -> Target Architecture -> AArch64 (little endian) (BR2_aarch64)
  • Filesystem images -> tar the root filesystem -> Compression method (xz) (BR2_TARGET_ROOTFS_CPIO_XZ)
  • Filesystem images -> cpio the root filesystem (BR2_TARGET_ROOTFS_CPIO)
  • Filesystem images -> Compression method (xz) (BR2_TARGET_ROOTFS_TAR_XZ)

This builds a ~500KiB output/images/root.tar.xz in less than 5 minutes on a typical Linux desktop.

If you also want buildroot to build a kernel provided from buildroot using the buildroot rootfs embedded as an initramfs then enable the following to create a kernel suitable for aarch64 and booting via U-Boot booti:

  • Kernel -> Linux Kernel (BR2_LINUX_KERNEL)
  • Kernel -> Kernel configuration (Using a custom (def)config file) -> newport_defconfig
  • Filesystem images -> initial RAM filesystem linked into linux kernel

Adding the kernel build produces a ~21MB Image in less than 10 minutes on a typical Linux desktop.

To boot this on a Newport bootloader:

tftpboot ${kernel_addr_r} newport/buildroot/Image && booti ${kernel_addr_r} - ${fdtcontroladdr}

Now you have a minimal Linux OS that booted in about 6 seconds.

A prebuilt image can be found here which contains:

  • Linux 4.14 kernel with ThunderX periperhals enabled
  • resize2fs (BR2_PACKAGE_E2FSPROGS_RESIZE2FS)
  • screen (BR2_PACKAGE_SCREEN)
  • pciutils (BR2_PACKAGE_PCIUTILS)
  • libusb (BR2_PACKAGE_LIBUSB)
  • eudev (BR2_PACKAGE_HAS_UDEV)
  • usbutils (BR2_PACKAGE_USBUTILS)
  • disk partitioning tools

Building .ext4 filesystem and compressed disk image

In the event you choose to use Buildroot but wish to avoid the drawbacks of a ramdisk you can install it to a block storage device. For this you will need to create a .ext4 filesystem image. This example will utilize the Linux-Newport branch of the mainline 4.14.4 kernel.

Prerequisite for this procedure you will need to have:

With the Newport BSP installed and Buildroot cloned set target architecture to AArch64:

cd buildroot
cat << EOF > configs/my_defconfig
BR2_aarch64=y
EOF

Now:

make my_defconfig
make -j8

After the build completes:

  • Export the location of the rootfs.tar located in your buildroot/output/images folder.
    ROOTFS=${PWD}/output/images/rootfs.tar
    
  • Create a tmp_mnt directory at a location of your discression, this is your temporary mounting point for compiling the filesystem.
    mkdir tmp_mnt
    TMP_MNT=${PWD}/tmp_mnt  
    
  • Create a filesystem of a specific size. It will be expandable later using resize2fs, make it large enough to fit what you have in your current build.
    OUT=buildroot-newport.ext4
    SIZEMB=1536 # 1.5GB
    truncate -s ${SIZEMB}M ${OUT}
    mkfs.ext4 -q -F -L rootfs ${OUT}
    
  • Mount this file to your 'tmp_mnt' directory
    sudo mount ${OUT} ${TMP_MNT}
    
  • Extract the Buildroot rootfs.tar and linux-newport.tar.xz which was downloaded to this mount point.
    sudo tar -C ${TMP_MNT} -xf ${ROOTFS}
    sudo tar -C ${TMP_MNT} -xf linux-newport.tar.xz
    
  • Convert the kernel 'Image' (uncompressed Kernel) to a fit image. For this you will need a tool from your Newport BSP directory, in this example the path to the BSP directory is named ${NEWPORT_BSP}.
    mv ${TMP_MNT}/boot/Image vmlinux
    gzip -f vmlinux
    ${NEWPORT_BSP}/newport/mkits.sh -o kernel.its -k vmlinux.gz -C gzip -v "buldroot-newport"
    mkimage -f kernel.its tmp_mnt/boot/kernel.itb #adds header
    
  • Create U-Boot bootscript using the existing Ubuntu one from the Newport BSP, use mkimage to add the u-boot header.
    mkimage -A arm64 -T script -C none -d ${NEWPORT_BSP}/newport/ubuntu.scr ${TMP_MNT}/boot/newport.scr
    
  • Unmount temporary mount point, and compress the .ext4 file to be used with the u-boot command 'gzwrite'.
    umount ${TMP_MNT}
    rm -rf ${TMP_MNT}eh
    gzip -k -f ${OUT}
    

To load this root file system without disturbing the existing boot firmware tftpboot can be used:

  • Boot your Newport SBC, at the prompt "Hit any key to stop autoboot" press a key.
  • Execute the following commands.
    setenv ipaddr <your boards IP>
    setenv serverip <your TFTP server IP>
    setenv dev 0
    setenv image buildroot-newport.ext4.gz
    run update_rootfs
    

To create a disk image from the .ext4 file:

  • Creating a disk image is useful if you would like to overwrite the existing boot firmware when the image is flashed.
    # http://dev.gateworks.com/newport/boot_firmware/firmware-newport.img
    cp firmware-newport.img buildroot-newport.img
    # copy buildroot rootfs .ext4 filesystem to image with an offset 
    dd if=buildroot-newport.ext4 of=buildroot-newport.img bs=16M seek=1
    # compress it
    gzip -k -f buildroot-newport.img
    

Ventana (IMX6)

The following details pertain to buildroot 2017.08 although newer versions will likely be similar if not the same.

To configure buildroot for the i.MX6 SoC found on the Ventana product family:

  • Target options -> Target Architecture -> ARM (little endian)
  • Target options -> Target Architecture Variant -> cortex-A9 (BR2_GCC_TARGET_CPU)
  • Target options -> Enable NEON SIMD extension support (BR2_ARM_ENABLE_NEON=y)
  • Target options -> Enable VFP extension support (BR2_ARM_ENABLE_VFP=y)
  • Target options -> Floating point strategy (NEON) (BR2_ARM_FPU_NEON=y)
  • Filesystem images -> tar the root filesystem -> Compression method (xz)

This builds a ~500KiB output/images/rootfs.tar.xz in less than 5 minutes on a typical Linux desktop.

If you also want buildroot to build a kernel provided from buildroot using the buildroot rootfs embedded as an initramfs then enable the following to create a kernel suitable for imx_v6_v7 and booting via U-Boot bootm:

  • Kernel -> Linux Kernel (BR2_LINUX_KERNEL)
  • Kernel -> Defconfig name (imx_v6_v7) (BR2_LINUX_KERNEL_DEFCONFIG)
  • Kernel -> Kernel binary format (uImage)
  • Kernel -> load address (0x10008000) (BR2_LINUX_KERNEL_UIMAGE_LOADADDR)
  • Kernel -> Device Tree Source file names (imx6dl-gw54xx imx6q-gw54xx imx6dl-gw53xx imx6q-gw53xx imx6dl-gw52xx imx6q-gw52xx imx6dl-gw51xx imx6q-gw51xx imx6dl-gw551x imx6q-gw551x imx6dl-gw552x imx6q-gw552x imx6dl-gw553x imx6q-gw553x) (BR2_LINUX_KERNEL_INTREE_DTS_NAME)
  • Filesystem images -> initial RAM filesystem linked into linux kernel

Adding the kernel build produces a ~6MB uImage in less than 10 minutes on a typical Linux desktop.

To boot this on a Ventana bootloader:

tftpboot ${loadaddr} ventana/uImage && tftpboot ${fdt_addr} ventana/${fdt_file2} && bootm ${loadaddr} - ${fdt_addr}

Now you have a minimal Linux OS that booted in about 6 seconds.

A prebuilt image of this can be found at: http://dev.gateworks.com/buildroot/ventana/minimal

Additional tools:

  • resize2fs
  • screen
  • ubi/ubifs tools
  • pciutils usb-utils
  • disk partitioning tools

SWUpdate

SWUpdate is a framework for providing firmware udpates. It is extremely flexible and provides support for many different scenarios. Because it exists as a package for buildroot it is a great choice for providing firmware updates when using a buildroot solution.

Here we will provide an example of building an SWUpdate Over-The-Air (OTA) update for buildroot with the following considerations:

  • Newport
  • Symmetric Image Update (Two copies of rootfs which works well when your filesystem is relatively small compared to memory/flash space)
  • We will not bother updating boot firmware (can be added later)
  • We will not bother updating GSC firmware (can be added later)
  • We will build the Gateworks 4.14 kernel for newport with a minimal kernel config
  • We will use an uncompressed kernel image (just avoids needing to kernel a kernel.itb)
  • We will use a modified uboot environment to handle our root partition toggling

Here are the relevant files to add to your buildroot directory:

  • configs/newport_swupdate_defconfig: Buildroot defconfig (represents the minimal 'changes' made to buildroot default config):
    BR2_aarch64=y
    BR2_KERNEL_HEADERS_4_14=y
    
    # we will add some files to the rootfs from the 'overlay' subdir
    BR2_ROOTFS_OVERLAY="overlay"
    
    #
    # Kernel:
    #  we will build the gateworks linux kernel using arm64 defconfig 
    #  plus some additional configs via newport_kernel_defconfig
    BR2_LINUX_KERNEL=y
    BR2_LINUX_KERNEL_CUSTOM_GIT=y
    BR2_LINUX_KERNEL_CUSTOM_REPO_URL="https://github.com/Gateworks/linux-newport.git"
    BR2_LINUX_KERNEL_CUSTOM_REPO_VERSION="v4.14.4-newport"
    BR2_LINUX_KERNEL_USE_ARCH_DEFAULT_CONFIG=y
    BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES="newport_kernel_defconfig"
    BR2_LINUX_KERNEL_INSTALL_TARGET=y
    
    #
    # Packages:
    #  we need u-boot env tools for fw_setenv support in our scripts
    #  we need zlib/openssl/libconfig/json for various features in SWUpdate we enable
    #  we will use swupdate.config to configure SWUpdate
    BR2_PACKAGE_UBOOT_TOOLS=y
    BR2_PACKAGE_ZLIB=y
    BR2_PACKAGE_OPENSSL=y
    BR2_PACKAGE_LIBCONFIG=y
    BR2_PACKAGE_JSON_C=y
    BR2_PACKAGE_LIBCURL=y
    BR2_PACKAGE_SWUPDATE=y
    BR2_PACKAGE_SWUPDATE_CONFIG="swupdate.config"
    
    #
    # Filesystem
    #
    BR2_TARGET_ROOTFS_EXT2=y
    BR2_TARGET_ROOTFS_EXT2_4=y
    BR2_TARGET_ROOTFS_EXT2_GZIP=y
    
    • Note that SWUpdate reuqires ZLIB if we are going to use gzip compression
  • newport_kernel_defconfig: Kernel config fragment that adds OcteonTX and newport drivers to arm64 kernel defconfig
    # Additionally OcteonTX peripheral drivers
    CONFIG_PCI_HOST_THUNDER_PEM=y
    CONFIG_PCI_HOST_THUNDER_ECAM=y
    CONFIG_CAN=y
    CONFIG_CAN_MCP251X=y
    CONFIG_THUNDER_NIC_PF=y
    CONFIG_THUNDER_NIC_VF=y
    CONFIG_MDIO_BITBANG=y
    CONFIG_I2C=y
    CONFIG_I2C_THUNDERX=y
    CONFIG_SPI_THUNDERX=y
    CONFIG_GPIO_THUNDERX=y
    CONFIG_MMC_CAVIUM_THUNDERX=y
    CONFIG_EDAC_THUNDERX=y
    CONFIG_EXT4_FS=y
    # Gateworks Newport GSC drivers
    CONFIG_SENSORS_GSC=y
    CONFIG_MFD_GSC=y
    
  • overlay/etc/fw_env.config: config file for u-boot env tools fw_setenv
    # Device offset size
    /dev/mmcblk0 0xff0000 0x8000
    /dev/mmcblk0 0xff8000 0x8000
    
    • Note that this file is whitespace sensitive
  • swupdate.config: config file to build swupdate executable which resides on your firmware and drives the update process
    # We do not need MTD or LUA support
    # CONFIG_MTD is not set
    # CONFIG_LUA is not set
    CONFIG_SIGNED_IMAGES=y
    CONFIG_ENCRYPTED_IMAGES=y
    # Suricatta provides support for fetching updates via a Hawkbit server if desired
    CONFIG_SURICATTA=y
    CONFIG_SURICATTA_SSL=y
    CONFIG_SURICATTA_STATE_CHOICE_BOOTLOADER=y
    # We need the raw handler to image to an MMC partition 
    CONFIG_RAW=y
    # We need the shellscript handler for our update.sh shellscript
    CONFIG_SHELLSCRIPTHANDLER=y
    # We need the bootloader handler to alter the u-boot environment
    CONFIG_BOOTLOADERHANDLER=y
    
  • sw-description: part of the actual firmware OTA which describes the process and file manifest of the update image. See here for syntax
    software =
    {
            version = "0.1.0";
            description = "Firmware update for XXXXX Project";
    
            /* images installed to the system */
            images: (
                    {
                            filename = "rootfs.ext4.gz";
                            device = "/dev/update";
                            type = "raw";
                            compressed = true;
                    }
            );
    
            scripts: (
                    {
                            filename = "update.sh";
                            type = "shellscript";
                    }
            );
    }
    
  • update.sh: This is the script that SWUpdate runs which we use as both a preinst and psotinst script (via cmdline). We determine the current root device and, flip it, and symlink /dev/update to the device to update to. We don't have to do the image install as we've configured SWUpdate to do that for us in sw-descrption images.
    #!/bin/sh
    
    if [ $# -lt 1 ]; then
            exit 0;
    fi
    
    function get_current_root_device
    {
            for i in `cat /proc/cmdline`; do
                    if [ ${i:0:5} = "root=" ]; then
                            CURRENT_ROOT="${i:5}"
                    fi
            done
    }
    
    # ping-pong between /dev/mmcblk0p2 and /dev/mmcblk0p3
    # (adapt for your partitioning scheme and/or root device type)
    function get_update_part
    {
            CURRENT_PART="${CURRENT_ROOT:-/dev/mmcblk0p2}"
            if [ $CURRENT_PART = "/dev/mmcblk0p2" ]; then
                    UPDATE_PART="3";
            else
                    UPDATE_PART="2";
            fi
    }
    
    function get_update_device
    {
            UPDATE_ROOT=${CURRENT_ROOT%?}${UPDATE_PART}
    }
    
    
    if [ $1 == "preinst" ]; then
            # get the current root device
            get_current_root_device
    
            # get the device to be updated
            get_update_part
            get_update_device
    
            # create a symlink for the update process
            ln -sf $UPDATE_ROOT /dev/update
    fi
    
    if [ $1 == "postinst" ]; then
            # get the current root device
            get_current_root_device
    
            # get the device to be updated
            get_update_part
            get_update_device
    
            # toggle rootpart between 2 and 3
            # we do it twice to write to both primary/secondary env
            fw_setenv mmcbootpart $UPDATE_PART
            fw_setenv mmcbootpart $UPDATE_PART
    fi
    

Once this is in place you can use the following to build:

# build buildroot rootfs
make newport_swupdate_defconfig
make

# build swupdate image
cp output/images/rootfs.ext4.gz .
for i in sw-description update.sh rootfs.ext4.gz; do
        echo $i; done | cpio -ov -H crc > my-software.swu

Here are some one-time steps you will need to do to your boot firmware:

  • create an MBR partition table that defines LinuxA and LinuxB partitions we will ping-pong between. Note that the FATFS must not be changed from the original boot firmware generated MBR and that we also create a general userdata partition.
    # 1: 2048:30720 (15MiB) FAT12 (required by boot firmware)
    # 2: 65536:4194304 (2GiB) LinuxA
    # 3: 4259840:4194304 (2GiB) LinuxB
    # 4: 19103744:4194304 (2GiB) userdata
    wget -q https://raw.githubusercontent.com/Gateworks/bsp-newport/sdk-10.1.1.0-newport/ptgen
    /bin/bash ptgen \
            -p 0x01:2048:30720 \
            -p 0x83:65536:4194304 \
            -p 0x83:4259840:4194304 \
            -p 0x83:19103744:4194304 \
            > $BUILDROOT/output/images/mbr.bin
    
  • In U-Boot we will update the partition table:
    tftpboot ${loadaddr} mbr.bin && mmc dev 0 && mmc write ${loadaddr} 0 1 # mbr is at 0 and 1 sector long
    
    • Note that if you ever update the entire boot firmware it will over-write this partition table so you will want to take care to not overwrite that portion
  • In U-Boot we will install the original rootfs to the first Linux partition offset (LinuxA)
    tftpboot ${loadaddr} rootfs.ext4.gz && gzwrite mmc 0 ${loadaddr} ${filesize} 0x100000 0x2000000 # rootfsA is at 0x2000000 (64MiB) and we use a 1MiB buffer
    
  • In U-Boot we will alter the env to use the mmcbootpart env variable that our update.sh manipulates after a successful update:
    setenv mmcbootpart 2
    setenv bootcmd "setenv bootargs 'console=${console} root=/dev/mmcblk0p${mmcbootpart} rootwait rw; load mmc 0:${mmcbootpart} ${kernel_addr_r} boot/Image' && booti ${kernel_addr_r} - ${fdtcontroladdr}"
    saveenv
    
    • Note the single quotes around the bootargs value as we do not want U-Boot to expand the args until runtime

After you boot to buildroot you can fetch and install the SWUpdate image with:

# bring up networking
udhcpc -i eth0
# fetch image
cd /tmp
wget http://myserver/my-software.swu
swupdate -i mysoftware.swu

Note that if you require support for SWUpdate to complete an install that isn't already there (for example you want to add the capability to update GSC firmware via the gsc_update utility) you will either need to a) add a static linked version of that tool to your image or b) do a 2-stage update where you add the required tools first, then use them in a future update

Last modified 4 weeks ago Last modified on 02/28/2020 07:10:21 PM

Attachments (3)

Download all attachments as: .zip