Custom bootloader

From OLPC
Revision as of 22:21, 16 January 2012 by Quozl (talk | contribs) (Reverted edits by 87.161.47.38 (Talk) to last revision by Wmb@firmworks.com)
Jump to navigation Jump to search

olpc.fth during boot

On an unsecured XO, Open Firmware auto-boots by executing a Forth script named /boot/olpc.fth. The search order is USB, then SD, then NAND. If OFW doesn't find /boot/olpc.fth on any on those devices, it will try to boot from a wired USB network interface or wireless LAN using DHCP and TFTP. (Firmware security tells how OFW boots a secured XO.)

The olpc.fth script that is present on OLPC system installations tries to boot Linux from the same device where olpc.fth was found, using command line arguments appropriate for that device. (Some versions of it do additional things, like automatically updating the firmware if a newer firmware image file is present.)

If you want to try a new operating system build, you can put it and a suitable olpc.fth on a removable device like a USB flash drive or SD card. If the device is inserted, the XO will try to boot from them; you can revert to the "normal" XO operating system on NAND by removing the device and rebooting.

You can make custom olpc.fth scripts to boot any way you want. For example, you could have an olpc.fth on a USB flash drive that boots Linux from an SD card. You could make an olpc.fth that boots a different operating system with arbitrary command line arguments. Or you could make an olpc.fth that does pretty much anything without needing an operating system, because olpc.fth can contain an arbitrary Forth language program that can call any of the thousands of functions that Open Firmware provides.

Some example olpc.fth scripts are shown below.

Boot Parameters

