Archive for the ‘howto’ Category

Embedded x86 Linux Revisited

March 4, 2009

Now that flash is really cheap, why use squashfs?  Save some time and effort and use this in your initrd for a readonly flash file system.

A non-wordpress-butchered version is available here:

http://pastebin.com/f3655ef7f

Here it is, read my notes at the top for howto actually use it:


# Local filesystem mounting -*- shell-script -*-
#
################################################################
# File:
# /scripts/local
#
# Root, or / in these notes is the initrd's (busybox) root,
# not your system's root!
#
# You need the aufs module for this to work. To do this install
# aufs on your base install, add aufs to: /etc/initramfs-tools/modules
# Now using your target kernel run:
# mkinitramfs -o initrd.img
#
# make a directory, copy an initrd to it then cd to it and:
#
# mv initrd.img initrd.img.gz
# gunzip initrd.img.gz
#
# make sure aufs exists somewhere in /lib/modules//
# After modifying local script, etc do the following in the initrd's
# root folder where /bin and /lib etc is to create a new initrd:
#
# find ./ -print | cpio -H newc -o > ../newinitrd.img
#
# Now tell grub to use this initrd for a RO root filesystem
#
# Note fstab gets hosed, if you need an fstab, make sure this
# script reflects that. I use an fstab for the default initrd for RW
# and none for RO, but you can make this script use a different one for
# RO if you want.
################################################################
# Parameter: device node to check
# Echos fstype to stdout
# Return value: indicates if an fs could be recognized
get_fstype ()
{
local FS FSTYPE FSSIZE RET
FS="${1}"

# vol_id has a more complete list of file systems,
# but fstype is more robust
eval $(fstype "${FS}" 2> /dev/null)
if [ "$FSTYPE" = "unknown" ] && [ -x /lib/udev/vol_id ]; then
FSTYPE=$(/lib/udev/vol_id -t "${FS}" 2> /dev/null)
fi
RET=$?

if [ -z "${FSTYPE}" ]; then
FSTYPE="unknown"
fi

echo "${FSTYPE}"
return ${RET}
}

# Parameter: Where to mount the filesystem
mountroot ()
{
[ "$quiet" != "y" ] && log_begin_msg "Running /scripts/local-top"
run_scripts /scripts/local-top
[ "$quiet" != "y" ] && log_end_msg

wait_for_udev 10

# If the root device hasn't shown up yet, give it a little while
# to deal with removable devices
if [ ! -e "${ROOT}" ] || ! $(get_fstype "${ROOT}" >/dev/null); then
log_begin_msg "Waiting for root file system"

# Default delay is 180s
if [ -z "${ROOTDELAY}" ]; then
slumber=180
else
slumber=${ROOTDELAY}
fi
if [ -x /sbin/usplash_write ]; then
/sbin/usplash_write "TIMEOUT ${slumber}" || true
fi

slumber=$(( ${slumber} * 10 ))
while [ ! -e "${ROOT}" ] \
|| ! $(get_fstype "${ROOT}" >/dev/null); do
/bin/sleep 0.1
slumber=$(( ${slumber} - 1 ))
[ ${slumber} -gt 0 ] || break
done

if [ ${slumber} -gt 0 ]; then
log_end_msg 0
else
log_end_msg 1 || true
fi
if [ -x /sbin/usplash_write ]; then
/sbin/usplash_write "TIMEOUT 15" || true
fi
fi

# We've given up, but we'll let the user fix matters if they can
while [ ! -e "${ROOT}" ]; do
# give hint about renamed root
case "${ROOT}" in
/dev/hd*)
suffix="${ROOT#/dev/hd}"
major="${suffix%[[:digit:]]}"
major="${major%[[:digit:]]}"
if [ -d "/sys/block/sd${major}" ]; then
echo "WARNING bootdevice may be renamed. Try root=/dev/sd${suffix}"
fi
;;
/dev/sd*)
suffix="${ROOT#/dev/sd}"
major="${suffix%[[:digit:]]}"
major="${major%[[:digit:]]}"
if [ -d "/sys/block/hd${major}" ]; then
echo "WARNING bootdevice may be renamed. Try root=/dev/hd${suffix}"
fi
;;
esac
echo "Gave up waiting for root device. Common problems:"
echo " - Boot args (cat /proc/cmdline)"
echo " - Check rootdelay= (did the system wait long enough?)"
echo " - Check root= (did the system wait for the right device?)"
echo " - Missing modules (cat /proc/modules; ls /dev)"
panic "ALERT! ${ROOT} does not exist. Dropping to a shell!"
done

