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).
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.