Intro

Most of Raspberry like ARM devices have system image written to sdcard. Whenever we need to update base image we need to take out memory stick, write image and put it back. Now imagine that you have 8 boards and you want to update all of them (e.g your current system is crashing). So, you won’t do it manually, will you?

In this article I’m going to present whole process of creating base image and installing it via network. I have automated process of imaging by witting goback. It works in similar way to expect language.

You might ask "Why don’t you use goexpect?". Answer is simple, it didn’t work for me in this particular case.

Create Ubuntu/Fedora system image

At first we start with creating empty raw image:

dd if=/dev/zero of=2g.img bs=1024k seek=2048 count=0

Then format:

fdisk 2g.img (o,p,enter,enter,enter, w)

Next create partitions and mount it:

sudo kpartx -a 2g.img sudo mkfs.ext4 /dev/mapper/loop0p1 sudo mount /dev/mapper/loop0p1 rootfs/

Note: in above snippet I’m creating only one partition. In many cases you’ll have to create extra partition for /boot – it’s totally dependent from your uboot configuration. Usually, on such partition uboot, (z/u)Image and initrd is placed (those files are required for boot process). If you need two partitions you may do as follows:

fdisk 2g.img input: o then p, select 1 partition, press enter to accept default first sector and pass +32M for the last sector input: t and e imput: n,p, select 2 partition, enter, enter input: w # Formatting sudo mkfs.vfat /dev/mapper/loop0p1 sudo mkfs.ext4 /dev/mapper/loop0p2

This will create 32MB FAT16 boot partition where you should place your kernel with initrd. Of course you may create ext4 partition (which is done in fedora images by default) instead fat16 but check your uboot settings first (or change them):

fatload - for fat ext4load - for ext4

Next step is to extract rootfs to raw image. Where can I find rootfs tarball?

Usually, board vendors are releasing system images with built-in kernel. You may simply copy rootfs from "ready" image to your own:

wget http://ftp.astral.ro/mirrors/fedora/pub/fedora/linux/releases/21/Images/armhfp/Fedora-Minimal-armhfp-21-5-sda.raw.xz # Extract raw image xzcat Fedora-Minimal-armhfp-21-5-sda.raw.xz | pv | dd of=fedora21.img bs=2M # Mount sudo kpartx -a fedora21.img sudo mount /dev/mapper/loop0p2 rootfs/ rm -Rf /boot/* # you may also remove /lib/modules/$kern-ver and firmare if you wish # Create rootfs cd rootfs/ && tar -czvf ../fedora21.rootfs.tar.gz .

You may also create your own minimal image with debbootstrap or pacstrap (Archlinux).

When you are finished with creating your own rootfs umount and compress image.

mount rootfs sudo kpartx -d 2g.img dd if=2g.img bs=16M | pv | xz -9 - > 2g.img.xz

Wow, you have your own base image! (Without kernel)

Build kernel

Now it’s time to build your own kernel. This operation is specific to boards. The kernel for odroid, wandboard, parallella will be very different but the process is almost the same.

This example shows how to create image for odroid u3. I’m using this process for all of boards, replacing the kernel source (or as you will see in next section with buildroot)

# Download toolchain sudo mkdir ~/toolchains wget releases.linaro.org/13.04/components/toolchain/binaries/gcc-linaro-arm-linux-gnueabihf-4.7-2013.04-20130415_linux.tar.bz2 sudo tar jxvf gcc-linaro-arm-linux-gnueabihf-4.7-2012.12-20121214_linux.tar.bz2 -C ~/toolchains/ # Set variables export ARCH=arm export CROSS_COMPILE=arm-linux-gnueabihf- export PATH=~/toolchains/gcc-linaro-arm-linux-gnueabihf-4.7-2013.04-20130415_linux/bin:$PATH # Build kernel git clone --depth 1 github.com/hardkernel/linux.git -b odroid-3.8.y cd linux make odroidu_defconfig # change for e.g wandboard_defconfig make export ver= make kernelversion # Copy compiled kernel with modules mkdir output cp .config output/config-$ver cp arch/arm/boot/zImage output/ sudo make modules_install ARCH=arm INSTALL_MOD_PATH=./output cd output tar czvf modules.tar.gz lib/* sudo rm -R lib scp * [email protected]:~/

