Finally we are ready to write our "Hello World" app.
First we create a new edk2 module in our package directory similar to the our SimplestApp
module:
$ mkdir UefiLessonsPkg/HelloWorld
$ vi UefiLessonsPkg/HelloWorld/HelloWorld.inf
[Defines]
INF_VERSION = 1.25
BASE_NAME = HelloWorld
FILE_GUID = 2e55fa38-f148-42d3-af90-1be247323e30
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 1.0
ENTRY_POINT = UefiMain
[Sources]
HelloWorld.c
[Packages]
MdePkg/MdePkg.dec
[LibraryClasses]
UefiApplicationEntryPoint
Don't forget to add our newly created app to the Components
section of the package DSC file
[Components]
UefiLessonsPkg/SimplestApp/SimplestApp.inf
+ UefiLessonsPkg/HelloWorld/HelloWorld.inf
Next we need to write the source code file. Let's remember the code for our SimplestApp:
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
return EFI_SUCCESS;
}
To print something to the console ("Hello World" message in our case) we need to use services from the EFI_SYSTEM_TABLE
that is passed to the entry point of our app.
The description of the EFI_SYSTEM_TABLE
can be found in the UEFI specification (https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf).
EFI_SYSTEM_TABLE is a struct that was populated by the UEFI firmware and contains pointers to the runtime and boot services tables.
typedef struct {
EFI_TABLE_HEADER Hdr;
CHAR16 *FirmwareVendor;
UINT32 FirmwareRevision;
EFI_HANDLE ConsoleInHandle;
EFI_SIMPLE_TEXT_INPUT_PROTOCOL *ConIn;
EFI_HANDLE ConsoleOutHandle;
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut;
EFI_HANDLE StandardErrorHandle;
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr;
EFI_RUNTIME_SERVICES *RuntimeServices;
EFI_BOOT_SERVICES *BootServices;
UINTN NumberOfTableEntries;
EFI_CONFIGURATION_TABLE *ConfigurationTable;
} EFI_SYSTEM_TABLE;
We are interested in the ConOut
field. ConOut
is abbreviaton for "Console Output" and according to the UEFI spec it is a pointer to the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
interface that is associated with ConsoleOutHandle
.
If we keep digging into UEFI spec we can find description of the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
.
According to the spec EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
defines the minimum requirements for a text-based ConsoleOut device.
As everything in UEFI it has GUID:
#define EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_GUID \
{0x387477c2,0x69c7,0x11d2,\
{0x8e,0x39,0x00,0xa0,0xc9,0x69,0x72,0x3b}}
And the interface description is:
typedef struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL {
EFI_TEXT_RESET Reset;
EFI_TEXT_STRING OutputString;
EFI_TEXT_TEST_STRING TestString;
EFI_TEXT_QUERY_MODE QueryMode;
EFI_TEXT_SET_MODE SetMode;
EFI_TEXT_SET_ATTRIBUTE SetAttribute;
EFI_TEXT_CLEAR_SCREEN ClearScreen;
EFI_TEXT_SET_CURSOR_POSITION SetCursorPosition;
EFI_TEXT_ENABLE_CURSOR EnableCursor;
SIMPLE_TEXT_OUTPUT_MODE *Mode;
} EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL;
Right now we are interested in a OutputString
method:
OutputString Displays the string on the device at the current cursor location.
This is what we need. Let's look at the function description:
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.OutputString()
Summary
Writes a string to the output device.
Prototype
typedef
EFI_STATUS
(EFIAPI *EFI_TEXT_STRING) (
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
IN CHAR16 *String
);
Parameters
This A pointer to the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL instance.
String The Null-terminated string to be displayed on the output device(s).
In edk2 EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
is defined in the header file:
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/SimpleTextOut.h
With all this knowledge we can write our source code file UefiLessonsPkg/HelloWorld/HelloWorld.c
:
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Hello World!\n");
return EFI_SUCCESS;
}
The L""
signifies that the string is composed from CHAR16 symbols, as was required in spec.
As for CHAR16
- UEFI uses special names like these for simple types. It is a proxy for a different type realization in different processor architectures.
When we compile our code code for X64, our realization would be picked from a file https://github.com/tianocore/edk2/blob/master/MdePkg/Include/X64/ProcessorBind.h:
typedef unsigned short CHAR16;
For example RISCV would define it like this (https://github.com/tianocore/edk2/blob/master/MdePkg/Include/RiscV64/ProcessorBind.h):
typedef unsigned short CHAR16 __attribute__ ((aligned (2)));
All the simple types for X64:
///
/// 8-byte unsigned value
///
typedef unsigned long long UINT64;
///
/// 8-byte signed value
///
typedef long long INT64;
///
/// 4-byte unsigned value
///
typedef unsigned int UINT32;
///
/// 4-byte signed value
///
typedef int INT32;
///
/// 2-byte unsigned value
///
typedef unsigned short UINT16;
///
/// 2-byte Character. Unless otherwise specified all strings are stored in the
/// UTF-16 encoding format as defined by Unicode 2.1 and ISO/IEC 10646 standards.
///
typedef unsigned short CHAR16;
///
/// 2-byte signed value
///
typedef short INT16;
///
/// Logical Boolean. 1-byte value containing 0 for FALSE or a 1 for TRUE. Other
/// values are undefined.
///
typedef unsigned char BOOLEAN;
///
/// 1-byte unsigned value
///
typedef unsigned char UINT8;
///
/// 1-byte Character
///
typedef char CHAR8;
///
/// 1-byte signed value
///
typedef signed char INT8;
Let's finally compile our edk2 module:
$ build --platform=UefiLessonsPkg/UefiLessonsPkg.dsc \
--module=UefiLessonsPkg/HelloWorld/HelloWorld.inf \
--arch=X64 \
--buildtarget=RELEASE --tagname=GCC5
Copy the app to our UEFI_disk
folder and run OVMF:
$ cp Build/UefiLessonsPkg/RELEASE_GCC5/X64/HelloWorld.efi ~/UEFI_disk/
$ qemu-system-x86_64 -drive if=pflash,format=raw,file=Build/OvmfX64/RELEASE_GCC5/FV/OVMF.fd \
-drive format=raw,file=fat:rw:~/UEFI_disk \
-nographic \
-net none
UEFI Interactive Shell v2.2
EDK II
UEFI v2.70 (EDK II, 0x00010000)
Mapping table
FS0: Alias(s):HD0a1:;BLK1:
PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)
BLK0: Alias(s):
PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)
BLK2: Alias(s):
PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)
Press ESC in 4 seconds to skip startup.nsh or any other key to continue.
Shell> fs0:
FS0:\> HelloWorld.efi
Hello World!
FS0:\>