Chapter 8
Loading an Operating System

A fanatic is one who sticks to his guns whether they’re loaded or not.

—Franklin P. Jones

When loading the OS, there are many ways to bootstrap the system and jump into the OS. For Intel architecture devices, there are two sets of OS interfaces we need to contend with: EFI and legacy OS. There are a number of second-stage boot loaders for each interface of Microsoft and Linux, the major operating system camps. RTOS and other proprietary operating systems will have either EFI, legacy, or both flavors; or support neither.

There is a third OS interface, the null option: when there is a blind handoff, there is no interface at all and at the end of the BIOS, it just grabs a specific address in a piece of NVRAM, loads it, and jumps to it, and the OS never looks back.

Before we get to the details, let’s back up and look into the Boot Device Selection phase theory of operation. In other BIOS implementations, it is known as the boot services. How does one get to the disk we want to get to?

The Boot Path

To boot to an OS, you must have the right boot path enabled to get to the target operating system. In a mainstream system, this means you have to hunt and peck to find all the bootable storage devices within the bootable buses and devices, locate a bootable partition somewhere, and then decide whether you want to boot to it or not. But how do you get there?

The Bus

From the CPU and chipset, there is likely a bus or two that must be traversed, such as PCI, AHCI, USB, or SD. We must communicate on that bus in order to know whether the OS is out there on a device somewhere, and that the next-stage boot loader is resident and ready to be loaded and executed. Fortunately, in the EFI case, there is often a bus driver that has been constructed. That bus driver would know the protocols for reads, writes, and so on. This is fundamental regardless of whether you are searching for bootable partitions, locating and identifying the boot target’s .efi file, or, in the case of a closed box, just reading a sector and blindly handing off control to it.

The Device

Once you can communicate across a particular bus to talk to the device, there may be multiple boot partitions or devices to choose from. For this a boot priority list is a requirement, unless you have only one specific target in mind. While you may be able to hard-code this for a closed box, for recovery cases, it is ideal to have a list of removable media that reflect a higher priority than the default storage. Alternate input methods may be used to drive the boot flow to a particular device (such as the use of jumpers or switches on GPIOs). The boot list can be used for giving priority to recovery, validation, or manufacturing OS over the production boot image.

The Partition Table

This is described in an earlier chapter.

The File System

Which file system is being used? There have been new file systems coming out about every year since 1964. The two we are going to talk about are FAT and EXT.

Microsoft provides a license for FAT to UEFI developers through the UEFI forum. Those using an EFI solution should receive that as part of the EDK. FAT stands for the file allocation table and has been around for many years in different bit-wise fashions. FAT12, FAT16, and FAT32 before NTFS took over. While FAT12 is reserved for floppies, FAT32 handles up to 2 TB of data.

Linux developed an alternative known as EXT, which is gaining favor in open-source communities. There are variations of both FAT and EXT; developers have to look at the requirements to determine which makes sense for them.

Booting via the Legacy OS Interface

There are three basic steps to loading a legacy OS: consuming the MBR, loading the legacy OS loader, and performing the hand off to the operating system itself.

Master Boot Record

Booting a legacy OS (non-EFI) consists of locating the right master boot record (MBR) on one of a potentially large number of bootable media.

On each disk an MBR may be sought by looking at LBA Sector 0 for signature 0xAA55 in the last two of the 512 bytes. In the master boot record, there are four 16-byte partition tables specifying whether there are any partitions and, if so, which are active.

The BIOS can choose any of the active primary partitions in the table for the drive and discover whether any of them are bootable. This bootable partition would then be listed in the boot target list, which can then be prioritized.

Loading the Legacy OS Loader

There are 440 bytes of executable code in the winning MBR that can be loaded into 0x7C00. Depending on the next step, that code can load the volume boot record (VBR) code from that partition into location 0x7C00 again and execute it. At some point down the chain a real OS loader is loaded, jumping to that location while the processor is in 16-bit Real Mode, and starts executing the OS loader.

Legacy BIOS to OS Handoff Requirements

The legacy OS loader relies on legacy BIOS services, including but not limited to:

Int 13h for disk including RAID option ROMs

Int 16h for PS/2 keyboard

Int 10h for video output from video BIOS

BIOS’s SMI handler providing legacy USB keyboard support via port 60/64 emulation.

Depending on the desired features enabled by the boot loader, the OS needs different tables. The following is a list of those tables:

Memory Map (INT15h / Function E820h)

Programmable Interrupt Routing ($PIR)

Multi-Processor Specification (_MP_)

Minimal Boot Loader for Intel® Architecture

Advanced Configuration and Power Interface (ACPI)

ACPI tables are needed only if those features are enabled by the boot loader and required by the OS. Most modern operating systems, including RTOS, are ACPI aware.

The _MP_ table is needed if there is more than one Intel processing agent (more than one thread or core) in the system. Details on the _MP_ table may be found in the Multi-Processor Specification.

The $PIR table and interrupt-based memory map are almost always needed. Details on the $PIR table may be found in the $PIR Specification. The memory map is discussed in more detail in the following sections.

The OS loader is often operating-system-specific; however, in most operating systems its main function is to load and execute the operating system kernel, which continues startup, and eventually you load an application. Examples for Linux include U-boot, Grub, and Sys-Linux. Examples for Windows CE are Eboot and ROMboot.