To boot Linux, you need to specify

  • The device and file that contains the OS kernel
  • The Linux command line arguments
  • Optionally, a ramdisk image to use as an initial root filesystem. (Linux can boot without a ramdisk, but it's tricky to set that up, especially on a USB flash drive.)

There are two different ways to specify the OS image file and the command line - either on the same line with the OFW "boot" command or in configuration variables. These two command sequences do the same thing:

 boot u:\boot\vmlinuz ro root=sda1

or

 " u:\boot\vmlinuz" to boot-device
 " ro root=sda1" to boot-file
 boot

OS Image names ("boot-device" configuration variable)

The "boot-device" configuration variable contains the name of the device and file from which to load the OS kernel image (unless overridden on the "boot" line). The form is "device-specifier:pathname". The most common "device-specifier" values for the XO are:

 u     - USB 2.0 mass storage device (e.g. USB flash drive)
 disk  - USB 1.1 or 2.0 mass storage device (USB 1.1 storage devices are supported, but not recommended)
 sd    - SD card
 nand  - NAND FLASH with JFFS2 filesystem
 n     - an even shorter alias for "nand"

On USB and SD devices, the supported filesystems are FAT (any variant) and ext2. ext3 also works if the journal is empty (ext3 is not a great choice for a removable device). When specifying the pathname to Open Firmware, you must use "\" instead of "/". (OFW uses "/" to separate device tree name components. Using "/" in the pathname would confuse OFW's device-specifier parser.) For FAT filesystems, the pathname is case-insensitive and 8.3 names must be used. (OFW doesn't support long FAT filenames because of patent issues.) For ext2/3 filesystems, the pathname is case-sensitive and names can be long.

The OS image file (e.g. u:\boot\vmlinuz) can be in either Linux "bzImage" format or ELF format. For bzImage format, OFW places the data at memory address 0x100000 (1 MiB). For ELF format, OFW places the various ELF program sections at the addresses specified in the ELF header. (The image file can also be in Forth source code format, signified by the first line starting with a " \ " comment. That is how OFW initially boots olpc.fth .)

If the OS image file is zlib-compressed, OFW will automatically uncompress it. OFW recognizes the compressed format by the image contents, not by the pathname.

Example values for the OS image name:

sd:\boot\vmlinuz
disk:\boot\vmlinuz
nand:\boot\vmlinuz
disk:\bzImage

OLPC system installations put OS images in the /boot directory (\boot in the OFW pathname representation), but that is just a convention, not a requirement. The olpc.fth file, however, must be in /boot if you wish to auto-boot, because the pathname "\boot\olpc.fth" is in OFW's default value for boot-device . olpc.fth typically then either replaces the value of boot-device with the name of an OS image file, or else supplies the image name on the "boot ..." line, thus "chaining" from olpc.fth to an OS.

Linux command line arguments (boot-file value)

The "boot-file" configuration variable contains the command line arguments to pass to the OS (unless overridden on the "boot" line). (The name "boot-file" is historical.) For booting Linux, the most important command line argument is "root=", which specifies the root filesystem device. When booting from NAND, you also have to specify "rootfstype=jffs2", because Linux can't auto-detect the JFFS2 filesystem type. The other "standard" OLPC command line arguments are not strictly necessary.

Some common Linux command line arguments for XO are:

  • ro - mount read only (it is remounted read-write during the boot process)
  • root=<device> - location of the root filesystem
  • rootfstype=jffs2 - needed only when the root filesystem is on the NAND FLASH
  • rootfstype=ext3 - conventionally supplied for EXT3 filesystems, but not strictly necessary, as EXT3 can be auto-detected
  • rootdelay=<seconds> - optional adds in a delay before booting (useful for slow-starting devices)
  • console=<device>[,arguments] - sets up the console display. You can have multiple console= arguments.
  • fbcon=font:SUN12x22 - chooses a font that's a good size for the OLPC display resolution

If you just want the normal Linux text console on the OLPC screen, you don't really need and "console=<device>" arguments, because screen console is the default. A lot of olpc.fth scripts say "console=ttyS0,115200 console=tty0" to get console messages on both the screen and on the serial debug port (which you can only get to by opening the machine and connecting a special adapter).

Some useful values for the "root=" device are:

  • mmcblk0p1 - SD card
  • sda1 - USB flash drive
  • mtd0 - internal NAND (also specify rootfstype=jffs2)
  • LABEL=OLPCRoot - instead of specifying a specific device, you can specify a label value and Linux will search for a disk that has that label on it

A complete list of linux kernel options may be found at Linux Kernel in a Nutshell (in PDF format).

Ramdisk

Linux can boot without a ramdisk, locating the initial root filesystem on a disk device, but it is now common practice to use a ramdisk. The ramdisk image contains a minimal root filesystem that Linux uses to get started, doing things like probing for USB devices with udev, before switching over to the real root filesystem on disk.

OFW doesn't get the ramdisk image name from the "boot ..." line. To boot with a ramdisk, you must specify the device and pathname of the ramdisk image file via the "ramdisk" configuration variable, as in:

 " u:\boot\initrd" to ramdisk

or

 " sd:\boot\initrd" to ramdisk

If the value of "ramdisk" is not empty, OFW will load the specified file into memory after it loads the Linux OS image, telling Linux the memory location of the ramdisk via Linux "zero page" startup-info data structure. The ramdisk loading mechanism only applies to Linux. If OFW loads an operating system image that doesn't look like Linux, OFW won't try to load a ramdisk. Only Linux has a defined mechanism for passing the ramdisk location to the OS, so OFW would have no way to tell another OS about the ramdisk.

The device:pathname format is the same as for the OS kernel image file. As with the kernel image, if the ramdisk image is zlib-compressed, OFW will automatically decompress it.

Simple /boot/olpc.fth script

Here is a simple olpc.fth script that doesn't do anything fancy; it just boots Linux from a USB disk with a ramdisk.

\ OLPC boot script
unfreeze
" u:\boot\initrd" to ramdisk
boot u:\boot\vmlinuz root=sda1

In many cases, that simple script is all you really need. See the next section for an explanation of "unfreeze".

In fact, if you just want to try something, and you don't want to auto-boot, you don't have to make the script at all. You can just type the commands at the "ok" prompt. At the ok prompt, you don't have to type the comment line ("\ OLPC boot script"), but olpc.fth must be begin with a comment so OFW will recognize that it contains Forth source code instead of a binary image format. Only the first two characters "\ " (backslash space) are required for recognition, so if you're really lazy, you don't need much of a comment line.

Sample menued bootloader to allow for selection of boot location

\ Menu bootscript for OLPC  Place in /boot as olpc.fth

cr
." 1 to boot from SD" cr
." 2 to boot from USB" cr
." 3 to boot from internal NAND" cr
." 4 to boot alternate image from NAND" cr
cr
key case
  [char] 1 of    \ SD boot info
    " ro root=mmcblk0p1 rootdelay=1 console=ttyS0,115200 console=tty0 fbcon=font:SUN12x22" to boot-file
    " sd:\boot\vmlinuz" to boot-device
    " sd:\boot\olpcrd.img" to ramdisk
  endof
  [char] 2 of    \ USB boot info
    " ro root=sda1 rootdelay=1 console=ttyS0,115200 console=tty0 fbcon=font:SUN12x22" to boot-file
    " disk:\boot\vmlinuz" to boot-device
    " disk:\boot\olpcrd.img" to ramdisk
  endof
  [char] 4 of    \ Alternate boot image info
     " ro root=mtd0 rootfstype=jffs2 console=ttyS0,115200 console=tty0 fbcon=font:SUN12x22" to boot-file
     " nand:\boot-alt\vmlinuz" to boot-device
     " nand:\boot-alt\olpcrd.img" to ramdisk
  endof
  ( default )    \ Default sugar boot image info
     " ro root=mtd0 rootfstype=jffs2 console=ttyS0,115200 console=tty0 fbcon=font:SUN12x22" to boot-file
     " nand:\boot\vmlinuz" to boot-device
     " nand:\boot\olpcrd.img" to ramdisk
endcase
unfreeze
boot

The "unfreeze" line near the end releases the display so that OS output can be seen. Open Firmware "freezes" the screen (by using the DCON chip to hold the last graphical image) just before starting the OS. This hides Linux's scrolling text messages to make the boot sequence prettier. The OLPC Linux/Sugar startup code "unfreezes" the screen when it is ready to display its own startup animation. If your OS doesn't know how to tell the DCON to unfreeze the screen, you need to say "unfreeze" in olpc.fth, otherwise your OS's output won't be seen. You can also use "unfreeze" if you want to see the Linux startup text with the normal OLPC OS.

overclock

You can also overclock your OLPC XO-1 with the olpc.fth file. In order to accomplish this you need to set your timings before it loads the operating system. You must add some numbers to the forth stack and when write these registers using wmrsr. Shown below is a specific overclock frequency and that can be changed by altering the middle number to what you find below in the overclock table.

7de009e
5dd
4c000014
wrmsr

You then add in any specific booting information you want (see above).

XO-1 Overclock Settings
in MHz; default is 433/166 (4d9)
CPU Bus Speed
133 166 200 216 233
333 3d3 4d3 5d3 653 6d3
366 3d5 4d5 5d5 655 6d5
400 3d7 4d7 5d7 657 6d7
433 3d9 4d9 5d9 659 6d9
466 3db 4db 5db 65b 6db
500 3dd 4dd 5dd 65d 6dd
533 3df 4df 5df 65f 6df
566 3e1 4e1 5e1 661 6e1

Sample Uber Bootscript

\ Menu bootscript for OLPC with overclocking.  Place in /boot as olpc.fth

\ select overclock level
cr
." 1 to set the clock speed as normal" cr
." 2 to under clock the machine to 333 MHZ memory 133" cr
." 3 to overclock it to 500 memory 200" cr
." 4 to overclock extreme to 533 233 WARNING MIGHT BE UNSTABLE" cr cr
key case
  [char] 2 of    \ underclock
    7de009e 3d3 4c000014 wrmsr ." underclock"
  endof
  [char] 3 of    \ over clock normal
    7de009e 5dd 4c000014 wrmsr ." overclock normal"
  endof
  [char] 4 of    \ overclock EXTREME
    7de009e 6df 4c000014 wrmsr ." overclock EXTREME"
  endof
  \ Default - keep the normal setting
endcase

\ select boot location
cr
." 1 to boot from SD" cr
." 2 to boot from USB" cr
." 3 to boot from internal nand" cr
." 4 to boot alternate image from Nand" cr
cr
key case
  [char] 1 of      \ SD boot info
     " ro root=mmcblk0p1 rootdelay=1 console=ttyS0,115200 console=tty0 fbcon=font:SUN12x22" to boot-file
     " sd:\boot\vmlinuz" to boot-device
     " sd:\boot\olpcrd.img" to ramdisk
  endof
  [char] 2 of      \ USB boot info
     " ro root=sda1 rootdelay=1 console=ttyS0,115200 console=tty0 fbcon=font:SUN12x22" to boot-file
     " disk:\boot\vmlinuz" to boot-device
     " disk:\boot\olpcrd.img" to ramdisk
  endof
  [char] 4 of      \ Alternate boot image info
      " ro root=mtd0 rootfstype=jffs2 console=ttyS0,115200 console=tty0 fbcon=font:SUN12x22" to boot-file
      " nand:\boot-alt\vmlinuz" to boot-device
      " nand:\boot-alt\olpcrd.img" to ramdisk
  endof
  ( default )      \ Default sugar boot image info
      " ro root=mtd0 rootfstype=jffs2 console=ttyS0,115200 console=tty0 fbcon=font:SUN12x22" to boot-file
      " nand:\boot\vmlinuz" to boot-device
      " nand:\boot\olpcrd.img" to ramdisk
endcase
unfreeze
boot

A single bootloader for both XO-1 and XO-1.5

With the availability of XO-1.5 and the hardware similarities between XO-1 and XO-1.5, yet with different kernels required, we need a bootloader that can identify the hardware and boot the appropriate version.

Other than the kernel and its associated initrd.img, the XO-1 and XO-1.5 software is similar enough to allow a common build for both machines. This is primarily useful in the case where the system is booted from an external device like a plug-in SD card or a USB FLASH drive. The internal storage (raw NAND FLASH on XO-1, micro-SD on XO-1.5) requires different filesystems (JFFS or UBIFS on XO-1, ext2/3/4 on XO-1.5), so a combined image is not worthwhile.

Mitch Bradley, the Open Firmware developer, made the following script that detects the XO-version and boots the appropriate initrd/vmlinuz.

In this example the olpc.fth script goes into the "/boot" folder while the XO-1 initrd.img and vmlinuz into the "/boot10" folder. Respectively, the XO-1.5 initrd.img and vmlinuz go into the "/boot15" folder.

The script also checks the firmware version and compares it with a preset minimum (in this case q2e45 and q3a54), displaying a message if the firmware is out of date. The olpc.fth in official OLPC builds automatically updates the firmware if a newer firmware version is present in the filesystem; this script only displays a message based on a value in the script.

The script lets you boot from the internal NAND/SDcard by pressing the "O" game key during power up. If an SD card or USB FLASH drive containing this script is inserted, you can still boot the internal OS without having to remove the external media.

Finally, the script defines the PD macro to pass specific kernel arguments depending on the boot media; different arguments can be passed for USB, SD card or internal NAND.

olpc.fth

\ OLPC boot script

visible

\ Returns a number identifying the XO version - 2 for XO-1, 3 for XO-1.5
: xo-version  ( -- n )  fw-version$ drop 1+ c@ [char] 0 -  ;

\ To pass hardware specific arguments we define the xo-1? and xo-1.5? flags   
\ Returns true if the machine is XO-1
: xo-1?  ( -- flag )  xo-version 2 =  ;

\ Returns true if the machine is XO-1.5
: xo-1.5?  ( -- flag )  xo-version 3 =  ;

\ Overclocking the  XO-1 is an example of the above.
\ Overclocking may affect reliability and longevity of the XO-1
\ Increases power consumption and decreases battery life per charge.
\ If you still want overclock your XO-1 to 500Mhz CPU, 200MHz bus speed
\ remove the back-slash ( "\" ) from the beginning of the next 3 lines only

\ xo-1? if
\	7de009e 5dd 4c000014 wrmsr
\ then

\ End overclock section 

\ We set hardware-specific  requirements based on the xo-version parameter.
\ in this case the desired OFW version
: desired-ofw-version$  ( -- adr len )
   xo-version case
      2 of  " q2e45 "  endof   \ XO-1
      3 of  " q3a56 "  endof   \ XO-1.5
      ( default )
         ." UNKNOWN XO VERSION" cr
      " q0000 "  rot
   endcase
;

\ Given a string like " q3a54 "  (note the trailing space)
\ or " q3a54b", return a numerical value that can be used
\ to compare OFW versions.
: $ofw-version-num  ( adr len -- n )  drop h# fffc6 -  (fw-version)  ;

\ Return the numerical value representing the currently-installed OFW version.
: this-ofw-version-num  ( -- n )  rom-pa (fw-version)  ;

\ Complain if the firmware is out of date
: check-ofw  ( -- )
   desired-ofw-version$ $ofw-version-num  this-ofw-version-num u>  if
      ." You need to update the firmware to "  desired-ofw-version$ type  cr
   then
;

\ Sets the DN macro to expand to the device name from which this script
\ was booted.  That's useful for subsequently booting the kernel from
\ the same device.
\ Also sets the PN macro depending on the XO version
: set-path-macros  ( -- )
   \ "O" game button forces boot from internal FLASH
   button-o game-key?  if
      " \boot"  pn-buf place
      " int:"   dn-buf place
      exit
   then

   \ Set DN to the device that was used to boot this script
   " /chosen" find-package  if                       ( phandle )
      " bootpath" rot  get-package-property  0=  if  ( propval$ )
         get-encoded-string                          ( bootpath$ )
         [char] \ left-parse-string  2nip            ( dn$ )
         dn-buf place                                ( )
      then
   then

   \ Set PN according to the XO version
   xo-version  case
      2 of  " \boot10"  endof
      3 of  " \boot15"  endof
      ( default )  " \" rot
   endcase  ( adr len )
   pn-buf place
;

\ We check if we are booting from USB or SDcard to specify device-specific parameters

: dn-contains?  ( $ -- flag )  " ${DN}" expand$  sindex 0>=  ;
: usb?    ( -- flag )  " /usb"     dn-contains?  ;
: sd?     ( -- flag )  " /sd"      dn-contains?  ;
: slot1?  ( -- flag )  " /disk@1"  dn-contains?  ;

: olpc-fth-boot-me  ( -- )
   set-path-macros

   \ We can pass boot-device-specific kernel arguments here with the PD macro
   \ PDEV1 tells Puppy Linux's init script where to find Puppy-specific files.
   \ If PDEV1 is omitted, Puppy searches for the files.  If a USB FLASH drive
   \ takes a long time to wake up after a USB reset, Puppy sometimes misses it,
   \ so it's better to avoid the search by telling Puppy where to find the files.  
   usb?  if
      " PDEV1=sda1"
   else
      sd?  if
         slot1?  if
            " PDEV1=mmcblk0p1"  \ External SD card
         else
            " PDEV1=mmcblk1p1"  \ Internal SD card
         then
      else
         " "   \ Internal raw NAND
      then
   then
   " PD" $set-macro

   check-ofw

   " console=ttyS0,115200 console=tty0 fbcon=font:SUN12x22 ${PD}" expand$ to boot-file

\ Uncomment the next 2 lines to see the command line
\   ." cmdline is " boot-file type cr
\   d# 4000 ms

   " ${DN}${PN}\vmlinuz"    expand$ to boot-device
   " ${DN}${PN}\initrd.img" expand$ to ramdisk
   boot
;
olpc-fth-boot-me

An alternate version supplied by Sascha Silbe to James Cameron uses the model description rather than relying on the firmware version string. This allows custom firmware to be used with version strings that are uncommon. One /boot/ directory is used, with different file names for the kernel and initrd.

Model detection:

: is-xo1?
   " model" " /" find-package
   drop get-package-property
   2drop c@ [char] D 
   <
;

: is-xo1.5?
   " model" " /" find-package
   drop get-package-property
   2drop c@ [char] D 
   =
;

Decision making:

: olpc-fth-boot-me
   set-path-macros
   ?ofw-reflash
   " ${ROOTDEV}" expand$ to boot-file
   is-xo1? if
      button-o game-key? if
         " ${DN}\vmlinuz-xo1.old"         expand$ to boot-device
         " ${DN}\initrd-xo1.img.old"      expand$ to ramdisk
      else
         " ${DN}\vmlinuz-xo1"             expand$ to boot-device
         " ${DN}\initrd-xo1.img"          expand$ to ramdisk
      then
   else
      is-xo1.5? if
         button-o game-key? if
            " ${DN}\vmlinuz-xo1.5.old"    expand$ to boot-device
            " ${DN}\initrd-xo1.5.img.old" expand$ to ramdisk
         else
            " ${DN}\vmlinuz-xo1.5"        expand$ to boot-device
            " ${DN}\initrd-xo1.5.img"     expand$ to ramdisk
         then
      else
         ." Unrecognized / unsupport machine"
         halt
      then
   then
   \ disable pretty boot
   dcon-unfreeze
   boot
;