by Roderick W. Smith, rodsmith@rodsbooks.com
Originally written: 5/3/2013
I'm a technical writer and consultant specializing in Linux technologies. This Web page is provided free of charge and with no annoying outside ads; however, I did take time to prepare it, and Web hosting does cost money. If you find this Web page useful, please consider making a small donation to help keep this site up and running. Thanks!
Donate $1.00 | Donate $2.50 | Donate $5.00 | Donate $10.00 | Donate another value |
Note: This page is a sub-page of my Programming for EFI document. If a Web search has brought you here, you may want to start at the introductory page.
Although libraries such as GNU-EFI and TianoCore EDK 2 provide system calls such as Print(), many EFI features are accessed through a more general interface. Recall from the "Hello, World" program that the efi_main() program received two parameters:
EFI_STATUS EFIAPI efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable);
The EFI_SYSTEM_TABLE is the key to accessing most of an EFI environment's features. This data structure includes a number of pointers, some of which point to additional data structures that provide access to EFI system calls. In GNU-EFI, the EFI_SYSTEM_TABLE structure is defined in /usr/include/efi/efiapi.h:
typedef struct _EFI_SYSTEM_TABLE { EFI_TABLE_HEADER Hdr; CHAR16 *FirmwareVendor; UINT32 FirmwareRevision; EFI_HANDLE ConsoleInHandle; SIMPLE_INPUT_INTERFACE *ConIn; EFI_HANDLE ConsoleOutHandle; SIMPLE_TEXT_OUTPUT_INTERFACE *ConOut; EFI_HANDLE StandardErrorHandle; SIMPLE_TEXT_OUTPUT_INTERFACE *StdErr; EFI_RUNTIME_SERVICES *RuntimeServices; EFI_BOOT_SERVICES *BootServices; UINTN NumberOfTableEntries; EFI_CONFIGURATION_TABLE *ConfigurationTable; } EFI_SYSTEM_TABLE;
Some of these variables have obvious meanings, such as FirmwareVendor. The ConIn, ConOut, and StdErr variables point to data structures that in turn contain pointers to functions to handle console I/O. Ultimately, the Print() call used in "Hello, World" relies on these calls, and in fact the program could use a direct call to ConOut->OutputString() instead of to Print(). (This would require wrapping the call, though, as described shortly.) The InitializeLib() call sets up the global ST variable as a quick way to access this data structure.
The RuntimeServices variable points to a structure that contains pointers to functions provided by the firmware. These services are available both in the pre-boot environment and after the OS has taken control of the computer. For the most part, these functions handle the system clock, NVRAM variables, and system reset operations. The InitializeLib() call in "Hello, World" sets up the global RT variable as a simpler way to reference SystemTable->RuntimeServices.
The BootServices variable is similar to RuntimeServices, but its functions are available only to EFI programs; boot loaders shut down access to these services when they hand control of the computer over to the OS. The call to InitializeLib in "Hello, World" sets up the global BS variable as a quicker way to reference SystemTable->BootServices. The Boot Services consist of a series of function pointers that manage memory, load EFI programs into memory, manage events, and so on. Much of your program's interactions with the firmware will occur through Boot Services interfaces.
The Print() function is a useful shorthand for a call to ST->ConOut->OutputString(), and in fact Print() adds useful features such as conversion specifiers. Thus, you're likely to use Print() more than ST->ConOut->OutputString() in real programs. For the purposes of the "Hello, World" program, though, a direct call to the underlying function can work as well, and is a useful demonstration. If you've created that program file, try the following variant:
#include <efi.h> #include <efilib.h> EFI_STATUS EFIAPI efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) { InitializeLib(ImageHandle, SystemTable); uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, L"Hello World!\n"); return EFI_SUCCESS; }
This program substitutes a call to uefi_call_wrapper() for the call to Print(), but is otherwise the same. It should function identically to the original.
But wait—what's this uefi_call_wrapper() function? It's a workaround for the problem noted on the "Hello, World" page: EFI uses the Microsoft ABI, whereas GNU-EFI uses the SysV ABI. Because so many EFI features are accessed through pointers to functions, it's necessary to call them in such a way that the ABI difference is handled properly. The uefi_call_wrapper() function does just that. It takes a variable number of arguments. The first is the pointer to the function that must be wrapped, the second is the number of arguments that the called function takes, and the remaining arguments are passed to the function unchanged. Thus, the uefi_call_wrapper() function in the preceding example could be written as follows, if no ABI translation were necessary:
ST->ConOut->OutputString(ST->ConOut, L"Hello World!\n");
In fact, if you were to use the TianoCore EDK 2 rather than GNU-EFI, you could use this form of the call, since TianoCore uses the Microsoft ABI internally. You can also use this form if you're programming exclusively for 32-bit x86 computers, even with GNU-EFI—but as most Linux EFI development is for 64-bit x86-64 systems, wrapping calls is a practical necessity. (32-bit builds of GNU-EFI also support the uefi_call_wrapper() call. This call isn't required on such systems, but supporting it means that the same code will compile on both 32-bit and 64-bit systems.)
In this specific case, using the EFI console service directly offers no advantage over using the GNU-EFI libary's Print() call. Sometimes, though, you might want to do more. For instance, suppose you wanted to clear the screen. The ConOut service offers options to do this. For instance, preceding the Print() or OutputString() call with one to ClearScreen() will do just that:
uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
Try adding this line to the "Hello, World" program, compiling it, and running it. (Note that you can add this line to either the original version that uses Print() or to the version that uses OutputString().) The result should be that the screen will clear before the text is displayed.
A great deal of EFI programming is a matter of learning what you can do with the various services accessible via the EFI system table. For now, I can't describe all of these features myself (although I may expand this documentation in the future). Instead, I recommend you look it up elsewhere. A set of documentation is available, in slightly different form, on various sites. One that I use frequently is the Phoenix wiki. The page on EFI_SYSTEM_TABLE is a particularly good starting point.
Another approach to learning more about EFI services is to examine existing source code. Projects you may want to study include the following:
If you want personal help on EFI development, you might check out the TianoCore EDK II mailing lists. Readers of that list are more likely to be able to help with general problems or problems related to TianoCore EDK II rather than GNU-EFI issues, though. Unfortunately, I don't know of a GNU-EFI mailing list or forum.
Return to the "Programming for EFI" main page
copyright © 2013 by Roderick W. Smith
If you have problems with or comments about this Web page, please e-mail me at rodsmith@rodsbooks.com. Thanks.
Return to my main Web page.