toaruos/boot/multiboot.c

617 lines
17 KiB
C
Raw Normal View History

/**
* @brief Main bootloader logic.
*
* Does all the heavy lifting after configuration options have
* been selected by the user. Loads the kernel and ramdisk,
* sets up multiboot structures, and jumps to the kernel.
*
* @copyright
* This file is part of ToaruOS and is released under the terms
* of the NCSA / University of Illinois License - see LICENSE.md
* Copyright (C) 2018-2021 K. Lange
*/
2021-06-14 05:11:37 +03:00
#include <stdint.h>
#include <stddef.h>
#include "multiboot.h"
#include "text.h"
#include "util.h"
#include "menu.h"
#include "elf.h"
#include "options.h"
#include "iso9660.h"
2021-09-07 05:45:58 +03:00
#include "kbd.h"
2021-06-14 05:11:37 +03:00
2021-10-26 10:13:50 +03:00
extern void draw_logo(int);
2021-06-14 05:11:37 +03:00
char * kernel_load_start = 0;
mboot_mod_t modules_mboot[1] = {
{0,0,0,1}
};
2021-09-07 05:45:58 +03:00
static struct multiboot multiboot_header = {
2021-06-14 05:11:37 +03:00
/* flags; */ MULTIBOOT_FLAG_CMDLINE | MULTIBOOT_FLAG_MODS | MULTIBOOT_FLAG_MEM | MULTIBOOT_FLAG_MMAP | MULTIBOOT_FLAG_LOADER,
/* mem_lower; */ 0x100000,
/* mem_upper; */ 0x640000,
/* boot_device; */ 0,
/* cmdline; */ 0,
/* mods_count; */ 1,
/* mods_addr; */ 0,
/* num; */ 0,
/* size; */ 0,
/* addr; */ 0,
/* shndx; */ 0,
/* mmap_length; */ 0,
/* mmap_addr; */ 0,
/* drives_length; */ 0,
/* drives_addr; */ 0,
/* config_table; */ 0,
/* boot_loader_name; */ 0,
/* apm_table; */ 0,
/* vbe_control_info; */ 0,
/* vbe_mode_info; */ 0,
/* vbe_mode; */ 0,
/* vbe_interface_seg; */ 0,
/* vbe_interface_off; */ 0,
/* vbe_interface_len; */ 0,
};
static uintptr_t ramdisk_off = 0;
static uintptr_t ramdisk_len = 0;
uintptr_t final_offset = 0;
2021-09-07 05:45:58 +03:00
uintptr_t _xmain = 0;
int disable_kaslr = 0;
2021-06-14 05:11:37 +03:00
2023-11-10 10:34:54 +03:00
static inline uint64_t read_tsc(void) {
uint32_t lo, hi;
asm volatile ( "rdtsc" : "=a"(lo), "=d"(hi) );
return ((uint64_t)hi << 32) | (uint64_t)lo;
}
2021-10-20 05:59:14 +03:00
static int load_aout(uint32_t * hdr) {
uintptr_t base_offset = (uintptr_t)hdr - (uintptr_t)kernel_load_start;
uintptr_t hdr_offset = hdr[3] - base_offset;
2023-11-10 10:34:54 +03:00
uint32_t rando = 0;
size_t xtra = 0;
if (!disable_kaslr) {
asm volatile ( "rdtsc" : "=a"(rando), "=d"((uint32_t){0}) );
xtra = (rando & 0xFF) << 12;
}
2021-10-20 05:59:14 +03:00
2023-11-10 09:40:41 +03:00
memcpy((void*)(uintptr_t)hdr[4] + xtra, kernel_load_start + (hdr[4] - hdr_offset), (hdr[5] - hdr[4]));
memset((void*)(uintptr_t)hdr[5] + xtra, 0, (hdr[6] - hdr[5]));
_xmain = (uintptr_t)hdr[7] + xtra;
2021-10-20 05:59:14 +03:00
2023-11-10 09:40:41 +03:00
if (hdr[6] + xtra > final_offset) final_offset = hdr[6] + xtra;
2021-10-20 05:59:14 +03:00
final_offset = (final_offset & ~(0xFFF)) + ((final_offset & 0xFFF) ? 0x1000 : 0);
2021-06-14 05:11:37 +03:00
2021-10-20 05:59:14 +03:00
return 1;
}
static int load_elf32(Elf32_Header * header) {
2021-06-14 05:11:37 +03:00
if (header->e_ident[0] != ELFMAG0 ||
header->e_ident[1] != ELFMAG1 ||
header->e_ident[2] != ELFMAG2 ||
header->e_ident[3] != ELFMAG3) {
print_("Not a valid ELF32.\n");
return 0;
}
uintptr_t entry = (uintptr_t)header->e_entry;
for (uintptr_t x = 0; x < (uint32_t)header->e_phentsize * header->e_phnum; x += header->e_phentsize) {
Elf32_Phdr * phdr = (Elf32_Phdr *)(kernel_load_start + header->e_phoff + x);
if (phdr->p_type == PT_LOAD) {
memcpy((uint8_t*)(uintptr_t)phdr->p_vaddr, kernel_load_start + phdr->p_offset, phdr->p_filesz);
uintptr_t r = phdr->p_filesz;
while (r < phdr->p_memsz) {
*(char *)(phdr->p_vaddr + r) = 0;
r++;
}
if (phdr->p_vaddr + r > final_offset) final_offset = phdr->p_vaddr + r;
}
}
_xmain = entry;
/* Round final offset */
final_offset = (final_offset & ~(0xFFF)) + ((final_offset & 0xFFF) ? 0x1000 : 0);
2021-06-14 06:06:31 +03:00
print("Loaded with end at 0x"); print_hex(final_offset); print("\n");
2021-06-14 05:11:37 +03:00
return 1;
}
2021-10-20 05:59:14 +03:00
static int load_kernel(void) {
clear();
/* Check for Multiboot header */
for (uintptr_t x = 0; x < 8192; x += 4) {
uint32_t * check = (uint32_t *)(kernel_load_start + x);
if (*check == 0x1BADB002) {
if (check[1] & (1 << 16)) {
return load_aout(check);
} else {
return load_elf32((void*)kernel_load_start);
}
}
}
return 0;
}
2021-09-07 05:45:58 +03:00
static void relocate_ramdisk(mboot_mod_t * mboot_mods) {
2021-06-14 05:11:37 +03:00
char * dest = (char*)final_offset;
char * src = (char*)ramdisk_off;
for (size_t s = 0; s < ramdisk_len; ++s) {
dest[s] = src[s];
}
2021-09-07 05:45:58 +03:00
mboot_mods->mod_start = final_offset;
mboot_mods->mod_end = final_offset + ramdisk_len;
2021-06-14 05:11:37 +03:00
final_offset += ramdisk_len;
final_offset = (final_offset & ~(0xFFF)) + ((final_offset & 0xFFF) ? 0x1000 : 0);
}
2021-09-07 05:45:58 +03:00
#ifdef EFI_PLATFORM
#include <efi.h>
extern EFI_GRAPHICS_OUTPUT_PROTOCOL * GOP;
/* EFI boot uses simple filesystem driver */
static EFI_GUID efi_simple_file_system_protocol_guid =
{0x0964e5b22,0x6459,0x11d2,0x8e,0x39,0x00,0xa0,0xc9,0x69,0x72,0x3b};
static EFI_GUID efi_loaded_image_protocol_guid =
{0x5B1B31A1,0x9562,0x11d2, {0x8E,0x3F,0x00,0xA0,0xC9,0x69,0x72,0x3B}};
extern EFI_SYSTEM_TABLE *ST;
extern EFI_HANDLE ImageHandleIn;
extern char do_the_nasty[];
static void finish_boot(void) {
/* Set up multiboot header */
struct multiboot * finalHeader = (void*)(uintptr_t)final_offset;
memcpy((void*)final_offset, &multiboot_header, sizeof(struct multiboot));
final_offset += sizeof(struct multiboot);
finalHeader->flags |= MULTIBOOT_FLAG_FB;
finalHeader->framebuffer_addr = GOP->Mode->FrameBufferBase;
finalHeader->framebuffer_pitch = GOP->Mode->Info->PixelsPerScanLine * 4;
finalHeader->framebuffer_width = GOP->Mode->Info->HorizontalResolution;
finalHeader->framebuffer_height = GOP->Mode->Info->VerticalResolution;
finalHeader->framebuffer_bpp = 32;
/* Copy in command line */
memcpy((void*)final_offset, cmdline, strlen(cmdline)+1);
finalHeader->cmdline = (uintptr_t)final_offset;
final_offset += strlen(cmdline) + 1;
/* Copy bootloader name */
memcpy((void*)final_offset, VERSION_TEXT, strlen(VERSION_TEXT)+1);
finalHeader->boot_loader_name = (uintptr_t)final_offset;
final_offset += strlen(VERSION_TEXT) + 1;
/* Copy module pointers */
memcpy((void*)final_offset, modules_mboot, sizeof(modules_mboot));
finalHeader->mods_addr = (uintptr_t)final_offset;
final_offset += sizeof(modules_mboot);
/* Realign for memory map */
final_offset = (final_offset & ~(0xFFF)) + ((final_offset & 0xFFF) ? 0x1000 : 0);
/* Write memory map */
mboot_memmap_t * mmap = (void*)final_offset;
memset((void*)final_offset, 0x00, 1024);
finalHeader->mmap_addr = (uint32_t)(uintptr_t)mmap;
{
EFI_STATUS e;
UINTN mapSize = 0, mapKey, descriptorSize;
UINT32 descriptorVersion;
e = uefi_call_wrapper(ST->BootServices->GetMemoryMap, 5, &mapSize, NULL, &mapKey, &descriptorSize, NULL);
EFI_MEMORY_DESCRIPTOR * efi_memory = (void*)(final_offset);
final_offset += mapSize;
while ((uintptr_t)final_offset & 0x3ff) final_offset++;
e = uefi_call_wrapper(ST->BootServices->GetMemoryMap, 5, &mapSize, efi_memory, &mapKey, &descriptorSize, NULL);
if (EFI_ERROR(e)) {
print_("EFI error.\n");
while (1) {};
}
uint64_t upper_mem = 0;
int descriptors = mapSize / descriptorSize;
for (int i = 0; i < descriptors; ++i) {
EFI_MEMORY_DESCRIPTOR * d = efi_memory;
mmap->size = sizeof(uint64_t) * 2 + sizeof(uint32_t);
mmap->base_addr = d->PhysicalStart;
mmap->length = d->NumberOfPages * 4096;
switch (d->Type) {
case EfiConventionalMemory:
case EfiLoaderCode:
case EfiLoaderData:
case EfiBootServicesCode:
case EfiBootServicesData:
case EfiRuntimeServicesCode:
case EfiRuntimeServicesData:
mmap->type = 1;
break;
case EfiReservedMemoryType:
case EfiUnusableMemory:
case EfiMemoryMappedIO:
case EfiMemoryMappedIOPortSpace:
case EfiPalCode:
case EfiACPIMemoryNVS:
case EfiACPIReclaimMemory:
default:
mmap->type = 2;
break;
}
if (mmap->type == 1 && mmap->base_addr >= 0x100000) {
upper_mem += mmap->length;
}
mmap = (mboot_memmap_t *) ((uintptr_t)mmap + mmap->size + sizeof(uint32_t));
efi_memory = (EFI_MEMORY_DESCRIPTOR *)((char *)efi_memory + descriptorSize);
}
finalHeader->mmap_length = (uintptr_t)mmap - finalHeader->mmap_addr;
finalHeader->mem_lower = 1024;
finalHeader->mem_upper = upper_mem / 1024;
}
relocate_ramdisk((void*)(uintptr_t)finalHeader->mods_addr);
{
EFI_STATUS e;
UINTN mapSize = 0, mapKey, descriptorSize;
UINT32 descriptorVersion;
uefi_call_wrapper(ST->BootServices->GetMemoryMap, 5, &mapSize, NULL, &mapKey, &descriptorSize, NULL);
e = uefi_call_wrapper(ST->BootServices->ExitBootServices, 2, ImageHandleIn, mapKey);
if (e != EFI_SUCCESS) {
print_("Exit services failed. \n");
print_hex_(e);
while (1) {};
}
}
/* Jump to entry with register arguments */
2021-09-07 05:45:58 +03:00
__asm__ __volatile__ (
"jmp %0" :: "r"(_xmain), "a"(MULTIBOOT_EAX_MAGIC), "b"(finalHeader));
2021-09-07 05:45:58 +03:00
__builtin_unreachable();
}
void boot(void) {
UINTN count;
EFI_HANDLE * handles;
EFI_LOADED_IMAGE * loaded_image;
EFI_FILE_IO_INTERFACE *efi_simple_filesystem;
EFI_FILE *root;
EFI_STATUS status;
uefi_call_wrapper(ST->BootServices->SetWatchdogTimer, 4, 0, 0, 0, NULL);
clear_();
2021-10-26 10:13:50 +03:00
draw_logo(0);
2021-09-07 05:45:58 +03:00
for (unsigned int i = 0; i < ST->NumberOfTableEntries; ++i) {
/* ACPI 1 table pointer. */
2021-09-07 05:45:58 +03:00
if (ST->ConfigurationTable[i].VendorGuid.Data1 == 0xeb9d2d30 &&
ST->ConfigurationTable[i].VendorGuid.Data2 == 0x2d88 &&
ST->ConfigurationTable[i].VendorGuid.Data3 == 0x11d3) {
multiboot_header.config_table = (uintptr_t)ST->ConfigurationTable[i].VendorTable & 0xFFFFffff;
break;
}
/* ACPI 2 table pointer. */
if (ST->ConfigurationTable[i].VendorGuid.Data1 == 0x8868e871 &&
ST->ConfigurationTable[i].VendorGuid.Data2 == 0xe4f1 &&
ST->ConfigurationTable[i].VendorGuid.Data3 == 0x11d3) {
multiboot_header.config_table = (uintptr_t)ST->ConfigurationTable[i].VendorTable & 0xFFFFffff;
break;
}
2021-09-07 05:45:58 +03:00
}
status = uefi_call_wrapper(ST->BootServices->HandleProtocol,
3, ImageHandleIn, &efi_loaded_image_protocol_guid,
(void **)&loaded_image);
if (EFI_ERROR(status)) {
print_("Could not obtain loaded_image_protocol\n");
while (1) {};
}
print("Found loaded image...\n");
status = uefi_call_wrapper(ST->BootServices->HandleProtocol,
3, loaded_image->DeviceHandle, &efi_simple_file_system_protocol_guid,
(void **)&efi_simple_filesystem);
if (EFI_ERROR(status)) {
print_("Could not obtain simple_file_system_protocol.\n");
while (1) {};
}
status = uefi_call_wrapper(efi_simple_filesystem->OpenVolume,
2, efi_simple_filesystem, &root);
if (EFI_ERROR(status)) {
print_("Could not open volume.\n");
while (1) {};
}
EFI_FILE * file;
CHAR16 kernel_name[16] = {0};
{
char * c = kernel_path;
char * ascii = c;
int i = 0;
while (*ascii) {
kernel_name[i] = *ascii;
i++;
ascii++;
}
if (kernel_name[i-1] == L'.') {
kernel_name[i-1] = 0;
}
}
/* Load kernel */
status = uefi_call_wrapper(root->Open,
5, root, &file, kernel_name, EFI_FILE_MODE_READ, 0);
if (EFI_ERROR(status)) {
print_("Error opening kernel.\n");
while (1) {};
}
#define KERNEL_LOAD_START 0x4000000ULL
kernel_load_start = (char*)KERNEL_LOAD_START;
{
EFI_PHYSICAL_ADDRESS addr = KERNEL_LOAD_START;
EFI_ALLOCATE_TYPE type = AllocateAddress;
EFI_MEMORY_TYPE memtype = EfiLoaderData;
UINTN pages = 8192;
EFI_STATUS status = 0;
status = uefi_call_wrapper(ST->BootServices->AllocatePages, 4, type, memtype, pages, &addr);
if (EFI_ERROR(status)) {
print_("Could not allocate space to load boot payloads: ");
print_hex_(status);
print_(" ");
print_hex_(addr);
while (1) {};
}
}
unsigned int offset = 0;
UINTN bytes = 134217728;
status = uefi_call_wrapper(file->Read,
3, file, &bytes, (void *)KERNEL_LOAD_START);
if (EFI_ERROR(status)) {
print_("Error loading kernel.\n");
while (1) {};
}
offset += bytes;
while (offset % 4096) offset++;
{
char * c = ramdisk_path;
CHAR16 name[16] = {0};
char * ascii = c;
int i = 0;
while (*ascii) {
name[i] = *ascii;
i++;
ascii++;
}
if (name[i-1] == L'.') {
name[i-1] == 0;
}
bytes = 134217728;
status = uefi_call_wrapper(root->Open,
5, root, &file, name, EFI_FILE_MODE_READ, 0);
if (!EFI_ERROR(status)) {
status = uefi_call_wrapper(file->Read,
3, file, &bytes, (void*)(uintptr_t)(KERNEL_LOAD_START + offset));
if (!EFI_ERROR(status)) {
ramdisk_off = KERNEL_LOAD_START + offset;
ramdisk_len = bytes;
} else {
print_("Failed to read ramdisk\n");
}
} else {
print_("Error opening "); print_(c); print_("\n");
}
}
load_kernel();
finish_boot();
}
#else
struct mmap_entry {
uint64_t base;
uint64_t len;
uint32_t type;
uint32_t reserved;
};
extern unsigned short mmap_ent;
extern unsigned short lower_mem;
2021-06-14 05:11:37 +03:00
static void finish_boot(void) {
print("Setting up memory map...\n");
print_hex(mmap_ent);
print("\n");
memset(kernel_load_start, 0x00, 1024);
mboot_memmap_t * mmap = (void*)final_offset;
multiboot_header.mmap_addr = (uintptr_t)mmap;
multiboot_header.mods_addr = (uintptr_t)&modules_mboot;
multiboot_header.boot_loader_name = (uintptr_t)VERSION_TEXT;
struct mmap_entry * e820 = (void*)0x5000;
uint64_t upper_mem = 0;
for (int i = 0; i < mmap_ent; ++i) {
print("entry "); print_hex(i);
print(" "); print_hex((uint32_t)(e820[i].base >> 32ULL)); print_hex((uint32_t)e820[i].base);
print(" "); print_hex((uint32_t)(e820[i].len >> 32ULL)); print_hex((uint32_t)e820[i].len);
print(" "); print_hex(e820[i].type); print("\n");
mmap->size = sizeof(uint64_t) * 2 + sizeof(uintptr_t);
mmap->base_addr = e820[i].base;
mmap->length = e820[i].len;
mmap->type = e820[i].type;
if (mmap->type == 1 && mmap->base_addr >= 0x100000) {
upper_mem += mmap->length;
}
mmap = (mboot_memmap_t *) ((uintptr_t)mmap + mmap->size + sizeof(uintptr_t));
}
multiboot_header.mmap_length = (uintptr_t)mmap - multiboot_header.mmap_addr;
print("lower "); print_hex(lower_mem); print("KB\n");
multiboot_header.mem_lower = 1024;
print("upper ");
print_hex(upper_mem >> 32);
print_hex(upper_mem);
print("\n");
multiboot_header.mem_upper = upper_mem / 1024;
2021-06-14 06:06:31 +03:00
print("Jumping to kernel...\n");
2021-06-14 05:11:37 +03:00
uint32_t foo[3];
2021-09-07 05:45:58 +03:00
foo[0] = MULTIBOOT_EAX_MAGIC;
foo[1] = (unsigned int)&multiboot_header;;
2021-06-14 05:11:37 +03:00
foo[2] = _xmain;
__asm__ __volatile__ (
"mov %%cr0,%%eax\n"
/* Disable paging */
"and $0x7FFeFFFF, %%eax\n"
"mov %%eax,%%cr0\n"
/* Ensure PAE is not enabled */
"mov %%cr4,%%eax\n"
"and $0xffffffdf, %%eax\n"
"mov %%eax,%%cr4\n"
"mov %1,%%eax \n"
"mov %2,%%ebx \n"
"jmp *%0" : : "g"(foo[2]), "g"(foo[0]), "g"(foo[1]) : "eax", "ebx"
);
}
2021-10-19 14:29:44 +03:00
extern void do_bios_call(uint32_t function, uint32_t arg1);
extern int bios_call(char * into, uint32_t sector);
static int spin_x = 0;
static void spin(void) {
static int spincnt = 0;
2021-10-26 10:13:50 +03:00
draw_logo(spincnt+1);
spincnt = (spincnt + 1) & 0x7;
}
2021-10-26 10:13:50 +03:00
static void clear_spin(void) {
y = 16;
//print_banner("");
}
2021-10-19 14:29:44 +03:00
extern uint16_t * vbe_cont_info_mode_off;
extern uint32_t *vbe_info_fbaddr;
extern uint16_t vbe_info_pitch;
extern uint16_t vbe_info_width;
extern uint16_t vbe_info_height;
extern uint8_t vbe_info_bpp;
extern void bios_text_mode(void);
2021-06-14 05:11:37 +03:00
void boot(void) {
2021-10-19 14:29:44 +03:00
/* Did we ask for VGA text mode and are currently in a video mode? */
if (boot_mode == 5) {
2021-10-19 14:29:44 +03:00
bios_text_mode();
}
2021-06-14 05:11:37 +03:00
clear_();
2021-10-26 10:13:50 +03:00
draw_logo(0);
2021-06-14 06:06:31 +03:00
print("Looking for ISO9660 filesystem... ");
2021-06-14 05:11:37 +03:00
for (int i = 0x10; i < 0x15; ++i) {
bios_call((char*)(DATA_LOAD_BASE + ISO_SECTOR_SIZE * i), i);
2021-06-14 05:11:37 +03:00
root = (void*)(DATA_LOAD_BASE + ISO_SECTOR_SIZE * i);
switch (root->type) {
case 1:
2021-06-14 06:06:31 +03:00
print("found.\n");
2021-06-14 05:11:37 +03:00
goto done;
}
}
return;
done:
2021-06-14 06:06:31 +03:00
print("Looking for kernel... ");
2021-06-14 05:11:37 +03:00
if (!navigate(kernel_path)) {
print_("Failed to locate kernel.\n");
return;
}
2021-06-14 06:06:31 +03:00
print("found.\n");
2021-06-14 05:11:37 +03:00
kernel_load_start = (char*)(DATA_LOAD_BASE + dir_entry->extent_start_LSB * ISO_SECTOR_SIZE);
2021-10-26 10:13:50 +03:00
print("Loading kernel... "); spin_x = x;
for (int i = 0, j = 0; i < dir_entry->extent_length_LSB; j++) {
if (!(j & 0x3FF)) spin();
bios_call(kernel_load_start + i, dir_entry->extent_start_LSB + j);
i += ISO_SECTOR_SIZE;
}
2021-10-26 10:13:50 +03:00
print("\n");
2021-06-14 06:06:31 +03:00
print("Looking for ramdisk... ");
2021-06-14 05:11:37 +03:00
if (!navigate(ramdisk_path)) {
print_("Failed to locate ramdisk.\n");
return;
}
2021-06-14 06:06:31 +03:00
print("found.\n");
2021-06-14 05:11:37 +03:00
ramdisk_off = DATA_LOAD_BASE + dir_entry->extent_start_LSB * ISO_SECTOR_SIZE;
2021-10-26 10:13:50 +03:00
print("Loading ramdisk... "); spin_x = x;
for (int i = 0, j = 0; i < dir_entry->extent_length_LSB; j++) {
if (!(j & 0x3FF)) spin();
bios_call((char*)(ramdisk_off + i), dir_entry->extent_start_LSB + j);
i += ISO_SECTOR_SIZE;
}
2021-10-26 10:13:50 +03:00
print("\n");
2021-06-14 05:11:37 +03:00
ramdisk_len = dir_entry->extent_length_LSB;
multiboot_header.cmdline = (uintptr_t)cmdline;
2021-10-26 10:13:50 +03:00
draw_logo(0);
2021-10-19 14:29:44 +03:00
if (vbe_info_width) {
multiboot_header.framebuffer_addr = (uint32_t)vbe_info_fbaddr;
multiboot_header.framebuffer_pitch = vbe_info_pitch;
multiboot_header.framebuffer_width = vbe_info_width;
multiboot_header.framebuffer_height = vbe_info_height;
multiboot_header.framebuffer_bpp = vbe_info_bpp;
}
2021-06-14 06:06:31 +03:00
print("Loading kernel from 0x"); print_hex((uint32_t)kernel_load_start); print("... ");
2021-06-14 05:11:37 +03:00
if (!load_kernel()) {
print_("Failed to load kernel.\n");
return;
}
2021-06-14 06:06:31 +03:00
print("Relocating ramdisk from 0x"); print_hex((uint32_t)ramdisk_off); print(":0x"); print_hex(ramdisk_len); print(" to 0x"); print_hex(final_offset); print("... ");
2021-09-07 05:45:58 +03:00
relocate_ramdisk(modules_mboot);
2021-10-26 10:13:50 +03:00
2021-06-14 05:11:37 +03:00
finish_boot();
}
2021-09-07 05:45:58 +03:00
#endif