Introduction
Modern computing systems rely on a well-structured boot process to transition from firmware to the operating system. Haiku OS, an open-source operating system, has adopted the Unified Extensible Firmware Interface (UEFI) for its boot sequence. In this chapter, we will explore the fascinating journey of control as it moves from the firmware to the EFI environment in Haiku OS.
Firmware To UEFI
Firmware is a low-level software that resides on a computer's hardware, providing essential functions to initialize and control the system's components. When you power on your computer, the firmware is the first piece of software to come to life. Traditionally, BIOS (Basic Input/Output System) was used, but modern systems employ UEFI, a more versatile and standardized alternative.
From Firmware the control comes to the efi_main(efi_handle image, efi_system_table *systemTable)
function at location src/system/boot/platform/efi/start.cpp
.
efi_main
efi_main - function serves as the entry point for the EFI application. It initializes various systems, such as console, serial communication, CPU, ACPI, and timers.
src/system/boot/platform/efi/start.cpp
.
#include <string.h>
#include <KernelExport.h>
#include <arch/cpu.h>
#include <arch_cpu_defs.h>
#include <kernel.h>
#include <boot/kernel_args.h>
#include <boot/platform.h>
#include <boot/stage2.h>
#include <boot/stdio.h>
#include "arch_mmu.h"
#include "arch_start.h"
#include "acpi.h"
#include "console.h"
#include "cpu.h"
#include "debug.h"
#ifdef _BOOT_FDT_SUPPORT
#include "dtb.h"
#endif
#include "efi_platform.h"
#include "mmu.h"
#include "quirks.h"
#include "serial.h"
#include "smp.h"
#include "timer.h"
extern void (*__ctor_list)(void);
extern void (*__ctor_end)(void);
const efi_system_table *kSystemTable;
const efi_boot_services *kBootServices;
const efi_runtime_services *kRuntimeServices;
efi_handle kImage;
static uint32 sBootOptions;
extern "C" int main(stage2_args *args);
extern "C" void _start(void);
extern "C" void efi_enter_kernel(uint64 pml4, uint64 entry_point, uint64 stack);
static void
call_ctors(void)
{
void (**f)(void);
for (f = &__ctor_list; f < &__ctor_end; f++)
(**f)();
}
extern "C" uint32
platform_boot_options()
{
return sBootOptions;
}
template<class T> static void
convert_preloaded_image(preloaded_image* _image)
{
T* image = static_cast<T*>(_image);
fix_address(image->next);
fix_address(image->name);
fix_address(image->debug_string_table);
fix_address(image->syms);
fix_address(image->rel);
fix_address(image->rela);
fix_address(image->pltrel);
fix_address(image->debug_symbols);
}
/*! Convert all addresses in kernel_args to virtual addresses. */
static void
convert_kernel_args()
{
fix_address(gKernelArgs.boot_volume);
fix_address(gKernelArgs.vesa_modes);
fix_address(gKernelArgs.edid_info);
fix_address(gKernelArgs.debug_output);
fix_address(gKernelArgs.boot_splash);
arch_convert_kernel_args();
if (gKernelArgs.kernel_image->elf_class == ELFCLASS64) {
convert_preloaded_image<preloaded_elf64_image>(gKernelArgs.kernel_image);
} else {
convert_preloaded_image<preloaded_elf32_image>(gKernelArgs.kernel_image);
}
fix_address(gKernelArgs.kernel_image);
// Iterate over the preloaded images. Must save the next address before
// converting, as the next pointer will be converted.
preloaded_image* image = gKernelArgs.preloaded_images;
fix_address(gKernelArgs.preloaded_images);
while (image != NULL) {
preloaded_image* next = image->next;
if (image->elf_class == ELFCLASS64) {
convert_preloaded_image<preloaded_elf64_image>(image);
} else {
convert_preloaded_image<preloaded_elf32_image>(image);
}
image = next;
}
// Fix driver settings files.
driver_settings_file* file = gKernelArgs.driver_settings;
fix_address(gKernelArgs.driver_settings);
while (file != NULL) {
driver_settings_file* next = file->next;
fix_address(file->next);
fix_address(file->buffer);
file = next;
}
}
static addr_t
get_kernel_entry(void)
{
if (gKernelArgs.kernel_image->elf_class == ELFCLASS64) {
preloaded_elf64_image *image = static_cast<preloaded_elf64_image *>(
gKernelArgs.kernel_image.Pointer());
return image->elf_header.e_entry;
} else if (gKernelArgs.kernel_image->elf_class == ELFCLASS32) {
preloaded_elf32_image *image = static_cast<preloaded_elf32_image *>(
gKernelArgs.kernel_image.Pointer());
return image->elf_header.e_entry;
}
panic("Unknown kernel format! Not 32-bit or 64-bit!");
return 0;
}
static void
get_kernel_regions(addr_range& text, addr_range& data)
{
if (gKernelArgs.kernel_image->elf_class == ELFCLASS64) {
preloaded_elf64_image *image = static_cast<preloaded_elf64_image *>(
gKernelArgs.kernel_image.Pointer());
text.start = image->text_region.start;
text.size = image->text_region.size;
data.start = image->data_region.start;
data.size = image->data_region.size;
return;
} else if (gKernelArgs.kernel_image->elf_class == ELFCLASS32) {
preloaded_elf32_image *image = static_cast<preloaded_elf32_image *>(
gKernelArgs.kernel_image.Pointer());
text.start = image->text_region.start;
text.size = image->text_region.size;
data.start = image->data_region.start;
data.size = image->data_region.size;
return;
}
panic("Unknown kernel format! Not 32-bit or 64-bit!");
}
extern "C" void
platform_start_kernel(void)
{
smp_init_other_cpus();
#ifdef _BOOT_FDT_SUPPORT
dtb_set_kernel_args();
#endif
addr_t kernelEntry = get_kernel_entry();
addr_range textRegion = {.start = 0, .size = 0}, dataRegion = {.start = 0, .size = 0};
get_kernel_regions(textRegion, dataRegion);
dprintf("kernel:\n");
dprintf(" text: %#" B_PRIx64 ", %#" B_PRIx64 "\n", textRegion.start, textRegion.size);
dprintf(" data: %#" B_PRIx64 ", %#" B_PRIx64 "\n", dataRegion.start, dataRegion.size);
dprintf(" entry: %#lx\n", kernelEntry);
debug_cleanup();
arch_mmu_init();
convert_kernel_args();
// map in a kernel stack
void *stack_address = NULL;
if (platform_allocate_region(&stack_address,
KERNEL_STACK_SIZE + KERNEL_STACK_GUARD_PAGES * B_PAGE_SIZE, 0, false)
!= B_OK) {
panic("Unabled to allocate a stack");
}
gKernelArgs.cpu_kstack[0].start = fix_address((addr_t)stack_address);
gKernelArgs.cpu_kstack[0].size = KERNEL_STACK_SIZE
+ KERNEL_STACK_GUARD_PAGES * B_PAGE_SIZE;
dprintf("Kernel stack at %#" B_PRIx64 "\n", gKernelArgs.cpu_kstack[0].start);
// Apply any weird EFI quirks
quirks_init();
// Begin architecture-centric kernel entry.
arch_start_kernel(kernelEntry);
panic("Shouldn't get here!");
}
extern "C" void
platform_exit(void)
{
kRuntimeServices->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);
return;
}
/**
* efi_main - The entry point for the EFI application
* @image: firmware-allocated handle that identifies the image
* @systemTable: EFI system table
*/
extern "C" efi_status
efi_main(efi_handle image, efi_system_table *systemTable)
{
stage2_args args;
memset(&args, 0, sizeof(stage2_args));
kImage = image;
kSystemTable = systemTable;
kBootServices = systemTable->BootServices;
kRuntimeServices = systemTable->RuntimeServices;
call_ctors();
console_init();
serial_init();
serial_enable();
sBootOptions = console_check_boot_keys();
// disable apm in case we ever load a 32-bit kernel...
gKernelArgs.platform_args.apm.version = 0;
cpu_init();
acpi_init();
#ifdef _BOOT_FDT_SUPPORT
dtb_init();
#endif
timer_init();
smp_init();
main(&args);
return EFI_SUCCESS;
}
efi_main is the main function that gets executed when the control goes to UEFI application. Below is the efi_main function code for the haiku OS:
extern "C" efi_status
efi_main(efi_handle image, efi_system_table *systemTable)
{
stage2_args args;
memset(&args, 0, sizeof(stage2_args));
kImage = image;
kSystemTable = systemTable;
kBootServices = systemTable->BootServices;
kRuntimeServices = systemTable->RuntimeServices;
call_ctors();
console_init();
serial_init();
serial_enable();
sBootOptions = console_check_boot_keys();
// disable apm in case we ever load a 32-bit kernel...
gKernelArgs.platform_args.apm.version = 0;
cpu_init();
acpi_init();
#ifdef _BOOT_FDT_SUPPORT
dtb_init();
#endif
timer_init();
smp_init();
main(&args);
return EFI_SUCCESS;
}
The efi_main
function is the entry point for an EFI (Extensible Firmware Interface) application, specifically, it's the entry point for the Haiku OS's stage 2 boot loader in the EFI environment. This function is called when the EFI firmware launches the boot loader application. Let's go through the code in detail:
extern "C" efi_status efi_main(efi_handle image, efi_system_table *systemTable)
{
- The
extern “C”
specifier is used to indicate that the function uses C linkage, ensuring compatibility with the EFI environment. efi_status
is a data type representing the status of an EFI operation.efi_handle
is a handle to the EFI image.efi_system_table
is a pointer to the EFI system table, which contains various services and pieces of information provided by the EFI firmware.
stage2_args args;
memset(&args, 0, sizeof(stage2_args));
stage2_args
is a structure that hold arguments and configuration information for the Haiku OS kernel.- The
memset
function initializes theargs
structure to all zeroes, effectively setting all its members to zero or NULL. This structure is used to pass information from the boot loader to the Haiku OS kernel.
kImage = image;
kSystemTable = systemTable;
kBootServices = systemTable->BootServices;
kRuntimeServices = systemTable->RuntimeServices;
These lines set various global variables to reference key EFI structures:
kImage
is set to theimage
parameter, which is a handle to the current EFI image. (global variable to current file).kSystemTable
is set tosystemTable
parameter, which is a pointer to the EFI system table. (global variable to current file).kBootServices
is set to theBootServices
field of the EFI system table andkRuntimeServices
is set toRuntimeServices
, both of these are set to pointers within the EFI system table, giving access to boot services and runtime services provided by the firmware. (global variables to current file)
call_ctors();
It is a function that calls global constructors (functions that initialize global objects and variables). This is common C++ construct used for initialization.
call_ctors code
Below is the code of call_ctors()
with explanation:
static void
call_ctors(void)
{
void (**f)(void);
for (f = &__ctor_list; f < &__ctor_end; f++)
(**f)();
}
It is often used in low-level programming, particularly in the context of embedded systems, operating systems, and system initialization. This function is responsible for calling the constructors for global objects in the code. Let's break down the code step by step and explain its purpose:
static void call_ctors(void)
- This is a function definition named
call_ctors
. It returnsvoid
, indicating that it doesn't return any value. - The function doesn't take any arguments (
void
in the parentheses).
void (**f)(void);
- This line declares a pointer to a pointer to a function. It's used to iterate through a list of function pointers. Let's break it down further:
f
is a pointer.*f
is a pointer to a function.(**f)
is the function itself.
for (f = &__ctor_list; f < &__ctor_end; f++);
- This is a
for
loop that iterates through a range of function pointers. It starts withf
pointing to the first constructor function and continues untilf
points to the end of constructor list.
(**f)();
- Inside the loop, this line calls the constructor function pointed to by
f
.
Purpose:
The purpose of this code is to call the constructors for global objects during the initialization of a program. In C and C++ programming, global objects can have constructors (and destructors) that are automatically executed when the program starts (before main
is called) and when it exits (after main
returns). Constructors are typically used to initialize global objects or perform other setup tasks.
Need:
The need for calling constructors is to ensure that global objects are properly initialized before the program starts executing main
. This initialization is crucial for the correct functioning of the program, as it prepares global objects and their associated resources.
Consequences of Not Doing It:
If you do not call the constructors for global objects, several issues may arise:
- Global objects may be left uninitialized, potentially leading to undefined behavior or crashes.
- Resources associated with global objects (e.g., memory allocations) may not be properly set up or released, leading to memory leaks or resource leaks.
- Objects that depend on global objects may not function correctly.
efi_main continues
console_init();
serial_init();
serial_enable();
These lines initialize various I/O devices and interfaces.
console_init()
initializes the console for text output.serial_init()
initializes serial communication (useful for debugging and logging).serial_enable()
enables serial output.
console_init()
It is defined at location src/system/boot/platform/efi/console.cpp
.
status_t
console_init(void)
{
#if 1
gConsoleNode = &sConsole;
update_screen_size();
console_hide_cursor();
console_clear_screen();
#else
// FIXME: This does not work because we cannot initialize video before VFS, as it
// needs to read the driver settings before setting a mode; and also because the
// heap does not yet exist.
platform_init_video();
platform_switch_to_logo();
gConsoleNode = video_text_console_init(gKernelArgs.frame_buffer.physical_buffer.start);
#endif
// enable stdio functionality
stdin = (FILE *)gConsoleNode;
stdout = stderr = (FILE *)gConsoleNode;
return B_OK;
}
Let's break down the code step by step and explain its purpose:
status_t console_init(void)
- This a function definition named
console_init
. It returns astatus_t
, which is a type often used to represent the status or success of an operation in the context of Haiku OS.
#if 1
and #else
- These are conditioal preprocessor directives. In this code,
#if 1
always evaluates to true, so the code inside the#if 1
block is executed, and the conde inside the#else
block is ignored.
gConsoleNode = &sConsole;
- It assigns the address of the
sConsole
object to thegConsoleNode
variable. This appears to be initializing a console node or structure.
update_screen_size();
- calls the
update_screen_size
function, which likely updates or sets the screen size for the console.
console_hide_cursor();
- Calls the
update_hide_cursor
function, which hides the cursor on the console.
console_clear_screen();
- Calls the
console_clear_screen
function, which clears the console screen.
stdin = (FILE *) gConsoleNode; stdout = stderr = (FILE*)gConsoleNode;
- Configures
stdin
,stdout
, andstderr
to usegConsoleNode
as the target for standard input and output operations, effectively redirecting them to the console.
update_screen_size()
static void update_screen_size(void)
{
size_t width, height;
size_t area = 0;
efi_simple_text_output_protocol *ConOut = kSystemTable->ConOut;
for (int mode = 0; mode < ConOut->Mode->MaxMode; ++mode) {
if (ConOut->QueryMode(ConOut, mode, &width, &height) == EFI_SUCCESS) {
if (width * height > area) {
sConsole.fScreenWidth = width;
sConsole.fScreenHeight = height;
sScreenMode = mode;
}
}
}
ConOut->SetMode(ConOut, sScreenMode);
}
This function updates the screen size based on available display modes provided by the UEFI firmware. Let's break down the code and explain its purpose:
static void update_screen_size(void)
{
size_t width, height;
size_t area = 0;
efi_simple_text_output_protocol *ConOut = kSystemTable->ConOut;
size_t width, height;
- declares two variables,
width
andheight
, which will be used to store the dimensions of display modes.
size_t area = 0;
- Initializes a variable
area
to zero. This variable will be used to keep track of the larges screen area found among available display modes.
efi_simple_text_output_protocol *ConOut = kSystemTable→ConOut;
- Declares a pointer variable
ConOut
of typeefi_simple_text_output_protocol
. This variable is initialized with a pointer to theConOut
field of thekSytemTable
. ThekSystemTable
likely represents the UEFI system table, andConOut
is a protocol for handling simple text output operations in UEFI.
for (int mode = 0; mode < ConOut->Mode->MaxMode; ++mode) {
for (int mode = 0; mode < ConOut->Mode->MaxMode; ++mode)
- This
for
loop iterates through all available display modes provided by the UEFI firmware. It starts from mode 0 and continues untilmode
is less thanMaxMode
, which represents the number of available display modes for text output.
if (ConOut→QueryMode(ConOut, mode, &width, &height) == EFI_SUCCESS)
- Within the loop, this condition checks if the UEFI function
QueryMode
is successful when called with theConOut
protocol, the currentmode
, and the addresses ofwidth
andheight
variables. TheQueryMode
function is used to query information about a specific display mode - If the query is successful (
EFI_SUCCESS
), it means that the display mode information is accessible.
if (width * height > area)
- If the product of
width
andheight
of the current mode is greater than thearea
variable (which was initialized to zero), it means the current mode has a larger screen area. - In this case, the code inside this block is executed.
sConsole.fScreenWidth = width; sConsole.fScreenHeight = height; sScreenMode = mode;
- If the current mode has a larger screen area, the code updates the
fScreenWidth
andfScreenHeight
fields of thesConsole
object with thewidth
andheight
of the current mode. - It also updates the
sScreenMode
variable with the currentmode
which is the mode that provides the largest screen area.
ConOut->SetMode(ConOut, sScreenMode);
}
ConOut→SetMode(ConOut, sScreenMode);
- After determining the mode with the largest screen area, this line sets the display mode to the selected
sScreenMode
. It appears that the system is configuring the display mode to match the mode that can accommodate the largest text screen area.
console_hide_cursor()
This function is defined at the location src/system/boot/platform/generic/text_console.cpp
.
void
console_hide_cursor(void)
{
gConsoleNode->SetCursorVisible(false);
}
gConsole→SetCursorVisible(false);
- This line of code is using an object or structure referred to as
gConsoleNode
to call a method or access a property namedSetCursorVisible
. This method or property is then passed afalse
argument. This implies that the cursor should be hidden.
console_clear_screen()
The function is defined at the location src/system/boot/platform/generic/text_console.cpp
.
void
console_clear_screen()
{
gConsoleNode->ClearScreen();
}
gConsoleNode→ClearScreen();
- This line of code is using an object or structure referred to as
gConsoleNode
to call a method namedClearScreen
. - This method is responsible for clearing the contents of the console's screen, effectively removing any text or graphics that were previously displayed.
efi_main continues
serial_init();
This function is defined at location src/system/boot/platform/efi/serial.cpp
.
extern "C" void
serial_init(void)
{
if (sEFIAvailable) {
// Check for EFI Serial
efi_status status = kSystemTable->BootServices->LocateProtocol(
&sSerialIOProtocolGUID, NULL, (void**)&sEFISerialIO);
if (status != EFI_SUCCESS)
sEFISerialIO = NULL;
if (sEFISerialIO != NULL) {
// Setup serial, 0, 0 = Default Receive FIFO queue and default timeout
status = sEFISerialIO->SetAttributes(sEFISerialIO, kSerialBaudRate, 0, 0, NoParity, 8,
OneStopBit);
if (status != EFI_SUCCESS)
sEFISerialIO = NULL;
// serial_io was successful.
return;
}
}
#if defined(__i386__) || defined(__x86_64__)
// On x86, we can try to setup COM1 as a gUART too
// while this serial port may not physically exist,
// the location is fixed on the x86 arch.
// TODO: We could also try to pull from acpi?
if (gUART == NULL) {
gUART = arch_get_uart_8250(0x3f8, 1843200);
// TODO: convert over to exclusively arch_args.uart?
memset(gKernelArgs.platform_args.serial_base_ports, 0,
sizeof(uint16) * MAX_SERIAL_PORTS);
gKernelArgs.platform_args.serial_base_ports[0] = 0x3f8;
}
#endif
if (gUART != NULL)
gUART->InitEarly();
}
The serial_init function is responsible for initializing serial communication in a system. It first attempts to use EFI Serial I/O. If EFI is available. If that fails or is not available, it falls back to initializing a generic UART, specifically on x86 architectures.
extern “C” void serial_init(void)
- This is a C function declaration (extern “C” is typically used in C++ code to indicate C linkage). It is named
serial_init
and returnsvoid
, indicating it doesn't return any value.
if (sEFIAvailable)
- Checks if
sEFIAvailable
is true. This suggests that the code is targeting a platform where EFI (Extensible Firmware Interface) is available.
EFI Serial Initialization
- Inside the first if block, the code attempts to initialize serial communication using EFI Serial I/O.
- It checks for the availability of the EFI Serial I/O protocol by calling
LocateProtocol
onsSerialIOProtocolGUID
. If successful, it initializes the serial communication attributes usingSetAttributes
. - If the EFI Serial I/O initialization is successful, the function returns, indicating that serial initialization is complete.
x86 Serial Initialization:
- If EFI Serial I/O is not available (or not successful), the code checks if the platform is x86 (intel architecture).
- On x86, it attempts to set up COM1 as generic UART (
gUART
) usingarch_get_uart_8250
with a specific base address (0x3f8) and baud rate (1843200). - It updates information in the kernel arguments related to serial base ports.
if (gUART ≠ NULL)
- Checks if the generic UART (
gUART
) is successfully initialized.
gUART→InitEarly();
- If
gUART
is not NULL, it calls theInitEarly
method of the UART. This likely performs additional initialization specific to the UART.
serial_enable();
This function is defined at the location src/system/boot/platform/efi/serial.cpp
.
extern "C" void
serial_enable(void)
{
sSerialEnabled = true;
if ((gUART != NULL) && !gUARTSkipInit)
gUART->InitPort(kSerialBaudRate);
}
extern “C” void serial_enable(void)
- This is a C function declaration using C linkage. It is named
serial_enable
and returnsvoid
, indicating it doesn't return any value. The functions takes no paramters.
sSerialEnabled = true;
- Sets the global boolean variable
sSerialEnabled
totrue
. This variable likely indicates whether serial communication is enabled.
if ((gUART ≠ NULL) && !gUARTSkipInit)
- Checks two conditions:
- Whether
gUART
(a global variable representing a UART object) is not NULL, indicating that a UART is available. - Whether
gUARTSkipInit
is false. This variable might be used to skip the initialization of the UART in certain cases.
- Whether
gUART→InitPort(kSerialBaudRate);
- If both conditions in the
if
statement are true, this line calls theInitPort
method on the UART object (gUART
). It passeskSerialBaudRate
as the argument, which likely specifies the baud rate for serial communication.
sBootOptions = console_check_boot_keys();
- Checks for special boot options, possibly based on user input or key combinations during boot.
sBootOptions = console_check_boot_keys();
console_check_boot_keys() = This function is defined at the location at the file src/system/boot/platform/efi/console.cpp
.
uint32
console_check_boot_keys(void)
{
efi_input_key key;
for (int i = 0; i < 3; i++) {
// give the user a chance to press a key
kBootServices->Stall(100000);
efi_status status = kSystemTable->ConIn->ReadKeyStroke(
kSystemTable->ConIn, &key);
if (status != EFI_SUCCESS)
continue;
if (key.UnicodeChar == 0 && key.ScanCode == SCAN_ESC)
return BOOT_OPTION_DEBUG_OUTPUT;
if (key.UnicodeChar == ' ')
return BOOT_OPTION_MENU;
}
return 0;
}
unit32 console_check_boot_keys(void)
- This is a function named
console_check_boot_keys
. It returns auint32
, indicating the type of boot option based on key presses, or 0 if no special keys are pressed.
efi_input_key key;
- Declares a variable named
key
of typeefi_input_key
, which is used to store information about a key press (Unicode character and scan code).
for (int i = 0; i < 3; i++)
- This
for
loops runs three times, giving the user three chances to press a key.
kBootServices→Stall(100000);
- Pauses the execution for a short period (100,000 microseconds = 0.1 seconds) to give the user a chance to press a key. This is achieved using the
Stall
function provided by the EFI Boot Services.
efi_status status = kSystemTable→ConIn→ReadKeyStroke(kSystemTable→ConIn, &key);
- Reads a key stroke from the console input using the
ReadKeyStroke
function provided by the EFI system table. - The key information is stored in the
key
variable.
if (status ≠ EFI_SUCCESS) continue;
- If the key reading operation is not successful, the loop continues to the next iteration.
Key Press Checks:
if (keu.UnicodeChar == 0 && key.ScanCode == SCAN_ESC)
:
- Checks if the pressed key has a Unicode character of 0 (indicating a non-Unicode key) and a scan code corresponding to the Escape key (
SCAN_ESC
). - If true, it returns
BOOT_OPTION_DEBUG_OUTPUT
, indicating a request for debug output during boot.
if (key.UnicodeChar == ‘ ’
:
- Checks if the pressed key is the spacebar.
- If true, it returns
BOOT_OPTION_MENU
, indicating a request for a boot menu.
return 0;
- If no special keys are pressed during the three iterations of the loop, the functions returns 0, indicating that there are no specific boot options requested.
efi_main continues
gKernelArgs.platform_args.apm.version = 0;
Disables Advanced Power Management (APM).
Initialization of CPU, ACPI, Device Tree Blob (if supported), Timer, and SMP:
cpu_init();
acpi_init();
#ifdef _BOOT_FDT_SUPPORT
dtb_init();
#endif
timer_init();
smp_init();
cpu_init()
This function is defined at file location src/system/boot/platform/efi/cpu.cpp
for the efi.
void
cpu_init()
{
gKernelArgs.num_cpus = 1;
// this will eventually be corrected later on
boot_arch_cpu_init();
}
void cpu_init()
- This is a function named
cpu_init
. It returnsvoid
indicating that it doesn't return any value. The function is responsible for initializing CPU-related settings.
gKernelArgs.num_cpu = 1;
- Sets the
num_cpus
field of thegKernelArgs
structure to 1. - This field likely represents the number of CPUs in the system. Setting it to 1 initially suggests that the system is being initialized with a single CPU.
// this will eventually be corrected later on
- This is a comment that the assumption of having only one CPU in the system is temporary and will be corrected later in the initialization process.
- The code might dynamically determine the actual number of CPUs and update
num_cpus
accordingly.
boot_arch_cpu_init();
- Calls the
boot_arch_cpu_init
function. - This function is likely an architecture-specific CPU initialization routine that performs low-level CPU setup based on the underlying hardware architecture.
- This function is defined at the file location
src/system/boot/arch/x86/arch_cpu.cpp
for x86.
extern "C" status_t
boot_arch_cpu_init()
{
// Nothing really to init on x86
return B_OK;
}
- The boot_arch_cpu_init function does nothing just return B_OK indicating function executed successfully.
acpi_init()
This function is defined at file location src/system/boot/platform/bio_ia32/acpi.cpp
.
The acpi_init
function is scanning memory regions for the ACPI RSDP (Root System Description Pointer) signature. It searches for this signature within specified memory ranges (acpi_scan_spots
) and, when found, validates the RSDT (Root System Description Table).
void
acpi_init()
{
efi_guid acpi = ACPI_20_TABLE_GUID;
efi_configuration_table *table = kSystemTable->ConfigurationTable;
size_t entries = kSystemTable->NumberOfTableEntries;
// Try to find the ACPI RSDP.
for (uint32 i = 0; i < entries; i++) {
if (!table[i].VendorGuid.equals(acpi))
continue;
acpi_rsdp *rsdp = (acpi_rsdp *)(table[i].VendorTable);
if (strncmp((char *)rsdp, ACPI_RSDP_SIGNATURE, 8) == 0)
TRACE(("acpi_init: found ACPI RSDP signature at %p\n", rsdp));
if (rsdp != NULL && acpi_check_rsdt(rsdp) == B_OK) {
gKernelArgs.arch_args.acpi_root = rsdp;
arch_handle_acpi();
break;
}
}
}
- Inside the function, an EFI GUID (Globally Unique Identifier) for ACPI tables is defined as
ACPI_20_TABLE_GUID
. This GUID is used to identify ACPI tables in the system configuration table. - The function accesses the EFI system configuration table (
kSystemTable->ConfigurationTable
) to retrieve information about ACPI tables. The number of table entries is obtained fromkSystemTable->NumberOfTableEntries
. - A loop iterates through each entry in the system configuration table to find ACPI tables. It checks if the vendor GUID of each table entry matches the ACPI GUID defined earlier.
- If a matching ACPI table is found, the function attempts to verify the ACPI Root System Description Pointer (RSDP) by checking its signature against
ACPI_RSDP_SIGNATURE
. - If the RSDP is valid and the Root System Description Table (RSDT) is also valid (verified by
acpi_check_rsdt()
), the ACPI root pointer (acpi_root
) is set to the address of the RSDP structure. Additionally, the functionarch_handle_acpi()
is called to further handle ACPI initialization. - The loop terminates once a valid ACPI RSDP and RSDT are found or when all entries in the configuration table have been examined.
arch_handle_acpi()
It does nothing for x86 both efi and bios.
void __attribute__((weak))
arch_handle_acpi()
{
}
dtb_init();
Initializes the Device Tree Blob (conditional compilation based on support). This function is responsible for initializing and processing Device Tree Blobs obtained from the UEFI configuration tables.
#ifdef _BOOT_FDT_SUPPORT
dtb_init();
#endif
This function is not executed for the x86, as it is defined only for non x86 architecture
This is src/system/boot/platform/efi/Jamfile
:
if $(TARGET_ARCH) != x86_64 && $(TARGET_ARCH) != x86 {
defines += _BOOT_FDT_SUPPORT ;
}
This function is defined in the file at location: src/system/boot/platform/efi/dtb.cpp
.
void
dtb_init()
{
efi_configuration_table *table = kSystemTable->ConfigurationTable;
size_t entries = kSystemTable->NumberOfTableEntries;
INFO("Probing for device trees from UEFI...\n");
// Try to find an FDT
for (uint32 i = 0; i < entries; i++) {
if (!table[i].VendorGuid.equals(DEVICE_TREE_GUID))
continue;
void* dtbPtr = (void*)(table[i].VendorTable);
int res = fdt_check_header(dtbPtr);
if (res != 0) {
ERROR("Invalid FDT from UEFI table %d: %s\n", i, fdt_strerror(res));
continue;
}
sDtbTable = dtbPtr;
sDtbSize = fdt_totalsize(dtbPtr);
INFO("Valid FDT from UEFI table %d, size: %" B_PRIu32 "\n", i, sDtbSize);
#ifdef TRACE_DUMP_FDT
dump_fdt(sDtbTable);
#endif
dtb_handle_chosen_node(sDtbTable);
int node = -1;
int depth = -1;
while ((node = fdt_next_node(sDtbTable, node, &depth)) >= 0 && depth >= 0) {
dtb_handle_fdt(sDtbTable, node);
}
break;
}
}
timer_init()
This function is defined in the file at location: src/system/boot/platform/efi/timer.cpp
.
void
timer_init(void)
{
arch_timer_init();
}
This function calls the another function arch_timer_init();
.
arch_timer_init();
This function is defined in a file at location: src/system/boot/platform/efi/arch/x86_64/arch_timer.cpp
.
// src/system/boot/platform/efi/arch/x86_64/arch_timer.cpp
void
arch_timer_init(void)
{
determine_cpu_conversion_factor(2);
hpet_init();
}
→ determine_cpu_conversion_factor(2) = src/system/boot/arch/x86/arch_cpu.cpp
void
determine_cpu_conversion_factor(uint8 channel)
{
// Before using the calibration loop, check if we are on a hypervisor.
cpuid_info info;
// For Hypervisor feature (bit 31 in ECX) is set
if (get_current_cpuid(&info, 1, 0) == B_OK
&& (info.regs.ecx & IA32_FEATURE_EXT_HYPERVISOR) != 0) {
get_current_cpuid(&info, 0x40000000, 0);
const uint32 maxVMM = info.regs.eax;
if (maxVMM >= 0x40000010) {
get_current_cpuid(&info, 0x40000010, 0);
uint64 clockSpeed = uint64(info.regs.eax) * 1000;
gTimeConversionFactor = (uint64(1000) << 32) / info.regs.eax;
gKernelArgs.arch_args.system_time_cv_factor = gTimeConversionFactor;
gKernelArgs.arch_args.cpu_clock_speed = clockSpeed;
dprintf("TSC frequency read from hypervisor CPUID leaf\n");
return;
}
}
calculate_cpu_conversion_factor(channel);
}
cpuid_info info;
- Declared a variable
info
of typecpuid_info
. This structure is likely used to store information obtained from the CPUID instruction.
if (get_current_cpuid(&info, 1, 0) == B_OK && (info.regs.ecx & IA32_FEATURE_EXT_HYPERVISOR) ≠ 0)
- Checks if the CPU supports the hypervisor feature by querying CPUID. If supported, it continues to the next steps.
get_current_cpuid(&infor, 0x40000000, 0);
- Queries the CPUID information with the input values 0x40000000 and 0. This is often used to check the maximum extended CPUID function supported.
const uint32 maxVMM = info.regs.eax;
- Retrieves the maximum extended CPUID function supported by the CPU.
if (maxVMM ≥ 0x40000010)
- Checks if the CPU supports the specific extended CPUID leaf 0x40000010.
get_current_cpuid(&info, 0x40000010, 0);
- Queries the CPUID information with the input values 0x40000010 and 0, obtaining information related to the hypervisor.
uint64 clockSpeed = uint64(info.regs.eax) * 1000;
- Calculates the TSC frequency (time-stamp counter frequency) by multiplying the value obtained from CPUID by 1000.
gTimeConversionFactor = (uint64(1000) << 32) / info.regs.eax;
- Calculates the conversion factor for the TSC. The conversion factor is used to convert TSC ticks to nanoseconds.
gKernelArgs.arch_args.system_time_cv_factor = gTimeConversionFactor;
- Updates a global variable in the kernel arguments with the calculated TSC conversion factor.
gKernelArgs.arch_args.cpu_clock_speed = clockSpeed;
- Updates a global variable in the kernel arguments with the calculated CPU clock speed.
dprintf("TSC frequency read from hypervisor CPUID leaf \n");
- Prints a debug message indicating that the TSC frequency was read from the hypervisor's CPUID leaf.
return;
- Exits the function after successfully obtaining TSC information from the hypervisor.
calculate_cpu_conversion_factor(channel);
- If the system is not running on a hypervisor or if the specific hypervisor leaf is not supported, it falls back to another (
calculate_cpu_conversion_factor
) to determine the TSC conversion factor.
- get_current_cpuid() = This function is defined in a file at a location:
src/system/kernel/arch/x86/64/cpuid.cpp
The purpose of this function is to encapsulate the process of obtaining CPUID information, making the code more readable and potentially allowing for easier modification or extension in the future. The caller of this function would pass the appropriate values for eax
and ecx
to get specific information from the CPU, and the results would ne stored in the provided cpuid_info
structure.
status_t
get_current_cpuid(cpuid_info* info, uint32 eax, uint32 ecx)
{
__cpuid_count(eax, ecx, info->regs.eax, info->regs.ebx, info->regs.ecx,
info->regs.edx);
return B_OK;
}
status_t
: This is a type used to represent the status of an operation. It is assumed that status_t
is a type defined elsewhere in the codebase, likely an enumeration that includes values like B_OK
to indicate success.
get_current_cpuid
: This is the function name it takes three parameters:
cpuid_info info
: A pointer to a structure ('cpuid_info') where the CPUID information will be stored.uint32_t eax
: The value to be passed in theEAX
register when executing the CPUID instruction.uint32_t ecx
: The value to be passed in theECX
register when executing the CPUID instruction.
__cpuid_cound
: This is an intrinsic function used to issue the CPUID instruction. It is typically compiler-specific, and the behavior may vary different compilers. the intrinsic is used here to get information based on the provided values of eax
and ecx
.
info→regs.eax, info→regs.ebx, info→regs.ecx, info→regs.edx
: These are structure members that represent the four registers ('EAX, EBX, ECX, EDX') where the CPUID results will be stored.return B_OK
: The function returns a status code indicating the success of the operation.B_OK
is used here, to represents a successful status.
calculate_cpu_conversion_factor(channel) = This function is defined at location src/system/boot/arch/x86/arch_cpu.cpp
.
Todo explain.
→ hpet_init(void) = This function is defined at location: src/system/boot/arch/x86/arch_hpet.cpp
.
void
hpet_init(void)
{
// Try to find the HPET ACPI table.
TRACE("hpet_init: Looking for HPET...\n");
acpi_hpet *hpet = (acpi_hpet *)acpi_find_table(ACPI_HPET_SIGNATURE);
// Clear hpet kernel args to known invalid state;
gKernelArgs.arch_args.hpet_phys = 0;
gKernelArgs.arch_args.hpet = NULL;
if (hpet == NULL) {
// No HPET table in the RSDT.
// Since there are no other methods for finding it,
// assume we don't have one.
TRACE("hpet_init: HPET not found.\n");
return;
}
TRACE("hpet_init: found HPET at 0x%" B_PRIx64 ".\n",
hpet->hpet_address.address);
gKernelArgs.arch_args.hpet_phys = hpet->hpet_address.address;
gKernelArgs.arch_args.hpet = (void *)mmu_map_physical_memory(
gKernelArgs.arch_args.hpet_phys, B_PAGE_SIZE, kDefaultPageFlags);
}
The HPET (High Precision Event Timer) is a hardware timer that provides a high-resolution time keeping mechanism. Let's break down the code step by step:
TRACE("hpet_init: Looking for HPET…\n");
= This line logs a trace message indicating that the HPET initialization process is attempting to locate the HPET ACPI table.acpi_hpet *hpet = (acpi_hpet*) acpi_find_table(ACPI_HPET_SIGNATURE);
= This line uses the ACPI (Advanced Configuration and Power Interface) subsystem to attempt to find the HPET table. Theacpi_find_table
function is used with the signatureACPI_HPET_SIGNATURE
to search for the HPET table. The result is stored in thehpet
variable.gKernelArgs.arch_args.hpet_phys = 0;
= This line initializes the physical address of the HPET to a known invalid state in case the HPET is not found.gKernelArgs.arch_args.hpet = NULL;
= This line initializes the virtual address of the HPET toNULL
.if (hpet == NULL) {…}
= If the HPET table is not found (hpet is NULL), the code logs a trace message indicating that the HPET is not found and returns from the function.TRACE("hpet_init: found HPET at 0x%" B_PRIx64 “.\n”, hpet→hpet_address.address);
= If the HPET table is found, this line logs a trace message indicating the physical address of the HPET.gKernelArgs.arch_args.hpet_phys = hpet→hpet_address.address;
= This line sets the physical address of the HPET to the value obtained from the ACPI table.gKernelArgs.arch_args.hpet = (void*)mmu_map_physical_memory(gKernelArgs.arch_args.hpet_phys, B_PAGE_SIZE, kDefaultPageFlags);
= This line maps the physical address of the HPET to a virtual address using the memory management unit (mmu_map_physical_memory
). The mapped memory isB_PAGE_SIZE
(page size) in length, andkDefaultPageFlags
represents the default page mapping flags.
smp_init(); = This function is defined at location: src/system/boot/platform/efi/smp.cpp
.
void
smp_init(void)
{
#if NO_SMP
gKernelArgs.num_cpus = 1;
return;
#endif
arch_smp_init();
}
This function calls the arch_smp_init()
.
- arch_smp_init() = This function is defined at location:
src/system/boot/platform/efi/smp.cpp
.
void
arch_smp_init(void)
{
cpuid_info info;
if (get_current_cpuid(&info, 1, 0) != B_OK)
return;
if ((info.eax_1.features & IA32_FEATURE_APIC) == 0) {
// Local APICs aren't present; As they form the basis for all inter CPU
// communication and therefore SMP, we don't need to go any further.
TRACE("no local APIC present, not attempting SMP init\n");
return;
}
// first try to find ACPI tables to get MP configuration as it handles
// physical as well as logical MP configurations as in multiple cpus,
// multiple cores or hyper threading.
if (acpi_do_smp_config() == B_OK) {
TRACE("smp init success\n");
return;
}
// Everything failed or we are not running an SMP system, reset anything
// that might have been set through an incomplete configuration attempt.
gKernelArgs.arch_args.apic_phys = 0;
gKernelArgs.arch_args.ioapic_phys = 0;
gKernelArgs.num_cpus = 1;
}
SMP is a design where two or more identical processors are connected to a single shred main memory and are controlled by a single operating system instance. The goal of this code is to initialize SMP on the system. Let's break down the code step by step:
cpuid_info info;
= This declares a structure (cpuid_info
) to store CPUID information.
if (get_current_cpuid(&info, 1, 0) ≠ B_OK) return;
= This calls a function (get_current_cpuid
) to get information about the CPU using the CPUID instruction. If the operation is not successful (B_OK
is a status indicating success), the function returns early, and SMP initialization is not attempted.
if ((info.eax_1.features & IA32_FEATURE_APIC) == 0) { .. }
= This checks if the Local Advanced Programmable Interrupt Controller (APIC) feature is present. If not, the system doesn't have local APICs, and SMP is not supported. The function logs a trace message and returns early.
if (acpi_do_smp_config() == B_OK) { … }
= This attempts to find ACPI tables to obtain MultiProcessor (MP) configuration information. If successful, it logs a trace message and returns, indicating successful SMP initialization.
gKernelArgs.arch_args.apic_phys = 0;
= This line resets the physical address of the Local APIC.
gKernelArgs.arch_args.ioapic_phys = 0;
= This line resets the physical address of the I/O APIC (Input/Output Advanced Programmable Interrupt Controller).
gKernelArgs.num_cpus = 1;
This line sets the number of CPUs to 1. If SMP initialization fails, the system assumes a single processor configuration.
→ acpi_do_smp_config()
static status_t
acpi_do_smp_config(void)
{
TRACE("smp: using ACPI to detect MP configuration\n");
// reset CPU count
gKernelArgs.num_cpus = 0;
acpi_madt *madt = (acpi_madt *)acpi_find_table(ACPI_MADT_SIGNATURE);
if (madt == NULL) {
TRACE("smp: Failed to find MADT!\n");
return B_ERROR;
}
gKernelArgs.arch_args.apic_phys = madt->local_apic_address;
TRACE("smp: local apic address is 0x%" B_PRIx32 "\n", madt->local_apic_address);
acpi_apic *apic = (acpi_apic *)((uint8 *)madt + sizeof(acpi_madt));
acpi_apic *end = (acpi_apic *)((uint8 *)madt + madt->header.length);
while (apic < end) {
switch (apic->type) {
case ACPI_MADT_LOCAL_APIC:
{
if (gKernelArgs.num_cpus == SMP_MAX_CPUS) {
TRACE("smp: already reached maximum CPUs (%d)\n",
SMP_MAX_CPUS);
break;
}
acpi_local_apic *localApic = (acpi_local_apic *)apic;
TRACE("smp: found local APIC with id %u\n",
localApic->apic_id);
if ((localApic->flags & ACPI_LOCAL_APIC_ENABLED) == 0) {
TRACE("smp: APIC is disabled and will not be used\n");
break;
}
gKernelArgs.arch_args.cpu_apic_id[gKernelArgs.num_cpus]
= localApic->apic_id;
// TODO: how to find out? putting 0x10 in to indicate a local apic
gKernelArgs.arch_args.cpu_apic_version[gKernelArgs.num_cpus]
= 0x10;
gKernelArgs.num_cpus++;
break;
}
case ACPI_MADT_IO_APIC: {
acpi_io_apic *ioApic = (acpi_io_apic *)apic;
TRACE("smp: found io APIC with id %" B_PRIu32 " and address 0x%" B_PRIx32 "\n",
ioApic->io_apic_id, ioApic->io_apic_address);
if (gKernelArgs.arch_args.ioapic_phys == 0)
gKernelArgs.arch_args.ioapic_phys = ioApic->io_apic_address;
break;
}
default:
break;
}
apic = (acpi_apic *)((uint8 *)apic + apic->length);
}
return gKernelArgs.num_cpus > 0 ? B_OK : B_ERROR;
}
acpi_find_table(ACPI_MADT_SIGNATURE)
= This function attempts to find the MADT table in the ACPI tables. The MADT table provides information about multiple APIC (Advanced Programmable Interrupt Controllers) in the system.
gKernelArgs.arch_args.apic_phys = madt→local_apic_address;
= This line sets the physical address of the local APIC. The physical address is obtained from the MADT table.
switch (apic→type) { .. }
= This switch statement processes different types of entries in the MADT. It mainly focuses on two types:
ACPI_MADT_LOCAL_APIC
= Represents a local APIC. It checks if the APIC is enabled and stores its information if the maximum CPU count hasn't been reached.ACPI_MADT_IO_APIC
= Represents an IO APIC. It stores the physical address of the IO APIC if it hasn't been set yet.
gKernelArgs.num_cpus > 0 ? B_OK : B_ERROR;
= The function returns B_OK
if at least one CPU is found; otherwise, it returns B_ERROR
.
main(&args);
This function is defined at location: src/system/boot/loader/main.cpp
.
It takes pointer to stage2_args structure as argument.
// defined at location.
// headers/private/kernel/boot/stage2_args.h
typedef struct stage2_args {
size_t heap_size;
const char **arguments;
int32 arguments_count;
struct platform_stage2_args platform;
} stage2_args ;
// platform_stage2_args is platform specific structure, for efi the structure is given below, defined in file at: headers/private/kernel/boot/platform/efi/platform_stage2_args.h
struct platform_stage2_args {
};