Loader in Haiku

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  the staging variable.
  • If the allocation is not successful (i.e., if AllocatePages does not return EFI_SUCCESS), the function returns B_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, and sMaxHeapSize 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 argument 64 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 a RootFileSystem object. The nothrow specifier is used so that if the allocation fails, it returns a nullptr 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 the BootVolume class representing the selected boot volume.
  • pathBlocklist = An instance of the PathBlocklist 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 type stage2_args.
    • _bootVolume : A reference to an object of type BootVolume, 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 populate gBootDevices 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 type efi_block_io_protocol and a variable memSize of type size_t.
  • Handle Size Determination: The code uses LocateHandle function to determine the size of the buffer needed to store handles for devices that support the BlockIo protocol. If the function fails with EFI_BUFFER_TOO_SMALL, it means the buffer size is not sufficient and the program panics.
  • Handle Retrieval: Allocates an array handles of type efi_handle with the appropriate size and uses LocateHandle 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 obtained blockIo and adds it to the deviceList.
  • Return Status: Returns B_OK if at least one device was added to the list, otherwise returns B_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 in src/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 to true.
  • Display a user menu, allowing interaction with the boot loader.
  • If the user chooses to quit, exit the loader
image-37.png

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 type BootVolume.
    • _pathBlocklist : A reference to an object of type PathBlocklist.
  • 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, and add_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 returns B_OK to indicate successful completion.