| 1 | #include <stddef.h> |
| 2 | #include <stdint.h> |
| 3 | #include <stdnoreturn.h> |
| 4 | #include <protos/chainload.h> |
| 5 | #include <lib/part.h> |
| 6 | #include <lib/config.h> |
| 7 | #include <lib/misc.h> |
| 8 | #include <drivers/disk.h> |
| 9 | #include <lib/term.h> |
| 10 | #include <lib/fb.h> |
| 11 | #include <lib/uri.h> |
| 12 | #include <lib/print.h> |
| 13 | #include <lib/libc.h> |
| 14 | #include <sys/idt.h> |
| 15 | #include <lib/bli.h> |
| 16 | #include <drivers/vga_textmode.h> |
| 17 | #include <mm/pmm.h> |
| 18 | #if defined (UEFI) |
| 19 | # include <efi.h> |
| 20 | #endif |
| 21 | |
| 22 | #if defined (BIOS) |
| 23 | |
| 24 | __attribute__((noinline, section(".realmode"))) |
| 25 | noreturn static void spinup(uint8_t drive, void *buf) { |
| 26 | struct idtr real_mode_idt; |
| 27 | real_mode_idt.limit = 0x3ff; |
| 28 | real_mode_idt.ptr = 0; |
| 29 | |
| 30 | asm volatile ( |
| 31 | "cli\n\t" |
| 32 | "cld\n\t" |
| 33 | |
| 34 | // Safe stack location |
| 35 | "mov $0x7c00, %%esp\n\t" |
| 36 | |
| 37 | // move buffer to final location |
| 38 | "mov $0x7c00, %%edi\n\t" |
| 39 | "mov $512, %%ecx\n\t" |
| 40 | "rep movsb\n\t" |
| 41 | |
| 42 | "lidt (%%eax)\n\t" |
| 43 | |
| 44 | "pushl $0x08\n\t" |
| 45 | "pushl $1f\n\t" |
| 46 | "lret\n\t" |
| 47 | "1: .code16\n\t" |
| 48 | "movw $0x10, %%ax\n\t" |
| 49 | "movw %%ax, %%ds\n\t" |
| 50 | "movw %%ax, %%es\n\t" |
| 51 | "movw %%ax, %%fs\n\t" |
| 52 | "movw %%ax, %%gs\n\t" |
| 53 | "movw %%ax, %%ss\n\t" |
| 54 | "movl %%cr0, %%eax\n\t" |
| 55 | "andb $0xfe, %%al\n\t" |
| 56 | "movl %%eax, %%cr0\n\t" |
| 57 | "movl $1f, %%eax\n\t" |
| 58 | "pushw $0\n\t" |
| 59 | "pushw %%ax\n\t" |
| 60 | "lret\n\t" |
| 61 | "1:\n\t" |
| 62 | "xorw %%ax, %%ax\n\t" |
| 63 | "movw %%ax, %%ds\n\t" |
| 64 | "movw %%ax, %%es\n\t" |
| 65 | "movw %%ax, %%fs\n\t" |
| 66 | "movw %%ax, %%gs\n\t" |
| 67 | "movw %%ax, %%ss\n\t" |
| 68 | |
| 69 | "sti\n\t" |
| 70 | |
| 71 | "pushw $0\n\t" |
| 72 | "pushw $0x7c00\n\t" |
| 73 | "lret\n\t" |
| 74 | |
| 75 | ".code32\n\t" |
| 76 | : |
| 77 | : "a" (&real_mode_idt), "d" (drive), "S"(buf) |
| 78 | : "memory" |
| 79 | ); |
| 80 | |
| 81 | __builtin_unreachable(); |
| 82 | } |
| 83 | |
| 84 | noreturn void chainload(char *config, char *cmdline) { |
| 85 | (void)cmdline; |
| 86 | |
| 87 | uint64_t val; |
| 88 | |
| 89 | int part; { |
| 90 | char *part_config = config_get_value(config, 0, "PARTITION"); |
| 91 | if (part_config == NULL) { |
| 92 | part = 0; |
| 93 | } else { |
| 94 | val = strtoui(part_config, NULL, 10); |
| 95 | if (val > 256) { |
| 96 | panic(true, "bios: BIOS partition number outside range 0-256"); |
| 97 | } |
| 98 | part = val; |
| 99 | } |
| 100 | } |
| 101 | int drive; { |
| 102 | char *drive_config = config_get_value(config, 0, "DRIVE"); |
| 103 | if (drive_config == NULL) { |
| 104 | drive = boot_volume->index; |
| 105 | } else { |
| 106 | val = strtoui(drive_config, NULL, 10); |
| 107 | if (val < 1 || val > 256) { |
| 108 | panic(true, "bios: BIOS drive number outside range 1-256"); |
| 109 | } |
| 110 | drive = val; |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | struct volume *p = volume_get_by_coord(false, drive, part); |
| 115 | if (p == NULL && config_get_value(config, 0, "GPT_GUID") == NULL |
| 116 | && config_get_value(config, 0, "GPT_UUID") == NULL |
| 117 | && config_get_value(config, 0, "MBR_ID") == NULL) { |
| 118 | panic(true, "bios: Specified drive/partition not found"); |
| 119 | } |
| 120 | |
| 121 | char *gpt_guid_s = config_get_value(config, 0, "GPT_GUID"); |
| 122 | if (gpt_guid_s == NULL) { |
| 123 | gpt_guid_s = config_get_value(config, 0, "GPT_UUID"); |
| 124 | } |
| 125 | if (gpt_guid_s != NULL) { |
| 126 | struct guid guid; |
| 127 | if (!string_to_guid_be(&guid, gpt_guid_s)) { |
| 128 | panic(true, "bios: Malformed GUID"); |
| 129 | } |
| 130 | |
| 131 | p = volume_get_by_guid(&guid); |
| 132 | if (p == NULL) { |
| 133 | if (!string_to_guid_mixed(&guid, gpt_guid_s)) { |
| 134 | panic(true, "bios: Malformed GUID"); |
| 135 | } |
| 136 | |
| 137 | p = volume_get_by_guid(&guid); |
| 138 | } |
| 139 | |
| 140 | if (p == NULL) { |
| 141 | panic(true, "bios: No matching GPT drive for GPT_GUID found"); |
| 142 | } |
| 143 | |
| 144 | if (p->partition != 0) { |
| 145 | panic(true, "bios: GPT_GUID is that of a partition, not a drive"); |
| 146 | } |
| 147 | |
| 148 | p = volume_get_by_coord(false, p->index, part); |
| 149 | |
| 150 | if (p == NULL) { |
| 151 | panic(true, "bios: Partition specified is not valid"); |
| 152 | } |
| 153 | |
| 154 | goto load; |
| 155 | } |
| 156 | |
| 157 | char *mbr_id_s = config_get_value(config, 0, "MBR_ID"); |
| 158 | if (mbr_id_s != NULL) { |
| 159 | uint32_t mbr_id = strtoui(mbr_id_s, NULL, 16); |
| 160 | |
| 161 | for (size_t i = 0; i < volume_index_i; i++) { |
| 162 | p = volume_index[i]; |
| 163 | |
| 164 | if (!is_valid_mbr(p)) { |
| 165 | continue; |
| 166 | } |
| 167 | |
| 168 | uint32_t mbr_id_1; |
| 169 | if (!volume_read(p, &mbr_id_1, 0x1b8, sizeof(uint32_t))) { |
| 170 | continue; |
| 171 | } |
| 172 | |
| 173 | if (mbr_id_1 == mbr_id) { |
| 174 | p = volume_get_by_coord(false, p->index, part); |
| 175 | |
| 176 | if (p == NULL) { |
| 177 | panic(true, "bios: Partition specified is not valid"); |
| 178 | } |
| 179 | |
| 180 | goto load; |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | panic(true, "bios: No matching MBR ID found"); |
| 185 | } |
| 186 | |
| 187 | load: |
| 188 | vga_textmode_init(false); |
| 189 | |
| 190 | void *buf = ext_mem_alloc(512); |
| 191 | |
| 192 | if (!volume_read(p, buf, 0, 512)) { |
| 193 | panic(true, "bios: Failed to read boot sector"); |
| 194 | } |
| 195 | |
| 196 | uint16_t *boot_sig = (uint16_t *)(buf + 0x1fe); |
| 197 | |
| 198 | if (*boot_sig != 0xaa55) { |
| 199 | panic(true, "bios: Volume is not bootable"); |
| 200 | } |
| 201 | |
| 202 | spinup(p->drive, buf); |
| 203 | } |
| 204 | |
| 205 | #elif defined (UEFI) |
| 206 | |
| 207 | static EFI_DEVICE_PATH_PROTOCOL *build_relative_efi_file_path(struct file_handle *image) { |
| 208 | // The file path stored in EFI_LOADED_IMAGE_PROTOCOL::FilePath is |
| 209 | // expected to be relative to the EFI_LOADED_IMAGE_PROTOCOL::DeviceHandle. |
| 210 | // For this reason the EFI_DEVICE_PATH_PROTOCOL of the efi_part_handle |
| 211 | // is not used as a prefix. This likely also means that the returned |
| 212 | // path cannot be given to gBS->LoadImage() directly. |
| 213 | |
| 214 | size_t original_path_chars = strlen(image->path); |
| 215 | |
| 216 | size_t efi_file_path_alloc_len = (original_path_chars + 1) * sizeof(CHAR16); |
| 217 | CHAR16 *efi_file_path = ext_mem_alloc(efi_file_path_alloc_len); |
| 218 | |
| 219 | bool leading_slash = true; |
| 220 | size_t j = 0; |
| 221 | for (size_t i = 0; i < original_path_chars; i++) { |
| 222 | if (image->path[i] == '/' && leading_slash) { |
| 223 | continue; |
| 224 | } |
| 225 | leading_slash = false; |
| 226 | efi_file_path[j++] = image->path[i] == '/' ? '\\' : image->path[i]; |
| 227 | } |
| 228 | efi_file_path[j] = 0; |
| 229 | |
| 230 | |
| 231 | size_t efi_file_path_len = ((j + 1) * sizeof(CHAR16)); |
| 232 | size_t path_item_len = sizeof(EFI_DEVICE_PATH_PROTOCOL) + efi_file_path_len; |
| 233 | size_t end_item_len = sizeof(EFI_DEVICE_PATH_PROTOCOL); |
| 234 | size_t alloc_len = path_item_len + end_item_len; |
| 235 | |
| 236 | EFI_DEVICE_PATH_PROTOCOL *device_path; |
| 237 | EFI_STATUS status = gBS->AllocatePool(EfiLoaderData, alloc_len, (void **)&device_path); |
| 238 | if (status) { |
| 239 | panic(true, "efi: AllocatePool() failure (%x)", status); |
| 240 | } |
| 241 | |
| 242 | FILEPATH_DEVICE_PATH *path_item = (FILEPATH_DEVICE_PATH *)device_path; |
| 243 | path_item->Header.Type = MEDIA_DEVICE_PATH; |
| 244 | path_item->Header.SubType = MEDIA_FILEPATH_DP; |
| 245 | path_item->Header.Length[0] = path_item_len; |
| 246 | path_item->Header.Length[1] = path_item_len >> 8; |
| 247 | memcpy(&path_item->PathName, efi_file_path, efi_file_path_len); |
| 248 | |
| 249 | EFI_DEVICE_PATH_PROTOCOL *end_item = (void *)device_path + path_item_len; |
| 250 | end_item->Type = END_DEVICE_PATH_TYPE; |
| 251 | end_item->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE; |
| 252 | end_item->Length[0] = end_item_len; |
| 253 | end_item->Length[1] = end_item_len >> 8; |
| 254 | |
| 255 | pmm_free(efi_file_path, efi_file_path_alloc_len); |
| 256 | return device_path; |
| 257 | } |
| 258 | |
| 259 | noreturn void chainload(char *config, char *cmdline) { |
| 260 | char *image_path = config_get_value(config, 0, "PATH"); |
| 261 | if (image_path == NULL) { |
| 262 | image_path = config_get_value(config, 0, "IMAGE_PATH"); |
| 263 | } |
| 264 | if (image_path == NULL) { |
| 265 | panic(true, "efi: Image path not specified"); |
| 266 | } |
| 267 | |
| 268 | // The firmware's LoadImage will verify the Secure Boot signature of the |
| 269 | // chainloaded EFI application, so Limine does not need to enforce its |
| 270 | // own hash check here. |
| 271 | bool saved_secure_boot_active = secure_boot_active; |
| 272 | secure_boot_active = false; |
| 273 | |
| 274 | struct file_handle *image; |
| 275 | if ((image = uri_open(image_path, MEMMAP_RESERVED, false |
| 276 | #if defined (__i386__) |
| 277 | , NULL, NULL |
| 278 | #endif |
| 279 | )) == NULL) |
| 280 | panic(true, "efi: Failed to open image with path `%s`. Is the path correct?", image_path); |
| 281 | |
| 282 | secure_boot_active = saved_secure_boot_active; |
| 283 | |
| 284 | EFI_STATUS status; |
| 285 | |
| 286 | EFI_HANDLE efi_part_handle = image->efi_part_handle; |
| 287 | |
| 288 | void *ptr = image->fd; |
| 289 | size_t image_size = image->size; |
| 290 | |
| 291 | memmap_alloc_range_in(untouched_memmap, &untouched_memmap_entries, |
| 292 | (uintptr_t)ptr, ALIGN_UP(image_size, 4096, panic(true, "chainload: Alignment overflow")), |
| 293 | MEMMAP_RESERVED, MEMMAP_USABLE, true, false, true); |
| 294 | |
| 295 | EFI_DEVICE_PATH_PROTOCOL *efi_file_path = build_relative_efi_file_path(image); |
| 296 | |
| 297 | fclose(image); |
| 298 | term_notready(); |
| 299 | |
| 300 | size_t req_width = 0, req_height = 0, req_bpp = 0; |
| 301 | |
| 302 | char *resolution = config_get_value(config, 0, "RESOLUTION"); |
| 303 | if (resolution != NULL) |
| 304 | parse_resolution(&req_width, &req_height, &req_bpp, resolution); |
| 305 | |
| 306 | struct fb_info *fbinfo; |
| 307 | size_t fb_count; |
| 308 | fb_init(&fbinfo, &fb_count, req_width, req_height, req_bpp, false, false); |
| 309 | |
| 310 | size_t cmdline_len = strlen(cmdline); |
| 311 | CHAR16 *new_cmdline; |
| 312 | status = gBS->AllocatePool(EfiLoaderData, CHECKED_MUL(cmdline_len + 1, sizeof(CHAR16), panic(true, "efi: Allocation size overflow")), (void **)&new_cmdline); |
| 313 | if (status) { |
| 314 | panic(true, "efi: Allocation failure"); |
| 315 | } |
| 316 | for (size_t i = 0; i < cmdline_len + 1; i++) { |
| 317 | new_cmdline[i] = cmdline[i]; |
| 318 | } |
| 319 | |
| 320 | pmm_release_uefi_mem(); |
| 321 | |
| 322 | MEMMAP_DEVICE_PATH memdev_path[2]; |
| 323 | |
| 324 | memdev_path[0].Header.Type = HARDWARE_DEVICE_PATH; |
| 325 | memdev_path[0].Header.SubType = HW_MEMMAP_DP; |
| 326 | memdev_path[0].Header.Length[0] = sizeof(MEMMAP_DEVICE_PATH); |
| 327 | memdev_path[0].Header.Length[1] = sizeof(MEMMAP_DEVICE_PATH) >> 8; |
| 328 | |
| 329 | memdev_path[0].MemoryType = EfiLoaderCode; |
| 330 | memdev_path[0].StartingAddress = (uintptr_t)ptr; |
| 331 | memdev_path[0].EndingAddress = (uintptr_t)ptr + image_size; |
| 332 | |
| 333 | memdev_path[1].Header.Type = END_DEVICE_PATH_TYPE; |
| 334 | memdev_path[1].Header.SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE; |
| 335 | memdev_path[1].Header.Length[0] = sizeof(EFI_DEVICE_PATH); |
| 336 | memdev_path[1].Header.Length[1] = sizeof(EFI_DEVICE_PATH) >> 8; |
| 337 | |
| 338 | EFI_HANDLE new_handle = 0; |
| 339 | |
| 340 | status = gBS->LoadImage(0, efi_image_handle, |
| 341 | (EFI_DEVICE_PATH *)memdev_path, |
| 342 | ptr, image_size, &new_handle); |
| 343 | if (status) { |
| 344 | panic(false, "efi: LoadImage failure (%X)", (uint64_t)status); |
| 345 | } |
| 346 | |
| 347 | EFI_GUID loaded_img_prot_guid = EFI_LOADED_IMAGE_PROTOCOL_GUID; |
| 348 | |
| 349 | EFI_LOADED_IMAGE_PROTOCOL *new_handle_loaded_image = NULL; |
| 350 | status = gBS->HandleProtocol(new_handle, &loaded_img_prot_guid, |
| 351 | (void **)&new_handle_loaded_image); |
| 352 | if (status) { |
| 353 | panic(false, "efi: HandleProtocol failure (%X)", (uint64_t)status); |
| 354 | } |
| 355 | |
| 356 | if (efi_part_handle != 0) { |
| 357 | new_handle_loaded_image->DeviceHandle = efi_part_handle; |
| 358 | } |
| 359 | |
| 360 | new_handle_loaded_image->FilePath = efi_file_path; |
| 361 | |
| 362 | new_handle_loaded_image->LoadOptionsSize = (cmdline_len + 1) * sizeof(CHAR16); |
| 363 | new_handle_loaded_image->LoadOptions = new_cmdline; |
| 364 | |
| 365 | bli_on_boot(); |
| 366 | |
| 367 | UINTN exit_data_size = 0; |
| 368 | CHAR16 *exit_data = NULL; |
| 369 | EFI_STATUS exit_status = gBS->StartImage(new_handle, &exit_data_size, &exit_data); |
| 370 | |
| 371 | status = gBS->Exit(efi_image_handle, exit_status, exit_data_size, exit_data); |
| 372 | if (status) { |
| 373 | panic(false, "efi: Exit failure (%X)", (uint64_t)status); |
| 374 | } |
| 375 | |
| 376 | __builtin_unreachable(); |
| 377 | } |
| 378 | |
| 379 | #endif |