Building a Root Filesystem For the Beaglebone Black

Embedded Linux Development Assignment 1 (part 4)

This is Part 3 of this blog series, this is a continuation of the write-up for my assignment.

This blog is based on Bootlin's Embedded Linux Course as well as Frank Vasquez and Chris Simmonds Mastering Embedded Linux Programming, so you may have some success reading their materials alongside this blog.

In this section, we will finish our setup of a barebones Linux system for the Beaglebone Black. The Root Filesystem is the last essential piece of the Linux system. Ours will contain a familiar directory structure, as well as many of the basic programs you might be used to from a desktop Linux distribution.

Creating a Root Filesystem From Scratch

The first step in this process is to create a staging directory in which to place the filesystem hierarchy for our future Beaglebones rootfs.

mkdir bin dev etc home lib proc sbin sys tmp usr var && mkdir usr/bin usr/lib usr/sbin var/log

Take a look at this structure with tree -d, which should now look like:

.
├── bin
├── dev
├── etc
├── home
├── lib
├── proc
├── sbin
├── sys
├── tmp
├── usr
│   ├── bin
│   ├── lib
│   └── sbin
└── var
    └── log

Programs Needed for the rootfs

According to Mastering Embedded Linux Programming - "Even for a basic root filesystem, you need approximately 50 utilities..." It goes on to mention that tracking down this source code for ourselves might be a true pain. (BusyBox)[busybox.net/] solves that problem for us. You can read more about BusyBox somewhere else, but all that is necessary to know here, is it provides us with the essential Linux utilities that we need, in a minimal package size.

Clone the git repo, and switch to a stable release with:

git clone git://busybox.net/busybox.git
cd busybox
git checkout 1_35_0

Build the default configuration of BusyBox

make distclean
make defconfig

