Column 10: More on payloads. (2011-08-31)

The goal of Coreboot is to run a payload program. Once Coreboot has performed its task of initializing the hardware, the "easy" part of bare metal access starts and there is much less diversity among x86 machines. For instance the same SeaBIOS image is likely to run across most PC class machines, while Coreboot itself must be configured to exactly match the hardware.

Coreboot itself is designed to run only one payload, but some payloads may in turn load other payloads. The "bayou" program was designed to do this and also SeaBIOS offers the capability to run other Coreboot payloads.

ELF executables

Coreboot payloads have to be created as ELF executable images. The GNU binutils linker is fully capable of doing this. These are not compatible with normal executable under Linux though. For one thing, the Linux system call interface is not available, let alone access to shared libraries. Further, section addresses are likely to be incompatible with Linux executables and of course a Coreboot payload will start out in ring 0 (having all privileges) and a Linux executable will start out in ring 3 (having restricted privileges).

ELF is an executable format, originally designed for System V Unix and later adopted by Linux and the open source BSD versions as well. It is very flexible: it can define an arbitrary number of sections, each can have certain properties and each can start at a different address. It is flexible enough to define everything that is normally needed for any use of object code or executable code, be it ROM-based systems or shared libraries.

Anything that is not an ELF executable in the required format must be converted to this format first. The program mkelfImage (included with the Coreboot source code) can be used to convert a Linux kernel (which may include an initramfs root file system) into an ELF image usable as a payload.

Payloads are not stored in CBFS in their native ELF format though. When cbfstool stores an ELF payload in a ROM image, it converts the sections to the format in which Coreboot expects them. It can further compress each section separately.

Libpayload

Reimplementing common C library functions such as memcpy, malloc or printf for each payload is tedious and inefficient. Therefore a common library exists: libpayload. Each payload that uses it, is statically linked with libpayload. It does include terminal input and output (even a subset of Curses), but it lacks file I/O and advanced mathematical functions.

Coreboot payloads do not have to use libpayload of course. The Linux kernel was designed to run on the bare hardware from the start (the "easy" bare hardware though), so it does not need libpayload. SeaBIOS (besides being designed as a separate project) runs in real mode, so it does not benefit much from it.

Real mode

So let me first explain what real mode is. The original 8086 had only 16-bit registers, but the total address range could span 1 Megabyte. An address was made of a segment part and an offset. The segment part was shifted left by 4 bits (multiplied by 16) and the the 16-bit offset was added to it. For example the (linear) memory address 0x90000 could be reached with a segment value of 0x9000 and an offset value of 0 or segment:offset = 0x8800:0x8000 or with segment:offset=0x8001:0xfff0, actually in 4096 different ways.

Some programs needed only 64kB of code and data space. They could set the segment registers once (or MS-DOS did if for them) and use only offsets for addresses. Other programs needed more than 64kB of code or data space and they needed to represent pointers with both segments and offsets. Programs could contain some data (or code) addressed with near pointers (offset only) and some with far pointers (segment and offset). C compilers for the 8086 (such as Turbo C) had support for various memory models and also for explicit near and far pointers.

Real mode is the old 8086-style CPU mode in which the segment registers behave in the way described above. Only 1 megabyte of memory can be addressed using 16-bit segment values and 16-bit offsets. In protected mode one can define the start address and size of each segment much more flexibly. On the 80386, segment sizes and offsets can be up to 4 gigabytes. Most operating systems and also Coreboot itself set the segment registers to address 0 at the start and forget about segments. They just use 32-bit offsets as if they were addresses.

The problem is that most boot loaders and some operating systems on the x86 platform expect to run in real mode and expect to be able to make BIOS calls. The same goes for option ROMs that are required for some hardware. Therefore Coreboot cannot ignore real mode completely. Fortunately most of the dirty real mode work is done by the SeaBIOS payload.

SeaBIOS

SeaBIOS implements the traditional PC BIOS interface, so most of it runs in real mode.

The GNU C compiler can only generate 32-bit code, not 16-bit code, but that is not a real problem: even in real mode, the x86 CPU can execute 32-bit instructions as long as they get the correct prefix, which is something the assembler can easily do.

What is more of a problem is that GCC is not aware of segmenting. It basically assumes that it can access all data relative to its data segment and that it is identical to the stack segment. Traditional C compilers could use several memory models, from tiny (everything in a single segment) to huge (code and data can each span multiple segments and even individual arrays can be larger than 64kB, meaning that convoluted pointer arithmetic is implemented by the compiler). In real mode, GCC basically supports only the equivalent of the "tiny" memory model. Any access outside the single segment requires explicit use of assembly language macros (inline asm statements).