Minimalist Gentoo for the Raspberry Pi
I've spent the last several days working on a minimalist build of Gentoo Linux for my Raspberry Pi. By minimalist, I mean only the absolute smallest set of packages required to boot and log in. I intend to build another MPD Appliance, or something similar, with it, so I don't need a full-blown Gentoo installation. The bare minimum packages are:
In addition, I installed the following to make my life easier:
Cross-compiling the system will take place in three stages:
- Build root
- Deployment root
We'll take advantage of Portage's
buildpkg FEATURES flag so that we don't have to compile everything three times.
This stage is where the toolchain will be built and installed. Because of how crossdev works, build-time dependencies of all of our required software will also have to be installed here. The toolchain will look for headers and shared objects in the directory hierarchy under the sysroot when when compiling and linking. Unfortunately, that means all the dependencies will end up being installed here as well as in the build root.
This stage is where we'll actually build all the software we want to install on the Raspberry Pi. This intermediate stage is necessary because some software has runtime dependencies on toolchain components like glibc, and we can't install those in the sysroot because it will break the cross-compiling toolchain.
This stage is the final destination for the packages we built in the build root: the SD card (or QEMU disk image, if you don't have a Raspberry Pi yet). While it isn't strictly necessary to separate the build and deployment roots, it can make it easier to correct problems that may arise, and speed things up if you're building for more than one device.
The first thing you'll need to do is set up Crossdev. The Raspberry Pi's System-on-a-Chip is a Broadcom BCM2835, which contains an ARM1176JZF-S CPU. I'll be using the GNU standard C library, so the toolchain tuple will be
crossdev -S -t armv6j-hardfloat-linux-gnueabi
Make sure to use the
-S option for crossdev; the current unstable versions of the toolchain do not work together and GCC fails to build.
This will create the "sysroot" stage in
/usr/armv6j-hardfloat-linux-gnueabi, where we'll install the build-time dependencies for the software we want on the Pi.
Now that we have a working toolchain for cross compiling, we need to configure Portage to build the software how we want. We'll create a directory structure just like that of
/etc/portage and fill it with a few important files.
mkdir -p configroot/etc/portage/ cp /usr/armv6j-hardfloat-linux-gnueabi/etc/portage/make.conf configroot/etc/portage
You will probably want to modify the
make.conf file crossdev produces. For example, I made these changes:
- Add the GCC flags to MARCH_TUNE for the Raspberry Pi, per the RPi Wiki. Note,
-Ofastdoesn't work very well, and I had trouble compiling several packages with it. Using
-O4work just fine, though.
ipv6USE flags. I also removed the
make-symlinksUSE flag because I will also install Bash and a few other packages that collide with Busybox when that's set.
Next, you'll need to pick a profile. Crossdev defaults to the embedded profile, which is fine. You could also use the arch/arm/armv6j profile, but then you'll need to add some extra variables in
ln -s /usr/portage/profiles/embedded configroot/etc/portage/make.profile
You may also want to make
package.keywords directories and populate them to your liking. I had to add
package.mask due to GNU Bug #12741, for example.
Finally, we need to prevent Portage from installing any of the toolchain components in the SYSROOT (they're already there) while we build the rest of the dependencies. You'll need a
package.provided directory in
configroot/etc/portage/profile, and it should contain the full atom for each of the four packages built by crossdev (binutils, gcc, glibc, and linux-headers):
mkdir -p configroot/etc/portage/profile/package.provided touch configroot/etc/portage/profile/package.provided/crossdev
To find the versions of the cross toolchain, use
equery list 'cross-armv6j-hardfloat-linux-gnueabi/*'
Then, put each atom (with the proper category, not the cross category) and version on a separate line in
package.provided/crossdev. Something like this:
sys-devel/binutils-2.22-r1 sys-devel/gcc-4.5.4 sys-libs/glibc-2.15-r3 sys-kernel/linux-headers-3.6
Now, Portage won't pull in any of those packages when building the dependency tree for our packages.
Now, set the PORTAGE_CONFIG environment variable to tell Portage to use the settings in this directory, instead of the one in /usr/armv6j-hardfloat-linux-gnueabi:
Install Build Dependencies
No that we've got our cross toolchain, sysroot, and configuration ready to go, it is time to install the build dependencies for our packages. Put the list of packages you want to install in a variable (i.e.
install_pkgs="busybox coreutils …"), and then install just their dependencies:
armv6j-hardfloat-linux-gnueabi-emerge --onlydeps --buildpkg --oneshot --ask $install_pkgs
Installing in the Build Root
Once all the build dependencies are installed, it is time to start the build root stage. The build root will be almost identical to the deployment root, so we'll everything in the build root first, so Portage will build a binary package and speed up the final step.
Before installing anything, you need to remove the
package.provided/crossdev file you created earlier. Since we're no longer installing things in the sysroot, we do want any toolchain components to be installed, if necessary.
Next, set the
ROOT environment variable to the absolute path of your build root directory:
Remember, the build root is not your Raspberry Pi's SD card, so don't use that path just yet.
Baselayout needs to be installed in two passes. Baselayout needs to be the first package installed on the system, or it will fail to create directories and symbolic links correctly. To ensure it gets installed before anything else, we explicitly install it, without dependencies:
armv6j-hardfloat-linux-gnueabi-emerge --nodeps --buildpkg --ask baselayout
This basically creates an empty directory structure and some symlinks for compatibility. Once that's done, we'll go ahead and install the rest of the base system:
armv6j-hardfloat-linux-gnueabi-emerge --onlydeps --buildpkg --usepkg --ask baselayout
This will pull in the rest of the core system packages, including sysvinit, OpenRC, etc.
Installing Selected Packages
Once baselayout is installed, it is time to install the rest of the core packages:
armv6j-hardfloat-linux-gnueabi-emerge --buildpkg --usepkg --ask $install_pkgs
You'll notice that most of the packages being installed at this point are binaries. That's because we've already compiled them in the sysroot, so we don't need to do it again.
Installing in the Deployment Root
Finally! Now we actually get to install stuff on the SD card!
Make sure you've partitioned and formatted the SD card correctly. See the Raspberry Pi Gentoo Wiki Page for details.
Mount the SD card partition you've designated as the root partition, and then reset the
ROOT environment variable to point to it:
mkdir /mnt/raspberrypi mount /dev/mmcblk0p2 /mnt/raspberrypi export ROOT=/mnt/raspberrypi/
Before installing anything, there are a few empty directories we need to make manually. They aren't created by any package, but are critical to boot the system:
Then, install the packages. Everything should just be binary merges at this point, so it won't take too long.
armv6j-hardfloat-linux-gnueabi-emerge --usepkg --ask $install_pkgs
On ARM, Bash (and possibly other packages) depend on the libgcc runtime. This confused me for a while, because on my other minimalist system (which runs on an Atom 230), I didn't need to install GCC and Bash worked fine. Fortunately, all you need is
libgcc_s.so.1 to make it happy, not the whole GCC installation. You can copy the one from the cross toolchain:
cp /usr/lib/gcc/armv6j-hardfloat-linux-gnueabi/4.5.4/libgcc_s.so.1 $ROOT/lib/
You need to set the time zone, just as you would on a full system:
echo 'America/Chicago' > $ROOT/etc/timezone ln -snf /usr/share/zoneinfo/America/Chicago $ROOT/etc/localtime
Setting the root password can be tricky. Although
passwd has a
--root option, it doesn't seem to work in any situation I've tried. Normally, I'd recommend blanking the password and forcing it to be set at first log in, but since the Raspberry Pi has no idea what time it is initially, password expiration doesn't work. Thus, you'll just have to blank the password and hope you remember to set it to something secure on your own.
sed -i 's/^root:.*/root::::::::/' $ROOT/etc/shadow
Since the Raspberry Pi has no real-time clock, the hwclock service just complains. We'll remove it and add the swclock service instead. While not an accurate way of keeping time (setting the clock based on the mtime of a file created at last shutdown), it will hopefully at least get the clock in the right decade.
rm $ROOT/etc/runlevels/boot/hwclock ln -s /etc/init.d/swclock $ROOT/etc/runlevels/boot/
If you have a Model B device and intend to use the Ethernet port, you can have it start at boot:
ln -s net.lo $ROOT/etc/init.d/net.eth0 ln -s /etc/init.d/net.eth0 $ROOT/etc/runlevels/default
If you installed NTP, you'll want it to start at boot as well, so the time on the device is accurate:
ln -s /etc/init.d/ntp-client $ROOT/etc/runlevels/default ln -s /etc/init.d/ntpd $ROOT/etc/runlevels/default
Firmware, Kernel, and Modules
Clone the Raspberry Pi firmware project on Github. This will get you the latest GPU firmware and bootloader, as well as a precompiled Linux kernel with modules. You can always compile your own kernel later, if you want.
git clone git://github.com/raspberrypi/firmware.git
Mount the first partition of your SD card and copy the firmware there:
mount /dev/mmcblk0p1 /mnt/raspberrypi/boot cp firmware/boot/* /mnt/raspberrypi/boot
Copy the pre-compiled kerne modules to the
/lib/ directory on your SD card's root partition:
cp -a firmware/modules $ROOT/lib/
You may want to make a couple of changes to
/etc/inittab. First, I don't like the new agetty behavior of clearing the screen before displaying the login prompt, at least on the first TTY; it makes it difficult to see error messages during the boot process. To change it, add
--noclear to the
sed -i 's/^c1\(.*\)agetty 38400\(.*\)/c1\1agetty --noclear 38400\2/' $ROOT/etc/inittab
Also, the default inittab sets up a serial console on
/dev/ttyS0, but that port doesn't exist on a Raspberry Pi. You can either comment out that line, or change it to use the UART port on the Pi:
sed -i 's/ttyS0/ttyAMA0/g' $ROOT/etc/inittab
Finally, you need to make sure the
fstab file in the deployment root is correct. For the Raspberry Pi, the SD card's block device will always be
/dev/mmcblk0. Each partition will be numbered, starting with 1, and prefixed with a "p". The first partition would be
/dev/mmcblk0p1, etc. Make sure you set the "type" column to
vfat for the boot partition.
...but don't get in a hurry! Make sure you sync all filesystem changes before you remove the SD card from your computer, since SD cards report writes as complete before actually committing them to the flash.
sync ; sync ; sync umount /mnt/raspberrypi/boot umount /mnt/raspberrypi
Now you can safely remove the SD card and pop it in your Raspberry Pi. Congratulations, and good luck!