# Get the root filesystem type if not set
if [ -z "${ROOTFSTYPE}" ]; then
FSTYPE=$(get_fstype "${ROOT}")
else
FSTYPE=${ROOTFSTYPE}
fi

[ "$quiet" != "y" ] && log_begin_msg "Running /scripts/local-premount"
run_scripts /scripts/local-premount
[ "$quiet" != "y" ] && log_end_msg

if [ "${readonly}" = "y" ]; then
roflag=-r
else
roflag=-w
fi

# FIXME This has no error checking

modprobe ${FSTYPE}
##########################################################################
###### Mod'd by Brian Phelps aka Electronjunkie ##########################

mkdir /root/.tmpfs
mkdir /root/.rootfs

mount -r -t ext2 /dev/sda1 /root/.rootfs

modprobe aufs
modprobe loop
mount -t tmpfs -o size=20M tmpfs /root/.tmpfs

mount -t aufs -o br:/root/.tmpfs=rw:/root/.rootfs=ro none /root/
mv /root/etc/fstab /root/etc/fstab.defunct

#########################################################################

# FIXME This has no error checking
# Mount root

[ "$quiet" != "y" ] && log_begin_msg "Running /scripts/local-bottom"
run_scripts /scripts/local-bottom
[ "$quiet" != "y" ] && log_end_msg
}

Advertisements

Embedded x86 Linux

October 11, 2007

Another Update: Check out the new version without squashfs here.
Updated!  I modified the squashfs and init so now the entire init script is executed properly, before it would work, but we were missing a few things in the final init stage.

DebianThis rough draft howto describes my experience, minus most of the goof-ups, of how you can setup a small x86 embedded system. A typical linux box that boots grub does the following:

  • Read the MBR
  • load the kernel pointed to by the bootloader
  • load the initial ram disk
  • execute an init script who’s main purpose is to set the environment up so that the real root file system can be mounted
  • then it mounts the FS and executes the real init script, but only after the preliminary init mounts the real root file system read/write.

Confused yet? It gets worse if you must use a read only file system like an IDE/CF flash disk. The preliminary init (ran from initial ram disk) must mount the real file system (usually squashfs) read only. It also sets up a read/write FS in RAM so that the system may store and use things later like /proc, /dev, and /var. After creating both read only and read/write file systems (FS) it merges them with the unionfs/aufs kernel function in init. Then it switches root and executes the real init. There fortunately are many “LiveCD” scripts that set this up for you automatically. These init scripts (like casper in Debian) are overkill and can break an embedded system whose environment is known ahead of time. Its a bit like using this toilet paper dispenser in a mission critical application, like after eating the Habenero Hotwings and drinking 5 pints of beer from the local pub.

Also I hate all the autodetecting that comes with most live cds, power glitches and other things can trick it and it is slooowwww, Embedded systems must be reliable! Mine are 500MHz dinosaurs with no electro-mechanical parts whatsoever.

I like to boot the target filesystem root on the target hardware as an actual r/w FS first and set everything up the way I want it. This has the side effect of also testing my hardware configuration and allows me to make major changes quickly.

Then I halt and mount the target on another host to freeze the system into my own read-only install. Remember! It must be readonly if we are to run off of flash in the end, b/c of the limited writes.

Here we go………….

First create skeleton debian install:
Usage: debootstrap [OPTION]… <suite> <target> [<mirror> [<script>]]

