PatchworkOS  19e446b
A non-POSIX operating system.
Loading...
Searching...
No Matches
main.c
Go to the documentation of this file.
1#include <boot/boot_info.h>
2#include <efi.h>
3#include <efilib.h>
4#include <kernel/mem/paging.h>
6#include <kernel/version.h>
7#include <stddef.h>
8#include <sys/defs.h>
9#include <sys/elf.h>
10#include <sys/list.h>
11#include <sys/math.h>
12#include <sys/proc.h>
13
14// Include functions directly to avoid multiple object files
19
20/**
21 * @brief PatchworkOS UEFI Bootloader
22 * @defgroup boot Bootloader
23 *
24 * @warning UEFI is exceptionally finicky as such to simpify the linking process which can have machine specific issues,
25 * the bootloader should be compiled into a single object file.
26 *
27 * @{
28 */
29
30#define GOP_WIDTH 1920 ///< Ignored if `GOP_USE_DEFAULT_RES` is set to 1
31#define GOP_HEIGHT 1080 ///< Ignored if `GOP_USE_DEFAULT_RES` is set to 1
32#define GOP_USE_DEFAULT_RES 1
33
34#define MEM_BASIC_ALLOCATOR_RESERVE_PERCENTAGE 10
35#define MEM_BASIC_ALLOCATOR_MIN_PAGES 8192
36
37#define EXIT_BOOT_SERVICES_MAX_RETRIES 5
38
39/**
40 * @brief Locates the ACPI RSDP from the EFI configuration table.
41 *
42 * @param systemTable Pointer to the EFI system table.
43 * @return Pointer to the RSDP if found, `NULL` otherwise.
44 */
45static void* rsdp_locate(EFI_SYSTEM_TABLE* systemTable)
46{
47 if (systemTable == NULL || systemTable->ConfigurationTable == NULL)
48 {
49 return NULL;
50 }
51
52 EFI_GUID acpi2TableGuid = ACPI_20_TABLE_GUID;
53 EFI_GUID acpi1TableGuid = ACPI_TABLE_GUID;
54
55 void* rsdp = NULL;
56 for (uint64_t i = 0; i < systemTable->NumberOfTableEntries; i++)
57 {
58 if (CompareGuid(&systemTable->ConfigurationTable[i].VendorGuid, &acpi2TableGuid) == 0)
59 {
60 if (CompareMem("RSD PTR ", systemTable->ConfigurationTable[i].VendorTable, 8) == 0)
61 {
62 rsdp = systemTable->ConfigurationTable[i].VendorTable;
63 if (*((uint8_t*)rsdp + 15) < 2) // Check revision
64 {
65 rsdp = NULL;
66 continue;
67 }
68 }
69 }
70 }
71
72 if (rsdp != NULL)
73 {
74 Print(L" ACPI 2.0+ RSDP found at 0x%lx\n", rsdp);
75 return rsdp;
76 }
77
78 Print(L" ACPI 2.0+ RSDP not found, falling back to ACPI 1.0\n");
79
80 for (uint64_t i = 0; i < systemTable->NumberOfTableEntries; i++)
81 {
82 if (CompareGuid(&systemTable->ConfigurationTable[i].VendorGuid, &acpi1TableGuid) == 0)
83 {
84 if (CompareMem("RSD PTR ", systemTable->ConfigurationTable[i].VendorTable, 8) == 0)
85 {
86 rsdp = systemTable->ConfigurationTable[i].VendorTable;
87 }
88 }
89 }
90
91 if (rsdp != NULL)
92 {
93 Print(L" ACPI 1.0 RSDP found at 0x%lx\n", rsdp);
94 return rsdp;
95 }
96
97 Print(L" WARNING: No ACPI RSDP found in configuration table\n");
98 return NULL;
99}
100
101#if !(GOP_USE_DEFAULT_RES)
102
103/**
104 * @brief Finds the best matching graphics mode for the requested resolution.
105 *
106 * @param gop Pointer to the GOP protocol.
107 * @param requestedWidth Desired horizontal resolution.
108 * @param requestedHeight Desired vertical resolution.
109 * @return The index of the best matching mode.
110 */
111static UINT32 gop_find_best_mode(EFI_GRAPHICS_OUTPUT_PROTOCOL* gop, int64_t requestedWidth, int64_t requestedHeight)
112{
113 UINT32 bestMode = 0;
114 uint64_t bestDistance = UINT64_MAX;
115
116 for (UINT32 modeIndex = 0; modeIndex < gop->Mode->MaxMode; modeIndex++)
117 {
118 EFI_GRAPHICS_OUTPUT_MODE_INFORMATION* modeInfo = NULL;
119 UINTN infoSize = 0;
120
121 EFI_STATUS status = uefi_call_wrapper(gop->QueryMode, 4, gop, modeIndex, &infoSize, &modeInfo);
122 if (EFI_ERROR(status))
123 {
124 continue;
125 }
126
127 int64_t deltaX = (int64_t)modeInfo->HorizontalResolution - requestedWidth;
128 int64_t deltaY = (int64_t)modeInfo->VerticalResolution - requestedHeight;
129 uint64_t distance = (uint64_t)(deltaX * deltaX) + (uint64_t)(deltaY * deltaY);
130
131 if (distance < bestDistance)
132 {
133 bestMode = modeIndex;
134 bestDistance = distance;
135 }
136
137 if (distance == 0)
138 {
139 break;
140 }
141 }
142
143 return bestMode;
144}
145
146/**
147 * @brief Sets the graphics mode to best match the requested resolution.
148 *
149 * @param gop Pointer to the GOP protocol.
150 * @param requestedWidth Desired horizontal resolution.
151 * @param requestedHeight Desired vertical resolution.
152 * @return On success, `EFI_SUCCESS`. On failure, an EFI error code.
153 */
154static EFI_STATUS gop_set_mode(EFI_GRAPHICS_OUTPUT_PROTOCOL* gop, int64_t requestedWidth, int64_t requestedHeight)
155{
156 UINT32 targetMode = gop_find_best_mode(gop, requestedWidth, requestedHeight);
157 return uefi_call_wrapper(gop->SetMode, 2, gop, targetMode);
158}
159
160#endif
161
162/**
163 * @brief Initializes the GOP buffer structure with framebuffer information.
164 *
165 * Locates the Graphics Output Protocol, optionally sets the desired resolution,
166 * and populates the `boot_gop_t` structure with framebuffer details.
167 *
168 * @param buffer Pointer to the `boot_gop_t` structure to populate.
169 * @return On success, `EFI_SUCCESS`. On failure, an EFI error code.
170 */
171static EFI_STATUS gop_init(boot_gop_t* buffer)
172{
173 if (buffer == NULL)
174 {
175 return EFI_INVALID_PARAMETER;
176 }
177
178 EFI_GRAPHICS_OUTPUT_PROTOCOL* gop = NULL;
179 EFI_GUID gopGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
180
181 EFI_STATUS status = uefi_call_wrapper(BS->LocateProtocol, 3, &gopGuid, NULL, (void**)&gop);
182 if (EFI_ERROR(status))
183 {
184 Print(L" ERROR: Failed to locate GOP (0x%lx)\n", status);
185 return status;
186 }
187
188#if !(GOP_USE_DEFAULT_RES)
189 Print(L" Setting mode to %dx%d... ", GOP_WIDTH, GOP_HEIGHT);
191 if (EFI_ERROR(status))
192 {
193 Print(L" WARNING: Failed to set requested mode, using default (0x%lx)\n", status);
194 }
195#endif
196
197 buffer->physAddr = gop->Mode->FrameBufferBase;
198 buffer->virtAddr = (uint32_t*)PML_LOWER_TO_HIGHER(gop->Mode->FrameBufferBase);
199 buffer->size = gop->Mode->FrameBufferSize;
200 buffer->width = gop->Mode->Info->HorizontalResolution;
201 buffer->height = gop->Mode->Info->VerticalResolution;
202 buffer->stride = gop->Mode->Info->PixelsPerScanLine;
203
204 switch (gop->Mode->Info->PixelFormat)
205 {
206 case PixelRedGreenBlueReserved8BitPerColor:
207 Print(L" Pixel format: RGB (32-bit)\n");
208 break;
209 case PixelBlueGreenRedReserved8BitPerColor:
210 Print(L" Pixel format: BGR (32-bit)\n");
211 break;
212 case PixelBitMask:
213 Print(L" WARNING: Pixel format is bitmask-based, may require custom handling\n");
214 break;
215 case PixelBltOnly:
216 Print(L" WARNING: Framebuffer not available, BLT only mode\n");
217 break;
218 default:
219 Print(L" WARNING: Unknown pixel format (%d)\n", gop->Mode->Info->PixelFormat);
220 break;
221 }
222
223 Print(L" GOP: 0x%lx-0x%lx %lux%lu, stride=%lu, size=%lu bytes\n", buffer->physAddr,
224 (uintptr_t)buffer->physAddr + buffer->size, buffer->width, buffer->height, buffer->stride, buffer->size);
225
226 return EFI_SUCCESS;
227}
228
229/**
230 * @brief Basic page allocator state.
231 *
232 * This allocator is used after exiting boot services when UEFI memory
233 * allocation is no longer available.
234 */
235static struct
236{
237 EFI_PHYSICAL_ADDRESS buffer;
238 size_t maxPages;
243
244/**
245 * @brief Panic error codes for debugging when boot services are unavailable.
246 */
247typedef enum
248{
251 PANIC_IDENTITY_MAP = 2, // Yellow
253 PANIC_HIGHER_HALF_MAP = 4, // Magenta
254 PANIC_KERNEL_MAP = 5, // Cyan
255 PANIC_GOP_MAP = 6, // White
257
258/**
259 * @brief Color values for panic display.
260 */
261static const uint32_t PANIC_COLORS[] = {
262 0xFFFF0000, // Red
263 0xFF00FF00, // Green
264 0xFFFFFF00, // Yellow
265 0xFF0000FF, // Blue
266 0xFFFF00FF, // Magenta
267 0xFF00FFFF, // Cyan
268 0xFFFFFFFF, // White
269};
270
271/**
272 * @brief Halts the system with a colored screen indicating the error.
273 *
274 * @param code The panic code indicating the type of error.
275 */
277{
278 if (basicAllocator.gop != NULL)
279 {
282
283 for (size_t y = 0; y < gop->height; y++)
284 {
285 for (size_t x = 0; x < gop->width; x++)
286 {
287 ((uint32_t*)gop->physAddr)[x + (y * gop->stride)] = color;
288 }
289 }
290 }
291
292 for (;;)
293 {
294 ASM("cli; hlt");
295 }
296}
297
298/**
299 * @brief Initializes the EFI memory map structure.
300 *
301 * @param map Pointer to the memory map structure to initialize.
302 * @return On success, `EFI_SUCCESS`. On failure, an EFI error code.
303 */
305{
306 if (map == NULL)
307 {
308 return EFI_INVALID_PARAMETER;
309 }
310
311 map->descriptors = LibMemoryMap(&map->length, &map->key, &map->descSize, &map->descVersion);
312 if (map->descriptors == NULL)
313 {
314 return EFI_OUT_OF_RESOURCES;
315 }
316
317 for (size_t i = 0; i < map->length; i++)
318 {
319 EFI_MEMORY_DESCRIPTOR* desc = BOOT_MEMORY_MAP_GET_DESCRIPTOR(map, i);
320 desc->VirtualStart = (EFI_VIRTUAL_ADDRESS)PML_LOWER_TO_HIGHER(desc->PhysicalStart);
321 }
322
323 return EFI_SUCCESS;
324}
325
326/**
327 * @brief Free the memory map structure.
328 *
329 * @param map Pointer to the memory map structure to free.
330 */
332{
333 if (map != NULL && map->descriptors != NULL)
334 {
335 FreePool(map->descriptors);
337 map->length = 0;
338 }
339}
340
341/**
342 * @brief Calculates the total available conventional memory.
343 *
344 * @param map Pointer to the memory map.
345 * @return Total number of available pages.
346 */
348{
349 size_t availablePages = 0;
350
351 for (size_t i = 0; i < map->length; i++)
352 {
353 EFI_MEMORY_DESCRIPTOR* desc = BOOT_MEMORY_MAP_GET_DESCRIPTOR(map, i);
354 if (desc->Type == EfiConventionalMemory)
355 {
356 availablePages += desc->NumberOfPages;
357 }
358 }
359
360 return availablePages;
361}
362
363/**
364 * @brief Initializes the basic page allocator.
365 *
366 * @return On success, `EFI_SUCCESS`. On failure, an EFI error code.
367 */
368static EFI_STATUS mem_allocator_init(void)
369{
371 EFI_STATUS status = mem_map_init(&map);
372 if (EFI_ERROR(status))
373 {
374 Print(L" ERROR: Failed to get memory map (0x%lx)\n", status);
375 return status;
376 }
377
378 size_t availablePages = mem_count_available_pages(&map);
379 size_t reservePages = (availablePages * MEM_BASIC_ALLOCATOR_RESERVE_PERCENTAGE) / 100;
380 basicAllocator.maxPages = MAX(reservePages, MEM_BASIC_ALLOCATOR_MIN_PAGES);
381
382 Print(L" Reserving %lu pages...\n", basicAllocator.maxPages);
383
384 status = uefi_call_wrapper(BS->AllocatePages, 4, AllocateAnyPages, EfiLoaderData, basicAllocator.maxPages,
385 &basicAllocator.buffer);
386
388
389 if (EFI_ERROR(status))
390 {
391 Print(L" ERROR: Failed to allocate page pool (0x%lx)\n", status);
392 return status;
393 }
394
395 basicAllocator.pagesAllocated = 0;
396 basicAllocator.gop = NULL;
397 basicAllocator.map = NULL;
398
399 Print(L" Using %lu pages at 0x%lx\n", basicAllocator.maxPages, basicAllocator.buffer);
400
401 return EFI_SUCCESS;
402}
403
404/**
405 * @brief Allocate pages from the basic allocator.
406 *
407 * @param pfns Pointer to store allocated page PFNs.
408 * @param amount Number of pages to allocate.
409 * @return Always returns `0`.
410 */
411static uint64_t basic_allocator_alloc_pages(pfn_t* pfns, size_t amount)
412{
413 if (basicAllocator.pagesAllocated + amount > basicAllocator.maxPages)
414 {
416 }
417
418 for (size_t i = 0; i < amount; i++)
419 {
420 pfns[i] = PHYS_TO_PFN(basicAllocator.buffer + ((basicAllocator.pagesAllocated + i) * PAGE_SIZE));
421 }
422 basicAllocator.pagesAllocated += amount;
423
424 return 0;
425}
426
427/**
428 * @brief Initializes the kernel page table with all required mappings.
429 *
430 * @param table Pointer to the page table structure.
431 * @param map Pointer to the memory map.
432 * @param gop Pointer to the GOP information.
433 * @param kernel Pointer to the kernel information.
434 */
436{
437 basicAllocator.gop = gop;
438 basicAllocator.map = map;
439
441 {
443 }
444
445 uintptr_t maxPhysicalAddress = 0;
446 for (size_t i = 0; i < map->length; i++)
447 {
448 const EFI_MEMORY_DESCRIPTOR* desc = BOOT_MEMORY_MAP_GET_DESCRIPTOR(map, i);
449 uintptr_t regionEnd = desc->PhysicalStart + (desc->NumberOfPages * PAGE_SIZE);
450 if (regionEnd > maxPhysicalAddress)
451 {
452 maxPhysicalAddress = regionEnd;
453 }
454 }
455
456 if (page_table_map(table, 0, 0, BYTES_TO_PAGES(maxPhysicalAddress), PML_WRITE | PML_PRESENT, PML_CALLBACK_NONE) ==
457 ERR)
458 {
460 }
461
462 for (size_t i = 0; i < map->length; i++)
463 {
464 const EFI_MEMORY_DESCRIPTOR* desc = BOOT_MEMORY_MAP_GET_DESCRIPTOR(map, i);
465
466 if (desc->VirtualStart < PML_HIGHER_HALF_START)
467 {
469 }
470
471 if (page_table_map(table, (void*)desc->VirtualStart, desc->PhysicalStart, desc->NumberOfPages,
473 {
475 }
476 }
477
478 Elf64_Addr minVaddr = 0;
479 Elf64_Addr maxVaddr = 0;
480 elf64_get_loadable_bounds(&kernel->elf, &minVaddr, &maxVaddr);
481 size_t kernelPageCount = BYTES_TO_PAGES(maxVaddr - minVaddr);
482
483 if (page_table_map(table, (void*)minVaddr, kernel->physAddr, kernelPageCount, PML_WRITE | PML_PRESENT,
485 {
487 }
488
491 {
493 }
494}
495
496/**
497 * @brief Copies a wide character string to a narrow character buffer.
498 *
499 * @param dest Destination buffer.
500 * @param destSize Size of the destination buffer.
501 * @param src Source wide string.
502 * @return `true` if the string was fully copied, `false` if truncated.
503 */
504static bool wstr_to_str(char* dest, size_t destSize, const CHAR16* src)
505{
506 if (dest == NULL || destSize == 0)
507 {
508 return false;
509 }
510
511 size_t i;
512 for (i = 0; i < destSize - 1 && src != NULL && src[i] != L'\0'; i++)
513 {
514 dest[i] = (char)src[i];
515 }
516 dest[i] = '\0';
517
518 return src == NULL || src[i] == L'\0';
519}
520
521/**
522 * @brief Frees a boot file structure and its data.
523 *
524 * @param file Pointer to the file structure to free.
525 */
526static void boot_file_free(boot_file_t* file)
527{
528 if (file != NULL)
529 {
530 if (file->data != NULL)
531 {
532 FreePool(file->data);
533 }
534 FreePool(file);
535 }
536}
537
538/**
539 * @brief Recursively frees a directory tree.
540 *
541 * @param dir Pointer to the directory structure to free.
542 */
544{
545 if (dir == NULL)
546 {
547 return;
548 }
549
550 while (!list_is_empty(&dir->children))
551 {
553 boot_dir_t* child = CONTAINER_OF(entry, boot_dir_t, entry);
554 boot_dir_free(child);
555 }
556
557 while (!list_is_empty(&dir->files))
558 {
559 list_entry_t* entry = list_pop_front(&dir->files);
560 boot_file_t* file = CONTAINER_OF(entry, boot_file_t, entry);
561 boot_file_free(file);
562 }
563
564 FreePool(dir);
565}
566
567/**
568 * @brief Loads a single file from an EFI file handle.
569 *
570 * @param parentDir The parent directory handle.
571 * @param fileName The name of the file to load.
572 * @return On success, pointer to the loaded file structure. On failure, `NULL`.
573 */
574static boot_file_t* disk_load_file(EFI_FILE* parentDir, const CHAR16* fileName)
575{
576 if (parentDir == NULL || fileName == NULL)
577 {
578 return NULL;
579 }
580
581 EFI_FILE* efiFile = NULL;
582 boot_file_t* file = NULL;
583
584 EFI_STATUS status = uefi_call_wrapper(parentDir->Open, 5, parentDir, &efiFile, (CHAR16*)fileName,
585 EFI_FILE_MODE_READ, EFI_FILE_READ_ONLY | EFI_FILE_HIDDEN | EFI_FILE_SYSTEM);
586 if (EFI_ERROR(status))
587 {
588 return NULL;
589 }
590
591 file = AllocateZeroPool(sizeof(boot_file_t));
592 if (file == NULL)
593 {
594 goto cleanup_file;
595 }
596
597 list_entry_init(&file->entry);
598 wstr_to_str(file->name, MAX_NAME, fileName);
599
600 EFI_FILE_INFO* fileInfo = LibFileInfo(efiFile);
601 if (fileInfo == NULL)
602 {
603 goto cleanup_struct;
604 }
605
606 file->size = fileInfo->FileSize;
607 FreePool(fileInfo);
608
609 if (file->size > 0)
610 {
611 file->data = AllocatePool(file->size);
612 if (file->data == NULL)
613 {
614 goto cleanup_struct;
615 }
616
617 UINTN readSize = file->size;
618 status = uefi_call_wrapper(efiFile->Read, 3, efiFile, &readSize, file->data);
619 if (EFI_ERROR(status) || readSize != file->size)
620 {
621 goto cleanup_struct;
622 }
623 }
624
625 uefi_call_wrapper(efiFile->Close, 1, efiFile);
626 return file;
627
628cleanup_struct:
629 boot_file_free(file);
630cleanup_file:
631 if (efiFile != NULL)
632 {
633 uefi_call_wrapper(efiFile->Close, 1, efiFile);
634 }
635 return NULL;
636}
637
638/**
639 * @brief Recursively loads a directory and its contents.
640 *
641 * @param dirHandle The directory handle to read from.
642 * @param dirName The name of the directory.
643 * @return On success, pointer to the loaded directory structure. On failure, `NULL`.
644 */
645static boot_dir_t* disk_load_dir(EFI_FILE* dirHandle, const CHAR16* dirName)
646{
647 if (dirHandle == NULL || dirName == NULL)
648 {
649 return NULL;
650 }
651
652 boot_dir_t* dir = AllocateZeroPool(sizeof(boot_dir_t));
653 if (dir == NULL)
654 {
655 return NULL;
656 }
657
658 list_entry_init(&dir->entry);
659 wstr_to_str(dir->name, MAX_NAME, dirName);
661 list_init(&dir->files);
662
663 UINTN infoBufferSize = sizeof(EFI_FILE_INFO) + 256 * sizeof(CHAR16);
664 EFI_FILE_INFO* fileInfo = AllocatePool(infoBufferSize);
665 if (fileInfo == NULL)
666 {
668 return NULL;
669 }
670
671 while (TRUE)
672 {
673 UINTN readSize = infoBufferSize;
674 EFI_STATUS status = uefi_call_wrapper(dirHandle->Read, 3, dirHandle, &readSize, fileInfo);
675
676 if (EFI_ERROR(status))
677 {
678 FreePool(fileInfo);
680 return NULL;
681 }
682
683 if (readSize == 0)
684 {
685 break;
686 }
687
688 if (fileInfo->FileName[0] == L'.' && fileInfo->FileName[1] == L'\0')
689 {
690 continue;
691 }
692
693 if (fileInfo->FileName[0] == L'.' && fileInfo->FileName[1] == L'.' && fileInfo->FileName[2] == L'\0')
694 {
695 continue;
696 }
697
698 if (fileInfo->Attribute & EFI_FILE_DIRECTORY)
699 {
700 EFI_FILE* childHandle = NULL;
701 status = uefi_call_wrapper(dirHandle->Open, 5, dirHandle, &childHandle, fileInfo->FileName,
702 EFI_FILE_MODE_READ, EFI_FILE_READ_ONLY | EFI_FILE_HIDDEN | EFI_FILE_SYSTEM);
703
704 if (EFI_ERROR(status))
705 {
706 FreePool(fileInfo);
708 return NULL;
709 }
710
711 boot_dir_t* child = disk_load_dir(childHandle, fileInfo->FileName);
712 uefi_call_wrapper(childHandle->Close, 1, childHandle);
713
714 if (child == NULL)
715 {
716 FreePool(fileInfo);
718 return NULL;
719 }
720
721 list_push_back(&dir->children, &child->entry);
722 }
723 else
724 {
725 boot_file_t* file = disk_load_file(dirHandle, fileInfo->FileName);
726 if (file == NULL)
727 {
728 FreePool(fileInfo);
730 return NULL;
731 }
732
733 list_push_back(&dir->files, &file->entry);
734 }
735 }
736
737 FreePool(fileInfo);
738 return dir;
739}
740
741/**
742 * @brief Loads the initial RAM disk from the boot volume.
743 *
744 * @param disk Pointer to the disk structure to populate.
745 * @param rootHandle Handle to the root of the boot volume.
746 * @return EFI_SUCCESS on success, error code otherwise.
747 */
748static EFI_STATUS disk_init(boot_disk_t* disk, EFI_FILE* rootHandle)
749{
750 if (disk == NULL || rootHandle == NULL)
751 {
752 return EFI_INVALID_PARAMETER;
753 }
754
755 EFI_FILE* rootDir = NULL;
756 EFI_STATUS status = uefi_call_wrapper(rootHandle->Open, 5, rootHandle, &rootDir, L"root", EFI_FILE_MODE_READ,
757 EFI_FILE_READ_ONLY | EFI_FILE_HIDDEN | EFI_FILE_SYSTEM);
758
759 if (EFI_ERROR(status))
760 {
761 Print(L" No 'root' subdirectory, loading from volume root...\n");
762 disk->root = disk_load_dir(rootHandle, L"root");
763 }
764 else
765 {
766 disk->root = disk_load_dir(rootDir, L"root");
767 uefi_call_wrapper(rootDir->Close, 1, rootDir);
768 }
769
770 if (disk->root == NULL)
771 {
772 Print(L" ERROR: Failed to load root directory contents\n");
773 return EFI_LOAD_ERROR;
774 }
775
776 return EFI_SUCCESS;
777}
778
779/**
780 * @brief Loads and validates the kernel ELF file.
781 *
782 * @param kernel Pointer to the kernel structure to populate.
783 * @param rootHandle Handle to the root of the boot volume.
784 * @return On success, `EFI_SUCCESS`. On failure, an EFI error code.
785 */
786static EFI_STATUS kernel_load(boot_kernel_t* kernel, EFI_FILE* rootHandle)
787{
788 if (kernel == NULL || rootHandle == NULL)
789 {
790 return EFI_INVALID_PARAMETER;
791 }
792
793 EFI_FILE* kernelDir = NULL;
794 EFI_FILE* kernelFile = NULL;
795 void* fileData = NULL;
796 EFI_PHYSICAL_ADDRESS kernelPhys = 0;
797 uint64_t kernelPageCount = 0;
798 Elf64_Addr minVaddr = 0;
799 Elf64_Addr maxVaddr = 0;
800 EFI_STATUS status;
801
802 status = uefi_call_wrapper(rootHandle->Open, 5, rootHandle, &kernelDir, L"kernel", EFI_FILE_MODE_READ, 0);
803 if (EFI_ERROR(status))
804 {
805 Print(L" ERROR: Failed to open kernel directory (0x%lx)\n", status);
806 goto cleanup;
807 }
808
809 status = uefi_call_wrapper(kernelDir->Open, 5, kernelDir, &kernelFile, L"kernel", EFI_FILE_MODE_READ, 0);
810 if (EFI_ERROR(status))
811 {
812 Print(L" ERROR: Failed to open kernel file (0x%lx)\n", status);
813 goto cleanup;
814 }
815
816 EFI_FILE_INFO* fileInfo = LibFileInfo(kernelFile);
817 if (fileInfo == NULL)
818 {
819 Print(L" ERROR: Failed to get kernel file info\n");
820 status = EFI_LOAD_ERROR;
821 goto cleanup;
822 }
823
824 size_t fileSize = fileInfo->FileSize;
825 FreePool(fileInfo);
826
827 if (fileSize == 0)
828 {
829 Print(L" ERROR: Kernel file is empty\n");
830 status = EFI_LOAD_ERROR;
831 goto cleanup;
832 }
833
834 fileData = AllocatePool(fileSize);
835 if (fileData == NULL)
836 {
837 Print(L" ERROR: Failed to allocate memory for kernel file\n");
838 status = EFI_OUT_OF_RESOURCES;
839 goto cleanup;
840 }
841
842 UINTN readSize = fileSize;
843 status = uefi_call_wrapper(kernelFile->Read, 3, kernelFile, &readSize, fileData);
844 if (EFI_ERROR(status))
845 {
846 Print(L" ERROR: Failed to read kernel file: (0x%lx)\n", status);
847 goto cleanup;
848 }
849
850 if (readSize != fileSize)
851 {
852 Print(L" ERROR: Incomplete kernel read (%lu of %lu bytes)\n", readSize, fileSize);
853 status = EFI_LOAD_ERROR;
854 goto cleanup;
855 }
856
857 Print(L" File size %lu bytes\n", fileSize);
858
859 uint64_t elfResult = elf64_validate(&kernel->elf, fileData, fileSize);
860 if (elfResult != 0)
861 {
862 Print(L" ERROR: Invalid kernel ELF (%lu)\n", elfResult);
863 status = EFI_LOAD_ERROR;
864 goto cleanup;
865 }
866
867 elf64_get_loadable_bounds(&kernel->elf, &minVaddr, &maxVaddr);
868 size_t kernelSize = maxVaddr - minVaddr;
869 kernelPageCount = BYTES_TO_PAGES(kernelSize);
870
871 Print(L" Allocating %lu pages for kernel...\n", kernelPageCount);
872 status =
873 uefi_call_wrapper(BS->AllocatePages, 4, AllocateAnyPages, EfiReservedMemoryType, kernelPageCount, &kernelPhys);
874 if (EFI_ERROR(status))
875 {
876 Print(L" ERROR: Failed to allocate kernel pages (0x%lx)", status);
877 goto cleanup;
878 }
879
880 Print(L" Loading kernel...\n");
881 elf64_load_segments(&kernel->elf, kernelPhys, minVaddr);
882 kernel->physAddr = kernelPhys;
883
884 Print(L" Entry Code: ");
885 for (size_t i = 0; i < 16; i++)
886 {
887 Print(L"%02x ", ((uint8_t*)kernel->physAddr)[kernel->elf.header->e_entry - minVaddr + i]);
888 }
889 Print(L"\n");
890
891 Print(L" Kernel loaded at 0x%lx, entry=0x%lx, size=%lu\n", kernelPhys, kernel->elf.header->e_entry, kernelSize);
892
893 status = EFI_SUCCESS;
894
895cleanup:
896 if (EFI_ERROR(status) && fileData != NULL)
897 {
898 FreePool(fileData);
899 }
900 if (kernelFile != NULL)
901 {
902 uefi_call_wrapper(kernelFile->Close, 1, kernelFile);
903 }
904 if (kernelDir != NULL)
905 {
906 uefi_call_wrapper(kernelDir->Close, 1, kernelDir);
907 }
908 if (EFI_ERROR(status) && kernelPhys != 0)
909 {
910 uefi_call_wrapper(BS->FreePages, 2, kernelPhys, kernelPageCount);
911 }
912
913 return status;
914}
915
916/**
917 * @brief Displays the bootloader splash screen.
918 */
919static void splash_screen_display(void)
920{
921#ifdef NDEBUG
922 Print(L"Start %a-bootloader %a (Built %a %a)\n", OS_NAME, OS_VERSION, __DATE__, __TIME__);
923#else
924 Print(L"Start %a-bootloader DEBUG %a (Built %a %a)\n", OS_NAME, OS_VERSION, __DATE__, __TIME__);
925#endif
926 Print(L"Copyright (C) 2026 Kai Norberg. MIT Licensed.\n");
927}
928
929/**
930 * @brief Opens the root volume of the boot device.
931 *
932 * @param rootFile Output pointer to receive the root file handle.
933 * @param imageHandle The loaded image handle.
934 * @return On success, `EFI_SUCCESS`. On failure, an EFI error code.
935 */
936static EFI_STATUS volume_open_root(EFI_FILE** rootFile, EFI_HANDLE imageHandle)
937{
938 if (rootFile == NULL)
939 {
940 return EFI_INVALID_PARAMETER;
941 }
942
943 EFI_LOADED_IMAGE* loadedImage = NULL;
944 EFI_GUID lipGuid = EFI_LOADED_IMAGE_PROTOCOL_GUID;
945 EFI_GUID fsGuid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
946 EFI_FILE_IO_INTERFACE* ioVolume = NULL;
947
948 EFI_STATUS status = uefi_call_wrapper(BS->HandleProtocol, 3, imageHandle, &lipGuid, (void**)&loadedImage);
949 if (EFI_ERROR(status))
950 {
951 Print(L" ERROR: Failed to get loaded image protocol (0x%lx)\n", status);
952 return status;
953 }
954
955 status = uefi_call_wrapper(BS->HandleProtocol, 3, loadedImage->DeviceHandle, &fsGuid, (void**)&ioVolume);
956 if (EFI_ERROR(status))
957 {
958 Print(L" ERROR: Failed to get file system protocol (0x%lx)\n", status);
959 return status;
960 }
961
962 status = uefi_call_wrapper(ioVolume->OpenVolume, 2, ioVolume, rootFile);
963 if (EFI_ERROR(status))
964 {
965 Print(L" ERROR: Failed to open root volume (0x%lx)\n", status);
966 return status;
967 }
968
969 return EFI_SUCCESS;
970}
971
972/**
973 * @brief Populates the boot information structure.
974 *
975 * Initializes all boot data including GOP, RSDP, disk, and kernel.
976 *
977 * @param imageHandle The loaded image handle.
978 * @param systemTable Pointer to the EFI system table.
979 * @param bootInfo Pointer to the boot info structure to populate.
980 * @return EFI_SUCCESS on success, error code otherwise.
981 */
982static EFI_STATUS boot_info_populate(EFI_HANDLE imageHandle, EFI_SYSTEM_TABLE* systemTable, boot_info_t* bootInfo)
983{
984 if (bootInfo == NULL || systemTable == NULL)
985 {
986 return EFI_INVALID_PARAMETER;
987 }
988
989 EFI_STATUS status;
990
991 Print(L"Initializing GOP...\n");
992 status = gop_init(&bootInfo->gop);
993 if (EFI_ERROR(status))
994 {
995 Print(L"FATAL: Failed to initialize graphics (0x%lx)\n", status);
996 return status;
997 }
998
999 Print(L"Locating ACPI RSDP...\n");
1000 bootInfo->rsdp = rsdp_locate(systemTable);
1001 if (bootInfo->rsdp == NULL)
1002 {
1003 Print(L"FATAL: ACPI RSDP not found in system configuration table\n");
1004 return EFI_NOT_FOUND;
1005 }
1006
1007 bootInfo->runtimeServices = systemTable->RuntimeServices;
1008
1009 Print(L"Loading boot volume...\n");
1010 EFI_FILE* rootHandle = NULL;
1011 status = volume_open_root(&rootHandle, imageHandle);
1012 if (EFI_ERROR(status))
1013 {
1014 Print(L"FATAL: Failed to open boot volume (0x%lx)\n", status);
1015 return status;
1016 }
1017
1018 Print(L"Initializing disk...\n");
1019 status = disk_init(&bootInfo->disk, rootHandle);
1020 if (EFI_ERROR(status))
1021 {
1022 uefi_call_wrapper(rootHandle->Close, 1, rootHandle);
1023 return status;
1024 }
1025
1026 Print(L"Loading kernel...\n");
1027 status = kernel_load(&bootInfo->kernel, rootHandle);
1028 if (EFI_ERROR(status))
1029 {
1030 uefi_call_wrapper(rootHandle->Close, 1, rootHandle);
1031 return status;
1032 }
1033
1034 uefi_call_wrapper(rootHandle->Close, 1, rootHandle);
1035
1036 Print(L"Boot info populated.\n");
1037 return EFI_SUCCESS;
1038}
1039
1040/**
1041 * @brief Exits UEFI boot services and prepares for kernel handoff.
1042 *
1043 * @param imageHandle The loaded image handle.
1044 * @param bootInfo Pointer to the boot info structure.
1045 * @return On success, `EFI_SUCCESS`. On failure, an EFI error code.
1046 */
1047static EFI_STATUS boot_services_exit(EFI_HANDLE imageHandle, boot_info_t* bootInfo)
1048{
1049 if (bootInfo == NULL)
1050 {
1051 return EFI_INVALID_PARAMETER;
1052 }
1053
1054 EFI_STATUS status;
1055 UINT32 retryCount = 0;
1056
1057 Print(L"Exiting boot services...\n");
1058 do
1059 {
1060 Print(L" Attempt %d of %d...\n", retryCount + 1, EXIT_BOOT_SERVICES_MAX_RETRIES);
1061 if (retryCount > 0)
1062 {
1064 }
1065
1066 status = mem_map_init(&bootInfo->memory.map);
1067 if (EFI_ERROR(status))
1068 {
1069 Print(L" ERROR: Failed to get memory map: 0x%lx", status);
1070 return status;
1071 }
1072
1073 status = uefi_call_wrapper(BS->ExitBootServices, 2, imageHandle, bootInfo->memory.map.key);
1074
1075 if (status == EFI_SUCCESS)
1076 {
1077 break;
1078 }
1079
1080 if (status == EFI_INVALID_PARAMETER)
1081 {
1082 Print(L" WARNING: Stale key, retrying.\n");
1083 retryCount++;
1084
1085 if (retryCount >= EXIT_BOOT_SERVICES_MAX_RETRIES)
1086 {
1087 Print(L" ERROR: Maximum retries reached, aborting.\n");
1089 return EFI_ABORTED;
1090 }
1091
1092 uefi_call_wrapper(BS->Stall, 1, 1000);
1093 }
1094 else
1095 {
1096 Print(L" ERROR: Failed to exit boot services (0x%lx)\n", status);
1098 return status;
1099 }
1100
1101 } while (retryCount < EXIT_BOOT_SERVICES_MAX_RETRIES);
1102
1104
1105 return EFI_SUCCESS;
1106}
1107
1108/**
1109 * @brief UEFI entry point.
1110 *
1111 * @param imageHandle Handle to the loaded image.
1112 * @param systemTable Pointer to the EFI system table.
1113 * @return Will not return if successful. On failure, an EFI error code.
1114 */
1115EFI_STATUS efi_main(EFI_HANDLE imageHandle, EFI_SYSTEM_TABLE* systemTable)
1116{
1117 InitializeLib(imageHandle, systemTable);
1118
1119 uefi_call_wrapper(BS->SetWatchdogTimer, 4, 0, 0, 0, NULL);
1120 uefi_call_wrapper(systemTable->ConOut->ClearScreen, 1, systemTable->ConOut);
1121
1123
1124 Print(L"Initializing memory allocator...\n");
1125 EFI_STATUS status = mem_allocator_init();
1126 if (EFI_ERROR(status))
1127 {
1128 Print(L"FATAL: Failed to initialize memory allocator (0x%lx)\n", status);
1129 return status;
1130 }
1131
1132 Print(L"Allocating boot info structure...\n");
1133 boot_info_t* bootInfo = AllocateZeroPool(sizeof(boot_info_t));
1134 if (bootInfo == NULL)
1135 {
1136 Print(L"FATAL: Failed to allocate boot info structure\n");
1137 return EFI_OUT_OF_RESOURCES;
1138 }
1139
1140 status = boot_info_populate(imageHandle, systemTable, bootInfo);
1141 if (EFI_ERROR(status))
1142 {
1143 FreePool(bootInfo);
1144 return status;
1145 }
1146
1147 status = boot_services_exit(imageHandle, bootInfo);
1148 if (EFI_ERROR(status))
1149 {
1150 return status;
1151 }
1152
1154
1155 EFI_RUNTIME_SERVICES* rt = bootInfo->runtimeServices;
1156 uefi_call_wrapper(rt->SetVirtualAddressMap, 4, bootInfo->memory.map.length * bootInfo->memory.map.descSize,
1158
1159 void (*kernel_entry)(boot_info_t*) = (void (*)(boot_info_t*))bootInfo->kernel.elf.header->e_entry;
1160 kernel_entry(bootInfo);
1161
1163}
1164
1165/** @} */
#define MAX_NAME
Maximum length of names.
Definition MAX_NAME.h:11
#define _NORETURN
Definition config.h:28
boot_gop_t * gop
Definition main.c:240
boot_memory_map_t * map
Definition main.c:241
EFI_PHYSICAL_ADDRESS buffer
Definition main.c:237
boot_info_t * bootInfo
Definition boot_info.c:14
int64_t x
Definition main.c:152
int64_t y
Definition main.c:153
static dentry_t * dir
Definition fb.c:16
#define BOOT_MEMORY_MAP_GET_DESCRIPTOR(map, index)
Definition boot_info.h:52
size_t pagesAllocated
Definition main.c:239
boot_memory_map_t * map
Definition main.c:241
#define GOP_WIDTH
Ignored if GOP_USE_DEFAULT_RES is set to 1.
Definition main.c:30
static EFI_STATUS volume_open_root(EFI_FILE **rootFile, EFI_HANDLE imageHandle)
Opens the root volume of the boot device.
Definition main.c:936
#define EXIT_BOOT_SERVICES_MAX_RETRIES
Definition main.c:37
static void mem_page_table_init(page_table_t *table, boot_memory_map_t *map, boot_gop_t *gop, boot_kernel_t *kernel)
Initializes the kernel page table with all required mappings.
Definition main.c:435
static size_t mem_count_available_pages(boot_memory_map_t *map)
Calculates the total available conventional memory.
Definition main.c:347
EFI_STATUS efi_main(EFI_HANDLE imageHandle, EFI_SYSTEM_TABLE *systemTable)
UEFI entry point.
Definition main.c:1115
static EFI_STATUS gop_set_mode(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop, int64_t requestedWidth, int64_t requestedHeight)
Sets the graphics mode to best match the requested resolution.
Definition main.c:154
#define MEM_BASIC_ALLOCATOR_RESERVE_PERCENTAGE
Definition main.c:34
panic_code_t
Panic error codes for debugging when boot services are unavailable.
Definition main.c:248
size_t maxPages
Definition main.c:238
static uint64_t basic_allocator_alloc_pages(pfn_t *pfns, size_t amount)
Allocate pages from the basic allocator.
Definition main.c:411
static EFI_STATUS mem_map_init(boot_memory_map_t *map)
Initializes the EFI memory map structure.
Definition main.c:304
#define MEM_BASIC_ALLOCATOR_MIN_PAGES
Definition main.c:35
static EFI_STATUS boot_info_populate(EFI_HANDLE imageHandle, EFI_SYSTEM_TABLE *systemTable, boot_info_t *bootInfo)
Populates the boot information structure.
Definition main.c:982
static bool wstr_to_str(char *dest, size_t destSize, const CHAR16 *src)
Copies a wide character string to a narrow character buffer.
Definition main.c:504
static EFI_STATUS disk_init(boot_disk_t *disk, EFI_FILE *rootHandle)
Loads the initial RAM disk from the boot volume.
Definition main.c:748
static void mem_map_cleanup(boot_memory_map_t *map)
Free the memory map structure.
Definition main.c:331
static void boot_dir_free(boot_dir_t *dir)
Recursively frees a directory tree.
Definition main.c:543
static EFI_STATUS boot_services_exit(EFI_HANDLE imageHandle, boot_info_t *bootInfo)
Exits UEFI boot services and prepares for kernel handoff.
Definition main.c:1047
static EFI_STATUS kernel_load(boot_kernel_t *kernel, EFI_FILE *rootHandle)
Loads and validates the kernel ELF file.
Definition main.c:786
EFI_PHYSICAL_ADDRESS buffer
Definition main.c:237
static void * rsdp_locate(EFI_SYSTEM_TABLE *systemTable)
Locates the ACPI RSDP from the EFI configuration table.
Definition main.c:45
static const uint32_t PANIC_COLORS[]
Color values for panic display.
Definition main.c:261
static _NORETURN void panic_halt(panic_code_t code)
Halts the system with a colored screen indicating the error.
Definition main.c:276
static UINT32 gop_find_best_mode(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop, int64_t requestedWidth, int64_t requestedHeight)
Finds the best matching graphics mode for the requested resolution.
Definition main.c:111
boot_gop_t * gop
Definition main.c:240
static void boot_file_free(boot_file_t *file)
Frees a boot file structure and its data.
Definition main.c:526
static struct @40 basicAllocator
Basic page allocator state.
#define GOP_HEIGHT
Ignored if GOP_USE_DEFAULT_RES is set to 1.
Definition main.c:31
static EFI_STATUS mem_allocator_init(void)
Initializes the basic page allocator.
Definition main.c:368
static boot_file_t * disk_load_file(EFI_FILE *parentDir, const CHAR16 *fileName)
Loads a single file from an EFI file handle.
Definition main.c:574
static void splash_screen_display(void)
Displays the bootloader splash screen.
Definition main.c:919
static boot_dir_t * disk_load_dir(EFI_FILE *dirHandle, const CHAR16 *dirName)
Recursively loads a directory and its contents.
Definition main.c:645
@ PANIC_PAGE_TABLE_INIT
Definition main.c:250
@ PANIC_GOP_MAP
Definition main.c:255
@ PANIC_ALLOCATOR_EXHAUSTED
Definition main.c:249
@ PANIC_HIGHER_HALF_MAP
Definition main.c:253
@ PANIC_IDENTITY_MAP
Definition main.c:251
@ PANIC_KERNEL_MAP
Definition main.c:254
@ PANIC_HIGHER_HALF_INVALID
Definition main.c:252
static uint64_t gop_init(void)
Definition gop.c:87
#define PML_CALLBACK_NONE
Special callback ID that indicates no callback is associated with the page.
static uint64_t page_table_map(page_table_t *table, void *addr, phys_addr_t phys, size_t amount, pml_flags_t flags, pml_callback_id_t callbackId)
Maps a range of virtual addresses to physical addresses in the page table.
Definition paging.h:396
#define PHYS_TO_PFN(_addr)
Convert a physical address to its PFN.
#define PML_LOWER_TO_HIGHER(addr)
Converts an address from the lower half to the higher half.
size_t pfn_t
Page Frame Number type.
static uint64_t page_table_init(page_table_t *table, pml_alloc_pages_t allocPages, pml_free_pages_t freePages)
Initializes a page table.
Definition paging.h:132
#define PML_HIGHER_HALF_START
The start of the higher half of the address space.
@ PML_WRITE_THROUGH
@ PML_PRESENT
@ PML_WRITE
#define ASM(...)
Inline assembly macro.
Definition defs.h:160
#define ARRAY_SIZE(x)
Get the number of elements in a static array.
Definition defs.h:111
void elf64_load_segments(const Elf64_File *elf, Elf64_Addr base, Elf64_Off offset)
Load all loadable segments of an ELF file into memory.
uint64_t elf64_validate(Elf64_File *elf, void *data, uint64_t size)
Validate a files content and initalize a ELF64_File structure using it.
void elf64_get_loadable_bounds(const Elf64_File *elf, Elf64_Addr *minAddr, Elf64_Addr *maxAddr)
Get the loadable virtual memory bounds of an ELF file.
uint64_t Elf64_Addr
ELF64 Unsigned program address.
Definition elf.h:30
static void list_push_back(list_t *list, list_entry_t *entry)
Pushes an entry to the end of the list.
Definition list.h:322
static bool list_is_empty(list_t *list)
Checks if a list is empty.
Definition list.h:210
static void list_entry_init(list_entry_t *entry)
Initializes a list entry.
Definition list.h:173
static list_entry_t * list_pop_front(list_t *list)
Pops the first entry from the list.
Definition list.h:366
static void list_init(list_t *list)
Initializes a list.
Definition list.h:185
#define MAX(x, y)
Definition math.h:17
#define BYTES_TO_PAGES(amount)
Convert a size in bytes to pages.
Definition proc.h:107
#define NULL
Pointer error value.
Definition NULL.h:25
#define ERR
Integer error value.
Definition ERR.h:17
#define PAGE_SIZE
The size of a memory page in bytes.
Definition PAGE_SIZE.h:8
#define CONTAINER_OF(ptr, type, member)
Container of macro.
static void cr3_write(uint64_t value)
Definition regs.h:111
__UINT32_TYPE__ uint32_t
Definition stdint.h:15
__UINT64_TYPE__ uint64_t
Definition stdint.h:17
#define UINT64_MAX
Definition stdint.h:74
__UINT8_TYPE__ uint8_t
Definition stdint.h:11
__UINTPTR_TYPE__ uintptr_t
Definition stdint.h:43
__INT64_TYPE__ int64_t
Definition stdint.h:16
Elf64_Addr e_entry
Entry point virtual address.
Definition elf.h:100
Elf64_Ehdr * header
The data in the file, pointed to the start of the ELF header.
Definition elf.h:782
list_entry_t entry
Definition boot_info.h:74
boot_dir_t * root
Definition boot_info.h:82
list_entry_t entry
Definition boot_info.h:66
size_t size
Definition boot_info.h:69
void * data
Definition boot_info.h:68
char name[MAX_NAME]
Definition boot_info.h:67
size_t size
Definition boot_info.h:46
size_t stride
Definition boot_info.h:49
phys_addr_t physAddr
Definition boot_info.h:44
uint32_t * virtAddr
Definition boot_info.h:45
size_t width
Definition boot_info.h:47
size_t height
Definition boot_info.h:48
boot_memory_t memory
Definition boot_info.h:106
void * runtimeServices
Definition boot_info.h:103
boot_disk_t disk
Definition boot_info.h:104
boot_gop_t gop
Definition boot_info.h:101
void * rsdp
Definition boot_info.h:102
boot_kernel_t kernel
Definition boot_info.h:105
phys_addr_t physAddr
Definition boot_info.h:90
Elf64_File elf
Definition boot_info.h:89
EFI_MEMORY_DESCRIPTOR * descriptors
Definition boot_info.h:57
page_table_t table
Definition boot_info.h:96
boot_memory_map_t map
Definition boot_info.h:95
list_t children
Definition dentry.h:162
char name[MAX_NAME]
The name of the dentry, immutable after creation.
Definition dentry.h:158
A entry in a doubly linked list.
Definition list.h:37
A page table structure.