Loader in Haiku Continued

Introduction

In this chapter we complete the remaining code part of the loader.

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();
		}
	}

Boot Volume Validation

if (bootVolume.IsValid()) {
   // code inside this block executes if a valid boot volume is present
}
  • Checks if bootVolume is valid. If it is, the code proceeds to boot from it.

Load Driver Settings

#ifndef __riscv
   load_driver_settings(args, bootVolume.RootDirectory());
#endif
  • Loads driver settings from the root directory of the boot volume. This section is excluded for the RISC-V architecture.
  • This function is defined at: src/system/boot/loader/load_driver_settings.cpp.

load_driver_settings(…)

status_t
load_driver_settings(stage2_args* /*args*/, Directory* volume)
{
	int fd = open_from(volume, "home/config/settings/kernel/drivers", O_RDONLY);
	if (fd < B_OK)
		return fd;

	Directory* settings = (Directory*)get_node_from(fd);
	if (settings == NULL)
		return B_ENTRY_NOT_FOUND;

	void* cookie;
	if (settings->Open(&cookie, O_RDONLY) == B_OK) {
		char name[B_FILE_NAME_LENGTH];
		while (settings->GetNextEntry(cookie, name, sizeof(name)) == B_OK) {
			if (!strcmp(name, ".") || !strcmp(name, ".."))
				continue;

			status_t status = load_driver_settings_file(settings, name);
			if (status != B_OK)
				dprintf("Could not load \"%s\" error %" B_PRIx32 "\n", name, status);
		}

		settings->Close(cookie);
	}

	return B_OK;
}

Let's break down the code:

Function Signature

status_t load_driver_settings(stage2_args* /*args*/, Directory* volume)
  • Parameters:
    • args : A pointer to an object of type stage2_args. (Note: Not used in the function)
    • volume : A pointer to a Directory object representing the volume from which driver settings should be loaded.
  • 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.

Open the Driver Settings File:

int fd = open_from(volume, "home/config/settings/kernel/drivers", O_RDONLY);
if (fd < B_OK)
    return fd;
  • Attempts to open the driver settings file located at “home/config/settings/kernel/drivers” on the specified volume.
  • If unsuccessful, returns the error code.

Get the Settings Directory:

Directory* settings = (Directory*)get_node_from(fd);
if (settings == NULL)
    return B_ENTRY_NOT_FOUND;
  • Retrieves the Directory object corresponding to the opened file descriptor (fd).
  • If the directory is not found, returns B_ENTRY_NOT_FOUND.

Iterate Over Entries in the Settings Directory:

void* cookie;
if (settings->Open(&cookie, O_RDONLY) == B_OK) {
    char name[B_FILE_NAME_LENGTH];
    while (settings->GetNextEntry(cookie, name, sizeof(name)) == B_OK) {
        // ...
    }
    settings->Close(cookie);
}
  • Opens the setting directory for reading and initializes an iteration cookie.
  • Iterates over entries in the directory.

Load Driver Settings From Each File:

if (!strcmp(name, ".") || !strcmp(name, ".."))
    continue;

status_t status = load_driver_settings_file(settings, name);
if (status != B_OK)
    dprintf("Could not load \"%s\" error %" B_PRIx32 "\n", name, status);
  • Skips entries with the name “.” and “..”.
  • Calls load_driver_settings_file function to load settings from each file.
  • Prints an error message if the loading fails.

Close the Settings Directory and Return Success:

settings->Close(cookie);
return B_OK;

Load Kernel in Loop

status_t status;
while ((status = load_kernel(args, bootVolume)) < B_OK) {
   // loading the kernel failed
   // let the user choose another volume to boot from until it works
   bootVolume.Unset();

   // code to mount file systems if not already happened
   // and prompt the user to choose another volume
   // ...
}
  • Uses a loop to attempt loading the kernel from the boot volume.
  • If loading fails, the loop allows the user to choose another volume until successful.
  • load_kernel(args, bootVolume) function is defined at location: src/system/boot/loader/loader.cpp.
