A loader does the hardware initialization and loading of kernel.
Entry Point
The entry Point of the loader is called during the system specific initialization done. The loader entry point is main(stage2_args *args)
. For EFI, it is called from the efi_main at src/system/boot/platform/efi/start.cpp
.
main(stage2_args *args)
This function is defined at location: src/system/boot/loader/main.cpp
.
extern "C" int
main(stage2_args *args)
{
TRACE(("boot(): enter\n"));
if (heap_init(args) < B_OK)
panic("Could not initialize heap!\n");
TRACE(("boot(): heap initialized...\n"));
// set debug syslog default
#if KDEBUG_ENABLE_DEBUG_SYSLOG
gKernelArgs.keep_debug_output_buffer = true;
gKernelArgs.previous_debug_size = true;
// used as a boolean indicator until initialized for the kernel
#endif
add_stage2_driver_settings(args);
platform_init_video();
// the main platform dependent initialisation
// has already taken place at this point.
if (vfs_init(args) < B_OK)
panic("Could not initialize VFS!\n");
dprintf("Welcome to the Haiku boot loader!\n");
dprintf("Haiku revision: %s\n", get_haiku_revision());
bool mountedAllVolumes = false;
BootVolume bootVolume;
PathBlocklist pathBlocklist;
if (get_boot_file_system(args, bootVolume) != B_OK
|| (platform_boot_options() & BOOT_OPTION_MENU) != 0) {
if (!bootVolume.IsValid())
puts("\tno boot path found, scan for all partitions...\n");
if (mount_file_systems(args) < B_OK) {
// That's unfortunate, but we still give the user the possibility
// to insert a CD-ROM or just rescan the available devices
puts("Could not locate any supported boot devices!\n");
}
// ToDo: check if there is only one bootable volume!
mountedAllVolumes = true;
if (user_menu(bootVolume, pathBlocklist) < B_OK) {
// user requested to quit the loader
goto out;
}
}
if (bootVolume.IsValid()) {
// we got a volume to boot from!
// TODO: fix for riscv64
#ifndef __riscv
load_driver_settings(args, bootVolume.RootDirectory());
#endif
status_t status;
while ((status = load_kernel(args, bootVolume)) < B_OK) {
// loading the kernel failed, so let the user choose another
// volume to boot from until it works
bootVolume.Unset();
if (!mountedAllVolumes) {
// mount all other file systems, if not already happened
if (mount_file_systems(args) < B_OK)
panic("Could not locate any supported boot devices!\n");
mountedAllVolumes = true;
}
if (user_menu(bootVolume, pathBlocklist) != B_OK
|| !bootVolume.IsValid()) {
// user requested to quit the loader
goto out;
}
}
// if everything is okay, continue booting; the kernel
// is already loaded at this point and we definitely
// know our boot volume, too
if (status == B_OK) {
if (bootVolume.IsPackaged()) {
packagefs_apply_path_blocklist(bootVolume.SystemDirectory(),
pathBlocklist);
}
register_boot_file_system(bootVolume);
if ((platform_boot_options() & BOOT_OPTION_DEBUG_OUTPUT) == 0)
platform_switch_to_logo();
load_modules(args, bootVolume);
gKernelArgs.ucode_data = NULL;
gKernelArgs.ucode_data_size = 0;
platform_load_ucode(bootVolume);
// TODO: fix for riscv64
#ifndef __riscv
// apply boot settings
apply_boot_settings();
#endif
// set up kernel args version info
gKernelArgs.kernel_args_size = sizeof(kernel_args);
gKernelArgs.version = CURRENT_KERNEL_ARGS_VERSION;
if (gKernelArgs.ucode_data == NULL)
gKernelArgs.kernel_args_size = kernel_args_size_v1;
// clone the boot_volume KMessage into kernel accessible memory
// note, that we need to 8-byte align the buffer and thus allocate
// 7 more bytes
void* buffer = kernel_args_malloc(gBootVolume.ContentSize() + 7);
if (!buffer) {
panic("Could not allocate memory for the boot volume kernel "
"arguments");
}
buffer = (void*)(((addr_t)buffer + 7) & ~(addr_t)0x7);
memcpy(buffer, gBootVolume.Buffer(), gBootVolume.ContentSize());
gKernelArgs.boot_volume = buffer;
gKernelArgs.boot_volume_size = gBootVolume.ContentSize();
platform_cleanup_devices();
// TODO: cleanup, heap_release() etc.
heap_print_statistics();
platform_start_kernel();
}
}
out:
heap_release(args);
return 0;
}
Let's break the code:
heap_init(args)
In the context of operating systems, the heap is a region of a computer's memory that is used for dynamic memory allocation. It is a part of the memory where the program can request memory blocks at runtime, and it is separate from the stack, which is used for function calls and local variables.
status_t
heap_init(stage2_args* args)
{
void* base;
void* top;
if (platform_init_heap(args, &base, &top) < B_OK)
return B_ERROR;
sHeapBase = base;
sHeapEnd = top;
sMaxHeapSize = (uint8*)top - (uint8*)base;
// declare the whole heap as one chunk, and add it
// to the free list
FreeChunk* chunk = (FreeChunk*)base;
chunk->SetTo(sMaxHeapSize);
sFreeChunkTree.Insert(chunk);
sAvailable = chunk->Size();
#ifdef DEBUG_MAX_HEAP_USAGE
sMaxHeapUsage = sMaxHeapSize - sAvailable;
#endif
if (sLargeAllocations.Init(64) != B_OK)
return B_NO_MEMORY;
return B_OK;
}
Function Signature:
status_t heap_init(stage2_args* args)
- This function takes a pointer to
stage2_args
as an argument and returns a status code (status_t
).
Heap Initialization:
if (platform_init_heap(args, &base, &top) < B_OK)
return B_ERROR;
- The heap initialization is platform-specific and is delegated to the
platform_init_heap
function. It initializes the heap and retrieves the base and top addresses. platform_init_heap
is defined at file location:src/system/boot/platform/efi/heap.cpp
extern "C" status_t
platform_init_heap(struct stage2_args *args, void **_base, void **_top)
{
if (kBootServices->AllocatePages(AllocateAnyPages, EfiLoaderData,
STAGE_PAGES, &staging) != EFI_SUCCESS)
return B_NO_MEMORY;
*_base = (void*)staging;
*_top = (void*)((int8*)staging + STAGE_PAGES * B_PAGE_SIZE);
return B_OK;
}
Let's break this code:
extern "C" status_t
platform_init_heap(struct stage2_args *args, void **_base, void **_top)
{
- This function is declared with the
extern “C”
specifier to ensure C linkage, indicating that it can be called from C code or other languages without name mangling. - It returns a
status_t
value, which is a Haiku-specific type for representing success or error conditions. - The function takes three parameters:
struct stage2_args *args
: A pointer to a structure containing arguments for stage 2 bootloader.void **_base
: A pointer to a pointer that will store the base address of the allocated heap.void **_top
: A pointer to a pointer that will store the top address (end) of the allocated heap.
if (kBootServices->AllocatePages(AllocateAnyPages, EfiLoaderData,
STAGE_PAGES, &staging) != EFI_SUCCESS)
return B_NO_MEMORY;
- This block of code uses the EFI Boot Services to allocate a specific number of pages for the initial heap. The
AllocatePages
function is part of the EFI Boot Services interface.AllocateAnyPages
: The types of pages to allocate. This specifies that the EFI firmware can allocate any available pages.EfiLoaderData
: The type of memory to allocate.EfiLoaderData
is used that is likely to persist throughout the entire boot process.STAGE_PAGES
: The number of pages to allocate. The value of “STAGE_PAGES” is 0x2000 = 32 MB&staging
: The address of the allocated memory is stored in thestaging
variable.
- If the allocation is not successful (i.e., if
AllocatePages
does not returnEFI_SUCCESS
), the function returnsB_NO_MEMORY
, indicating a failure to allocate memory.
*_base = (void*)staging;
*_top = (void*)((int8*)staging + STAGE_PAGES * B_PAGE_SIZE);
- If the memory allocation is successful, the function updates the pointers passed as arguments (
_base
and_top
) with the base and top addresses of the allocated memory region.*_base
: Dereferences the pointer to_base
and sets its value to the base address of the allocated memory (staging
).*_top
: Dereferences the pointer to_top
and sets its value to the top address (end) of the allocated memory, calculated by adding the size of the allocated memory region (STAGE_PAGES * B_PAGE_SIZE
) to the base address.
return B_OK;
- Finally, if the heap initialization is successful, the function returns
B_OK
, indicating success.
Global Variable Update:
sHeapBase = base;
sHeapEnd = top;
sMaxHeapSize = (uint8*)top - (uint8*)base;
- Update global variables
sHeapBase
,sHeapEnd
, andsMaxHeapSize
with the values obtained from the platform-specific heap initialization.
Whole Heap as One Chunk:
FreeChunk* chunk = (FreeChunk*)base;
chunk->SetTo(sMaxHeapSize);
sFreeChunkTree.Insert(chunk);
- Treat the entire heap as one free chunk and add it to the free chunk tree. The
FreeChunk
class is likely part of a data structure used to manage free memory blocks.
Update Available Heap Size:
sAvailable = chunk->Size();
- Update the variable
sAvailable
with the size of the initial chunk, representing the available heap space.
Large Allocations Initialization:
if (sLargeAllocations.Init(64) != B_OK)
return B_NO_MEMORY;
- Initialize a data structure (
sLargeAllocations
) for tracking large allocations. The argument64
likely represents some initial capacity or size.
Return Status:
return B_OK;
- Return
B_OK
to indicate successful heap initialization.
Debug Syslog Defaults:
// set debug syslog default
#if KDEBUG_ENABLE_DEBUG_SYSLOG
gKernelArgs.keep_debug_output_buffer = true;
gKernelArgs.previous_debug_size = true;
// used as a boolean indicator until initialized for the kernel
#endif
#if KDEBUG_ENABLE_DEBUG_SYSLOG … #endif
: Block sets debug syslog defaults if debugging is enabled.
add_stage2_driver_setting(args);
add_stage2_driver_settings(args);
- This function is defined at location:
src/system/boot/loader/load_driver_settings.cpp
status_t
add_stage2_driver_settings(stage2_args* args)
{
// TODO: split more intelligently
for (const char** arg = args->arguments;
arg != NULL && args->arguments_count-- && arg[0] != NULL; arg++) {
//dprintf("adding args: '%s'\n", arg[0]);
add_safe_mode_settings((char*)arg[0]);
}
return B_OK;
}
Explanation:
status_t add_stage2_driver_settings(stage2_args* args){
:
- Declares a function names add_stage2_driver_settings that takes a pointer to a stage2_args structure as an argument and returns status_t (Haiku OS status type).
for (const char** arg = args→arguments; arg ≠ NULL && args→arguments_count-- && arg[0] ≠ NULL; arg++) {
:
- Initializes a loop that iterates through the arguments provided to the bootloader.
- The loop condition includes:
arg ≠ NULL
: Ensures that that current pointer is not null.args→arguments_count--
: Decrements the argument count in the stage2_args structure.arg[0] ≠ NULL
: Ensures that the string pointed to by current argument is not null.
add_safe_mode_settings((char*)arg[0]);
:
- Calls a function named add_safe_mode_settings and passes the current argument as a character pointer.
- It's important to note that the cast (char*)arg[0] is used to convert the argument from a constant character pointer to a mutable character pointer.
} return B_OK;
:
- Closes the loop.
- Returns a status_t value indicating success (B_OK). This is a common practice in Haiku OS functions to indicate whether the operation was successful.
The purpose of this function is to process the arguments provided to the bootloader. It iterates through the arguments and, for each argument, calls another function (add_sage_mode_settings
) with the argument as a parameter. The actual functionality of processing the argument is delegated to add_safe_mode_settings
.
add_safe_mode_settings(const char* settings)
status_t
add_safe_mode_settings(const char* settings)
{
if (settings == NULL || settings[0] == '\0')
return B_OK;
size_t length = strlen(settings);
char* buffer = (char*)kernel_args_malloc(length + 1);
if (buffer == NULL)
return B_NO_MEMORY;
driver_settings_file* file = (driver_settings_file*)kernel_args_malloc(
sizeof(driver_settings_file));
if (file == NULL) {
kernel_args_free(buffer);
return B_NO_MEMORY;
}
strlcpy(file->name, B_SAFEMODE_DRIVER_SETTINGS, sizeof(file->name));
memcpy(buffer, settings, length + 1);
file->buffer = buffer;
file->size = length;
// add it to the list
file->next = gKernelArgs.driver_settings;
gKernelArgs.driver_settings = file;
return B_OK;
}
Explanation:
status_t add_safe_mode_settings(const char* settings) {
:
- Declares a function named add_safe_mode_settings that takes a constant character pointer (settings) as an argument and returns a status_t (Haiku OS status type).
if (settings == NULL || settings[0] == ‘\0’) return B_OK;
:
- Checks if the settings string is either null or empty. If so, it returns
B_OK
(success) since there are no settings to add.
size_t length = strlen(settings);
:
- Calculates the length of the settings string using the strlen function.
char* buffer = (char*)kernel_args_malloc(length + 1);
:
- Allocates memory for a buffer to store a copy of the setting. The “+1” is for the null terminator.
if (buffer == NULL) return B_NO_MEMORY;
:
- Checks if the memory allocation for the buffer was successful. If not, it returns “B_NO_MEMORY” to indicate a memory allocation failure.
driver_settings_file* file = (driver_settings_file*) kernel_args_malloc(sizeof(driver_settings_file));
:
- Allocates memory for a structure ("driver_settings_file") to store information about the driver settings file.
if (file == NULL) { kernel_args_free(buffer); return B_NO_MEMORY; }
:
- Checks if the memory allocation for the file structure was successful. If not, it frees the previously allocated buffer and returns “B_NO_MEMORY” to indicate a memory allocation failure.
strlcpy(file→name, B_SAFEMODE_DRIVER_SETTINGS, sizeof(file→name));
:
- Copies the name of the driver settings file ("B_SAFEMODE_DRIVER_SETTINGS") to the "name" field of the file structure using strlcpy.
memcpy(buffer, settings, length + 1);
:
- Copies the settings string to the allocated buffer, including the null terminator.
file→buffer = buffer; file→size = length;
:
- Sets the buffer and size fields in the file structure with the copied settings.
file→next = gKernelArgs.driver_settings;
gKernelArgs.driver_settings = file;
:
- Adds the file structure to the list of driver settings. It sets the "next" field to the current list and updates the list head with the new file structure.
return B_OK
:
- Returns success status (B_OK) to indicate that the settings were successfully added.
platform_init_video()
platform_init_video();
This function is defined at location: src/system/boot/platform/efi/video.cpp
.
extern "C" status_t
platform_init_video(void)
{
list_init(&sModeList);
// we don't support VESA modes
gKernelArgs.vesa_modes = NULL;
gKernelArgs.vesa_modes_size = 0;
gKernelArgs.edid_info = NULL;
// make a guess at the best video mode to use, and save the mode ID for switching to graphics
// mode
efi_status status = kBootServices->LocateProtocol(&sGraphicsOutputGuid, NULL,
(void **)&sGraphicsOutput);
if (sGraphicsOutput == NULL || status != EFI_SUCCESS) {
dprintf("GOP protocol not found\n");
gKernelArgs.frame_buffer.enabled = false;
sGraphicsOutput = NULL;
return B_ERROR;
}
size_t bestArea = 0;
size_t bestDepth = 0;
TRACE(("looking for best graphics mode...\n"));
for (size_t mode = 0; mode < sGraphicsOutput->Mode->MaxMode; ++mode) {
efi_graphics_output_mode_information *info;
size_t size, depth;
sGraphicsOutput->QueryMode(sGraphicsOutput, mode, &size, &info);
size_t area = info->HorizontalResolution * info->VerticalResolution;
TRACE((" mode: %lu\n", mode));
TRACE((" width: %u\n", info->HorizontalResolution));
TRACE((" height: %u\n", info->VerticalResolution));
TRACE((" area: %lu\n", area));
if (info->PixelFormat == PixelRedGreenBlueReserved8BitPerColor) {
depth = 32;
} else if (info->PixelFormat == PixelBlueGreenRedReserved8BitPerColor) {
// seen this in the wild, but acts like RGB, go figure...
depth = 32;
} else if (info->PixelFormat == PixelBitMask
&& info->PixelInformation.RedMask == 0xFF0000
&& info->PixelInformation.GreenMask == 0x00FF00
&& info->PixelInformation.BlueMask == 0x0000FF
&& info->PixelInformation.ReservedMask == 0) {
depth = 24;
} else {
TRACE((" pixel format: %x unsupported\n",
info->PixelFormat));
continue;
}
TRACE((" depth: %lu\n", depth));
video_mode *videoMode = (video_mode*)malloc(sizeof(struct video_mode));
if (videoMode != NULL) {
videoMode->mode = mode;
videoMode->width = info->HorizontalResolution;
videoMode->height = info->VerticalResolution;
videoMode->bits_per_pixel = info->PixelFormat == PixelBitMask ? 24 : 32;
videoMode->bytes_per_row = info->PixelsPerScanLine * depth / 8;
add_video_mode(videoMode);
}
area *= depth;
TRACE((" area (w/depth): %lu\n", area));
if (area >= bestArea) {
TRACE(("selected new best mode: %lu\n", mode));
bestArea = area;
bestDepth = depth;
sGraphicsMode = mode;
}
}
if (bestArea == 0 || bestDepth == 0) {
sGraphicsOutput = NULL;
gKernelArgs.frame_buffer.enabled = false;
return B_ERROR;
}
gKernelArgs.frame_buffer.enabled = true;
sModeChosen = false;
sSettingsLoaded = false;
status = kBootServices->LocateProtocol(&sEdidActiveGuid, NULL, (void **)&sEdidActiveProtocol);
if ((sEdidActiveProtocol != NULL) && (status == EFI_SUCCESS)
&& (sEdidActiveProtocol->SizeOfEdid) != 0) {
edid1_info* edid_info = (edid1_info*)kernel_args_malloc(sizeof(edid1_info));
if (edid_info != NULL) {
edid_decode(edid_info, (edid1_raw*)sEdidActiveProtocol->Edid);
gKernelArgs.edid_info = edid_info;
}
}
return B_OK;
}
This function is responsible for initializing video setting, including choosing the best available graphics mode based on the capabilities of the graphics output protocol. Let's break down each part of the code:
Initialization:
list_init(&sModeList);
gKernelArgs.vesa_modes = NULL;
gKernelArgs.vesa_modes_size = 0;
gKernelArgs.edid_info = NULL;
list_init(&sModeList);
:Initializes the list of video modes.
gKernelArgs.vesa_modes = NULL;
gKernelArgs.vesa_modes_size = 0;
- Clears VESA mode information in kernel arguments.
gKernelArgs.edid_info = NULL;
- Clears EDID information in kernel arguments.
Locate Graphics Output Protocol:
status = kBootServices->LocateProtocol(&sGraphicsOutputGuid, NULL, (void **)&sGraphicsOutput);
status = kBootServices→LocateProtocol(&sGraphicsOutputGuid, NULL, (void**)&sGraphicsOutput);
:
- Attempts to locate the Graphics Output Protocol (GOP) using UEFI services.
Check Graphics Output Protocol:
if (sGraphicsOutput == NULL || status != EFI_SUCCESS) {
dprintf("GOP protocol not found\n");
gKernelArgs.frame_buffer.enabled = false;
sGraphicsOutput = NULL;
return B_ERROR;
}
if (sGraphicOutput == NULL || status ≠ EFI_SUCCESS) { … }
:
- Checks if the Graphics Output Protocol was not found or an error occurred. If so, disables the frame buffer.
Iterate through Graphics Modes:
for (size_t mode = 0; mode < sGraphicsOutput->Mode->MaxMode; ++mode) {
efi_graphics_output_mode_information *info;
size_t size, depth;
sGraphicsOutput->QueryMode(sGraphicsOutput, mode, &size, &info);
- Iterates through available graphics mode using the
QueryMode
function of the GOP.
Check Pixel Format and Handle Supported Formats:
if (info->PixelFormat == PixelRedGreenBlueReserved8BitPerColor) {
depth = 32;
} else if (info->PixelFormat == PixelBlueGreenRedReserved8BitPerColor) {
depth = 32;
} else if (info->PixelFormat == PixelBitMask
&& info->PixelInformation.RedMask == 0xFF0000
&& info->PixelInformation.GreenMask == 0x00FF00
&& info->PixelInformation.BlueMask == 0x0000FF
&& info->PixelInformation.ReservedMask == 0) {
depth = 24;
} else {
TRACE((" pixel format: %x unsupported\n", info->PixelFormat));
continue;
}
- Check the pixel format of each mode and set the depth accordingly. Skip unsupported formats.
Create and Add Video Mode:
video_mode *videoMode = (video_mode*)malloc(sizeof(struct video_mode));
if (videoMode != NULL) {
videoMode->mode = mode;
videoMode->width = info->HorizontalResolution;
videoMode->height = info->VerticalResolution;
videoMode->bits_per_pixel = info->PixelFormat == PixelBitMask ? 24 : 32;
videoMode->bytes_per_row = info->PixelsPerScanLine * depth / 8;
add_video_mode(videoMode);
}
- Allocate memory for a
video_mode
structure, fill it with information, and add it to the list of video modes.
Update Best Graphics Mode:
area *= depth;
if (area >= bestArea) {
bestArea = area;
bestDepth = depth;
sGraphicsMode = mode;
}
- Update the best graphics mode based on the calculated area (width x height x depth).
Check for Suitable Graphics Mode:
if (bestArea == 0 || bestDepth == 0) {
sGraphicsOutput = NULL;
gKernelArgs.frame_buffer.enabled = false;
return B_ERROR;
}
- Check if a suitable graphics mode was found. If not, disable the frame buffer.
Set Frame Buffer as Enabled:
gKernelArgs.frame_buffer.enabled = true;
sModeChosen = false;
sSettingsLoaded = false;
- Set frame buffer as enabled and reset mode and settings flags.
Locate Active EDID Protocol:
status = kBootServices->LocateProtocol(&sEdidActiveGuid, NULL, (void **)&sEdidActiveProtocol);
- Attempt to locate the Active EDID Protocol.
Check and Retrieve EDID Information:
if ((sEdidActiveProtocol != NULL) && (status == EFI_SUCCESS) && (sEdidActiveProtocol->SizeOfEdid) != 0) {
edid1_info* edid_info = (edid1_info*)kernel_args_malloc(sizeof(edid1_info));
if (edid_info != NULL) {
edid_decode(edid_info, (edid1_raw*)sEdidActiveProtocol->Edid);
gKernelArgs.edid_info = edid_info;
}
}
- Check if the Active EDID Protocol was found and retrieve EDID information.
vfs_init(args)
This function is defined at location: src/system/boot/loader/vfs.cpp
.
if (vfs_init(args) < B_OK)
panic("Could not initialize VFS!\n");
The vfs_init function is responsible for initializing the Virtual File System (VFS) in the second stage of the Haiku operating system boot loader. It creates an instance of the RootFileSystem
and returns an appropriate status code based on the success or failure of the memory allocation for the root file system.
status_t
vfs_init(stage2_args *args)
{
gRoot = new(nothrow) RootFileSystem();
if (gRoot == NULL)
return B_NO_MEMORY;
return B_OK;
}
Root File System Initialization:
gRoot = new(nothrow) RootFileSystem();
new (nothrow)
: Allocates memory for aRootFileSystem
object. Thenothrow
specifier is used so that if the allocation fails, it returns anullptr
instead of throwing an exception.
Check for Memory Allocation Failure:
if (gRoot == NULL)
return B_NO_MEMORY;
- Check if the memory allocation for
RootFileSystem
was successful. If not, return an error code indicating memory allocation failure (B_NO_MEMORY)
Return Success:
return B_OK;
- If the memory allocation was successful, return an OK status (B_OK).
Welcome Messages
dprintf("Welcome to the Haiku boot loader!\n");
dprintf("Haiku revision: %s\n", get_haiku_revision());
These lines of code is used for debugging, they print the messages into the log file.
Boot Volume and FileSystem Initialization
bool mountedAllVolumes = false;
BootVolume bootVolume;
PathBlocklist pathBlocklist;
if (get_boot_file_system(args, bootVolume) != B_OK
|| (platform_boot_options() & BOOT_OPTION_MENU) != 0) {
if (!bootVolume.IsValid())
puts("\tno boot path found, scan for all partitions...\n");
if (mount_file_systems(args) < B_OK) {
// That's unfortunate, but we still give the user the possibility
// to insert a CD-ROM or just rescan the available devices
puts("Could not locate any supported boot devices!\n");
}
// ToDo: check if there is only one bootable volume!
mountedAllVolumes = true;
if (user_menu(bootVolume, pathBlocklist) < B_OK) {
// user requested to quit the loader
goto out;
}
}
Let's break the code:
Variable Initialization:
bool mountedAllVolumes = false;
BootVolume bootVolume;
PathBlocklist pathBlocklist;
mountedAllVolumes
= A boolean flag indicating if all volumes have been mounted.bootVolume
= An instance of theBootVolume
class representing the selected boot volume.pathBlocklist
= An instance of thePathBlocklist
class.
Boot FileSystem Detection:
if (get_boot_file_system(args, bootVolume) != B_OK || (platform_boot_options() & BOOT_OPTION_MENU) != 0) {
- Check if the boot file system can be detected.
- If detection fails or a specific boot option is set (e.g., menu), executes the following block.
get_boot_file_system(stage2_args* args, BootVolume& _bootVolume)
:
/*! Gets the boot device, scans all of its partitions, gets the
boot partition, and mounts its file system.
\param args The stage 2 arguments.
\param _bootVolume On success set to the boot volume.
\return \c B_OK on success, another error code otherwise.
*/
status_t
get_boot_file_system(stage2_args* args, BootVolume& _bootVolume)
{
status_t error = platform_add_boot_device(args, &gBootDevices);
if (error != B_OK)
return error;
NodeIterator iterator = gBootDevices.GetIterator();
while (iterator.HasNext()) {
Node *device = iterator.Next();
error = add_partitions_for(device, false, true);
if (error != B_OK)
continue;
NodeList bootPartitions;
error = platform_get_boot_partitions(args, device, &gPartitions, &bootPartitions);
if (error != B_OK)
continue;
NodeIterator partitionIterator = bootPartitions.GetIterator();
while (partitionIterator.HasNext()) {
Partition *partition = (Partition*)partitionIterator.Next();
Directory *fileSystem;
error = partition->Mount(&fileSystem, true);
if (error != B_OK) {
// this partition doesn't contain any known file system; we
// don't need it anymore
gPartitions.Remove(partition);
delete partition;
continue;
}
// init the BootVolume
error = _bootVolume.SetTo(fileSystem);
if (error != B_OK)
continue;
sBootDevice = device;
return B_OK;
}
}
return B_ERROR;
}
Let's break down the code:
status_t get_boot_file_system(stage2_args* args, BootVolume& _bootVolume)
- Parameters:
args
: A pointer to an object of typestage2_args
._bootVolume
: A reference to an object of typeBootVolume
, which represents the boot volume.
- Return Type:
status_t
- A type commonly used in Haiku OS for error handling.B_OK
indicates success, while other values represent specific error codes.
status_t error = platform_add_boot_device(args, &gBootDevices);
if (error != B_OK)
return error;
- Calls a function
platform_add_boot_device
to initialize boot devices and populategBootDevices
with the available devices. - If an error occurs the function returns the error code.
- This function body is explained below.
platform_add_boot_device(…)
This function is defined at location: src/system/boot/platform/efi/devices.cpp
.
status_t
platform_add_boot_device(struct stage2_args *args, NodeList *devicesList)
{
TRACE("%s: called\n", __func__);
efi_block_io_protocol *blockIo;
size_t memSize = 0;
// Read to zero sized buffer to get memory needed for handles
if (kBootServices->LocateHandle(ByProtocol, &BlockIoGUID, 0, &memSize, 0)
!= EFI_BUFFER_TOO_SMALL)
panic("Cannot read size of block device handles!");
uint32 noOfHandles = memSize / sizeof(efi_handle);
efi_handle handles[noOfHandles];
if (kBootServices->LocateHandle(ByProtocol, &BlockIoGUID, 0, &memSize,
handles) != EFI_SUCCESS)
panic("Failed to locate block devices!");
// All block devices has one for the disk and one per partition
// There is a special case for a device with one fixed partition
// But we probably do not care about booting on that kind of device
// So find all disk block devices and let Haiku do partition scan
for (uint32 n = 0; n < noOfHandles; n++) {
if (kBootServices->HandleProtocol(handles[n], &BlockIoGUID,
(void**)&blockIo) != EFI_SUCCESS)
panic("Cannot get block device handle!");
TRACE("%s: %p: present: %s, logical: %s, removeable: %s, "
"blocksize: %" PRIu32 ", lastblock: %" PRIu64 "\n",
__func__, blockIo,
blockIo->Media->MediaPresent ? "true" : "false",
blockIo->Media->LogicalPartition ? "true" : "false",
blockIo->Media->RemovableMedia ? "true" : "false",
blockIo->Media->BlockSize, blockIo->Media->LastBlock);
if (!blockIo->Media->MediaPresent || blockIo->Media->LogicalPartition)
continue;
// The qemu flash device with a 256K block sizes sometime show up
// in edk2. If flash is unconfigured, bad things happen on arm.
// edk2 bug: https://bugzilla.tianocore.org/show_bug.cgi?id=2856
// We're not ready for flash devices in efi, so skip anything odd.
if (blockIo->Media->BlockSize > 8192)
continue;
EfiDevice *device = new(std::nothrow)EfiDevice(blockIo);
if (device == NULL)
panic("Can't allocate memory for block devices!");
devicesList->Insert(device);
}
return devicesList->Count() > 0 ? B_OK : B_ENTRY_NOT_FOUND;
}
- Variable Declaration: Declares variables, including a pointer to
blockIo
of typeefi_block_io_protocol
and a variablememSize
of typesize_t
. - Handle Size Determination: The code uses
LocateHandle
function to determine the size of the buffer needed to store handles for devices that support theBlockIo
protocol. If the function fails withEFI_BUFFER_TOO_SMALL
, it means the buffer size is not sufficient and the program panics. - Handle Retrieval: Allocates an array
handles
of typeefi_handle
with the appropriate size and usesLocateHandle
again to retrieve the actual handles inot this array. - Loop Through Handles: Iterates through each handle and retrieves the corresponding
BlockIo
protocol for each handle. - Debug Print: Prints information about the block device, such as whether it's present, logical, removable, block size, etc.
- Device Filtering: Skips further processing if the block device is not present or is a logical partition.
- Device Size Filtering: Skips block devices with a block size greater than 8192, potentially excluding flash devices.
- Device Instantiation: Creates an
EfiDevice
object with the obtainedblockIo
and adds it to thedeviceList
. - Return Status: Returns
B_OK
if at least one device was added to the list, otherwise returnsB_ENTRY_NOT_FOUND
.
NodeIterator iterator = gBootDevices.GetIterator();
while (iterator.HasNext()) {
Node *device = iterator.Next();
- Uses an iterator to loop over each boot device.
error = add_partitions_for(device, false, true);
if (error != B_OK)
continue;
- Calls
add_partitions_for
function to add partitions for the current device. - If an error occurs, it continue to the next iteration.
Node *device = iterator.Next();
error = platform_get_boot_partitions(args, device, &gPartitions, &bootPartitions);
if (error != B_OK)
continue;
- Calls
platform_get_boot_partitions
to retrieve boot partitions for the current device. - If an error occurs, it continues to the next iteration.
NodeIterator partitionIterator = bootPartitions.GetIterator();
while (partitionIterator.HasNext()) {
Partition *partition = (Partition*)partitionIterator.Next();
- Iterates over each boot partition.
Directory *fileSystem;
error = partition->Mount(&fileSystem, true);
if (error != B_OK) {
// Handle the case where the partition doesn't contain a known file system.
gPartitions.Remove(partition);
delete partition;
continue;
}
- Calls the
Mount
method on each partition to mount its file system. - If an error occurs, it removes the partition from the list and continues to the next iteration.
error = _bootVolume.SetTo(fileSystem);
if (error != B_OK)
continue;
- Initializes the
BootVolume
object with the mounted file system. - If an error occurs, it continues to the next iteration.
sBootDevice = device;
return B_OK;
- Sets a global variable,
sBootDevice
to the current device. - Returns
B_OK
to indicate successful completion.
return B_ERROR;
- If no boot volume is found, the function returns an error code (B_ERROR).
platform_boot_options()
extern "C" uint32
platform_boot_options()
{
return sBootOptions;
}
- This function is defined at location:
src/system/boot/platform/efi/start.cpp
. - This function returns the sBootOptions, which is set in the
efi_main
function insrc/system/boot/platform/efi/start.cpp
file. When user press dedicated key during booting.
Handling Missing Boot Path:
if (!bootVolume.IsValid())
puts("\tno boot path found, scan for all partitions...\n");
- If the detected boot volume is not valid, print a message indicating that no boot path is found.
Mounting File Systems:
if (mount_file_systems(args) < B_OK) {
// Unfortunate situation; still allow the user to insert a CD-ROM or rescan devices.
puts("Could not locate any supported boot devices!\n");
}
- Attempt to mount file systems. If unsuccessful, print a message indicating the failure.
User Menu Interaction
mountedAllVolumes = true;
if (user_menu(bootVolume, pathBlocklist) < B_OK) {
// user requested to quit the loader
goto out;
}
- Set
mountedAllVolumes
totrue
. - Display a user menu, allowing interaction with the boot loader.
- If the user chooses to quit, exit the loader
user_menu(BootVolume& _bootVolume, PathBlocklist& _pathBlocklist)
status_t
user_menu(BootVolume& _bootVolume, PathBlocklist& _pathBlocklist)
{
Menu* menu = new(std::nothrow) Menu(MAIN_MENU);
sMainMenu = menu;
sBootVolume = &_bootVolume;
sPathBlocklist = &_pathBlocklist;
Menu* safeModeMenu = NULL;
Menu* debugMenu = NULL;
MenuItem* item;
TRACE(("user_menu: enter\n"));
// Add boot volume
menu->AddItem(item = new(std::nothrow) MenuItem("Select boot volume/state",
add_boot_volume_menu()));
// Add safe mode
menu->AddItem(item = new(std::nothrow) MenuItem("Select safe mode options",
safeModeMenu = add_safe_mode_menu()));
// add debug menu
menu->AddItem(item = new(std::nothrow) MenuItem("Select debug options",
debugMenu = add_debug_menu()));
// Add platform dependent menus
platform_add_menus(menu);
menu->AddSeparatorItem();
menu->AddItem(item = new(std::nothrow) MenuItem("Reboot"));
item->SetTarget(user_menu_reboot);
item->SetShortcut('r');
menu->AddItem(item = new(std::nothrow) MenuItem("Continue booting"));
if (!_bootVolume.IsValid()) {
item->SetLabel("Cannot continue booting (Boot volume is not valid)");
item->SetEnabled(false);
menu->ItemAt(0)->Select(true);
} else
item->SetShortcut('b');
menu->Run();
apply_safe_mode_options(safeModeMenu);
apply_safe_mode_options(debugMenu);
apply_safe_mode_path_blocklist();
add_safe_mode_settings(sSafeModeOptionsBuffer.String());
delete menu;
TRACE(("user_menu: leave\n"));
sMainMenu = NULL;
sBlocklistRootMenu = NULL;
sBootVolume = NULL;
sPathBlocklist = NULL;
return B_OK;
}
Let's break this code:
status_t user_menu(BootVolume& _bootVolume, PathBlocklist& _pathBlocklist)
- Parameters:
_bootVolume
: A reference to and object of typeBootVolume
._pathBlocklist
: A reference to an object of typePathBlocklist
.
- Return Type:
status_t
- A type commonly used in Haiku OS for error handling.B_OK
indicate success, while other values represent specific error codes.
Menu* menu = new(std::nothrow) Menu(MAIN_MENU);
- Creates a new menu object, possibly allocating memory dynamically.
MAIN_MENU
is presumably an enumeration or constant defining the type of menu.
sMainMenu = menu;
sBootVolume = &_bootVolume;
sPathBlocklist = &_pathBlocklist;
- Assigns global variables, possibly for later reference throughout the program.
Menu* safeModeMenu = NULL;
Menu* debugMenu = NULL;
- Initializes pointers for safe mode and debug menus.
menu->AddItem(item = new(std::nothrow) MenuItem("Select boot volume/state", add_boot_volume_menu()));
menu->AddItem(item = new(std::nothrow) MenuItem("Select safe mode options", safeModeMenu = add_safe_mode_menu()));
menu->AddItem(item = new(std::nothrow) MenuItem("Select debug options", debugMenu = add_debug_menu()));
- Creates menu items with associated submenus using functions like
add_boot_volume_menu
,add_safe_mode_menu
, andadd_debug_menu
.
platform_add_menus(menu);
- Calls a platform-specific function to add additional menus.
menu->AddSeparatorItem();
- Adds a separator item to the main menu.
menu->AddItem(item = new(std::nothrow) MenuItem("Reboot"));
menu->AddItem(item = new(std::nothrow) MenuItem("Continue booting"));
- Adds menu items for rebooting and continuing booting.
- Disables the “Continue booting” option and provides a label if the boot volume is not valid.
menu->Run();
- Executes the menu, allowing the user to interact with it.
apply_safe_mode_options(safeModeMenu);
apply_safe_mode_options(debugMenu);
apply_safe_mode_path_blocklist();
- Calls functions to apply safe mode options and path blocklists.
delete menu;
- Deletes the menu object to free up memory.
sMainMenu = NULL;
sBlocklistRootMenu = NULL;
sBootVolume = NULL;
sPathBlocklist = NULL;
return B_OK;
- Resets global variables to
NULL
and returnsB_OK
to indicate successful completion.