rulimine/common/protos/chainload.c
2022-02-03 10:38:43 +01:00

203 lines
5.7 KiB
C

#include <stddef.h>
#include <stdint.h>
#include <protos/chainload.h>
#include <lib/part.h>
#include <lib/config.h>
#include <lib/blib.h>
#include <drivers/disk.h>
#include <lib/term.h>
#include <lib/fb.h>
#include <lib/uri.h>
#include <lib/print.h>
#include <lib/libc.h>
#include <sys/idt.h>
#include <drivers/vga_textmode.h>
#include <mm/pmm.h>
#if uefi == 1
# include <efi.h>
#endif
#if bios == 1
__attribute__((noinline, section(".realmode")))
static void spinup(uint8_t drive) {
struct idtr real_mode_idt;
real_mode_idt.limit = 0x3ff;
real_mode_idt.ptr = 0;
asm volatile (
"cli\n\t"
"cld\n\t"
"lidt (%%eax)\n\t"
"pushl $0x08\n\t"
"pushl $1f\n\t"
"lret\n\t"
"1: .code16\n\t"
"movw $0x10, %%ax\n\t"
"movw %%ax, %%ds\n\t"
"movw %%ax, %%es\n\t"
"movw %%ax, %%fs\n\t"
"movw %%ax, %%gs\n\t"
"movw %%ax, %%ss\n\t"
"movl %%cr0, %%eax\n\t"
"andb $0xfe, %%al\n\t"
"movl %%eax, %%cr0\n\t"
"movl $1f, %%eax\n\t"
"pushw $0\n\t"
"pushw %%ax\n\t"
"lret\n\t"
"1:\n\t"
"xorw %%ax, %%ax\n\t"
"movw %%ax, %%ds\n\t"
"movw %%ax, %%es\n\t"
"movw %%ax, %%fs\n\t"
"movw %%ax, %%gs\n\t"
"movw %%ax, %%ss\n\t"
"sti\n\t"
"pushw $0\n\t"
"pushw $0x7c00\n\t"
"lret\n\t"
".code32\n\t"
:
: "a" (&real_mode_idt), "d" (drive)
: "memory"
);
}
void chainload(char *config) {
uint64_t val;
int part; {
char *part_config = config_get_value(config, 0, "PARTITION");
if (part_config == NULL) {
part = 0;
} else {
val = strtoui(part_config, NULL, 10);
if (val > 256) {
panic(true, "chainload: BIOS partition number outside range 0-256");
}
part = val;
}
}
int drive; {
char *drive_config = config_get_value(config, 0, "DRIVE");
if (drive_config == NULL) {
drive = boot_volume->index;
} else {
val = strtoui(drive_config, NULL, 10);
if (val < 1 || val > 256) {
panic(true, "chainload: BIOS drive number outside range 1-256");
}
drive = val;
}
}
struct volume *p = volume_get_by_coord(false, drive, part);
size_t rows, cols;
init_vga_textmode(&rows, &cols, false);
volume_read(p, (void *)0x7c00, 0, 512);
spinup(p->drive);
}
#elif uefi == 1
void chainload(char *config) {
EFI_STATUS status;
char *image_path = config_get_value(config, 0, "IMAGE_PATH");
if (image_path == NULL)
panic(true, "chainload: IMAGE_PATH not specified");
struct file_handle *image;
if ((image = uri_open(image_path)) == NULL)
panic(true, "chainload: Failed to open image with path `%s`. Is the path correct?", image_path);
EFI_HANDLE efi_part_handle = image->efi_part_handle;
void *_ptr = freadall(image, MEMMAP_RESERVED);
size_t image_size = image->size;
void *ptr;
status = gBS->AllocatePool(EfiLoaderData, image_size, &ptr);
if (status)
panic(true, "chainload: Allocation failure");
memcpy(ptr, _ptr, image_size);
pmm_free(_ptr, image->size);
fclose(image);
term_deinit();
size_t req_width = 0, req_height = 0, req_bpp = 0;
char *resolution = config_get_value(config, 0, "RESOLUTION");
if (resolution != NULL)
parse_resolution(&req_width, &req_height, &req_bpp, resolution);
struct fb_info fbinfo;
if (!fb_init(&fbinfo, req_width, req_height, req_bpp))
panic(true, "chainload: Unable to set video mode");
pmm_release_uefi_mem();
MEMMAP_DEVICE_PATH memdev_path[2];
memdev_path[0].Header.Type = HARDWARE_DEVICE_PATH;
memdev_path[0].Header.SubType = HW_MEMMAP_DP;
memdev_path[0].Header.Length[0] = sizeof(MEMMAP_DEVICE_PATH);
memdev_path[0].Header.Length[1] = sizeof(MEMMAP_DEVICE_PATH) >> 8;
memdev_path[0].MemoryType = EfiLoaderData;
memdev_path[0].StartingAddress = (uintptr_t)ptr;
memdev_path[0].EndingAddress = (uintptr_t)ptr + image_size;
memdev_path[1].Header.Type = END_DEVICE_PATH_TYPE;
memdev_path[1].Header.SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE;
memdev_path[1].Header.Length[0] = sizeof(EFI_DEVICE_PATH);
memdev_path[1].Header.Length[1] = sizeof(EFI_DEVICE_PATH) >> 8;
EFI_HANDLE new_handle = 0;
status = gBS->LoadImage(0, efi_image_handle,
(EFI_DEVICE_PATH *)memdev_path,
ptr, image_size, &new_handle);
if (status) {
panic(false, "chainload: LoadImage failure (%x)", status);
}
// Apparently we need to make sure that the DeviceHandle field is the same
// as us (the loader) for some EFI images to properly work (Windows for instance)
EFI_GUID loaded_img_prot_guid = EFI_LOADED_IMAGE_PROTOCOL_GUID;
EFI_LOADED_IMAGE_PROTOCOL *new_handle_loaded_image = NULL;
status = gBS->HandleProtocol(new_handle, &loaded_img_prot_guid,
(void **)&new_handle_loaded_image);
if (status) {
panic(false, "chainload: HandleProtocol failure (%x)", status);
}
if (efi_part_handle != 0) {
new_handle_loaded_image->DeviceHandle = efi_part_handle;
}
UINTN exit_data_size = 0;
CHAR16 *exit_data = NULL;
EFI_STATUS exit_status = gBS->StartImage(new_handle, &exit_data_size, &exit_data);
status = gBS->Exit(efi_image_handle, exit_status, exit_data_size, exit_data);
if (status) {
panic(false, "chainload: Exit failure (%x)", status);
}
__builtin_unreachable();
}
#endif