status_t
load_kernel(stage2_args* args, BootVolume& volume)
{
	const char *name;
	int fd = find_kernel(volume, &name);
	if (fd < B_OK)
		return fd;

	dprintf("load kernel %s...\n", name);

	elf_init();
	preloaded_image *image;
	status_t status = elf_load_image(fd, &image);

	close(fd);

	if (status < B_OK) {
		dprintf("loading kernel failed: %" B_PRIx32 "!\n", status);
		return status;
	}

	gKernelArgs.kernel_image = image;

	status = elf_relocate_image(gKernelArgs.kernel_image);
	if (status < B_OK) {
		dprintf("relocating kernel failed: %" B_PRIx32 "!\n", status);
		return status;
	}

	gKernelArgs.kernel_image->name = kernel_args_strdup(name);

	return B_OK;
}

Find Kernel and Open File Descriptor:

const char *name;
int fd = find_kernel(volume, &name);
if (fd < B_OK)
    return fd;
  • Calls find_kernel function to locate the kernel on the specified BootVolume.
  • If unsuccessful (returns an error code), the function returns the error.

Print Debug Message:

dprintf("load kernel %s...\n", name);

// Ouput = load kernel kernel_x86_64...
  • Prints a debug message indicating that the kernel is being loaded.

Initialize ELF and Load Kernel Image:

elf_init();
preloaded_image *image;
status_t status = elf_load_image(fd, &image);
close(fd);
  • Initializes ELF (Executable and Linkable Format) processing.
  • Calls elf_load_image to load the kernel image from the file descriptor (fd).
  • Closes the file descriptor after loading.

Handle Loading Errors:

if (status < B_OK) {
    dprintf("loading kernel failed: %" B_PRIx32 "!\n", status);
    return status;
}
  • Prints an error message and returns the error code if loading the kernel image fails.

Set Kernel Image in Global Structure:

gKernelArgs.kernel_image = image;
  • Sets the kernel image in the global gKernelArgs structure.

Relocate Kernel Image:

status = elf_relocate_image(gKernelArgs.kernel_image);
if (status < B_OK) {
    dprintf("relocating kernel failed: %" B_PRIx32 "!\n", status);
    return status;
}
  • Calls elf_relocate_image to relocate the loaded kernel image.
  • Prints an error message and returns the error code if relocation fails.

Set Kernel Image Name in Global Structure:

gKernelArgs.kernel_image->name = kernel_args_strdup(name);
  • Allocates memory for and sets the name of the kernel image in the global gKernelArgs structure.

Return Success:

return B_OK;
  • Returns B_OK to indicate successful loading and relocation of the kernel image.

Continue Booting

if (status == B_OK) {
   // code to continue booting after successful kernel loading
   // ...
}
  • If kernel loading is successful, it proceeds to continue the boot process.

Handle Packaged Boot Volume

if (bootVolume.IsPackaged()) {
   packagefs_apply_path_blocklist(bootVolume.SystemDirectory(), pathBlocklist);
}
  • If the boot volume is packaged, it applied a path blocklist to the system directory.

Register Boot File System

register_boot_file_system(bootVolume);
  • Registers the boot file system.

Switch to Logo or Debug Output

if ((platform_boot_options() & BOOT_OPTION_DEBUG_OUTPUT) == 0)
   platform_switch_to_logo();
  • Switches to logo display unless debug output is requested.

Load Modules

load_modules(args, bootVolume);
  • Load modules needed for the operating system.

Load Microcode

gKernelArgs.ucode_data = NULL;
gKernelArgs.ucode_data_size = 0;
platform_load_ucode(bootVolume);
  • Prepares to load microcode. Sets microcode data and size to zero and calls a platform-specific function to load microcode from the boot volume.

Apply Boot Settings

#ifndef __riscv
   apply_boot_settings();
#endif
  • Applies boot settings. This section is excluded for the RISC-V architecture.

Set Up Kernel Args Version Info

#ifndef __riscv
   apply_boot_settings();
#endif
  • Sets up version information for kernel arguments.

Clone Boot Volume KMessage into Kernel Accessible Memory

void* buffer = kernel_args_malloc(gBootVolume.ContentSize() + 7);
// ...
memcpy(buffer, gBootVolume.Buffer(), gBootVolume.ContentSize());
gKernelArgs.boot_volume = buffer;
gKernelArgs.boot_volume_size = gBootVolume.ContentSize();
  • Allocates memory for boot volume kernel arguments, aligns the buffer, and clones the boot volume information into this memory.

Cleanup Devices and Start Kernel

platform_cleanup_devices();
// TODO: cleanup, heap_release() etc.
heap_print_statistics();
platform_start_kernel();
  • Cleans up devices, prints heap statistics, and starts the kernel.