Export the toolchain and architecture like we did in (Part 3)[blog.billvanleeuwen.ca/compiling-and-portin.. of this blog series to avoid typing it in each make command:

export ARCH=arm
export CROSS_COMPILE=arm-training-linux-uclibcgnueabihf-

We want to install BusyBox in our staging directory. Enter the menu config with

make menuconfig

Once in the menu, set the install path in Settings -> Destination path for 'make install'. This should be the path for your rootfs directory we just created.

We also want busybox to statically compile. This will reduce complexity for us.

Note on Static Compilation

You might not want to do this in most cases, as any further programs you install must also be statically compiled.

For the uninitiated Static Compilation is the process of compiling all of the needed libraries together with the program itself, rather than using them externally. This saves complexity, as you don't need to worry about including extra object files in your rootfs, but the compromise is size. Many of these programs rely on the same libraries. You can imagine it would save on space if we didn't have to include these in each compiled program, and instead have a single object file for these to all dynamically link against. For our purposes (essentially building a toy Linux system nobody is ever going to use in production) this is fine.

Now run make install to build Busybox.

On following the instructions from bootlin and Mastering Embedded Linux, this step returned me an error:

networking/ping.c:158:11: fatal error: netinet/icmp6.h: No such file or directory
  158 | # include <netinet/icmp6.h>

I likely made a mistake in configuring my toolchain.

We dont need ipv6 for our limited scope project, so if this happens to you, it can be solved by disabling ipv6 support in the BusyBox menu config under Networking Utilities -> Enable IPv6 Support

Try ```tree`` in your staging directory to see what buysbox installed

.
├── bin
│   ├── arch -> busybox
│   ├── ash -> busybox
│   ├── base32 -> busybox
│   ├── base64 -> busybox
│   ├── busybox
│   ├── cat -> busybox
│   ├── chattr -> busybox
│   ├── chgrp -> busybox
│   ├── chmod -> busybox
│   ├── chown -> busybox
│   ├── conspy -> busybox
│   ├── cp -> busybox

...

├── dev
├── etc
├── home
├── lib
├── linuxrc -> bin/busybox
├── proc
├── sbin
│   ├── acpid -> ../bin/busybox
│   ├── adjtimex -> ../bin/busybox
│   ├── arp -> ../bin/busybox
│   ├── blkid -> ../bin/busybox
│   ├── blockdev -> ../bin/busybox
│   ├── bootchartd -> ../bin/busybox
│   ├── depmod -> ../bin/busybox
│   ├── devmem -> ../bin/busybox
│   ├── fbsplash -> ../bin/busybox
│   ├── fdisk -> ../bin/busybox
│   ├── findfs -> ../bin/busybox

...

└── var
    └── log

16 directories, 397 files

Wow, look at all those programs! Imagine compiling and loading all of these yourself. Sheesh.

Testing our system

You should have two partitions on your SD card that we created in (part 2)[blog.billvanleeuwen.ca/porting-u-boot-onto-.. of this series.

Insert your SD card into the PC, and copy all of the files and folders from your staging directory to the rootfs partition on your SD card.

Eject the card, and slot it into your Beaglebone.

Like before, hold the USR button as you power up the Beaglebone, and spam the spacebar in the picocom terminal.

You should see the u-boot command line: =>

You should still have the bootcmd environment variable defined. We need to set another so that we can mount our rootfs and avoid the kernel panic from earlier.

setenv bootargs console=ttyO0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait init=/bin/sh +m

This tells the system that we have our root filesystem on the second partition of our SD card.

Go ahead and reboot the system with boot.

You should now avoid the kernel panic, and see something like this in your console:

[    2.266649] VFS: Mounted root (ext4 filesystem) readonly on device 179:2.
[    2.274829] devtmpfs: mounted
[    2.279609] Freeing unused kernel image (initmem) memory: 1024K
[    2.288104] Run /sbin/init as init process
[    2.304033] mmc1: new high speed MMC card at address 0001
[    2.311922] mmcblk1: mmc1:0001 M62704 3.56 GiB 
[    2.319848]  mmcblk1: p1
[    2.323613] mmcblk1boot0: mmc1:0001 M62704 2.00 MiB 
[    2.335241] mmcblk1boot1: mmc1:0001 M62704 2.00 MiB 
[    2.350386] mmcblk1rpmb: mmc1:0001 M62704 512 KiB, chardev (250:0)
[    4.887550] random: crng init done
can't run '/etc/init.d/rcS': No such file or directory

Please press Enter to activate this console.

Great! Hit enter, and you now have a rather minimal Linux system running!

/ # ls
bin      etc      lib      proc     sys      usr
dev      home     linuxrc  sbin     tmp      var
/ # cd bin/
/bin # ls
arch           dumpkmap       kill           netstat        setserial
ash            echo           link           nice           sh
base32         ed             linux32        pidof          sleep
base64         egrep          linux64        ping           stat
busybox        false          ln             pipe_progress  stty
cat            fatattr        login          printenv       su
chattr         fdflush        ls             ps             sync
chgrp          fgrep          lsattr         pwd            tar
chmod          fsync          lzop           reformime      touch
chown          getopt         makemime       resume         true
conspy         grep           mkdir          rev            umount
cp             gunzip         mknod          rm             uname
cpio           gzip           mktemp         rmdir          usleep
cttyhack       hostname       more           rpm            vi
date           hush           mount          run-parts      watch
dd             ionice         mountpoint     scriptreplay   zcat
df             iostat         mpstat         sed
dmesg          ipcalc         mt             setarch
dnsdomainname  kbd_mode       mv             setpriv
/bin #

There is however an error from the kernel:

can't run '/etc/init.d/rcS': No such file or directory

/etc/init.d/rcS is supposed to be an init script called by busybox's init system. This needs to be defined by us, which we will do in the next step.

The Init system

Back in our staging directory, create two files.

cd etc
mkdir init.d
touch init.d/rcS
touch inittab

Copy the sample inittab file from (bootlins example)[elixir.bootlin.com/busybox/1.35.0/source/ex.. into your inittab file

Because we aren't doing anything serious with our system. init.d/rCs can be left empty. Normally you could define any extra init scripts or commands in this file.

Copy these files over to your rootfs directory. Start up your Beaglebone by the method before and viola! The error should be gone!

Conclusion

You've now successfully ported a minimal Linux system onto the Beaglebone!

You could take this one step further and flash it onto the onboard eMMC, but for the sake of this blog series, I won't go into that here.