Now create initrd on one of currently running machines:

ssh [email protected] mount /dev/mmcblk0p1 /mnt cp zImage /mnt/ cp config-$ver /boot tar xvf modules.tar.gz -C / update-initramfs -c -k $ver mkimage -A arm -O linux -T ramdisk -C none -a 0 -e 0 -n uInitrd -d /boot/initrd.img-$ver /boot/uInitrd-$ver # Update cp /boot/uInitrd-$ver /mnt/uInitrd

Generated uInitrd, kernel config, kernel and modules you may copy to your fresh image (e.g 2g.img)

Vioula you have created your first fully functional base image!

Buildroot

You’ve seen how much work you have to put in building your base image. But what if that process could be automated?

Article #Embedded development with Qemu, Beagleboard, Yocto, Ångström, Buildroot. Where to begin? contains some useful IMO information for those who has never used it before.

My forked version of buildroot contains some extra modifications for wandboard, odroid and parallella. If you have one of these boards you may use this repo, otherwise feel free to use mainline.

How to compile kernel using buildroot?

git clone https://github.com/mkaczanowski/buildroot cd buildroot # Choose board specific config make odroidu_config make linux # Copy kernel cp output/images/zImage ....

To make your own modifications use:

make xconfig make linux-xconfig

This method is obviously more convenient and faster than doing it manually.

Uboot

Uboot is primirary booltloader used with ARM boards. MLO and X-loader are oftenly used in omap boards. Either way you’ll need it.

Uboot installation depends on memory type that you have, if you use

Sdcard sudo dd if=u-boot.img of=/dev/<MICROSD_DEVICE_NAME> bs=512 seek=2

Emmc flash memory has boot partitions, so you’ll have to use script such as sd_fusing.sh

Buildroot is also able to compile uboot for you. Try it!

Network imaging

Network imaging works as the following:

Enter boot console

Load lightweight system image to RAM

Download base image and write it to sdcard/emm

Run postinstall operations (optional)

"lightweight system image" = kernel + ramdisk containing very little of tools (such as curl, resize2fs, e2fsprogs etc).

In my github buildroot fork, you may find:

odroidu_minimal_defconfig

parallella_defconfig

wandboard_defconfig

which contains thin kernel configuration for specific boards. As the output you will get zImage with merged ramdisk. Copy it to your tftp server.

Odroid modifications

Above steps worked fine for wandboard and paralla,but odroid requires a bit more modifications:

Uboot – odroid uses smsc95xx ethernet. As it came out, it uses common usb bus for communication. As hardkernel is using one uboot for each board type, they turned off ethernet for odroid u3. Below branch contains patched and working usbnet enabled uboot version Odroid U3 uboot-usbnet-enabled

Kernel patch for smsc95xx – with this patch you’re able to pass "macaddr=<your_hw_addr>" as kernel arg and you will finally have static mac address (without changing /etc/smsc*). Author of patch: Danny Kukawka MAC addr kernel patch

Parallella modifications

By default parallella has disabled UART. So we’ll have to enable it by modyfing device tree:

dtc -I dtb -O dts -o devicetree.dts devicetree.dtb dtc -I dtb -O dts -o devicetree.dts devicetree.dtb

This file is a dts file which enables serial output.

Uboot boot parameters

Odroid & Wandboard:

