Bootable FreeBSD image on CompactFlash Card

One of the hacking toys I have is an Alix 1.C board which runs FreeBSD (9.1 at the time of writing). I started out net-booting the board but I thought it would be nice to boot the Alix board from a CompactFlash (CF) card I had laying around, too. In fact, I wanted to create a bootable image for the CF card which I could transfer to the CF card via dd(1).

Pre-requisites

Before the show could start, I needed to find out the size of the CF card I had. I did this by writing all 0s to the card.

root@T61p:~# dd if=/dev/zero of=/dev/sdb bs=512
dd: writing `/dev/sdb': No space left on device
1000945+0 records in
1000944+0 records out
512483328 bytes (512 MB) copied, 1099.51 s, 466 kB/s

I did this by inserting the card in a laptop through an PCCard adapter. The laptop on which I do my hacking runs Linux (Ubuntu 12.10 at the time of writing), so the above is the output of a Linux command. So what this revealed is that the card has 1,000,944 blocks of 512 Bytes which is a total capacity of 512,483,328 Bytes or 488.74 MBytes.

Setting up the Partition Table

Now that we know the size of the CF card, we can create the bootable image. The first step is to create a file of exactly the size as the CF card. This can be done simply by running a command like this:

[phs@freebsd ~]$ dd if=/dev/zero of=freebsd-alix.img bs=512 count=1000944
1000944+0 records in
1000944+0 records out
512483328 bytes transferred in 18.620906 secs (27521933 bytes/sec)

Next, we need a virtual disk based on that image to work with. The solution is to create memory disk as described in the FreeBSD handbook’s section 19.13 on virtual disks. As root, do this:

freebsd# mdconfig -a -t vnode -f /home/phs/freebsd-alix.img -u 0

This creates /dev/md0. Of course, nothing’s on that disk, yet. The first thing to do is creating a partition table.

freebsd# gpart create -s gpt md0
md0 created

This creates an empty UEFI-compatible GPT partition table, but no partitions, yet. You can examine the partition table like this:

freebsd# gpart show
(...)
=>     34  1000877  md0  GPT  (488M)
       34  1000877       - free -  (488M)

In order to boot FreeBSD from the image later, at least two partitions are required: A partition of 512K containing the boot code, and one partition containing the actual operating system. Because I wrote an I2C device driver for the Alix 1.C, I also want a swap partition so I have some space for kernel dumps.

freebsd# gpart add -t freebsd-boot -l boot -s 512K md0
md0p1 added
freebsd# gpart add -t freebsd-swap -l swap -s 64M md0
md0p2 added

Before adding the third partition for the FreeBSD operating system, find out how much space is left on the device by examining the partition table:

freebsd# gpart show md0
=>     34  1000877  md0  GPT  (488M)
       34     1024    1  freebsd-boot  (512k)
     1058   131072    2  freebsd-swap  (64M)
   132130   868781       - free -  (424M)

Ok, now create the data partition:

freebsd# gpart add -t freebsd-ufs -l rootfs -s 868781 md0
md0p3 added

Embedding the boot code

Now that the partition structure is laid out, it’s time to make the image bootable. FreeBSD’s gpart(8) manual page offers great information on what those files are and do: pmbr is a "protective MBF" which allows legacy, BIOS-based systems to boot the image. The MBR code searches the GPT for a freebsd-boot partition and starts the secondary boot code from there. In this case, that’s gptboot which searches the GPT for a freebsd-ufs partition from which it loads /boot/loader.

The following command writes the boot code to the image. Note that I’m using the files from /usr/obj, i.e. the ones most recently build from source and not the one’s currently installed.

freebsd# gpart bootcode -b /usr/obj/usr/src/sys/boot/i386/pmbr/pmbr
  -p /usr/obj/usr/src/sys/boot/i386/gptboot/gptboot -i 1 md0
bootcode written to md0

Initializing the root file system partition

The third partition which is supposed to store the operating system must be initialized with a file system before it can be written.

freebsd# newfs -L FreeBSD -U /dev/gpt/rootfs 
/dev/gpt/rootfs: 424.2MB (868776 sectors) block size 32768, fragment size 4096
        using 4 cylinder groups of 106.06MB, 3394 blks, 13696 inodes.
        with soft updates
super-block backups (for fsck_ffs -b #) at:
 192, 217408, 434624, 651840

This creates an empty UFS2-based file system with soft-updates enabled on the partition which can be mounted:

freebsd# mount /dev/md0p3 /mnt
freebsd# df -h
Filesystem     Size    Used   Avail Capacity  Mounted on
(...)
/dev/md0p3     410M    8.0k    377M     0%    /mnt

Installing FreeBSD to the bootable image

Installing the operating system is as easy as this:

[phs@freebsd /usr/src]$ sudo make installkernel KERNCONF=ALIX1C DESTDIR=/mnt
--------------------------------------------------------------
>>> Installing kernel ALIX1C
--------------------------------------------------------------
(...)
[phs@freebsd /usr/src]$ sudo make installworld DESTDIR=/mnt
(...)
[phs@freebsd /usr/src]$ sudo make DESTDIR=/mnt distrib-dirs distribution
(...)

Before the image is really useful, you probably want to adjust a few config files here and there. Especially etc/fstab should be configured so that the root file system is found and mounted when booting:

[phs@freebsd /mnt]$ cat etc/fstab 
# Device        Mountpoint  FSType  Options Dump    Pass
/dev/ada0p3     /           ufs     ro      0       0
/dev/ada0p2     none        swap    sw      0       0

In my case, the Alix 1.C board has a serial console but no screen. In order to get a login prompt on the serial console, etc/ttys needs to contain a line like this one:

ttyu0   "/usr/libexec/getty std.115200" vt100   on  secure

Finally, a root password must be set:

# pw -V /mnt/etc usermod root -h 0

When done, transfer the image to the CF card using dd(1).

Update Jan 24th, 2013: There are a few more files than just /etc/fstab that should be adjusted – added a few words about those.