Another term has been used in the past for the OS loader was the initial program loader or IPL. While the term predates most of the current mechanisms, it can pop up in certain crowds or segments of industry. The job is the same as a BIOS or a boot loader or an OS loader. The names are just changed to protect the innocent.

Booting via the EFI Interface

Booting a UEFI operating system consists of locating the global partition table record (GPT) on one of the same potentially large number of bootable media.

Default EFI Boot Behavior

By default, UEFI firmware will search the path /EFI/BOOT/ on a FAT GPT partition for an executable second-stage bootloader. Each FAT GPT partition will be checked in its discovered order for an appropriate boot executable. On a 64-bit architecture, the name of the default bootloader will be bootia64.efi; on a 32-bit machine it will be bootia32.efi.

Operating system installation programs will typically rewrite the NvVar that controls the boot process, thus installing a new path to the operating system’s standard bootloader. For example: the new path will be /EFI/BOOT/grub.efi for a Linux system or C:WindowsSystem32winload.efi for a Windows system.

A second-stage bootloader, as shown in Figure 8.1, will then hand off control from the second stage to the runtime system by first locating the bootloader configuration file, if any, and then executing commands contained in that configuration file in the following order:

  1. Locate a kernel image
  2. Locate any ancillary boot data, such as an init RAM disk
  3. Optional: convert an EFI memory map to E820h tables, for systems that do not support UEFI Runtime Services
  4. Call ExitBootServices()
  5. Execute the kernel with parameters given in the boot loader configuration file

Direct Execution of a Linux Kernel

Modern UEFI systems, in conjunction with modern versions of the Linux kernel, can directly execute a Linux kernel without need of a second-stage bootloader such as Grub.

Since the Linux kernel now can get the EFI memory map via a call to UEFI Runtime Services, the necessity of using a second-stage bootloader to convert an EFI memory map to E820h/E801h tables is mitigated.

This extension to the Linux kernel is fully backward compatible, meaning it is possible to add the EFI_STUB kernel configuration option to your kernel configuration and to seamlessly use this to execute the kernel directly from an UEFI firmware, or to execute the kernel as specified above from a standard second-stage boot loader.

Figure 8.1: Default Second-Stage Boot Loader Flow

UEFI Runtime Services

Operating system kernels such as those used in Windows and Linux systems assume and require the second-stage boot loader to have called ExitBootServices() before handing control over.

In order to support calling UEFI services from a virtual-memory-enabled kernel, however, it is necessary for the kernel and UEFI callback mechanism to agree on a new address structure. Recall UEFI systems operate in protected unpaged mode, whereas Windows and Linux systems are fully fledged paged OS environments. The UEFI runtime subsystem operates in protected unpaged mode, whereas the kernel has switched execution over to paged mode; therefore, when a kernel makes a runtime call to UEFI one of two things must logically happen.

1.Each callback to UEFI could switch the processor back to UEFI’s default mode of protected unpaged mode. This is undesirable, however, for a number of reasons:

Slow to switch modes back and forth

Complex to enable/disable paging between the two modes

Considered unsafe security-wise as the UEFI callback has unfettered access to memory.

2.The UEFI callback must now itself operate in paged mode, replacing function and data references/pointers to physical addresses with paged references. Example: data at 0x12345678 physical may now be mapped by Linux to 0xD2345678, and the UEFI system should reference the new address, not the old.

In order to achieve this agreement between a paging kernel and the UEFI Runtime Services, the UEFI standard defines the function SetVirtualAddressMap(). After mapping UEFI runtime data/code to a set of virtual addresses, the OS kernel will make this callback to EFI, before making any further UEFI Runtime Service calls.

Neither Option

This option should be reserved for an RTOS or modified Linux kernel. The OS loader (if there is one) must not rely on any BIOS boot services or legacy BIOS calls. The OS must not rely on any BIOS legacy or runtime services. The system should be a closed box with a single permanent boot path. This is an ideal case if this is an SPI-based (flash-based) operating system, such as a microkernel or free RTOS.

In a UEFI-based solution, a developer may choose to have the OS as a DXE payload where the operating system is loaded and jumped to as part of the initial BIOS driver load.

In a legacy-based solution, it is possible to do something similar with a DMA of the OS kernel in a known memory location and a jump to start the kernel.

While this may prove limiting to the boot solution on the platform, there is an alternative OS-loading model via this solution where the UEFI-based firmware is bifurcated and DXE and later is placed on removable media. A jumper or other physical access is likely required to trigger the alternate boot path, but this is important if the solution is going to go beyond the simple prototype and into high volume manufacturing. The blind handoff to disk means reading the first sector of a target device and jumping to a known (good) address. It is the preference of RTOS vendors who don’t call back into system firmware during runtime and have not yet implemented either a legacy OS interface or an EFI interface.

Summary

Loading an operating system after the platform is initialized can be done in a variety of ways. Known and tested standards are the fastest and easiest methods to provide for a flexible solution. More deeply embedded solutions have several other options but must be fully thought through beyond the lab environment if the system is meant for bigger, better, and broader things.

Choose wisely and let the circumstances dictate the development (and boot) path; with UEFI, one doesn’t need to be married to a single boot solution.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.51.36