setenv ethact sms0 setenv ethaddr 00:10:75:2A:AE:E0 setenv gatewayip 192.168.4.1 setenv netmask 255.255.255.0 setenv serverip 192.168.4.2 setenv usbethaddr 00:10:75:2A:AE:E0 setenv ipaddr 192.168.4.43 setenv bootargs console=${console},${baudrate} ${optargs} root=/dev/ram video=${video} usb start tftp 0x40008000 odroid/zImage bootm

Parallella:

setenv ethaddr 00:10:75:2A:AE:E0 setenv gatewayip 192.168.4.1 setenv netmask 255.255.255.0 setenv serverip 192.168.4.2 setenv usbethaddr 00:10:75:2A:AE:E0 setenv ipaddr 192.168.4.43 tftp 0x4000000 parallella/parallella.bit.bin fpga load 0 0x4000000 0x3dbafc tftp 0x3000000 parallella/uImage tftp 0x2A00000 parallella/devicetree.dtb tftp 0x1100000 parallella/initrd bootm 0x3000000 0x1100000 0x2A00000

Executing above commands will load system in memory and run it.

What these 0x4000000, 0x40008000, 0x2A00000 etc. adresses means?

Complete explanation you may find in here

On 32-bit systems, memory is now divided into ”high” and ”low” memory. Low memory continues to be mapped directly into the kernel’s address space, and is thus always reachable via a kernel-space pointer. High memory, instead, has no direct kernel mapping. When the kernel needs to work with a page in high memory, it must explicitly set up a special page table to map it into the kernel‘s address space first. This operation can be expensive, and there are limits on the number of high-memory pages which can be mapped at any particular time.

So let’s look at our configuration. Odroid U3 has 2GB of RAM memory. Where 0x40008000 address is pointing to?

Max Ram capacity (2GB) = 2147483648 = 0x80000000 (hex)

Kernel LoadAddr = 0x40008000 = 1073774592 / 1024 / 1024 = 1024.03125 (MB) = ~1GB

As you see, this address is pointing to approximately the middle of the memory.

On the other hand parallella has only 1GB of memory an additional fpga binaries to load, so the address distribution looks fairly different.

On 4GB RAM device, memory will be split in 3G/1G manner.

Address distribution presented above is not a rule, but you may encounter this ratio very often.

Imaging with goback

To put it all together I decided to create project named goback. The main idea is to automate imaging.

Program uses serial console to interact with device. So it is extreemly important to have proper ttySAC0, ttyPS0, ttyS0 enabled.

How does it work?:

Loads "steps" from config (StepList struct is a linked-list)

Read lines from serial line

If line contains "Step.Expect" or "Step.Trigger" phrase, then program will continue to the next step (Expect) or will execute onTrigger (Trigger)

Program finishes when it reaches the end of the list or there is an error

Let’s look into example steps:

stEnterUboot := &step.Step{ Trigger: "ModeKey Check...", OnTrigger: func() { util.MustSendCmd(w, "

", true) }, Expect: "Exynos4412 #", Msg: "Enter u-boot console", Timeout: 10 * time.Second, } stStartEthernet := &step.Step{ OnTrigger: func() { // Double

-> uboot bug util.MustSendCmd(w, "usb start", true) }, Expect: "1 Ethernet Device(s) found", Msg: "USB Ethernet start", SendProbe: true, }

stEnterUboot enters uboot console, when "ModeKey Check…" text appears then it is a right time to send "enter" to stop countdown. When "Exynos4412 #" text appears it means that we are logged in console.

stStartEthernet starts usbnet ethernet in uboot. When "1 Ethernet Device(s) found" appears it means that eth is ready to use.

Note: Now it is a hacky project, but if you wish to add your configuration feel free to modify config and flasher.go 🙂

How to use it?

Usage of ./goback: -action="flash": What do you want to do? [flash, power_[on|off|switch]] -boards="": List boards to flash -debug=false: Set true to print serial console output -system="ubuntu": Choose system [Ubuntu|Fedora]

Important: tty.conf contains json map of ttyUSB for each machine. Don’t forget to modify it.

Demo

Prebuilt images: