參考來源
http://www.denx.de/wiki/publish/DULG/DULG-tqm8xxl.html

 

7. Booting Embedded Linux

7.1. Introduction

In principle, if you have a Linux kernel image somewhere in system memory (RAM, ROM, flash...), then all you need to boot the system is the bootm command. Assume a Linux kernel image has been stored at address 0x40080000 - then you can boot this image with the following command:

=> bootm 40080000

7.2. Passing Kernel Arguments

In nearly all cases, you will want to pass additional information to the Linux kernel; for instance, information about the root device or network configuration.

In U-Boot, this is supported using the bootargs environment variable. Its contents are automatically passed to the Linux kernel as boot arguments (or "command line" arguments). This allows the use of the same Linux kernel image in a wide range of configurations. For instance, by just changing the contents of the bootargs variable you can use the very same Linux kernel image to boot with an initrd ramdisk image, with a root filesystem over NFS, with a CompactFlash disk or from a flash filesystem.

As one example, to boot the Linux kernel image at address 0x200000 using the initrd ramdisk image at address 0x400000 as root filesystem, you can use the following commands:

=> setenv bootargs root=/dev/ram rw
=> bootm 200000 400000

To boot the same kernel image with a root filesystem over NFS, the following command sequence can be used. This example assumes that your NFS server has the IP address "10.0.0.2" and exports the directory "/opt/eldk/ppc_8xx" as root filesystem for the target. The target has been assigned the IP address "10.0.0.99" and the hostname "tqm". A netmask of "255.0.0.0" is used:

=> setenv bootargs root=/dev/nfs rw nfsroot=10.0.0.2:/opt/eldk/ppc_8xx ip=10.0.0.99:10.0.0.2:10.0.0.2:255.0.0.0:tqm::off
=> bootm 200000

Please see also the files Documentation/initrd.txt and Documentation/nfsroot.txt in your Linux kernel source directory for more information about which options can be passed to the Linux kernel.

Note: Once your system is up and running, if you have a simple shell login, you can normally examine the boot arguments that were used by the kernel for the most recent boot with the command:

$ cat /proc/cmdline

7.3. Boot Arguments Unleashed

Passing command line arguments to the Linux kernel allows for very flexible and efficient configuration which is especially important in Embedded Systems. It is somewhat strange that these features are nearly undocumented everywhere else. One reason for that is certainly the very limited capabilities of other boot loaders.

It is especially U-Boot's capability to easily define, store, and use environment variables that makes it such a powerful tool in this area. In the examples above we have already seen how we can use for instance the root and ip boot arguments to pass information about the root filesystem or network configuration. The ip argument is not only useful in configurations with root filesystem over NFS; if the Linux kernel has the CONFIG_IP_PNP configuration enabled (IP kernel level autoconfiguration), this can be used to enable automatic configuration of IP addresses of devices and of the routing table during kernel boot, based on either information supplied on the kernel command line or by BOOTP or RARP protocols.

The advantage of this mechanism is that you don't have to spend precious system memory (RAM and flash) for network configuration tools like ifconfig or route - especially in Embedded Systems where you seldom have to change the network configuration while the system is running.

We can use U-Boot environment variables to store all necessary configuration parameters:

=> setenv ipaddr 10.0.0.99
=> setenv serverip 10.0.0.2
=> setenv netmask 255.0.0.0
=> setenv hostname tqm
=> setenv rootpath /opt/eldk/ppc_8xx
=> saveenv

Then you can use these variables to build the boot arguments to be passed to the Linux kernel:

=> setenv nfsargs 'root=/dev/nfs rw nfsroot=${serverip}:${rootpath}'

Note how apostrophes are used to delay the substitution of the referenced environment variables. This way, the current values of these variables get inserted when assigning values to the "bootargs" variable itself later, i. e. when it gets assembled from the given parts before passing it to the kernel. This allows us to simply redefine any of the variables (say, the value of "ipaddr" if it has to be changed), and the changes will automatically propagate to the Linux kernel.

Note: You cannot use this method directly to define for example the "bootargs" environment variable, as the implicit usage of this variable by the "bootm" command will not trigger variable expansion - this happens only when using the "setenv" command.

In the next step, this can be used for a flexible method to define the "bootargs" environment variable by using a function-like approach to build the boot arguments step by step:

=> setenv ramargs setenv bootargs root=/dev/ram rw
=> setenv nfsargs 'setenv bootargs root=/dev/nfs rw nfsroot=${serverip}:${rootpath}'
=> setenv addip 'setenv bootargs ${bootargs} ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${hostname}::off'
=> setenv ram_root 'run ramargs addip;bootm ${kernel_addr} ${ramdisk_addr}'
=> setenv nfs_root 'run nfsargs addip;bootm ${kernel_addr}'

In this setup we define two variables, ram_root and nfs_root, to boot with root filesystem from a ramdisk image or over NFS, respecively. The variables can be executed using U-Boot's run command. These variables make use of the run command itself:

  • First, either run ramargs or run nfsargs is used to initialize the bootargs environment variable as needed to boot with ramdisk image or with root over NFS.
  • Then, in both cases, run addip is used to append the ip parameter to use the Linux kernel IP autoconfiguration mechanism for configuration of the network settings.
  • Finally, the bootm command is used with two resp. one address argument(s) to boot the Linux kernel image with resp. without a ramdisk image. (We assume here that the variables kernel_addr and ramdisk_addr have already been set.)

This method can be easily extended to add more customization options when needed.

If you have used U-Boot's network commands before (and/or read the documentation), you will probably have recognized that the names of the U-Boot environment variables we used in the examples above are exactly the same as those used with the U-Boot commands to boot over a network using DHCP or BOOTP. That means that, instead of manually setting network configuration parameters like IP address, etc., these variables will be set automatically to the values retrieved with the network boot protocols. This will be explained in detail in the examples below.

7.4. Networked Operation with Root Filesystem over NFS

You can use the printenv command on the Target to find out which commands get executed by U-Boot to load and boot the Linux kernel:

=> printenv
bootcmd=bootp; setenv bootargs root=/dev/nfs rw nfsroot=${serverip}:${rootpath} ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${hostname}::off; bootm
bootdelay=5
baudrate=115200
stdin=serial
stdout=serial
stderr=serial
...

After Power-On or reset the system will initialize and then wait for a key-press on the console port. The duration of this countdown is determined by the contents of the bootdelay environment variable (default: 5 seconds).

If no key is pressed, the command (or the list of commands) stored in the environment variable bootcmd is executed. If you press a key, you get a prompt at the console port which allows for interactive command input.

In the example above the following commands are executed sequentially:

bootp
setenv bootargs root=/dev/nfs nfsroot=${serverip}:${rootpath} ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${hostname}::off
bootm

These commands take the following effect (pay attention for the modification of environment variables by these commands):

  • bootp: This command uses the BOOTP protocol to ask a boot server for information about our system and to load a boot image (which will usually be a Linux kernel image). Since no aguments are passed to this command, it will use a default address to load the kernel image (0x100000 or the last address used by other operations).
=> bootp
BOOTP broadcast 1
ARP broadcast 0
TFTP from server 10.0.0.2; our IP address is 10.0.0.99
Filename '/tftpboot/TQM8xxL/uImage'.
Load address: 0x100000

Loading: ########################################################################################
done

=> printenv
bootcmd=bootp; setenv bootargs root=/dev/nfs rw nfsroot=${serverip}:${rootpath} ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${hostname}::off; bootm
bootdelay=5

baudrate=115200
stdin=serial
stdout=serial
stderr=serial
bootfile=/tftpboot/TQM8xxL/uImage 
gatewayip=10.0.0.2
netmask=255.0.0.0
hostname=tqm
rootpath=/opt/eldk/ppc_8xx
ipaddr=10.0.0.99
serverip=10.0.0.2
dnsip=10.0.0.2
...

The Target sends a BOOTP request on the network, and (assuming there is a BOOTP server available) receives a reply that contains the IP address (ipaddr=10.0.0.99) and other network information for the target (hostname=tqm, serverip=10.0.0.2, gatewayip=10.0.0.2, netmask=255.0.0.0).

Also, the name of the boot image (bootfile= /tftpboot/TQM8xxL/uImage ) and the root directory on a NFS server (rootpath=/opt/eldk/ppc_8xx) was transmitted.

U-Boot then automatically downloaded the bootimage from the server using TFTP.

You can use the command iminfo (Image Info, or short imi) to verify the contents of the loaded image:

=> imi 100000
 
## Checking Image at 00100000 ...
   Image Name:   Linux-2.4.4
   Created:      2002-04-07  21:31:59 UTC
   Image Type:   PowerPC Linux Kernel Image (gzip compressed)
   Data Size:    605429 Bytes = 591 kB = 0 MB
   Load Address: 00000000
   Entry Point:  00000000
   Verifying Checksum ... OK
=>

This tells you that we loaded a compressed Linux kernel image, and that the file was not corrupted, since the CRC32 checksum is OK.

setenv bootargs root=/dev/nfs rw nfsroot=${serverip}:${rootpath} \
ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${hostname}::off

This command defines the environment variable bootargs. (If an old definition exists, it is deleted first). The contents of this variable is passed as command line to the LInux kernel when it is booted (hence the name). Note how U-Boot uses variable substitution to dynamically modify the boot arguments depending on the information we got from the BOOTP server.

To verify, you can run this command manually:

=> setenv bootargs root=/dev/nfs rw nfsroot=${serverip}:${rootpath} ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${hostname}::off
 
=> printenv
...
bootargs=root=/dev/nfs rw nfsroot=10.0.0.2:/opt/eldk/ppc_8xx ip=10.0.0.99:10.0.0.2:10.0.0.2:255.0.0.0:tqm::off
...

This command line passes the following information to the Linux kernel:

  • root=/dev/nfs rw: the root filesystem will be mounted using NFS, and it will be writable.
  • nfsroot=10.0.0.2:/opt/eldk/ppc_8xx: the NFS server has the IP address 10.0.0.2, and exports the directory /opt/eldk/ppc_8xx for our system to use as root filesystem.
  • ip=10.0.0.99:10.0.0.2:10.0.0.2:255.0.0.0:tqm::off: the target has the IP address 10.0.0.99; the NFS server is 10.0.0.2; there is a gateway at IP address 10.0.0.2; the netmask is 255.0.0.0 and our hostname is tqm. The first ethernet interface (eth0) willbe used, and the Linux kernel will immediately use this network configuration and not try to re-negotiate it (IP autoconfiguration is off).

See Documentation/nfsroot.txt in you Linux kernel source directory for more information about these parameters and other options.

  • bootm: This command boots an operating system image that resides somewhere in the system memory (RAM or flash - the m in the name is for memory). In this case we do not pass any memory address for the image, so the load address 0x100000 from the previous TFTP transfer is used:
=> run flash_nfs
## Booting image at 40040000 ...
   Image Name:   Linux-2.4.4
   Created:      2002-04-07  21:31:59 UTC
   Image Type:   PowerPC Linux Kernel Image (gzip compressed)
   Data Size:    605429 Bytes = 591 kB = 0 MB
   Load Address: 00000000
   Entry Point:  00000000
   Verifying Checksum ... OK
   Uncompressing Kernel Image ... OK
Linux version 2.4.4 (wd@larry.denx.de) (gcc version 2.95.3 20010111 (prerelease/franzo/20010111)) #1 Sun Apr 7 23:28:08 MEST 2002
On node 0 totalpages: 16384
zone(0): 16384 pages.
zone(1): 0 pages.
zone(2): 0 pages.
Kernel command line: root=/dev/nfs rw nfsroot=10.0.0.2:/opt/hardhat/devkit/ppc/8xx/target ip=10.0.0.99:10.0.0.2::255.0.0.0:tqm:eth0:off panic=1
Decrementer Frequency: 3125000
Calibrating delay loop... 49.86 BogoMIPS
Memory: 62580k available (1164k kernel code, 564k data, 52k init, 0k highmem)
Dentry-cache hash table entries: 8192 (order: 4, 65536 bytes)
Buffer-cache hash table entries: 4096 (order: 2, 16384 bytes)
Page-cache hash table entries: 16384 (order: 4, 65536 bytes)
Inode-cache hash table entries: 4096 (order: 3, 32768 bytes)
POSIX conformance testing by UNIFIX
Linux NET4.0 for Linux 2.4
Based upon Swansea University Computer Society NET3.039
Starting kswapd v1.8
CPM UART driver version 0.03
ttyS0 on SMC1 at 0x0280, BRG1
ttyS1 on SMC2 at 0x0380, BRG2
pty: 256 Unix98 ptys configured
block: queued sectors max/low 41520kB/13840kB, 128 slots per queue
RAMDISK driver initialized: 16 RAM disks of 4096K size 1024 blocksize
Uniform Multi-Platform E-IDE driver Revision: 6.31
ide: Assuming 50MHz system bus speed for PIO modes; override with idebus=xx
PCMCIA slot B: phys mem e0000000...ec000000 (size 0c000000)
No card in slot B: PIPR=ff00ff00
eth0: CPM ENET Version 0.2 on SCC1, 00:d0:93:00:28:81
JFFS version 1.0, (C) 1999, 2000  Axis Communications AB
JFFS2 version 2.1. (C) 2001 Red Hat, Inc., designed by Axis Communications AB.^M Amd/Fujitsu Extended Query Table v1.0 at 0x0040
number of JEDEC chips: 1
0: offset=0x0,size=0x8000,blocks=1
1: offset=0x8000,size=0x4000,blocks=2
2: offset=0x10000,size=0x10000,blocks=1
3: offset=0x20000,size=0x20000,blocks=31
 Amd/Fujitsu Extended Query Table v1.0 at 0x0040
number of JEDEC chips: 1
0: offset=0x0,size=0x8000,blocks=1
1: offset=0x8000,size=0x4000,blocks=2
2: offset=0x10000,size=0x10000,blocks=1
3: offset=0x20000,size=0x20000,blocks=31
TQM flash bank 0: Using static image partition definition
Creating 4 MTD partitions on "TQM8xxL Bank 0":
0x00000000-0x00040000 : "ppcboot"
0x00040000-0x00100000 : "kernel"
0x00100000-0x00200000 : "user"
0x00200000-0x00400000 : "initrd"
TQM flash bank 1: Using static file system partition definition
Creating 2 MTD partitions on "TQM8xxL Bank 1":
0x00000000-0x00200000 : "cramfs"
0x00200000-0x00400000 : "jffs"
NET4: Linux TCP/IP 1.0 for NET4.0
IP Protocols: ICMP, UDP, TCP
IP: routing cache hash table of 512 buckets, 4Kbytes
TCP: Hash tables configured (established 4096 bind 4096)
NET4: Unix domain sockets 1.0/SMP for Linux NET4.0.
Looking up port of RPC 100003/2 on 10.0.0.2
Looking up port of RPC 100005/2 on 10.0.0.2
VFS: Mounted root (nfs filesystem).
Freeing unused kernel memory: 52k init
modprobe: modprobe: Can't locate module char-major-4
INIT: version 2.78 booting
Activating swap...
Checking all file systems...                                                                                
Parallelizing fsck version 1.19 (13-Jul-2000)
Mounting local filesystems...
not mounted anything
Cleaning: /etc/network/ifstate.
Setting up IP spoofing protection: rp_filter.
Configuring network interfaces: done.
Starting portmap daemon: portmap.
Cleaning: /tmp /var/lock /var/run.
INIT: Entering runlevel: 2
Starting internet superserver: inetd.
                                                                                
MontaVista Software's Hard Hat Linux 2.0
                                                                                
tqm login: root
PAM-securetty[76]: Couldn't open /etc/securetty
PAM_unix[76]: (login) session opened for user root by LOGIN(uid=0)
Last login: Fri Feb  1 02:30:32 2030 on console
Linux tqm 2.4.4 #1 Sun Apr 7 23:28:08 MEST 2002 ppc unknown
login[76]: ROOT LOGIN on `console'

root@tqm:~#

7.5. Boot from Flash Memory

The previous section described how to load the Linux kernel image over ethernet using TFTP. This is especially well suited for your development and test environment, when the kernel image is still undergoing frequent changes, for instance because you are modifying kernel code or configuration.

Later in your development cycle you will work on application code or device drivers, which can be loaded dynamically as modules. If the Linux kernel remains the same then you can save the time needed for the TFTP download and put the kernel image into the flash memory of your TQM8xxL board.

The U-Boot command flinfo can be used to display information about the available on-board flash on your system:

=> fli
 
Bank # 1: FUJITSU AM29LV160B (16 Mbit, bottom boot sect)
  Size: 4 MB in 35 Sectors
  Sector Start Addresses:
    40000000 (RO) 40008000 (RO) 4000C000 (RO) 40010000 (RO) 40020000 (RO)
    40040000      40060000      40080000      400A0000      400C0000
    400E0000      40100000      40120000      40140000      40160000
    40180000      401A0000      401C0000      401E0000      40200000
    40220000      40240000      40260000      40280000      402A0000
    402C0000      402E0000      40300000      40320000      40340000
    40360000      40380000      403A0000      403C0000      403E0000
 
Bank # 2: FUJITSU AM29LV160B (16 Mbit, bottom boot sect)
  Size: 4 MB in 35 Sectors
  Sector Start Addresses:
    40400000      40408000      4040C000      40410000      40420000
    40440000      40460000      40480000      404A0000      404C0000
    404E0000      40500000      40520000      40540000      40560000
    40580000      405A0000      405C0000      405E0000      40600000
    40620000      40640000      40660000      40680000      406A0000
    406C0000      406E0000      40700000      40720000      40740000
    40760000      40780000      407A0000      407C0000      407E0000
=>

From this output you can see the total amount of flash memory, and how it is divided in blocks (Erase Units or Sectors). The RO markers show blocks of flash memory that are write protected (by software) - this is the area where U-Boot is stored. The remaining flash memory is available for other use.

For instance, we can store the Linux kernel image in flash starting at the start address of the next free flash sector. Before we can do this we must make sure that the flash memory in that region is empty - a Linux kernel image is typically around 600...700 kB, so to be on the safe side we dedicate the whole area from 0x40080000 to 0x4027FFFF for the kernel image. Keep in mind that with flash memory only whole erase units can be cleared.

After having deleted the target flash area, you can download the Linux image and write it to flash. Below is a transcript of the complete operation with a final iminfo command to check the newly placed Linux kernel image in the flash memory.

Note: Included topic DULGData.tqm8xxlInstallKernelTftp does not exist yet

Note how the filesize variable (which gets set by the TFTP transfer) is used to automatically adjust for the actual image size.

Now we can boot directly from flash. All we need to do is passing the in-flash address of the image (40080000) with the bootm command; we also make the definition of the bootargs variable permanent now:

=> setenv bootcmd bootm 40080000
=> setenv bootargs root=/dev/nfs rw nfsroot=${serverip}:${rootpath} ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${hostname}::off

Use printenv to verify that everything is OK before you save the environment settings:

=> printenv
bootdelay=5
baudrate=115200
stdin=serial
stdout=serial
stderr=serial
bootcmd=bootm 40080000
bootargs=root=/dev/nfs rw nfsroot=10.0.0.2:/opt/eldk/ppc_8xx
ip=10.0.0.99:10.0.0.2:10.0.0.2:255.0.0.0:tqm::off
....

=> saveenv

To test booting from flash you can now reset the board (either by power-cycling it, or using the U-Boot command reset), or you can manually call the boot command which will run the commands in the bootcmd variable:

Note: Included topic DULGData.tqm8xxlLinuxBootSelf does not exist yet

7.6. Standalone Operation with Ramdisk Image

When your application development is completed, you usually will want to run your Embedded System standalone, i. e. independent from external resources like NFS filesystems. Instead of mounting the root filesystem from a remote server you can use a compressed ramdisk image, which is stored in flash memory and loaded into RAM when the system boots.

Ramdisk images for tests can be found in the ftp://ftp.denx.de/pub/LinuxPPC/usr/src/SELF/images/ directories.

Load the ramdisk image into RAM and write it to flash as follows:

Note: Included topic DULGData.tqm8xxlUBootInstallRamdisk does not exist yet

To tell the Linux kernel to use the ramdisk image as root filesystem you have to modify the command line arguments passed to the kernel, and to pass two arguments to the bootm command, the first is the memory address of the Linux kernel image, the second that of the ramdisk image:

Note: Included topic DULGData.tqm8xxlLinuxBootSelf does not exist yet

9. Advanced Topics

This section lists some advanced topics of interest to users of U-Boot and Linux.

9.1. Flash Filesystems

9.1.1. Memory Technology Devices

All currently available flash filesystems are based on the Memory Technology Devices MTD layer, so you must enable (at least) the following configuration options to get flash filesystem support in your system:

CONFIG_MTD=y
CONFIG_MTD_PARTITIONS=y
CONFIG_MTD_CHAR=y
CONFIG_MTD_BLOCK=y
CONFIG_MTD_CFI=y
CONFIG_MTD_GEN_PROBE=y
CONFIG_MTD_CFI_AMDSTD=y
CONFIG_MTD_ROM=y
CONFIG_MTD_tqm8xxl=y

Note: this configuration uses CFI conformant AMD flash chips; you may need to adjust these settings on other boards.

The layout of your flash devices ("partitioning") is defined by the mapping routines for your board in the Linux MTD sources (see drivers/mtd/maps/). The configuration for the TQM8xxL looks like this:

/* partition definition for first flash bank
 * also ref. to "drivers\char\flash_config.c"
 */
static struct mtd_partition tqm8xxl_partitions[] = {
        {
          name: "ppcboot",
          offset: 0x00000000,
          size: 0x00020000,           /* 128KB           */
          mask_flags: MTD_WRITEABLE,  /* force read-only */
        },
        {
          name: "kernel",             /* default kernel image */
          offset: 0x00020000,
          size: 0x000e0000,
          mask_flags: MTD_WRITEABLE,  /* force read-only */
        },
        {
          name: "user",
          offset: 0x00100000,
          size: 0x00100000,
        },
        {
          name: "initrd",
          offset: 0x00200000,
          size: 0x00200000,
        }
};
/* partition definition for second flahs bank */
static struct mtd_partition tqm8xxl_fs_partitions[] = {
        {
          name: "cramfs",
          offset: 0x00000000,
          size: 0x00200000,
        },
        {
          name: "jffs",
          offset: 0x00200000,
          size: 0x00200000,
          //size: MTDPART_SIZ_FULL,
        }
};

This splits the available flash memory (8 MB in this case) into 6 separate "partitions":

  • uboot: size: 128 kB; used to store the U-Boot firmware
  • kernel: size: 896kB; used to store the (compressed) Linux kernel image
  • user: size: 1 MB; not used
  • initrd: size: 2 MB; used to store a (compressed) ramdisk image
  • cramfs: size: 2 MB; used for a compressed ROM filesystem (read-only)
  • jffs: size: 2 MB; used for a flash filesystem (using JFFS)

When you boot a system with this configuration you will see the following kernel messages on the console:

Note: Included topic DULGData.tqm8xxlLinuxMtdBoot does not exist yet

Another way to check this information when the system is running is using the proc filesystem:

Note: Included topic DULGData.tqm8xxlLinuxProcMtd does not exist yet

Now we can run some basic tests to verify that the flash driver routines and the partitioning works as expected:

# xd /dev/mtd0 | head -4
       0  27051956 7fe5f641  3be91e9d 0008061f  |'  V   A;       |
      10  00000000 00000000  7667315e 05070201  |        vg1^    |
      20  4c696e75 782d322e  342e3400 00000000  |Linux-2.4.4     |
      30  00000000 00000000  00000000 00000000  |                |
# xd /dev/mtd1 | head -4
       0  27051956 6735cb88  3be79508 000d11bf  |'  Vg5  ;       |
      10  00000000 00000000  7d5cbfc8 05070301  |        }\      |
      20  4170706c 69636174  696f6e20 72616d64  |Application ramd|
      30  69736b20 696d6167  65000000 00000000  |isk image       |
# xd /dev/mtd6 | head -10
       0  6a0358f7 626f6f74  64656c61 793d3500  |j X bootdelay=5 |
      10  62617564 72617465  3d393630 30006c6f  |baudrate=9600 lo|
      20  6164735f 6563686f  3d310063 6c6f636b  |ads_echo=1 clock|
      30  735f696e 5f6d687a  3d310065 74686164  |s_in_mhz=1 ethad|
      40  64723d30 303a6362  3a62643a 30303a30  |dr=00:cb:bd:00:0|
      50  303a3131 006e6673  61726773 3d736574  |0:11 nfsargs=set|
      60  656e7620 626f6f74  61726773 20726f6f  |env bootargs roo|
      70  743d2f64 65762f6e  66732072 77206e66  |t=/dev/nfs rw nf|
      80  73726f6f 743d2428  73657276 65726970  |sroot=$(serverip|
      90  293a2428 726f6f74  70617468 29007261  |):$(rootpath) ra|
# xd /dev/mtd7
       0  ffffffff ffffffff  ffffffff ffffffff  |                |
                    *** same ***
   80000

In the tex-dumps of the MTD devices you can identify some strings that verify that we indeed see an U-Boot environment, a Linux kernel, a ramdisk image and an empty partition to play wih.

The last output shows the partition to be empty. We can try write some data into it:

# date >/dev/mtd7
# xd /dev/mtd7
       0  57656420 4e6f7620  20372031 353a3339  |Wed Nov  7 15:39|
      10  3a313220 4d455420  32303031 0affffff  |:12 MET 2001    |
      20  ffffffff ffffffff  ffffffff ffffffff  |                |
                    *** same ***
   80000                                        |                |
# sleep 10 ; date >/dev/mtd7
Last[3] is 3aa73020, datum is 3a343020
date: write error: Input/output error

As you can see it worked the first time. When we tried to write the (new date) again, we got an error. The reason is that the date has changed (probably at least the seconds) and flash memory cannot be simply overwritten - it has to be erased first.

You can use the eraseall Linux commands to erase a whole MTD partition:

# xd /dev/mtd7
       0  57656420 4e6f7620  20372031 353a3339  |Wed Nov  7 15:39|
      10  3a303020 4d455420  32303031 0affffff  |:00 MET 2001    |
      20  ffffffff ffffffff  ffffffff ffffffff  |                |
                    *** same ***
   80000                                        |                |
# eraseall /dev/mtd7
Erased 512 Kibyte @ 0 -- 100% complete.
# xd /dev/mtd7
       0  ffffffff ffffffff  ffffffff ffffffff  |                |
                    *** same ***
   80000                                        |                |
# date >/dev/mtd7
# xd /dev/mtd7
       0  57656420 4e6f7620  20372031 353a3432  |Wed Nov  7 15:42|
      10  3a313920 4d455420  32303031 0affffff  |:19 MET 2001    |
      20  ffffffff ffffffff  ffffffff ffffffff  |                |
                    *** same ***
   80000 

We have now sufficient proof that the MTD layer is working as expected, so we can try creating a flash filesystem.

9.1.2. Journalling Flash File System

At the moment it seems that the Journalling Flash File System JFFS is the best choice for filesystems in flash memory of embedded devices. You must enable the following configuration options to get JFFS support in your system:

CONFIG_JFFS_FS=y
CONFIG_JFFS_FS_VERBOSE=0

If the flash device is erased, we can simply mount it, and the creation of the JFFS filesystem is performed automagically.

Note: For simple accesses like direct read or write operations or erasing you use the character device interface (/dev/mtd*) of the MTD layer, while for filesystem operations like mounting we must use the block device interface (/dev/mtdblock*).

# eraseall /dev/mtd2
Erased 4096 Kibyte @ 0 -- 100% complete.       
# mount -t jffs /dev/mtdblock2 /mnt
# mount
/dev/root on / type nfs (rw,v2,rsize=4096,wsize=4096,hard,udp,nolock,addr=10.0.0.2)
proc on /proc type proc (rw)
devpts on /dev/pts type devpts (rw)
/dev/mtdblock2 on /mnt type jffs (rw)
# df
Filesystem           1k-blocks      Used Available Use% Mounted on
/dev/root              2087212   1232060    855152  60% /
/dev/mtdblock2            3584         0      3584   0% /mnt

Now you can access the files in the JFFS filesystem in the /mnt directory.

9.1.3. Second Version of JFFS

Probably even more interesting for embedded systems is the second version of JFFS, JFFS2, since it not only fixes a few design issues with JFFS, but also adds transparent compression, so that you can save a lot of precious flash memory.

The mkfs.jffs2 tool is used to create a JFFS2 filesystem image; it populates the image with files from a given directory. For instance, to create a JFFS2 image for a flash partition of 3 MB total size and to populate it with the files from the /tmp/flashtools directory you would use:

# mkfs.jffs2 --pad=3145728 --eraseblock=262144 \
--root=/tmp/flashtools/ --output image.jffs2
# eraseall /dev/mtd4
Erased 3072 Kibyte @ 0 -- 100% complete.       
\# dd if=image.jffs2 of=/dev/mtd4 bs=256k
12+0 records in
12+0 records out
# mount -t jffs2 /dev/mtdblock4 /mnt
# df /mnt
Filesystem           1k-blocks      Used Available Use% Mounted on
/dev/mtdblock4            3072      2488       584  81% /mnt

Note: Especially when you are running time-critical applications on your system you should carefully study if the behaviour of the flash filesystem might have any negative impact on your application. After all, a flash device is not a normal harddisk. This is especially important when your flash filesystem gets full; JFFS2 acts a bit weird then:

  • You will note that an increasing amount of CPU time is spent by the filesystem's garbage collection kernel thread.
  • Access times to the files on the flash filesystem may increase drastically.
  • Attempts to truncate a file (to free space) or to rename it may fail:
    ...
    # cp /bin/bash file
    cp: writing `file': No space left on device
    # >file
    bash: file: No space left on device
    # mv file foo
    mv: cannot create regular file `foo': No space left on device
    
    You will have to use rm to actually delete a file in this situation.

This is especially critical when you are using the flash filesystem to store log files: when your application detects some abnormal condition and produces lots of log messages (which usually are especially important in this situation) the filesystem may fill up and cause extreme long delays - if your system crashes, the most important messages may never be logged at all.

9.1.4. Compressed ROM Filesystem

In some cases it is sufficent to have read-only access to some files, and if the files are big enough it becomes desirable to use some method of compression. The Compressed ROM Filesystem CramFs might be a solution here.

Please note that CramFs has - beside the fact that it is a read-only filesystem - some severe limitations (like missing support for timestamps, hard links, and 16/32 bit uid/gids), but there are many situations in Embedded Systems where it's still useful.

To create a CramFs filesystem a special tool mkcramfs is used to create a file which contains the CramFs image. Note that the CramFs filesystem can be written and read only by kernels with PAGE_CACHE_SIZE == 4096, and some versions of the mkcramfs program may have other restrictions like that the filesystem must be written and read with architectures of the same endianness. Especially the endianness requirement makes it impossible to build the CramFs image on x86 PC host when you want to use it on a PowerPC target. The endianness problem has been fixed in the version of mkcramfs that comes with the ELDK.

In some cases you can use a target system running with root filesystem mounted over NFS to create the CramFs image on the native system and store it to flash for further use.

Note: The normal version of the mkcramfs program tries to initialize some entries in the filesystem's superblock with random numbers by reading /dev/random; this may hang permanently on your target because there is not enough input (like mouse movement) to the entropy pool. You may want to use a modified version of mkcramfs which does not depend on /dev/random.

To create a CramFs image, you put all files you want in the filesystem into one directory, and then use the mkcramfs= program as follows:

$ mkdir /tmp/test
$ cp ... /tmp/test
$ du -sk /tmp/test
64      /tmp/test
$ mkcramfs /tmp/test test.cramfs.img
Super block: 76 bytes
  erase
  eraseall
  mkfs.jffs
  lock
  unlock
Directory data: 176 bytes
-54.96% (-4784 bytes)   erase
-55.46% (-5010 bytes)   eraseall
-51.94% (-8863 bytes)   mkfs.jffs
-58.76% (-4383 bytes)   lock
-59.68% (-4215 bytes)   unlock
Everything: 24 kilobytes
$ ls -l test.cramfs.img
-rw-r--r--    1 wd       users       24576 Nov 10 23:44 test.cramfs.img

As you can see, the CramFs image test.cramfs.img takes just 24 kB, while the input directory contained 64 kB of data. Savings of some 60% like in this case are typical CramFs.

Now we write the CramFs image to a partition in flash and test it:

# cp test.cramfs.img /dev/mtd3
# mount -t cramfs /dev/mtdblock3 /mnt
# mount
/dev/root on / type nfs (rw,v2,rsize=4096,wsize=4096,hard,udp,nolock,addr=10.0.0.2)
proc on /proc type proc (rw)
devpts on /dev/pts type devpts (rw)
/dev/mtdblock3 on /mnt type cramfs (rw)
# ls -l /mnt
total 54
-rwxr-xr-x    1 wd       users        8704 Jan  9 16:32 erase
-rwxr-xr-x    1 wd       users        9034 Jan  1 01:00 eraseall
-rwxr-xr-x    1 wd       users        7459 Jan  1 01:00 lock
-rwxr-xr-x    1 wd       users       17063 Jan  1 01:00 mkfs.jffs
-rwxr-xr-x    1 wd       users        7063 Jan  1 01:00 unlock

Note that all the timestamps in the CramFs filesyste are bogus, and so is for instance the output of the df command for such filesystems:

# df /mnt
Filesystem           1k-blocks      Used Available Use% Mounted on
/dev/mtdblock3               0         0         0   -  /mnt

9.2. The TMPFS Virtual Memory Filesystem

The tmpfs filesystem, formerly known as shmfs, is a filesystem keeping all files in virtual memory.

Everything in tmpfs is temporary in the sense that no files will be created on any device. If you unmount a tmpfs instance, everything stored therein is lost.

tmpfs puts everything into the kernel internal caches and grows and shrinks to accommodate the files it contains and is able to swap unneeded pages out to swap space. It has maximum size limits which can be adjusted on the fly via 'mount -o remount ...'

If you compare it to ramfs (which was the template to create tmpfs) you gain swapping and limit checking. Another similar thing is the RAM disk (/dev/ram*), which simulates a fixed size hard disk in physical RAM, where you have to create an ordinary filesystem on top. Ramdisks cannot swap and you do not have the possibility to resize them.

9.2.1. Mount Parameters

tmpfs has a couple of mount options:

  • size: The limit of allocated bytes for this tmpfs instance. The default is half of your physical RAM without swap. If you oversize your tmpfs instances the machine will deadlock since the OOM handler will not be able to free that memory.
  • nr_blocks: The same as size, but in blocks of PAGECACHE_SIZE.
  • nr_inodes: The maximum number of inodes for this instance. The default is half of the number of your physical RAM pages.

These parameters accept a suffix k, m or g for kilo, mega and giga and can be changed on remount.

To specify the initial root directory you can use the following mount options:

  • mode: The permissions as an octal number
  • uid: The user id
  • gid: The group id

These options do not have any effect on remount. You can change these parameters with chmod(1), chown(1) and chgrp(1) on a mounted filesystem.

So the following mount command will give you a tmpfs instance on /mytmpfs which can allocate 12MB of RAM/SWAP and it is only accessible by root.

mount -t tmpfs -o size=12M,mode=700 tmpfs /mytmpfs

9.2.2. Kernel Support for tmpfs

In order to use a tmpfs filesystem, the CONFIG_TMPFS option has to be enabled for your kernel configuration. It can be found in the Filesystems configuration group. You can simply check if a running kernel supports tmpfs by searching the contents of /proc/fileysystems:

bash# grep tmpfs /proc/filesystems
nodev   tmpfs
bash#

9.2.3. Usage of tmpfs in Embedded Systems

In embedded systems tmpfs is very well suited to provide read and write space (e.g. /tmp and /var) for a read-only root file system such as CramFs described in section 9.1.4. Compressed ROM Filesystem. One way to achieve this is to use symbolic links. The following code could be part of the startup file /etc/rc.sh of the read-only ramdisk:

#!/bin/sh
...
# Won't work on read-only root: mkdir /tmpfs
mount -t tmpfs tmpfs /tmpfs
mkdir /tmpfs/tmp /tmpfs/var
# Won't work on read-only root: ln -sf /tmpfs/tmp /tmpfs/var /
...

The commented out sections will of course fail on a read-only root filesystem, so you have to create the /tmpfs mount-point and the symbolic links in your root filesystem beforehand in order to successfully use this setup.

9.3. Using PC Cards for Flash Disks, CompactFlash, and IDE Harddisks

If your board is equipped with a PC-Card adapter (also known as PCMCIA adapter) you can use this for miscellaneous types of mass storage devices like Flash Disks, CompactFlash, and IDE Harddisks.

Please note that there are other options to operate such devices on Embedded PowerPC Systems (for instace you can use the PCMCIA controller builtin to the MPC8xx CPUs to build a direct IDE interface, or you can use some external controller to provide such an interface). The following description does not cover such configurations. Only the solution which uses a standard PC Card Slot is described here.

9.3.1. PC Card Support in U-Boot

When PC Card support is enabled in your U-Boot configuration the target will try to detect any PC Cards in the slot when booting. If no card is present you will see a message like this:

PPCBoot 1.1.1 (Nov 11 2001 - 18:06:06)

CPU:   XPC862PZPnn0 at 48 MHz: 16 kB I-Cache 8 kB D-Cache FEC present
Board: ICU862 Board
DRAM:  32 MB
FLASH: 16 MB
In:    serial
Out:   serial
Err:   serial
PCMCIA:   No Card found

Depending on the type of PC Card inserted the boot messages vary; for instance with a Flash Disk card you would see:

...
PCMCIA: 3.3V card found: SunDisk SDP 5/3 0.6
            Fixed Disk Card
            IDE interface 
            [silicon] [unique] [single] [sleep] [standby] [idle] [low power]
Bus 0: OK 
  Device 0: Model: SanDisk SDP3B-8 Firm: Vdd 1.02 Ser#: fq9bu499900
            Type: Removable Hard Disk
            Capacity: 7.7 MB = 0.0 GB (15680 x 512)
...

With a CompactFlash Card you get:

...
PCMCIA: 3.3V card found:   CF 128MB CH
            Fixed Disk Card
            IDE interface 
            [silicon] [unique] [single] [sleep] [standby] [idle] [low power]
Bus 0: OK 
  Device 0: Model: CF 128MB Firm: Rev 1.01 Ser#: 1969C32AA0210002
            Type: Removable Hard Disk
            Capacity: 122.3 MB = 0.1 GB (250368 x 512)
...

Even more exotic memory devices (like the "MemoryStick as used in some Digital Cameras") will usually work without problems:

...
PCMCIA: 3.3V card found: SONY MEMORYSTICK(128M) 1.0
            Fixed Disk Card
            IDE interface 
            [silicon] [unique] [single] [sleep] [standby] [idle] [low power]
Bus 0: .OK 
  Device 0: Model: MEMORYSTICK 128M 16K                    Firm: SONY1.00` Ser#: 
            Type: Removable Hard Disk
            Capacity: 123.8 MB = 0.1 GB (253696 x 512)
