Forth Lesson 12
In the previous lesson, we learned:
- That a device drivers execute within an "instance context"
- That device drivers can call their parents
- That opening a device creates a chain of parent instances
- That device specifies can include arguments to the device nodes
We have learned a lot of grotty details about OFW device nodes and drivers, but what can you do with them?
In this lesson, we will look at some OFW capabilities that use those drivers.
When OFW comes up, it displays some information - text or graphics or both - on some display device. That depends on the "console out" function, which requires a device driver that can send text or graphics to the user. Usually that is either a graphics display or a serial port. For text output, the only necessary device methods are "open" and "write".
Graphics requires some additional methods like "fill-rectangle" and various others. For text display on frame-buffer devices, OFW provides standard support packages for text rendering and ANSI terminal emulation. A device driver for a frame buffer can concentrate on device-specific tasks like setting up the clocks and the basic geometry; the support packages take care of the rest.
Similarly, user input requires a "console input" function, which is typically something like a PS/2 or USB keyboard, or a serial port. The relevant device methods for that are "open" and "read".
OFW automatically selects and opens the console input and output devices at startup, requiring no explicit user action.
"Configuration variables" control various aspects of OFW, such things as the selection of console I/O devices and boot devices.
In most OFW systems, configuration variables are stored in non-volatile RAM, so their changes to their values persist across reboots. However, on OLPC, I have chosen to make configuration variables volatile, so they only last for one session. There are two reasons for this. The first reason is that the amount of CMOS RAM is rather limited (variables could also be stored in a reserved area of the SPI FLASH device, but writing to that device is somewhat risky due to the way the hardware works). The second reason is that OLPC is supposed to be very easy to use, robust, and predictable; options are contrary to those goals.
To display the list of configuration variables with their current and default values, type:
To show just one configuration value, type, for instance:
ok printenv boot-device
To set a configuration variable, type, for example:
ok setenv boot-device sd:\boot\vmlinuz
There is another way to set a configuration variable:
ok " sd:\boot\vmlinuz" to boot-device
This alternative way does not display any feedback confirming that the value has been set, which can be an advantage in scripts where you don't want unintended output.
The only configuration variables that are particularly useful for OLPC are the ones that control booting, as described below.
OFW's main purpose is, of course, to boot the main operating system.
Normally OFW auto-boots without user intervention, but you can control the process if you need to.
Auto-boot happens under the control of several configuration variables.
"auto-boot?" is either "true" or "false". If true, the OFW auto-boots on startup, otherwise OFW goes directly to the "ok" prompt. On OLPC, where configuration variables are volatile, the default value of "auto-boot?" is "true", so OLPC will always try to auto-boot on startup unless you type a key to interrupt the startup countdown. It is sometimes useful to set "auto-boot?" to "false" in auto-booted Forth scripts, to suppress further attempts to boot after the script exits.
"boot-device" is the list of devices/filenames to try and boot from. The default value on OLPC is "sd:\boot\olpc.fth disk:\boot\olpc.fth net nand:\boot\olpc.fth" (subject to change). OFW tries to load a boot image from each of those paths in turn. If it succeeds in loading an image, it executes it, otherwise it proceed to the next path in the list.
OFW can recognize and execute several different image formats, including Forth source code, ELF, and Linux bzImage format (other formats can be added easily).
"boot-file" is the "command line argument" that is passed to the loaded program. The name "boot-file" is an unfortunate historical artifact of the first OFW systems, which did not directly support disk file systems. Those OFW systems loaded an intermediate file system reader/booter, passing it the name of the file on the command line. Modern OFW systems can read file systems and load the OS image directly, passing it the final "cmdline".
OLPC's default value for "boot-file" is the empty string. OLPC Linux kernels currently require a non-empty command line, but it seems unwise to "hardwire" a specific value for that into OFW. We first boot an intermediate Forth script (/boot/olpc.fth). That script sets "boot-file" to an appropriate Linux cmdline value, then boots the kernel from the same device that the script was loaded from.
From the ok prompt, you can manually invoke the booting process with
Without arguments, "boot" uses "boot-device" and "boot-file" the same as with auto-boot.
ok boot disk:\boot\olpc.fth
With one argument, "boot" uses the argument instead of "boot-device", supplying the cmdline from "boot-file". (If you boot a Forth script, it might change "boot-file" before booting the next image, in which case it probably doesn't matter what cmdline is passed to the script.)
ok boot nand:\boot\vmlinuz ro root=mtd0 rootfstype=jffs2 ....
With more than one argument, "boot" gets both the boot device and the OS cmdline from the boot command line. The rest of the boot command line (after the boot device argument) is the OS cmdline.
The booting process consists of three steps:
- Loading the image into memory
- If the image is compressed (with gzip deflate), inflating it
- Recognizing the image format and preparing for execution
- Executing the image
For debugging purposes, it's often useful to execute those steps separately, especially if you suspect that the image is damaged. If you just do the load step, you can inspect the image data.
ok load nand:\boot\vmlinuz
The "load" command is steps 1, 2, and 3 of the booting process. It behaves like "boot" with respect to its argument handling - it can be called with 0, 1, or more arguments, using "boot-device" and "boot-file" accordingly.
"load" also performs the image recognition step, so the net result of "load" is an image in memory, already prepared for execution.
The image recognition step looks at the image and, if it is recognized, performs additional format-specific steps to make it ready. The image recognizer consists of the Forth word "init-program". It is a "chained word", a succession of redefinitions of the same word name. Each redefinition attempts to recognize a particular format, and if successful, prepares the image for execution and exits. Otherwise it calls the previous definition of "init-program", which tries its format, and so on.
For ELF images, OFW reads the ELF program headers, moves PT_LOAD segments to their execution locations, and zeros other regions as specified.
For Linux bzImage format, preparation consists of creating a Linux argument block that contains the cmdline and memory layout information, moving the Linux image from OFW's default load address (h# 800000 for OLPC) to the physical address where Linux wants to execute (0x400000), and setting up a virtual address mapping of virtual 0xc0000000 to physical 0. See also the "Linux Ramdisk" topic below.
OFW recognizes Forth source code by the presence of "\ " as the first two characters. Thus, to ensure that a Forth source file can be executed by "boot", the first line in the file must be a comment line.
If you want to test the step of loading into memory without doing the image recognition, one good way is to attach the debugger to "init-program" then do "load". The debugger will fire up after the loading step, and then you can inspect as necessary.
The final step of "boot", executing the image, depends on the format. For Forth source code, the Forth interpreter just processed the code as if it were an ordinary source file (apart from the fact that it has already been read into memory in its entirety). For machine code, the format recognizer has already determined such things as the entry address and the correct initial values for the general registers, storing them in the same "saved program state" array that contains information saved at a breakpoint. Program entry, accomplished by the Forth word "go", is equivalent to continuing after a breakpoint.
Some Linux boot scenarios require loading an "initrd" ramdisk image. This is done as part of the preparation of bzImage images. If the "ramdisk" configuration variable is not the empty string, the bzImage handler attempts to load the initrd image from the device specified by the value of "ramdisk". The initrd image is left at OFW's default load address (h# 800000 on OLPC), which does not conflict with the Linux kernel location because that has already been moved to 0x400000.
As with "boot-file", the default value of "ramdisk" on OLPC is the empty string. If an initrd is necessary, the /boot/olpc.fth script sets the value of "ramdisk" prior to booting the Linux kernel.
Open Firmware can boot from network devices using a variety of techniques and protocols.
Automatic Net Booting
In the simplest case, you would say:
ok boot net
If everything is setup correctly, that would proceed as follows:
- OFW has already created a devalias named "net" during startup, referring to a suitable default network device
- The boot command attempts to open that device, which performs the following steps with the help of the "/packages/obp-tftp" support package:
- The device driver tries to connect to an active network (requires link integrity for a wired device or association with a wireless access point)
- The obp-tftp support package uses DHCP to acquire an IP address, the netmask, the address of a TFTP boot server, and the name of a file to load.
- obp-tftp uses the TFTP protocol to read that file from the TFTP server.
Having loaded the file into memory, image recognition and subsequent steps proceed the same way as with disk booting.
The file that TFTP loads can be a Forth script that loads additional images via TFTP or another protocol.
Manual Net Booting
Instead of getting the network configuration via DHCP, you can specify various fields manually, as in:
ok boot net:tftp-server-ip,filename,client-ip,router-ip
With this syntax, DNS isn't supported; the IP addresses must be dotted quads.
You can omit fields you don't need. If everything is on the same subnet, the router-ip field is unnecessary. If you want to use DHCP to get the IP addresses, but override the filename that DHCP supplies, you could write:
ok boot net:,\tftpboot\myfile
Unless the DHCP server supplies a netmask, the netmask is determined from the client IP address using the old default rules for class A, B, and C networks.
Other network protocols
OFW can also load images using HTTP or NFS. The syntax is:
ok boot nfs:\\server\path ok boot http:\\server\path
In both of these cases, OFW must use DHCP to determine the client's IP address.
If the DHCP-supplied configuration specifies a DNS server (e.g. via the "name-servers" option in dhcpd.conf on the DHCP server), "server" can be a hostname; otherwise "server" must be a dotted quad.
For NFS, the path must be exported from the server with fairly loose permissions, as OFW cannot supply much in the way of credentials.
The OFW NFS protocol implementation uses UDP. HTTP uses TCP.
OLPC OFW can use the wireless network interface in access point mode.
To set the SSID under OFW, use:
ok wifi <SSID>
<SSID> can contain spaces, as in:
ok wifi media lab 802.11
The default SSID is "OLPCOFW". That is useful if you need to set up a special network for testing OLPC systems in a manufacturing environment, for example. Firmware versions after 2013-02-13 support a manufacturing tag NN which changes the default SSID.
You can set a PMK key with:
ok pmk <32-byte-key>
You can set up to 4 WEP keys by repeated uses of:
ok wep <5-byte-or-13-byte-key>
More information on boot options is available in the page describing Custom bootloaders.
Thus endeth the lesson.