% debootstrap sid fs-root-debian-sid

Put this file system on a drive and boot your real hardware. Set everything up the best you can (Minor changes can bee made later, don’t worry).

Back at the host:

chroot to new install or boot the real system hardware using a non flash hard drive:

install squashfs-tools, kernel, and same versions of squashfs and unionfs kernel modules
You may have to fix /etc/resolv.conf for this and other apt based installations
# apt-get install kernel-image-xxx unionfs-modules-xxx squashfs-modules-xxx squashfs-tools

unchroot, do:

Create the boot-dir:

# mkdir -p fs-boot/boot/grub

chroot, do:
# apt-get install grub

make sure mkinitramfs and busybox is installed

unchroot, do:

Copy the boot loader

# cp -a fs-root/usr/lib/grub/i386-pc/* fs-boot/boot/grub/

Copy the kernel over:

$ cp -a fs-root/boot/* fs-boot/boot/

Create the squashed FS

% mksquashfs fs-root fs-squashfs.squashfs -all-root # Makes all files owned by root

On the chrooted target, install initramfs-tools
# apt-get install initramfs-tools

Now the tricky part, edit init scripts, then create the initramfs:

chroot to the fs-root and create our squashfs script:
# vim /usr/share/initramfs-tools/scripts/squashfs

modify it to look like the included file “squashfs_script”, some highlights are:

# This is how we mount the rootfs
# substitute /dev/sda1 for what it will be on the target upon boot, I’m using a usb flashdrive
mount -r -t ext2 /dev/sda1 /.tmpfs/.mnt
losetup /dev/loop0 /.tmpfs/.mnt/boot/fs-squashfs.squashfs
mount -r -t squashfs /dev/loop0 /.tmpfs/.sqfs

# Here we create the final rootfs
mount -w -t unionfs -o dirs=/.tmpfs/.overlay=rw:/.tmpfs/.cdrom=ro:/.tmpfs/.sqfs=ro unionfs /.union

# And switch over to the new FS and run the real init, This is done as the last line of the default debian etch init script, not the squashfs script, you should comment out the old last line:

# Chain to real filesystem
maybe_break init
#exec run-init ${rootmnt} ${init} “$@” <${rootmnt}/dev/console >${rootmnt}/dev/console
exec run-init /.union /sbin/init “$@” </.union/dev/console >/.union/dev/console

Now, still crooted as the target, edit /boot/grub/menu.lst to be:

# menu.lst – See: grub(8), info grub, update-grub(8)
default 0
timeout 5

title Debian live
root (hd0,0)
kernel /boot/vmlinuz vga=791 root=/dev/loop0 ramdisk_size=100000 boot=squashfs #username=user hostname=debian persistent
initrd /boot/file1.img
boot
# Note the boot=squashfs tells init to execute scripts/squashfs
# root=/dev/loop0 is our readonly root squashfs mounted in the initscript
# ramdisk_size=100000 gives the kernel (and us) some space (100M) to work with our unionfs

We’re almost done, now make the initramfs, recall we are still chrooted

# mkinitramfs -o file1.img <kernel-version-here>
please put your kenel version int the appropriate spot above (whatever string is appended to /lib/modules on the target)

Exit from chroot and copy the resulting file1.img to your targets’ boot directory /fs-boot/boot/

# cp file1.img /fs-boot/boot/

Now copy fs-root and fs-boot files to your production device, I’m using a usb flash drive (I assume is partitioned this way):

# cp -a fs-boot/* /mnt/sda1/
# cp -a fs-root/fs-squashfs.squashfs /mnt/sda1/

Make sure that the /proc /dev and /sys directories are empty, they will be filled later by the last stages of init on the unionfs

Install grub to the target device so it will boot, I will not cover that here, its covered everywhere else on the net quite well

I usually like to test the setup under vmware or qemu, as it can save time when debugging.

Comments? Mistakes? Help me make this better. I welcome all non-spam feedback.