...

And with a harddisk adapter you would see:

...
PCMCIA: 5.0V card found: ARGOSY PnPIDE D5
Bus 0: OK 
  Device 0: Model: IBM-DKLA-24320 Firm: KL4AA43A Ser#: YD2YD246800
            Type: Hard Disk
            Capacity: 4126.10 MB = 4.0 GB (8452080 x 512)
...

Note that most other cards will be detected by U-Boot, but not supported otherwise, for instance:

...
PCMCIA: 5.0V card found: ELSA AirLancer MC-11 Version 01.01
            Network Adapter Card
...

or

...
PCMCIA: 5.0V card found: Elsa MicroLink 56k MC Internet 021 A
            Serial Port Card
...

9.3.2. PC Card Support in Linux

The standard way to use PC Cards in a Linux system is to install the "PCMCIA Card Services" package. This is a quite complex set of kernel modules and tools that take care of things like automatic detection and handling of "card insert" or "remove" events, identification of the inserted cards, loading the necessary device drivers, etc. This is a very powerful package, but for embedded applications it has several serious disadvantages:

  • Memory footprint - the package consists of a lot of tools and modules that take a lot of space both in the root filesystem and in system RAM when running
  • Chicken and Egg Problem - the package loads the needed device drivers as kernel modules, so it needs a root filesystem on another device; that means that you cannot easily put the root filesystem on a PC Card.

For "disk" type PC Cards (FlashDisks, CompactFlash, Hard Disk Adapters - basicly anything that looks like an ordinary IDE drive) an alternative solution is available: direct support within the Linux kernel. This has the big advantage of minimal memory footprint, but of course it comes with a couple of disadvantages, too:

  • It works only with "disk" type PC Cards - no support for modems, network cards, etc; for these you still need the PCMCIA Card Services package.
  • There is no support for "hot plug", i. e. you cannot insert or remove the card while Linux is running. (Well, of course you can do this, but either you willnot be able to access any card inserted, or when you remove a card you will most likely crash the system. Don't do it - you have been warned!)
  • The code relies on initialization of the PCMCIA controller by the firmware (of course U-Boot will do exactly what's required).

On the other hand these are no real restrictions for use in an Embedded System.

To enable the "direct IDE support" you have to select the following Linux kernel configuration options:

CONFIG_IDE=y
CONFIG_BLK_DEV_IDE=y
CONFIG_BLK_DEV_IDEDISK=y
CONFIG_IDEDISK_MULTI_MODE=y
CONFIG_BLK_DEV_MPC8xx_IDE=y
CONFIG_BLK_DEV_IDE_MODES=y

and, depending on which partition types and languages you want to support:

CONFIG_PARTITION_ADVANCED=y
CONFIG_MAC_PARTITION=y
CONFIG_MSDOS_PARTITION=y
CONFIG_NLS=y
CONFIG_NLS_DEFAULT="y"
CONFIG_NLS_ISO8859_1=y
CONFIG_NLS_ISO8859_15=y

With these options you will see messages like the following when you boot the Linux kernel:

...
Uniform Multi-Platform E-IDE driver Revision: 6.31
ide: Assuming 50MHz system bus speed for PIO modes; override with idebus=xx
PCMCIA slot B: phys mem e0000000...ec000000 (size 0c000000)
Card ID:   CF 128MB CH
 Fixed Disk Card
 IDE interface
 [silicon] [unique] [single] [sleep] [standby] [idle] [low power]
hda: probing with STATUS(0x50) instead of ALTSTATUS(0x41)
hda: CF 128MB, ATA DISK drive
ide0 at 0xc7000320-0xc7000327,0xc3000106 on irq 13
hda: 250368 sectors (128 MB) w/16KiB Cache, CHS=978/8/32
Partition check:
 hda: hda1 hda2 hda3 hda4
...

You can now access your PC Card "disk" like any normal IDE drive. If you start with a new drive, you have to start by creating a new partition table. For PowerPC systems, there are two commonly used options:

9.3.2.1. Using a MacOS Partition Table

A MacOS partition table is the "native" partition table format on PowerPC systems; most desktop PowerPC systems use it, so you may prefer it when you have PowerPC development systems around.

To format your "disk" drive with a MacOS partition table you can use the pdisk command:

We start printing the help menu, re-initializing the partition table and then printing the new, empty partition table so that we know the block numbers when we want to create new partitions:

# pdisk /dev/hda
Edit /dev/hda -
Command (? for help): ?
Notes:
  Base and length fields are blocks, which vary in size between media.
  The base field can be <nth>p; i.e. use the base of the nth partition.
  The length field can be a length followed by k, m, g or t to indicate
  kilo, mega, giga, or tera bytes; also the length can be <nth>p; i.e. use
  the length of the nth partition.
  The name of a partition is descriptive text.

Commands are:
  h    help
  p    print the partition table
  P    (print ordered by base address)
  i    initialize partition map
  s    change size of partition map
  c    create new partition (standard MkLinux type)
  C    (create with type also specified)
  n    (re)name a partition
  d    delete a partition
  r    reorder partition entry in map
  w    write the partition table
  q    quit editing (don't save changes)
Command (? for help): i
map already exists
do you want to reinit? [n/y]: y
Command (? for help): p

Partition map (with 512 byte blocks) on '/dev/hda'
 #:                type name    length   base    ( size )
 1: Apple_partition_map Apple       63 @ 1
 2:          Apple_Free Extra  1587536 @ 64      (775.2M)

Device block size=512, Number of Blocks=1587600 (775.2M)
DeviceType=0x0, DeviceId=0x0

At first we create two small partitions that will be used to store a Linux boot image; a compressed Linux kernel is typically around 400 ... 500 kB, so chosing a partition size of 2 MB is more than generous. 2 MB coresponds to 4096 disk blocks of 512 bytes each, so we enter:

Command (? for help): C
First block: 64
Length in blocks: 4096
Name of partition: boot0
Type of partition: PPCBoot
Command (? for help): p

Partition map (with 512 byte blocks) on '/dev/hda'
 #:                type name    length   base    ( size )
 1: Apple_partition_map Apple       63 @ 1
 2:             PPCBoot boot0     4096 @ 64      (  2.0M)
 3:          Apple_Free Extra  1583440 @ 4160    (773.2M)

Device block size=512, Number of Blocks=1587600 (775.2M)
DeviceType=0x0, DeviceId=0x0

To be able to select between two kernel images (for instance when we want to do a field upgrade of the Linux kernel) we create a second boot partition of exactly the same size:

Command (? for help): C
First block: 4160
Length in blocks: 4096
Name of partition: boot1
Type of partition: PPCBoot
Command (? for help): p

Partition map (with 512 byte blocks) on '/dev/hda'
 #:                type name    length   base    ( size )
 1: Apple_partition_map Apple       63 @ 1
 2:             PPCBoot boot0     4096 @ 64      (  2.0M)
 3:             PPCBoot boot1     4096 @ 4160    (  2.0M)
 4:          Apple_Free Extra  1579344 @ 8256    (771.2M)

Device block size=512, Number of Blocks=1587600 (775.2M)
DeviceType=0x0, DeviceId=0x0

Now we create a swap partition - 64 MB should be more than sufficient for our Embedded System; 64 MB means 64*1024*2 = 131072 disk blocks of 512 bytes:

Command (? for help): C
First block: 8256
Length in blocks: 131072
Name of partition: swap
Type of partition: swap
Command (? for help): p

Partition map (with 512 byte blocks) on '/dev/hda'
 #:                type name    length   base    ( size )
 1: Apple_partition_map Apple       63 @ 1
 2:             PPCBoot boot0     4096 @ 64      (  2.0M)
 3:             PPCBoot boot1     4096 @ 4160    (  2.0M)
 4:                swap swap    131072 @ 8256    ( 64.0M)
 5:          Apple_Free Extra  1448272 @ 139328  (707.2M)

Device block size=512, Number of Blocks=1587600 (775.2M)
DeviceType=0x0, DeviceId=0x0

Finally, we dedicate all the remaining space to the root partition:

Command (? for help): C
First block: 139328
Length in blocks: 1448272
Name of partition: root
Type of partition: Linux
Command (? for help): p

Partition map (with 512 byte blocks) on '/dev/hda'
 #:                type name    length   base    ( size )
 1: Apple_partition_map Apple       63 @ 1
 2:             PPCBoot boot0     4096 @ 64      (  2.0M)
 3:             PPCBoot boot1     4096 @ 4160    (  2.0M)
 4:                swap swap    131072 @ 8256    ( 64.0M)
 5:               Linux root   1448272 @ 139328  (707.2M)

Device block size=512, Number of Blocks=1587600 (775.2M)
DeviceType=0x0, DeviceId=0x0

To make our changes permanent we must write the new partition table to the disk, before we quit the pdisk program:

Command (? for help): w
Writing the map destroys what was there before. Is that okay? [n/y]: y
 hda: [mac] hda1 hda2 hda3 hda4 hda5
 hda: [mac] hda1 hda2 hda3 hda4 hda5
Command (? for help): q

Now we can initialize the swap space and the filesystem:

# mkswap /dev/hda4
Setting up swapspace version 1, size = 67104768 bytes
# mke2fs /dev/hda5
mke2fs 1.19, 13-Jul-2000 for EXT2 FS 0.5b, 95/08/09
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
90624 inodes, 181034 blocks
9051 blocks (5.00%) reserved for the super user
First data block=0
6 block groups
32768 blocks per group, 32768 fragments per group
15104 inodes per group
Superblock backups stored on blocks:
        32768, 98304, 163840

Writing inode tables: done
Writing superblocks and filesystem accounting information: done

9.3.2.2. Using a MS-DOS Partition Table

The MS-DOS partition table is especially common on PC type computers, which these days means nearly everywhere. You will prefer this format if you want to exchange your "disk" media with any PC type host system.

The fdisk command is used to create MS-DOS type partition tables; to create the same partitioning scheme as above you would use the following commands:

# fdisk /dev/hda
Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel
Building a new DOS disklabel. Changes will remain in memory only,
until you decide to write them. After that, of course, the previous
content won't be recoverable.


The number of cylinders for this disk is set to 1575.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs
   (e.g., DOS FDISK, OS/2 FDISK)

Command (m for help): m
Command action
   a   toggle a bootable flag
   b   edit bsd disklabel
   c   toggle the dos compatibility flag
   d   delete a partition
   l   list known partition types
   m   print this menu
   n   add a new partition
   o   create a new empty DOS partition table
   p   print the partition table
   q   quit without saving changes
   s   create a new empty Sun disklabel
   t   change a partition's system id
   u   change display/entry units
   v   verify the partition table
   w   write table to disk and exit
   x   extra functionality (experts only)
Command (m for help): n
Command action
   e   extended
   p   primary partition (1-4)
p
Partition number (1-4): 1
First cylinder (1-1575, default 1):
Using default value 1
Last cylinder or +size or +sizeM or +sizeK (1-1575, default 1575): +2M

Command (m for help): p

Disk /dev/hda: 16 heads, 63 sectors, 1575 cylinders
Units = cylinders of 1008 * 512 bytes

   Device Boot    Start       End    Blocks   Id  System
/dev/hda1             1         5      2488+  83  Linux
Command (m for help): n
Command action
   e   extended
   p   primary partition (1-4)
p
Partition number (1-4): 2
First cylinder (6-1575, default 6):
Using default value 6
Last cylinder or +size or +sizeM or +sizeK (6-1575, default 1575): +2M

Command (m for help): p

Disk /dev/hda: 16 heads, 63 sectors, 1575 cylinders
Units = cylinders of 1008 * 512 bytes

   Device Boot    Start       End    Blocks   Id  System
/dev/hda1             1         5      2488+  83  Linux
/dev/hda2             6        10      2520   83  Linux
Command (m for help): n
Command action
   e   extended
   p   primary partition (1-4)
p
Partition number (1-4): 3
First cylinder (11-1575, default 11):
Using default value 11
Last cylinder or +size or +sizeM or +sizeK (11-1575, default 1575): +64M

Command (m for help): t
Partition number (1-4): 3
Hex code (type L to list codes): 82
Changed system type of partition 3 to 82 (Linux swap)

Command (m for help): p

Disk /dev/hda: 16 heads, 63 sectors, 1575 cylinders
Units = cylinders of 1008 * 512 bytes

   Device Boot    Start       End    Blocks   Id  System
/dev/hda1             1         5      2488+  83  Linux
/dev/hda2             6        10      2520   83  Linux
/dev/hda3            11       141     66024   82  Linux swap

Note that we had to use the t command to mark this partition as swap space.

Command (m for help): n
Command action
   e   extended
   p   primary partition (1-4)
p
Partition number (1-4): 4
First cylinder (142-1575, default 142):
Using default value 142
Last cylinder or +size or +sizeM or +sizeK (142-1575, default 1575):
Using default value 1575

Command (m for help): p

Disk /dev/hda: 16 heads, 63 sectors, 1575 cylinders
Units = cylinders of 1008 * 512 bytes

   Device Boot    Start       End    Blocks   Id  System
/dev/hda1             1         5      2488+  83  Linux
/dev/hda2             6        10      2520   83  Linux
/dev/hda3            11       141     66024   82  Linux swap
/dev/hda4           142      1575    722736   83  Linux
Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.
 hda: hda1 hda2 hda3 hda4
 hda: hda1 hda2 hda3 hda4

WARNING: If you have created or modified any DOS 6.x
partitions, please see the fdisk manual page for additional
information.
Syncing disks.

Now we are ready to initialize the partitions:

# mkswap /dev/hda3
Setting up swapspace version 1, size = 67604480 bytes
# mke2fs /dev/hda4
mke2fs 1.19, 13-Jul-2000 for EXT2 FS 0.5b, 95/08/09
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
90432 inodes, 180684 blocks
9034 blocks (5.00%) reserved for the super user
First data block=0
6 block groups
32768 blocks per group, 32768 fragments per group
15072 inodes per group
Superblock backups stored on blocks:
        32768, 98304, 163840

Writing inode tables: done
Writing superblocks and filesystem accounting information: done

9.3.3. Using PC Card "disks" with U-Boot and Linux

U-Boot provides only basic functionality to access PC Card based "disks": you can print the partition table and read and write blocks (addressed by absolute block number), but there is no support to create new partitions or to read files from any type of filesystem.

[Such features could be easily added as U-Boot extensions aka "standalone programs", but so far it has not been implemented yet.]

As usual, you can get some information about the available IDE commands using the help command in U-Boot:

=> help ide      
ide reset - reset IDE controller
ide info  - show available IDE devices
ide device [dev] - show or set current device
ide part [dev] - print partition table of one or all IDE devices
ide read  addr blk# cnt
ide write addr blk# cnt - read/write `cnt' blocks starting at block `blk#'
    to/from memory address `addr'

That means you will have to partition the "disk" on your host system; U-Boot can be configured for DOS and MacOS type partition tables. Since U-Boot cannot read files from a filesystem you should create one (or more) small partitions (maybe 1 MB or so) if you want to boot from the "disk".

For example on a 128 MB CompactFlash card we could create the following partiton table under Linux:

# fdisk /dev/hda
 hda: hda1 hda2 hda3 hda4

Command (m for help): p

Disk /dev/hda: 8 heads, 32 sectors, 978 cylinders
Units = cylinders of 256 * 512 bytes

   Device Boot    Start       End    Blocks   Id  System
/dev/hda1             1        17      2160   83  Linux
/dev/hda2            18        34      2176   83  Linux
/dev/hda3            35       803     98432   83  Linux
/dev/hda4           804       978     22400   82  Linux swap

Command (m for help): q

# mkswap /dev/hda4
Setting up swapspace version 1, size = 22933504 bytes

Here we have two small boot partitions (/dev/hda1 and /dev/hda2, 2 MB each), one big partition to hold a filesystem (/dev/hda3, 99 MB), and a swap partition (/dev/hda4, 22 MB). We also initialized /dev/hda4 as swap space.

U-Boot will recognize this partition table as follows:

=> ide part

Partition Map for IDE device 0  --   Partition Type: DOS

Partition     Start Sector     Num Sectors     Type
    1                   32            4320      83
    2                 4352            4352      83
    3                 8704          196864      83
    4               205568           44800      82

We can now load a Linux kernel image over ethernet and store it both of the boot partitions:

=> tftp 100000 /tftpboot/uImage
ARP broadcast 1
TFTP from server 10.0.0.2; our IP address is 10.0.0.99
Filename '/tftpboot/uImage'.
Load address: 0x100000
Loading: #################################################################
         ##############################################
done
Bytes transferred = 566888 (8a668 hex)
=> ide write 100000 0x20 0x800

IDE write: device 0 block # 32, count 2048 ... 2048 blocks written: OK
=> ide write 100000 0x1100 0x800

IDE write: device 0 block # 4352, count 2048 ... 2048 blocks written: OK

This requires a little more explanation: as you can see from the output of the help ide command, the write subcommand takes 3 arguments: a memory address from where the data are read, an (absolute) block number on the disk where the writing starts, and a number of disk blocks.

Since U-Boot expects all input in hex notation we have to perform some calculation: partition 1 starts at block (or sector) number 32, which is 0x20; partition 2 starts at block number 4352 = 0x1100.

We used a block count of 0x800 = 2048 in both cases - this means we wrote 2048 block of 512 bytes each, or a 1024 kB - much more than the actual size of the LInux kernel image - but the partition is big enough and we are on the safe side, so we didn't bother to calculate the exact block count.

To boot from a disk you can use the diskboot command:

=> help diskboot
diskboot loadAddr dev:part

The diskboot command (or short disk) expects a load address in RAM, and a combination of device and partition numbers, separated by a colon. It then reads the image from disk and stores it in memory. We can now boot it using the bootm command [to automatically boot the image define the U-Boot environment autostart with the value =yes=].

=> disk 400000 0:1

Loading from IDE device 0, partition 1: Name: hda1
  Type: PPCBoot
   Image Name:   Linux-2.4.4
   Created:      2001-11-11  18:11:11 UTC
   Image Type:   PowerPC Linux Kernel Image (gzip compressed)
   Data Size:    566824 Bytes = 553 kB = 0 MB
   Load Address: 00000000
   Entry Point:  00000000
=> bootm 400000
## Booting image at 00400000 ...
   Image Name:   Linux-2.4.4
   Created:      2001-11-11  18:11:11 UTC
   Image Type:   PowerPC Linux Kernel Image (gzip compressed)
   Data Size:    566824 Bytes = 553 kB = 0 MB
   Load Address: 00000000
   Entry Point:  00000000
   Verifying Checksum ... OK
   Uncompressing Kernel Image ... OK
Linux version 2.4.4 (wd@denx.denx.de) (gcc version 2.95.2 19991024 (release)) #1 Sun Nov 11 19:05:47 MET 2001
On node 0 totalpages: 8192
...

We can use the same method that we used to store a Linux kernel image to a disk partition to load a filesystem image into another partiton - as long as the image fits into physical RAM - but usually it's easier to initialize the filesystem either on the host system (swapping the PC Card between host and target is easy enough), or you can use the configuration with root filesystem over NFS to populate the filesystem on the target.

You only have to set the bootargs variable to boot Linux with root filesystem on disk, for instance:

=> setenv bootargs root=/dev/hda3
=> setenv autostart yes
=> disk 400000 0:1

Loading from IDE device 0, partition 1: Name: hda1
  Type: PPCBoot
   Image Name:   Linux-2.4.4
   Created:      2001-11-11  18:11:11 UTC
   Image Type:   PowerPC Linux Kernel Image (gzip compressed)
   Data Size:    566824 Bytes = 553 kB = 0 MB
   Load Address: 00000000
   Entry Point:  00000000
Automatic boot of image at addr 0x00400000 ...
## Booting image at 00400000 ...
   Image Name:   Linux-2.4.4
   Created:      2001-11-11  18:11:11 UTC
   Image Type:   PowerPC Linux Kernel Image (gzip compressed)
   Data Size:    566824 Bytes = 553 kB = 0 MB
   Load Address: 00000000
   Entry Point:  00000000
   Verifying Checksum ... OK
   Uncompressing Kernel Image ... OK
Linux version 2.4.4 (wd@denx.denx.de) (gcc version 2.95.2 19991024 (release)) #1 Sun Nov 11 19:05:47 MET 2001
On node 0 totalpages: 8192
zone(0): 8192 pages.
zone(1): 0 pages.
zone(2): 0 pages.
Kernel command line: root=/dev/hda3 ip=10.0.0.99:10.0.0.2::255.0.0.0:tqm::off panic=1
Decrementer Frequency: 3000000
Calibrating delay loop... 47.82 BogoMIPS
Memory: 30548k available (1088k kernel code, 488k data, 48k init, 0k highmem)
Dentry-cache hash table entries: 4096 (order: 3, 32768 bytes)
Buffer-cache hash table entries: 1024 (order: 0, 4096 bytes)
Page-cache hash table entries: 8192 (order: 3, 32768 bytes)
Inode-cache hash table entries: 2048 (order: 2, 16384 bytes)
POSIX conformance testing by UNIFIX
Linux NET4.0 for Linux 2.4
Based upon Swansea University Computer Society NET3.039
Starting kswapd v1.8
CPM UART driver version 0.03
ttyS0 on SMC1 at 0x0280, BRG1
ttyS1 on SMC2 at 0x0380, BRG2
pty: 256 Unix98 ptys configured
block: queued sectors max/low 20226kB/6742kB, 64 slots per queue
RAMDISK driver initialized: 16 RAM disks of 4096K size 1024 blocksize
Uniform Multi-Platform E-IDE driver Revision: 6.31
ide: Assuming 50MHz system bus speed for PIO modes; override with idebus=xx
PCMCIA slot B: phys mem e0000000...ec000000 (size 0c000000)
Card ID:   CF 128MB CH
 Fixed Disk Card
 IDE interface 
 [silicon] [unique] [single] [sleep] [standby] [idle] [low power]
hda: probing with STATUS(0x50) instead of ALTSTATUS(0x41)
hda: CF 128MB, ATA DISK drive
ide0 at 0xc7000320-0xc7000327,0xc3000106 on irq 13
hda: 250368 sectors (128 MB) w/16KiB Cache, CHS=978/8/32
Partition check:
 hda: hda1 hda2 hda3 hda4
eth0: FEC ENET Version 0.2, FEC irq 3, MII irq 4, addr 00:cb:bd:00:00:11
JFFS version 1.0, (C) 1999, 2000  Axis Communications AB
 Amd/Fujitsu Extended Query Table v1.1 at 0x0040
number of JEDEC chips: 1
ICU862 flash bank 0: Using static image partition definition
Creating 8 MTD partitions on "ICU862 Bank 0":
0x00000000-0x00100000 : "kernel"
0x00100000-0x00400000 : "initrd"
0x00400000-0x00800000 : "jffs"
0x00800000-0x00c00000 : "cramfs"
0x00c00000-0x00f00000 : "jffs2"
0x00f00000-0x00f40000 : "ppcboot"
0x00f40000-0x00f80000 : "environment"
0x00f80000-0x01000000 : "spare"
NET4: Linux TCP/IP 1.0 for NET4.0
IP Protocols: ICMP, UDP, TCP, IGMP
IP: routing cache hash table of 512 buckets, 4Kbytes
TCP: Hash tables configured (established 2048 bind 2048)
NET4: Unix domain sockets 1.0/SMP for Linux NET4.0.
 hda: hda1 hda2 hda3 hda4
 hda: hda1 hda2 hda3 hda4
VFS: Mounted root (ext2 filesystem) readonly.
Freeing unused kernel memory: 48k init
init started:  BusyBox v0.51 (2001.11.06-02:06+0000) multi-call binary

BusyBox v0.51 (2001.11.06-02:06+0000) Built-in shell (lash)
Enter 'help' for a list of built-in commands.

# 

9.4. Adding Swap Space

If you are running out of system RAM, you can add virtual memory by using swap space. If you reserved a swap partition on your disk drive, you have to initialize it once using the mkswap command:

# fdisk -l /dev/hda

Disk /dev/hda: 16 heads, 63 sectors, 1575 cylinders
Units = cylinders of 1008 * 512 bytes

   Device Boot    Start       End    Blocks   Id  System
/dev/hda1             1         5      2488+  83  Linux
/dev/hda2             6        10      2520   83  Linux
/dev/hda3            11       141     66024   82  Linux swap
/dev/hda4           142      1575    722736   83  Linux
# mkswap /dev/hda3
Setting up swapspace version 1, size = 67604480 bytes

Then, to activate it, you use the swapon command like this:

# free
             total       used       free     shared    buffers     cached
Mem:         14628      14060        568       8056        100      11664
-/+ buffers/cache:       2296      12332
Swap:            0          0          0
# free
             total       used       free     shared    buffers     cached
Mem:         14628      14060        568       8056        100      11664
-/+ buffers/cache:       2296      12332
Swap:            0          0          0
# swapon /dev/hda3
Adding Swap: 66016k swap-space (priority -2)
# free
             total       used       free     shared    buffers     cached
Mem:         14628      14084        544       8056        100      11648
-/+ buffers/cache:       2336      12292
Swap:        66016          0      66016

If you forgot to reserve (sufficient) space in a separate partition on your disk, you can still use an ordinary file for swap space. You only have to create a file of appropriate size, and initialize it as follows:

# mount /dev/hda4 /mnt
# df
Filesystem           1k-blocks      Used Available Use% Mounted on
/dev/root              2087212   1378824    708388  67% /
/dev/hda4               711352        20    675196   1% /mnt
# dd if=/dev/zero of=/mnt/swapfile bs=1024k count=64
64+0 records in
64+0 records out
# mkswap /mnt/swapfile
Setting up swapspace version 1, size = 67104768 bytes

Then activate it:

# free
             total       used       free     shared    buffers     cached
Mem:         14628      14084        544       6200         96      11788
-/+ buffers/cache:       2200      12428
Swap:            0          0          0
# swapon /mnt/swapfile
Adding Swap: 65528k swap-space (priority -3)
# free
             total       used       free     shared    buffers     cached
Mem:         14628      14084        544       6200         96      11752
-/+ buffers/cache:       2236      12392
Swap:        65528          0      65528

9.5. Splash Screen Support in Linux

To complement the U-Boot Splash Screen feature the new configuration option "CONFIG_8xx_PRE_INIT_FB" was added to the Linux kernel. This allows the Linux kernel to skip certain parts of the framebuffer initialization and to reuse the framebuffer contents that was set up by the U-Boot firmware. This allows to have an image displayed nearly immediately after power-on, so the delay needed to boot the Linux kernel is masked to the user.

The current implementation has some limitations:

  • We did not succeed in reusing the previously allocated framebuffer contents directly. Instead, Linux will allocate a new framebuffer, copy the contents, and then switch the display. This adds a minimal delay to the boot time, but is otherwise invisible to the user.
  • Linux manages its own colormap, and we considered it too much effort to keep the same settings as used by U-Boot. Instead we use the "trick" that U-Boot will fill the color map table backwards (top down). This works pretty well for images which use no more than 200...255 colors. If the images uses more colors, a bad color mapping may result.
    TIP We strongly recommend to convert all images that will be loaded as Linux splash screens to use no more than 225 colors. The "ppmquant" tool can be used for this purpose (see Bitmap Support in U-Boot for details).
  • Usually there will be a Linux device driver that is used to adjust the brightness and contrast of the display. When this driver starts, a visible change of brightness will happen if the default settings as used by U-Boot differ.
    TIP We recommend to store settings of brightness and contrast in U-Boot environment variables that can be shared between U-Boot and Linux. This way it is possible (assuming adequate driver support) to adjust the display settings correctly already in U-Boot and thus to avoid any flicker of the display when Linux takes over control.

9.6. Root File System: Design and Building

It is not an easy task to design the root file system for an embedded system. There are three major problems to be solved:

  1. what to put in it
  2. which file system type to use
  3. where to store and how to boot it

For now we will assume that the contents of the root file system is aready known; for example, it is given to us as a directory tree or a tarball which contains all the required files.

We will also assume that our system is a typical resource-limited embedded system so we will especially look for solutions where the root file system can be stored on on-board flash memory or other flash memory based devices like CompactFlash or SD cards, MMC or USB memory sticks.

So our focus here is on the second item: the options we have for chosing a file system type and the consequences this has.

In all cases we will base our experiments on the same content of the root filesystem; we use the images of the SELF (Simple Embedded Linux Framework) that come with the ELDK. In a first step we will transform the SELF images into a tarball to meet the requirements mentioned above:

In a ELDK installation, the SELF images can be found in the /opt/eldk/<architecture>/images/ directory. There is already a compressed ramdisk image in this directory, which we will use (ramdisk_image.gz):

  1. Uncompress ramdisk image:
    bash$ gzip -d -c -v /opt/eldk/ppc_8xx/images/ramdisk_image.gz >/tmp/ramdisk_image
    /opt/eldk/ppc_8xx/images/ramdisk_image.gz:       61.4%
    

    ALERT! Note: The following steps require root permissions!
  2. Mount ramdisk image:
    bash# mount -o loop /tmp/ramdisk_image /mnt/tmp
    
  3. Create tarball; to avoid the need for root permissions in the following steps we don't include the device files in our tarball:
    bash# cd /mnt/tmp
    bash# tar -zc --exclude='dev/*' -f /tmp/rootfs.tar.gz *
    
  4. Instead, we create a separate tarball which contains only the device entries so we can use them when necessary (with cramfs):
    bash# tar -zcf /tmp/devices.tar.gz dev/
    bash# cd /tmp
    
  5. Unmount ramdisk image:
    bash# umount /mnt/tmp
    

We will use the /tmp/rootfs.tar.gz tarball as master file in all following experiments.

9.6.1. Root File System on a Ramdisk

Ram disks are used very often to hold the root file system of embedded systems. They have several advantages:

  • well-known
  • well-supported by the Linux kernel
  • simple to build
  • simple to use - you can even combine the ramdisk with the Linux kernel into a single image file
  • RAM based, thus pretty fast
  • writable file system
  • original state of file system after each reboot = easy recovery from accidental or malicious data corruption etc.

On the other hand, there are several disadvantages, too:

  • big memory footprint: you always have to load the complete filesystem into RAM, even if only small parts of are actually used
  • slow boot time: you have to load (and uncompress) the whole image before the first application process can start
  • only the whole image can be replaced (not individual files)
  • additional storage needed for writable persistent data

Actually there are only very few situations where a ramdisk image is the optimal solution. But because they are so easy to build and use we will discuss them here anyway.

In almost all cases you will use an ext2 file system in your ramdisk image. The following steps are needed to create it:

  1. Create a directory tree with the content of the target root filesystem. We do this by unpacking our master tarball:
    $ mkdir rootfs
    $ cd rootfs
    $ tar zxf /tmp/rootfs.tar.gz
    
  2. We use the genext2fs tool to create the ramdisk image as this allows to use a simple text file to describe which devices shall be created in the generated file system image. That means that no root permissions are required at all. We use the following device table rootfs_devices.tab:
    #<name>    <type> <mode> <uid> <gid> <major> <minor> <start>  <inc>  <count>
    /dev            d  755  0       0        -      -       -       -       -
    /dev/console    c  640  0       0        5      1       -       -       -
    /dev/fb0        c  640  0       0       29      0       -       -       -
    /dev/full       c  640  0       0        1      7       -       -       -
    /dev/hda        b  640  0       0        3      0       -       -       -
    /dev/hda        b  640  0       0        3      1       1       1       16
    /dev/kmem       c  640  0       0        1      2       -       -       -
    /dev/mem        c  640  0       0        1      1       -       -       -
    /dev/mtd        c  640  0       0       90      0       0       2       16
    /dev/mtdblock   b  640  0       0       31      0       0       1       16
    /dev/mtdchar    c  640  0       0       90      0       0       1       16
    /dev/mtdr       c  640  0       0       90      1       0       2       16
    /dev/nftla      b  640  0       0       93      0       -       -       -
    /dev/nftla      b  640  0       0       93      1       1       1       8
    /dev/nftlb      b  640  0       0       93      16      -       -       -
    /dev/nftlb      b  640  0       0       93      17      1       1       8
    /dev/null       c  640  0       0        1      3       -       -       -
    /dev/ptyp       c  640  0       0        2      0       0       1       10
    /dev/ptypa      c  640  0       0        2      10      -       -       -
    /dev/ptypb      c  640  0       0        2      11      -       -       -
    /dev/ptypc      c  640  0       0        2      12      -       -       -
    /dev/ptypd      c  640  0       0        2      13      -       -       -
    /dev/ptype      c  640  0       0        2      14      -       -       -
    /dev/ptypf      c  640  0       0        2      15      -       -       -
    /dev/ram        b  640  0       0        1      0       0       1       2
    /dev/ram        b  640  0       0        1      1       -       -       -
    /dev/rtc        c  640  0       0       10      135     -       -       -
    /dev/tty        c  640  0       0        4      0       0       1       4
    /dev/tty        c  640  0       0        5      0       -       -       -
    /dev/ttyS       c  640  0       0        4      64      0       1       8
    /dev/ttyp       c  640  0       0        3      0       0       1       10
    /dev/ttypa      c  640  0       0        3      10      -       -       -
    /dev/ttypb      c  640  0       0        3      11      -       -       -
    /dev/ttypc      c  640  0       0        3      12      -       -       -
    /dev/ttypd      c  640  0       0        3      13      -       -       -
    /dev/ttype      c  640  0       0        3      14      -       -       -
    /dev/ttypf      c  640  0       0        3      15      -       -       -
    /dev/zero       c  640  0       0        1      5       -       -       -
    
    A description of the format of this table is part of the manual page for the genext2fs tool, genext2fs(8).
  3. We can now create an ext2 file system image using the genext2fs tool:
    $ ROOTFS_DIR=rootfs                 # directory with root file system content
    $ ROOTFS_SIZE=3700                  # size of file system image
    $ ROOTFS_FREE=100                   # free space wanted
    $ ROOTFS_INODES=380                 # number of inodes
    $ ROOTFS_DEVICES=rootfs_devices.tab # device description file
    $ ROOTFS_IMAGE=ramdisk.img          # generated file system image
    
    $ genext2fs -U \
            -d ${ROOTFS_DIR} \
            -D ${ROOTFS_DEVICES} \
            -b ${ROOTFS_SIZE} \
            -r ${ROOTFS_FREE} \
            -i ${ROOTFS_INODES} \
            ${ROOTFS_IMAGE}
    
  4. Compress the file system image:
    $ gzip -v9 ramdisk.img
    rootfs.img:      55.6% -- replaced with ramdisk.img.gz
    
  5. Create an U-Boot image file from it:
    $ mkimage -T ramdisk -C gzip -n 'Test Ramdisk Image' \
    > -d ramdisk.img.gz uRamdisk
    Image Name:   Test Ramdisk Image
    Created:      Sun Jun 12 16:58:06 2005
    Image Type:   PowerPC Linux RAMDisk Image (gzip compressed)
    Data Size:    1618547 Bytes = 1580.61 kB = 1.54 MB
    Load Address: 0x00000000
    Entry Point:  0x00000000

We now have a root file system image uRamdisk that can be used with U-Boot.

9.6.2. Root File System on a JFFS2 File System

JFFS2 (Journalling Flash File System version 2) was specifically designed for use on flash memory devices in embedded systems. It is a log-structured file system which means that it is robust against loss of power, crashes or other unorderly shutdowns of the system ("robust" means that data that is just being written when the system goes down may be lost, but the file system itself does not get corrupted and the system can be rebootet without need for any kind of file system check).

Some of the advantages of using JFFS2 as root file system in embedded systems are:

  • file system uses compression, thus making efficient use of flash memory
  • log-structured file system, thus robust against unorderly shutdown
  • writable flash file system

Disadvantages are:

  • long mount times (especially older versions)
  • slow when reading: files to be read get uncompressed on the fly which eats CPU cycles and takes time
  • slow when writing: files to be written get compressed, which eats CPU cycles and takes time, but it may even take much longer until data gets actually stored in flash if the file system becomes full and blocks must be erased first or - even worse - if garbage collection becomes necessary
  • The garbage collector thread may run at any time, consuming CPU cycles and blocking accesses to the file system.

Despite the aforementioned disadvantages, systems using a JFFS2 based root file system are easy to build, make efficient use of the available resources and can run pretty reliably.

To create a JFFS2 based root file system please proceed as follows:

 

  1. Create a directory tree with the content of the target root filesystem. We do this by unpacking our master tarball:
    $ mkdir rootfs
    $ cd rootfs
    $ tar zxf /tmp/rootfs.tar.gz
    
  2. We can now create a JFFS2 file system image using the mkfs.jffs2 tool:
    $ ROOTFS_DIR=rootfs                 # directory with root file system content
    $ ROOTFS_EBSIZE=0x20000             # erase block size of flash memory
    $ ROOTFS_ENDIAN=b                   # target system is big endian
    $ ROOTFS_DEVICES=rootfs_devices.tab # device description file
    $ ROOTFS_IMAGE=jffs2.img            # generated file system image
    
    $ mkfs.jffs2 -U \
            -d ${ROOTFS_DIR} \
            -D ${ROOTFS_DEVICES} \
            -${ROOTFS_ENDIAN} \
            -e ${ROOTFS_EBSIZE} \
            -o ${ROOTFS_IMAGE}
    mkfs.jffs2: skipping device_table entry '/dev': no parent directory!

Note: When you intend to write the JFFS2 file system image to a NAND flash device, you should also add the "-n" (or "--no-cleanmarkers") option, as cleanmarkers are not needed then.

When booting the Linux kernel prints the following messages showing the default partition map which is used for the flash memory on the TQM8xxL boards:

 TQM flash bank 0: Using static image partition definition
Creating 7 MTD partitions on "TQM8xxL0":
0x00000000-0x00040000 : "u-boot"
0x00040000-0x00100000 : "kernel"
0x00100000-0x00200000 : "user"
0x00200000-0x00400000 : "initrd"
0x00400000-0x00600000 : "cramfs"
0x00600000-0x00800000 : "jffs"
0x00400000-0x00800000 : "big_fs"

We use U-Boot to load and store the JFFS2 image into the last partition and set up the Linux boot arguments to use this as root device:

  1. Erase flash:
    => era 40400000 407FFFFF
    
    ................. done
    Erased 35 sectors
    
  2. Download JFFS2 image:
    => tftp 100000 /tftpboot/TQM860L/jffs2.img
    Using FEC ETHERNET device
    TFTP from server 192.168.3.1; our IP address is 192.168.3.80
    Filename '/tftpboot/TQM860L/jffs2.img'.
    Load address: 0x100000
    Loading: #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             ########
    done
    Bytes transferred = 2033888 (1f08e0 hex)
    
  3. Copy image to flash:
    => cp.b 100000 40400000 ${filesize}
    Copy to Flash... done
    
  4. set up boot arguments to use flash partition 6 as root device:
    => setenv mtd_args setenv bootargs root=/dev/mtdblock6 rw rootfstype=jffs2
    => printenv addip
    addip=setenv bootargs ${bootargs} ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${hostname}:${netdev}:off panic=1
    => setenv flash_mtd 'run mtd_args addip;bootm ${kernel_addr}'
    => run flash_mtd
    Using FEC ETHERNET device
    TFTP from server 192.168.3.1; our IP address is 192.168.3.80
    Filename '/tftpboot/TQM860L/uImage'.
    Load address: 0x200000
    Loading: #################################################################
             #################################################################
             ###########
    done
    Bytes transferred = 719233 (af981 hex)
    ## Booting image at 40040000 ...
       Image Name:   Linux-2.4.25
       Created:      2005-06-12  16:32:24 UTC
       Image Type:   PowerPC Linux Kernel Image (gzip compressed)
       Data Size:    782219 Bytes = 763.9 kB
       Load Address: 00000000
       Entry Point:  00000000
       Verifying Checksum ... OK
       Uncompressing Kernel Image ... OK
    Linux version 2.4.25 (wd@xpert) (gcc version 3.3.3 (DENX ELDK 3.1.1 3.3.3-9)) #1 Sun Jun 12 18:32:18 MEST 2005
    On node 0 totalpages: 4096
    zone(0): 4096 pages.
    zone(1): 0 pages.
    zone(2): 0 pages.
    Kernel command line: root=/dev/mtdblock6 rw rootfstype=jffs2 ip=192.168.3.80:192.168.3.1::255.255.255.0:tqm860l:eth1:off panic=1
    Decrementer Frequency = 187500000/60
    Calibrating delay loop... 49.86 BogoMIPS
    ...
    NET4: Unix domain sockets 1.0/SMP for Linux NET4.0.
    VFS: Mounted root (jffs2 filesystem).
    Freeing unused kernel memory: 56k init
    
    
    BusyBox v0.60.5 (2005.03.07-06:54+0000) Built-in shell (msh)
    Enter 'help' for a list of built-in commands.
    
    # ### Application running ...
    # mount
    rootfs on / type rootfs (rw)
    /dev/mtdblock6 on / type jffs2 (rw)
    /proc on /proc type proc (rw)
    # df /
    Filesystem           1k-blocks      Used Available Use% Mounted on
    rootfs                    4096      2372      1724  58% /

9.6.3. Root File System on a cramfs File System

cramfs is a compressed, read-only file system.

Advantages are:

  • file system uses compression, thus making efficient use of flash memory
  • Allows for quick boot times as only used files get loaded and uncompressed

Disadvantages are:

  • only the whole image can be replaced (not individual files)
  • additional storage needed for writable persistent data
  • mkcramfs tool does not support device table, so we need root permissions to create the required device files

To create a cramfs based root file system please proceed as follows:

  1. Create a directory tree with the content of the target root filesystem. We do this by unpacking our master tarball:
    $ mkdir rootfs
    $ cd rootfs
    $ tar -zxf /tmp/rootfs.tar.gz
    
  2. Create the required device files. We do this here by unpacking a special tarball which holds only the device file entries. ALERT! Note: this requires root permissions!
    # cd rootfs
    # tar -zxf /tmp/devices.tar.gz
    
  3. Many tools require some storage place in a filesystem, so we must provide at least one (small) writable filesystem. For all data which may be lost when the system goes down, a "tmpfs" filesystem is the optimal choice. To create such a writable tmpfs filesystem we add the following lines to the /etc/rc.sh script:
    # mount TMPFS because root-fs is readonly
    /bin/mount -t tmpfs -o size=2M tmpfs /tmpfs
    
    Some tools require write permissions on some device nodes (for example, to change ownership and permissions), or dynamically (re-) create such files (for example, /dev/log which is usually a Unix Domain socket). The files are placed in a writable filesystem; in the root filesystem symbolic links are used to point to their new locations:
    dev/ptyp0 /tmpfs/dev/ptyp0     dev/ttyp0 /tmpfs/dev/ttyp0
    dev/ptyp1 /tmpfs/dev/ptyp1     dev/ttyp1 /tmpfs/dev/ttyp1
    dev/ptyp2 /tmpfs/dev/ptyp2     dev/ttyp2 /tmpfs/dev/ttyp2
    dev/ptyp3 /tmpfs/dev/ptyp3     dev/ttyp3 /tmpfs/dev/ttyp3
    dev/ptyp4 /tmpfs/dev/ptyp4     dev/ttyp4 /tmpfs/dev/ttyp4
    dev/ptyp5 /tmpfs/dev/ptyp5     dev/ttyp5 /tmpfs/dev/ttyp5
    dev/ptyp6 /tmpfs/dev/ptyp6     dev/ttyp6 /tmpfs/dev/ttyp6
    dev/ptyp7 /tmpfs/dev/ptyp7     dev/ttyp7 /tmpfs/dev/ttyp7
    dev/ptyp8 /tmpfs/dev/ptyp8     dev/ttyp8 /tmpfs/dev/ttyp8
    dev/ptyp9 /tmpfs/dev/ptyp9     dev/ttyp9 /tmpfs/dev/ttyp9
    dev/ptypa /tmpfs/dev/ptypa     dev/ttypa /tmpfs/dev/ttypa
    dev/ptypb /tmpfs/dev/ptypb     dev/ttypb /tmpfs/dev/ttypb
    dev/ptypc /tmpfs/dev/ptypc     dev/ttypc /tmpfs/dev/ttypc
    dev/ptypd /tmpfs/dev/ptypd     dev/ttypd /tmpfs/dev/ttypd
    dev/ptype /tmpfs/dev/ptype     dev/ttype /tmpfs/dev/ttype
    dev/ptypf /tmpfs/dev/ptypf     dev/ttypf /tmpfs/dev/ttypf
    tmp /tmpfs/tmp     var /tmpfs/var
    dev/log /var/log/log        
    In case you use dhclient also:
    etc/dhclient.conf /tmpfs/var/lib/dhclient.conf     etc/resolv.conf /tmpfs/var/lib/resolv.conf

    To place the corresponding directories and device files in the tmpfs file system, the following code is added to the /etc/rc.sh script:
    mkdir -p /tmpfs/tmp /tmpfs/dev \
             /tmpfs/var/lib/dhcp /tmpfs/var/lock /tmpfs/var/run
    
    while read name minor
    do      
            mknod /tmpfs/dev/ptyp$name c 2 $minor
            mknod /tmpfs/dev/ttyp$name c 3 $minor
    done <<__EOD__
    0  0    
    1  1    
    2  2    
    3  3    
    4  4    
    5  5    
    6  6    
    7  7    
    8  8    
    9  9    
    a 10    
    b 11    
    c 12    
    d 13    
    e 14    
    f 15    
    __EOD__ 
    chmod 0666 /tmpfs/dev/*
    
  4. We can now create a cramfs file system image using the mkcramfs tool:
    $ ROOTFS_DIR=rootfs                 # directory with root file system content
    $ ROOTFS_ENDIAN="-r"                # target system has reversed (big) endianess
    $ ROOTFS_IMAGE=cramfs.img           # generated file system image
    
    PATH=/opt/eldk/usr/bin:$PATH
    mkcramfs ${ROOTFS_ENDIAN} ${DEVICES} ${ROOTFS_DIR} ${ROOTFS_IMAGE}
    Swapping filesystem endian-ness
      bin
      dev
      etc
    ...
    -48.78% (-86348 bytes)  in.ftpd
    -46.02% (-16280 bytes)  in.telnetd
    -45.31% (-74444 bytes)  xinetd
    Everything: 1864 kilobytes
    Super block: 76 bytes
    CRC: c166be6d
    warning: gids truncated to 8 bits.  (This may be a security concern.)
    
  5. We can use the same setup as before for the JFFS2 filesystem, just changing the bootargument to "rootfstype=cramfs"

9.6.4. Root File System on a Read-Only ext2 File System

When storing the root file system in on-board flash memory it seems only natural to look for special falsh filesystems like JFFS2, or for other file system types that are designed for such environments like cramfs. It seems to be a bad idea to use a standard ext2 file system because it contains neither any type of wear levelling which is needed for writable file systems in flash memory, nor is it robust against unorderly shutdowns.

The situation changes if we use an ext2 file system which we mount read-only. Such a configuration can be very useful in some situations.

Advantages:

  • very fast
  • low RAM memory footprint

Disadvantages:

  • high flash memory footprint because no compression

To create an ext2 image that can be used as a read-only root file system the following steps are necessary:

  1. Create a directory tree with the content of the target root filesystem. We do this by unpacking our master tarball:
    $ mkdir rootfs
    $ cd rootfs
    $ tar -zxf /tmp/rootfs.tar.gz
    
  2. Like with the cramfs root file system, we use "tmpfs" for cases where a writable file system is needed and add the following lines to the /etc/rc.sh script:
    # mount TMPFS because root-fs is readonly
    /bin/mount -t tmpfs -o size=2M tmpfs /tmpfs
    
    We also create the same symbolic links for device files that must be placed in a writable filesystem:
    dev/ptyp0 /tmpfs/dev/ptyp0     dev/ttyp0 /tmpfs/dev/ttyp0
    dev/ptyp1 /tmpfs/dev/ptyp1     dev/ttyp1 /tmpfs/dev/ttyp1
    dev/ptyp2 /tmpfs/dev/ptyp2     dev/ttyp2 /tmpfs/dev/ttyp2
    dev/ptyp3 /tmpfs/dev/ptyp3     dev/ttyp3 /tmpfs/dev/ttyp3
    dev/ptyp4 /tmpfs/dev/ptyp4     dev/ttyp4 /tmpfs/dev/ttyp4
    dev/ptyp5 /tmpfs/dev/ptyp5     dev/ttyp5 /tmpfs/dev/ttyp5
    dev/ptyp6 /tmpfs/dev/ptyp6     dev/ttyp6 /tmpfs/dev/ttyp6
    dev/ptyp7 /tmpfs/dev/ptyp7     dev/ttyp7 /tmpfs/dev/ttyp7
    dev/ptyp8 /tmpfs/dev/ptyp8     dev/ttyp8 /tmpfs/dev/ttyp8
    dev/ptyp9 /tmpfs/dev/ptyp9     dev/ttyp9 /tmpfs/dev/ttyp9
    dev/ptypa /tmpfs/dev/ptypa     dev/ttypa /tmpfs/dev/ttypa
    dev/ptypb /tmpfs/dev/ptypb     dev/ttypb /tmpfs/dev/ttypb
    dev/ptypc /tmpfs/dev/ptypc     dev/ttypc /tmpfs/dev/ttypc
    dev/ptypd /tmpfs/dev/ptypd     dev/ttypd /tmpfs/dev/ttypd
    dev/ptype /tmpfs/dev/ptype     dev/ttype /tmpfs/dev/ttype
    dev/ptypf /tmpfs/dev/ptypf     dev/ttypf /tmpfs/dev/ttypf
    tmp /tmpfs/tmp     var /tmpfs/var
    dev/log /var/log/log        
    In case you use dhclient also:
    etc/dhclient.conf /tmpfs/var/lib/dhclient.conf     etc/resolv.conf /tmpfs/var/lib/resolv.conf

    To place the corresponding directories and device files in the tmpfs file system, the following code is added to the /etc/rc.sh script:
    mkdir -p /tmpfs/tmp /tmpfs/dev \
             /tmpfs/var/lib/dhcp /tmpfs/var/lock /tmpfs/var/run
    
    while read name minor
    do      
            mknod /tmpfs/dev/ptyp$name c 2 $minor
            mknod /tmpfs/dev/ttyp$name c 3 $minor
    done <<__EOD__
    0  0    
    1  1    
    2  2    
    3  3    
    4  4    
    5  5    
    6  6    
    7  7    
    8  8    
    9  9    
    a 10    
    b 11    
    c 12    
    d 13    
    e 14    
    f 15    
    __EOD__ 
    chmod 0666 /tmpfs/dev/*
    
  3. Like we did for the ramdisk, we now create an ext2 file system image using the genext2fs tool:
    $ ROOTFS_DIR=rootfs                 # directory with root file system content
    $ ROOTFS_SIZE=3700                  # size of file system image
    $ ROOTFS_FREE=100                   # free space wanted
    $ ROOTFS_INODES=380                 # number of inodes
    $ ROOTFS_DEVICES=rootfs_devices.tab # device description file
    $ ROOTFS_IMAGE=ext2.img             # generated file system image
    
    $ genext2fs -U \
            -d ${ROOTFS_DIR} \
            -D ${ROOTFS_DEVICES} \
            -b ${ROOTFS_SIZE} \
            -r ${ROOTFS_FREE} \
            -i ${ROOTFS_INODES} \
            ${ROOTFS_IMAGE}
    
  4. We can again use the same setup as before for the JFFS2 filesystem, just changing the bootargument to "rootfstype=ext2" (or simply omit it completely as this is the default anyway), and we must change the "rw" argument into "ro" to mount our root file system really read-only:
    ...
    Linux version 2.4.25 (wd@xpert) (gcc version 3.3.3 (DENX ELDK 3.1.1 3.3.3-9)) #1 Sun Jun 12 18:32:18 MEST 2005
    On node 0 totalpages: 4096
    zone(0): 4096 pages.
    zone(1): 0 pages.
    zone(2): 0 pages.
    Kernel command line: root=/dev/mtdblock6 ro rootfstype=ext2 ip=192.168.3.80:192.168.3.1::255.255.255.0:tqm860l:eth1:off panic=1
    Decrementer Frequency = 187500000/60
    Calibrating delay loop... 49.86 BogoMIPS
    ...

9.6.5. Root File System on a Flash Card

Using an ext2 file system on a flash memory card (like CompactFlash, SD, MMC or a USB memory stick) is standard technology. To avoid unnecessary flash wear it is a good idea to mount the root file system read-only, or at least using the "noatime" mount option.

For our test we can use the "ext2.img" file from the previous step without changes:

  1. In this test we use a standard CompactFlash card which comes with a single partition on it. We use U-Boot to copy the ext2 file system image into this partition:
    => tftp 100000 /tftpboot/TQM860L/ext2.img
    Using FEC ETHERNET device
    TFTP from server 192.168.3.1; our IP address is 192.168.3.80
    Filename '/tftpboot/TQM860L/ext2.img'.
    Load address: 0x100000
    Loading: #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             ##########################
    done
    Bytes transferred = 3788800 (39d000 hex)
    => ide part
    
    Partition Map for IDE device 0  --   Partition Type: DOS
    
    Partition     Start Sector     Num Sectors     Type
        1                   32          500704       6
    => ide write 100000 20 1ce8
    
    IDE write: device 0 block # 32, count 7400 ... 7400 blocks written: OK
    
    Note that the "ide write" command takes parameters as hex numbers, and the write count is in terms of disk blocks of 512 bytes each. So we have to use 0x20 for the starts sector of the first partition, and 3788800 / 512 = 7400 = 0x1CE8 for the block count.
  2. We now prepare the Linux boot arguments to take this partition as read-only root device:
    => setenv cf_args setenv bootargs root=/dev/hda1 ro
    => setenv flash_cf 'run cf_args addip;bootm ${kernel_addr}'
    => setenv bootcmd run flash_cf
    
  3. ...and boot the system:
    ...
    Linux version 2.4.25 (wd@xpert) (gcc version 3.3.3 (DENX ELDK 3.1.1 3.3.3-9)) #1 Sun Jun 12 18:32:18 MEST 2005
    On node 0 totalpages: 4096
    zone(0): 4096 pages.
    zone(1): 0 pages.
    zone(2): 0 pages.
    Kernel command line: root=/dev/hda1 ro ip=192.168.3.80:192.168.3.1::255.255.255.0:tqm860l:eth1:off panic=1
    Decrementer Frequency = 187500000/60
    Calibrating delay loop... 49.86 BogoMIPS
    ...

9.6.6. Root File System in a Read-Only File in a FAT File System

This is a more complicated example that shows that - depending on project requirements - many other alternatives for chosing a root file system for your embedded system exist.

The szenario is as follows: on your embedded device you use a cheap and popular storage medium like CompactFlash, MMC or SD cards or USB memory sticks to store both the Linux kernel and your root file system. You want to distribute software updates over the internet: your customers can download the file from your web site, or you sent the images by email. Your customers may use any flash card or memory stick they happen to find, so you have no information about brand or size of the storage device.

Unfortunately most of your customers use Windows systems. And they don't want to be bothered with long instructions how to create special partitions on the storage device or how to write binary images or things like that. A simple "copy file" operation is nearly exhausting their capabilities.

What to do? Well, if copying a file is all your customers can do we should not ask for more. Storage devices like CompactFlash cards etc. typically come with a single partition on it, which holds a FAT or VFAT file system. This cannot be used as a Linux root file system directly, so we have to use some trickery.

Here is one possible solution: Your software distribution consistes of two files: The first file is the Linux kernel with a minimal ramdisk image attached (using the multi-file image format for U-Boot); U-Boot can load and boot such files from a FAT or VFAT file system. The second file is your root file system. For convenience and speed we use again an image of an ext2 file system. When Linux boots, it will initially use the attached ramdisk as root file system. The programs in this ramdisk will mount the FAT or VFAT file system - read-only. Then we can use a loop device (see losetup(8)) to associate the root file system image with a block device which can be used as a mount point. And finally we use pivot_root(8) to change the root file system to our image on the CF card.

This sounds not so complicated, and actually it is quite simple once you understand what needs to be done. Here is a more detailed description:

  1. The root file system image is easy: as mantioned before, we will use an ext2 file system image, and to avoid wearing the flash storage device we will use it in read-only mode - we did a read-only ext2 root file system image before, and here we can just re-use the existing image file.
  2. The initial ramdisk image that performs the pivot_root step must be created from scratch, but we already know how to create ramdisk images, so we just have to figure out what to put in it.

    The most important tool here is nash, a script interpreter that was specifically designed for such purposes (see nash(8)). We don't need any additional tools, and if we use static linking, that the nash binary plus a small script to control it is all we need for our initial ramdisk.

    To be precise, we need a couple of (empty) directories (bin, dev, etc, lib, loopfs, mnt, proc, and sysroot), the bin/nash binary, the linuxrc script and a symbolic link sbin pointing to bin:
    drwxr-xr-x    2 wd       users        4096 Apr 13 01:11 bin
    -rwxr-xr-x    1 wd       users      469512 Apr 11 22:47 bin/nash
    drwxr-xr-x    2 wd       users        4096 Apr 12 00:04 dev
    drwxr-xr-x    2 wd       users        4096 Apr 12 00:04 etc
    drwxr-xr-x    2 wd       users        4096 Apr 12 00:04 lib
    -rwxr-xr-x    1 wd       users         511 Apr 13 01:28 linuxrc
    drwxr-xr-x    2 wd       users        4096 Apr 12 00:04 loopfs
    drwxr-xr-x    2 wd       users        4096 Apr 12 00:09 mnt
    drwxr-xr-x    2 wd       users        4096 Apr 12 00:04 proc
    lrwxrwxrwx    1 wd       users           3 Jun 12 18:54 sbin -> bin
    drwxr-xr-x    2 wd       users        4096 Apr 12 00:04 sysroot
    
  3. We also need only a minimal device table for creating the initial ramdisk:
    #<name>    <type> <mode> <uid> <gid> <major> <minor> <start>  <inc>  <count>
    /dev            d  755  0       0       -       -       -       -       -
    /dev/console    c  640  0       0        5      1       -       -       -
    /dev/hda        b  640  0       0        3      0       -       -       -
    /dev/hda        b  640  0       0        3      1       1       1       8
    /dev/loop       b  640  0       0        7      0       0       1       4
    /dev/null       c  640  0       0        1      3       -       -       -
    /dev/ram        b  640  0       0        1      0       0       1       2
    /dev/ram        b  640  0       0        1      1       -       -       -
    /dev/tty        c  640  0       0        4      0       0       1       4
    /dev/tty        c  640  0       0        5      0       -       -       -
    /dev/ttyS       c  640  0       0        4      64      0       1       4
    /dev/zero       c  640  0       0        1      5       -       -       -
    
  4. To create the initial ramdisk we perform the usual steps:
    $ INITRD_DIR=initrd
    $ INITRD_SIZE=490
    $ INITRD_FREE=0
    $ INITRD_INODES=54
    $ INITRD_DEVICES=initrd_devices.tab
    $ INITRD_IMAGE=initrd.img
    
    $ genext2fs -U \
            -d ${INITRD_DIR} \
            -D ${INITRD_DEVICES} \
            -b ${INITRD_SIZE} \
            -r ${INITRD_FREE} \
            -i ${INITRD_INODES} \
            ${INITRD_IMAGE}
    
    $ gzip -v9 ${INITRD_IMAGE}
    
    The result is a really small (233 kB) compressed ramdisk image.
  5. Assuming you already have your Linux kernel image, you can now use mkimage to build an U-Boot multi-file image that combines the Linux kernel and the initial ramdisk:
    $ LINUX_KERNEL=linuxppc_2_4_devel/arch/ppc/boot/images/vmlinux.gz
    $ mkimage -A ppc -O Linux -T multi -C gzip \
    > -n 'Linux with Pivot Root Helper' \
    > -d ${LINUX_KERNEL}:${INITRD_IMAGE}.gz linux.img
    Image Name:   Linux with Pivot Root Helper
    Created:      Mon Jun 13 01:48:11 2005
    Image Type:   PowerPC Linux Multi-File Image (gzip compressed)
    Data Size:    1020665 Bytes = 996.74 kB = 0.97 MB
    Load Address: 0x00000000
    Entry Point:  0x00000000
    Contents:
       Image 0:   782219 Bytes =  763 kB = 0 MB
       Image 1:   238433 Bytes =  232 kB = 0 MB
    
    The newly created file linux.img is the second image we have to copy to the CF card.

    We are done.

But wait - one essential part was not mentioned yet: the linuxrc script in our initial ramdisk image which contains all the magic. This script is quite simple:

#!/bin/nash

echo Mounting /proc filesystem
mount -t proc /proc /proc

echo Creating block devices
mkdevices /dev

echo Creating root device
mkrootdev /dev/root
echo 0x0100 > /proc/sys/kernel/real-root-dev

echo Mounting flash card
mount -o noatime -t vfat /dev/hda1 /mnt

echo losetup for filesystem image
losetup /dev/loop0 /mnt/rootfs.img

echo Mounting root filesystem image
mount -o defaults --ro -t ext2 /dev/loop0 /sysroot

echo Running pivot_root
pivot_root /sysroot /sysroot/initrd
umount /initrd/proc

Let's go though it step by step:

  • The first line says that it's a script file for the /bin/nash interpreter.
    ALERT! Note: even if this file looks like a shell script it is NOT interpreted by a shell, but by the nash interpreter. For a complete list of available nash commands and their syntax please refer to the manual page, nash(8).
  • The first action is to mount the /proc pseudo file system which is needed to find out some required information.
  • Then we create block device entries for all partitions listed in /proc/partitions (mkdevices command).
  • In the next step a block device for our new root file system is created (mkrootdev command).
  • Then we mount the CF card. We assume that there is only a single partition on it (/dev/hda1) which is of type VFAT (which also will work with FAT file systems). These assumptions work fine with basicly all memory devices used under Windows.
  • We further assume that the file name of the root file system image on the CF card is "rootfs.img" - this file now gets mounted using a loop device (losetup and mount commands).
  • Our file system image, is now mounted on the /sysroot directory. In the last step we use pivot_root to make this the new root file system.
  • As a final cleanup we unmount the /proc file system which is not needed any more.

There is one tiny flaw in this method: since we mount the CF card on a directory in the ramdisk to be able to access to root file system image. This means that we cannot unmount the CF card, which in turn prevents us from freeing the space for the inital ramdisk. The consequence is that you permanently lose approx. 450 kB of RAM for the ramdisk. [We could of course re-use this ramdisk space for temporary data, but such optimization is beyond the scope of this document.]

And how does this work on our target?

  1. First we copy the two images to the CF card; we do this on the target under Linux:
    bash-2.05b# fdisk -l /dev/hda
    
    Disk /dev/hda: 256 MB, 256376832 bytes
    16 heads, 32 sectors/track, 978 cylinders
    Units = cylinders of 512 * 512 = 262144 bytes
    
       Device Boot    Start       End    Blocks   Id  System
    /dev/hda1   *         1       978    250352    6  FAT16
    bash-2.05b# mkfs.vfat /dev/hda1
    mkfs.vfat 2.8 (28 Feb 2001)
    bash-2.05b# mount -t vfat /dev/hda1 /mnt
    bash-2.05b# cp -v linux.img rootfs.img /mnt/
    `linux.img' -> `/mnt/linux.img'
    `rootfs.img' -> `/mnt/rootfs.img'
    bash-2.05b# ls -l /mnt
    total 4700
    -rwxr--r--    1 root     root      1020729 Jun 14 05:36 linux.img
    -rwxr--r--    1 root     root      3788800 Jun 14 05:36 rootfs.img
    bash-2.05b# umount /mnt
    
  2. We now prepare U-Boot to load the "uMulti" file (combined Linux kernel and initial ramdisk) from the CF card and boot it:
    => setenv fat_args setenv bootargs rw
    => setenv fat_boot 'run fat_args addip;fatload ide 0:1 200000 linux.img;bootm'
    => setenv bootcmd run fat_boot
    
  3. And finally we try it out:
    U-Boot 1.1.3 (Jun 13 2005 - 02:24:00)
    
    CPU:   XPC86xxxZPnnD4 at 50 MHz: 4 kB I-Cache 4 kB D-Cache FEC present
    Board: TQM860LDB0A3-T50.202
    DRAM:  16 MB
    FLASH:  8 MB
    In:    serial
    Out:   serial
    Err:   serial
    Net:   SCC ETHERNET, FEC ETHERNET [PRIME]
    PCMCIA: 3.3V card found: Transcend    256M
                Fixed Disk Card
                IDE interface 
                [silicon] [unique] [single] [sleep] [standby] [idle] [low power]
    Bus 0: OK 
      Device 0: Model: Transcend    256M Firm: 1.1 Ser#: SSSC256M04Z27A25906T
                Type: Removable Hard Disk
                Capacity: 244.5 MB = 0.2 GB (500736 x 512)
    
    Type "run flash_nfs" to mount root filesystem over NFS
    
    Hit any key to stop autoboot:  0 
    reading linux.img
    
    1025657 bytes read
    ## Booting image at 00200000 ...
       Image Name:   Linux with Pivot Root Helper
       Created:      2005-06-13   0:32:41 UTC
       Image Type:   PowerPC Linux Multi-File Image (gzip compressed)
       Data Size:    1025593 Bytes = 1001.6 kB
       Load Address: 00000000
       Entry Point:  00000000
       Contents:
       Image 0:   787146 Bytes = 768.7 kB
       Image 1:   238433 Bytes = 232.8 kB
       Verifying Checksum ... OK
       Uncompressing Multi-File Image ... OK
       Loading Ramdisk to 00f3d000, end 00f77361 ... OK
    Linux version 2.4.25 (wd@xpert) (gcc version 3.3.3 (DENX ELDK 3.1.1 3.3.3-9)) #1 Mon Jun 13 02:32:10 MEST 2005
    On node 0 totalpages: 4096
    zone(0): 4096 pages.
    zone(1): 0 pages.
    zone(2): 0 pages.
    Kernel command line: rw ip=192.168.3.80:192.168.3.1::255.255.255.0:tqm860l:eth1:off panic=1
    Decrementer Frequency = 187500000/60
    Calibrating delay loop... 49.86 BogoMIPS
    ...
    NET4: Unix domain sockets 1.0/SMP for Linux NET4.0.
    RAMDISK: Compressed image found at block 0
    Freeing initrd memory: 232k freed
    VFS: Mounted root (ext2 filesystem).
    Red Hat nash version 4.1.18 starting
    Mounting /proc filesystem
    Creating block devices
    Creating root device
    Mounting flash card
     hda: hda1
     hda: hda1
    losetup for filesystem image
    Mounting root filesystem image
    Running pivot_root
    Freeing unused kernel memory: 60k init
    
    BusyBox v0.60.5 (2005.03.07-06:54+0000) Built-in shell (msh)
    Enter 'help' for a list of built-in commands.
    
    # ### Application running ...

9.7. Root File System Selection

Now we know several options for file systems we can use, and know how to create the corresponding images. But how can we decide which one to chose?

For practical purposes in embedded systems the following criteria are often essential:

  • boot time (i. e. time needed from power on until application code is running)
  • flash memory footprint
  • RAM memory footprint
  • effects on software updates

The following data was measured for the different configurations. All measurements were performed on the same TQM860L board (MPC860 CPU at 50 MHz, 16 MB RAM, 8 MB flash, 256 MB CompactFlash card):

File System Type Boot Time Free Mem Updates while running
ramdisk 16.3 sec 6.58 MB whole image yes
JFFS2 21.4 sec 10.3 MB per file only non-active files
cramfs 10.8 sec 10.3 MB whole image no
ext2 (ro) 9.1 sec 10.8 MB whole image no
ext2 on CF (ro) 9.3 sec 10.9 MB whole image no
File on FAT fs 11.4 sec 7.8 MB whole image yes

As you can see, the ramdisk solution is the worst of all in terms of RAM memory footprint; also it takes a pretty long time to boot. However, it is one of the few solutions that allow an in-situ update while the system is running.

JFFS2 is easy to use as it's a writable file system but it takes a long time to boot.

A read-only ext2 file system shines when boot time and RAM memory footprint are important; you pay for this with an increased flash memory footprint.

External flash memory devices like CompactFlash cards or USB memory sticks can be cheap and efficient solutions especially when lots of data need to be stored or when easy update procedures are required. -

9.8. Overlay File Systems

Introduction

Overlay File Systems provide an interesting approach to several frequent problems in Embedded Systems. For example, mini_fo is a virtual kernel file system that can make read-only file systems writable. This is done by redirecting modifying operations to a writeable location called "storage directory", and leaving the original data in the "base directory" untouched. When reading, the file system merges the modifed and original data so that only the newest versions will appear. This occurs transparently to the user, who can access the data like on any other read-write file system.

What it is good for?

In embedded systems the main use of mini_fo is to overlay the root file system. This means it is mounted on top of the regular root file system, thereby allowing applications or users to transparently make modifications to it but redirecting these to a different location.

Some examples of why this is usefull are explained in the following sections.

Making a read-only root filesystem writeable

Root file systems stored in flash are often read only, such as cramfs or read only ext2. While this offers major advantages in terms of speed and flash memory footprint, it nevertheless is often desireable to be able to modify the root file system, for example to

  • apply (small) software updates without having to burn a whole new root file system image to flash
  • make modifications during developement when frequent changes to the root file system occur.

This can be achieved by mounting mini_fo on top of the root file system and using a (probably small) writeable partition as the storage file system. This could be either a JFFS2 flash file system, or during development even an external hard disk. This has the following advantages:

  • read-only file systems (fast, small memory footprint) can be used like persistent writable file systems (in contrast to a ramdisk)
  • slow flash journalling file systems with large flash memory footprint can be avoided.

Non persistant changes

Ramdisks are often used when the root file system needs to be modified non-persistantly. This works well, but downsides are the large RAM memory footprint and the time costly operation of copying the ramdisk into RAM during startup. These can be avoided by overlaying the root file system as in the previous example but with the difference that the tmpfs file system is used as storage. Thus only modified files are stored in RAM, and can even be swapped out if neccessary. This saves boot time and RAM!

Resetable changes

Mini_fo can be easily used to implement a "reset to factory defaults" function by overlaying the default root file system. When configuration changes are made, these are automatically directed to the storage file system and take precedence over the original files. Now, to restore the system to factory defaults, all that needs to be done is delete the contents of the storage directory. This will remove all changes made to the root file system and return it to the original state.

Note: Deleting the contents of the storage directory should only be done when the overlay file system is unmounted.

Examples

Generally, there are two different ways of overlaying the root file system, which both make sense in different scenarios.

Starting a single application in a chrooted overlayed environment

This is easy. Let's assume "/" is the read-only root file system and /dev/mtdblock5 contains a small JFFS2 flash partition that shall be used to store modifications made by application "/usr/bin/autoPilot":

# mount -t jffs2 /dev/mtdblock5 /tmp/sto
# insmod mini_fo.o
# mount -t mini_fo -o base=/,sto=/tmp/sto/ / /mnt/mini_fo/
# cd /mnt/mini_fo/
# chroot . /usr/bin/autoPilot

The mini_fo file system is mounted with "/" as base directory, "/tmp/sto/" as storage directory to the mount point "/mnt/mini_fo". After that, chroot(1) is used to start the application with the new file system root "/mnt/mini_fo". All modifications made by the application will be stored to the JFFS2 file system in /tmp/sto.

Starting the whole system system in chrooted overlayed environment

This is more interesting, and a bit trickier, as mounting needs to be done during system startup after the root file system has been mounted, but before init is started. The best way to do this is to have a script that mounts the mini_fo file system on top of root and then starts init in the chrooted overlayed environment. For example assume the following script "overlay_init", stored in /sbin/:

#!/bin/bash
#
# mount mini_fo overlay file system and execute init
#

# make sure these exist in the read-only file system
STORAGE=/tmp/sto
MOUNT_POINT=/mnt/mini_fo/

# mount tmpfs as storage file system with a maximum size of 32MB
mount -t tmpfs -o rw,size=32M none $STORAGE

/sbin/modprobe mini_fo
mount -t mini_fo -o base=/,sto=$STORAGE / $MOUNT_POINT

exec /usr/sbin/chroot $MOUNT_POINT /sbin/init

echo "exec chroot failed, bad!"
exec /bin/sh

exit 1

Now its easy to choose between a mini_fo overlayed and the regular non overlayed system just by setting the "init" kernel parameter in the boot loader to "init=/sbin/overlay_init".

 

Tips

  • pivot_root(1) can be used with chroot if there is need to access the original non overlayed root file system from the chrooted overlayed environment.

Performance overhead

The mini_fo file system is inserted as an additional layer between the VFS and the native file system, and thus creates some overhead that varies strongly depending of the operation performed.

  1. modifying a regular file for the first time
    This results in a copy of the original file beeing created in the storage directory, that is then modified. Overhead depends on the size of the modified file.
  2. Reading from files, creating new files, modifying already modified files
    These operations are passed directly through to the lower native layer, and only impose an overhead of 1-2%.

Further information

This section discusses how the mini_fo overlay file system can be used in embedded systems. More general information is available at the mini_fo project page: http://www.denx.de/wiki/Know/MiniFOHome.

9.9. The Persistent RAM File system (PRAMFS)

The pramfs file system supports persistent memory devices such as SRAM. Instead of having a block emulation layer over such a memory area and using a normal file system on top of that, pramfs seeks to induce minimal overhead in this situation. Most important in this respect is that the normal block layer caching of the Linux kernel is circumvented in pramfs.

9.9.1. Mount Parameters

The most important parameters for normal usage are

 

  • physaddr: The physical address of the static memory.
  • init: When given, it will initialize the file system to that size.

9.9.2. Example

We will show a sample usage of pramfs in this section using normal DRAM on a board with at least 256MB of memory. For pramfs we reserve the upper 32MB by appending mem=224M to the kernel command line.

First off we generate some testdata on a persistent file system (/tmp) to demonstrate that pramfs survives a reboot (of course with power always applied to keep the DRAM refreshed):

bash-3.00# dd if=/dev/urandom bs=1M count=8 of=/tmp/testdata
8+0 records in
8+0 records out
bash-3.00# 

Next we mount the 32MB that we reserved and initialize it to be 32MB in size and copy the testfile. A final compare shows that the copy was indeed successful so we can reboot:

bash-3.00# mount -t pramfs -o physaddr=0xe000000,init=0x2000000 none /mnt
bash-3.00# cp /tmp/testdata /mnt
bash-3.00# cmp /tmp/testdata /mnt/testdata
bash-3.00# reboot

Having rebooted (using mem=224M on the kernel command line again of course) we mount the file system but this time without the init parameter because it is preinitialized. We then check the contents again:

bash-3.00# mount -t pramfs -o physaddr=0xe000000 none /mnt
bash-3.00# ls /mnt
testdata
bash-3.00# cmp /tmp/testdata /mnt/testdata
bash-3.00#
arrow
arrow
    全站熱搜

    BB 發表在 痞客邦 留言(0) 人氣()