Embedded NetBSD Part 1 : The tiniest boot floppy

After doing some research I developed a method to create a floppy that would boot a NetBSD kernel. I found that it is much faster to work on a virtual device rather than the floppy. The virtual device is a way of linking a regular file to a node in the /dev area. This way you can do all of your operations on the file instead of the slow floppy. So here is how I make the boot floppy.

1. Get a very small netbsd kernel. I built the GENERIC_TINY kernel and compressed it to around 720k because the uncompressed one is just a little too big for a 1.44MB floppy.

$ cd /sys/arch/i386/conf
$ config GENERIC_TINY
$ cd ../compile/GENERIC_TINY
$ make depend ; make
$ gzip -c netbsd > netbsd.gz

2. Create a virtual device that looks like a floppy drive. After the vnconfig step, the system will complain that the device does not have a disklabel. Do not worry about this too much, because it will still work. I'll show a workaround below.

$ cd ~
$ mkdir embed
$ mkdir imgs
$ cd embed
$ dd if=/dev/zero of=imgs/floppy1.img bs=1440k count=1
$ vnconfig -c -v /dev/rvnd0 imgs/floppy1.img

3. Put a filesystem on the virtual device. note that the 'r' in rvnd0c means to access the raw device. I'm not sure about the details, but there is a difference between accessing the raw versus the regular device file. Some commands will only work with the raw files. Newfs is putting a new filesystem on the device and using the floppy entry in the /etc/disktab file as a template.

$ newfs /dev/rvnd0a floppy

4. Mount it and make it bootable. Again do not worry about 'no disktab' errors.

$ mount -t ffs /dev/vnd0a /mnt/floppy
$ cp /usr/mdec/mbr /mnt/floppy
$ /usr/mdec/installboot -v /usr/mdec/biosboot.sym /dev/rvnd0a
$ cp /sys/arch/i386/compile/GENERIC_TINY/netbsd.gz /mnt/floppy
$ chmod +x /mnt/floppy/netbsd.gz

5. Unmount the virtual device and copy the contents to a floppy disk

$ umount /mnt/floppy
$ vnconfig -u /dev/vnd0
$ dd if=imgs/floppy1.img of=/dev/fd0a

At this point we should have a good boot disk. If you like, you can stick this into a test machine and check it out. It should boot and then prompt you to insert a filesystem disk. This is what we will make next.

After booting, the NetBSD kernel attempts to load /sbin/init off of the root filesystem. This is simply a statically linked binary that does something. I tried substituting a "Hello World" program in place of init, but that did not work. One of init's jobs is to initialize the console. I have a minimal init program, provided to me by Erik Anggard, that does this (and a little more). You can take a look at it here. You could also use the stock init to initialize everything. Init then calls /bin/sh and begins parsing the /etc/rc shell script. I'll use this procedure here.

So after the kernel boots, the filesystem needs 3 files - /sbin/init, /bin/sh, and /etc/rc. The binary files init and sh are statically linked and no special work needs to be done. The rc file is just a text script file and is really easy to modify.

The kernel also needs the /dev nodes so that it can attach hardware to mount points. This is also easy to do.

6. Making the filesystem floppy.

$ dd if=/dev/zero of=imgs/floppy2.img bs=1440k count=1
$ vnconfig -c -v /dev/rvnd0 imgs/floppy2.img
$ newfs /dev/rvnd0a floppy
$ mount /dev/vnd0a /mnt/floppy

7. Create the /dev nodes

$ mkdir /mnt/floppy/dev
$ cp /dev/MAKEDEV /mnt/floppy/dev
$ cd /mnt/floppy/dev
$ ./MAKEDEV floppy

8. Copy the required files

$ cd ~
$ mkdir /mnt/floppy/sbin
$ cp /sbin/init /mnt/floppy/sbin
$ mkdir /mnt/floppy/bin
$ cp /bin/sh /mnt/floppy/bin
$ mkdir /mnt/floppy/etc
$ echo '#!/bin/sh' > /mnt/floppy/etc/rc
$ echo 'echo Hello World!' >> /mnt/floppy/etc/rc
$ umount /mnt/floppy
$ vnconfig -u /dev/vnd0c
$ dd if=imgs/floppy2.img of=/dev/fd0a

9. You could easily call simple program from the rc script. For instance if you build the following C program, you could call the output from /etc/rc. Please do not flame me for the program (style, use of formatted string routines, etc). It's just an example.

1 :
2 : #include
3 :
4 : int main( int argc, char * argv[] )
5 : {
6 :     char name[256];
7 :     printf( "What is your name? " );
8 :     scanf( "%s", name );
9 :     printf( "Hello %s.\n", name );
10:     exit(0);
11: }

$ mkdir hello
$ cd hello
$ vi hello.c
$ gcc -static -o hello hello.c
$ mount -t ffs /dev/fd0a /mnt/floppy
$ cp hello /mnt/floppy/bin
$ echo '/bin/hello' >> /mnt/floppy/etc/rc
$ umount /mnt/floppy

When you insert the filesystem disk after the kernel has booted, you should be prompted for your name and greeted accordingly. After this it will appear to hang. It does not do any more, because part of a normal /etc/rc script is to initialize the login terminals. Check your /etc/rc files for more details.



Notes

From what I have read, disklabel should not be run on virtual devices. So, to keep the system from complaining about the lack of a disklabel, you can create an empty disk image. Then instead of the dd input file being /dev/zero, you can use your empty floppy image (with a disklabel) instead.

$ dd if=/dev/zero of=/dev/fd0a bs=1440k count=1
$ newfs /dev/fd0a floppy
$ disklabel -w -r /dev/fd0a floppy
$ dd if=/dev/fd0a of=imgs/empty.img bs=1440k count=1