| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <efi/protocol/block-io.h> |
| #include <efi/protocol/disk-io.h> |
| #include <efi/protocol/loaded-image.h> |
| #include <efi/protocol/device-path.h> |
| #include <efi/protocol/device-path-to-text.h> |
| |
| #include <zircon/hw/gpt.h> |
| |
| #include "osboot.h" |
| |
| static bool path_node_match(efi_device_path_protocol* a, |
| efi_device_path_protocol* b) { |
| size_t alen = a->Length[0] | (a->Length[1] << 8); |
| size_t blen = b->Length[0] | (b->Length[1] << 8); |
| if (alen != blen) { |
| return false; |
| } |
| if (memcmp(a, b, alen)) { |
| return false; |
| } |
| return true; |
| } |
| |
| static efi_device_path_protocol* path_node_next(efi_device_path_protocol* node) { |
| if (node->Type == DEVICE_PATH_END) { |
| return NULL; |
| } |
| return ((void*) node) + (node->Length[0] | (node->Length[1] << 8)); |
| } |
| |
| static bool path_prefix_match(efi_device_path_protocol* path, |
| efi_device_path_protocol* prefix) { |
| if ((path == NULL) || (prefix == NULL)) { |
| return false; |
| } |
| for (;;) { |
| if (prefix->Type == DEVICE_PATH_END) { |
| return true; |
| } |
| if (!path_node_match(path, prefix)) { |
| return false; |
| } |
| if ((path = path_node_next(path)) == NULL) { |
| return false; |
| } |
| prefix = path_node_next(prefix); |
| } |
| } |
| |
| static void print_path(efi_boot_services* bs, efi_device_path_protocol* path) { |
| efi_device_path_to_text_protocol* ptt; |
| efi_status status = bs->LocateProtocol(&DevicePathToTextProtocol, NULL, (void**) &ptt); |
| if (status != EFI_SUCCESS) { |
| printf("<cannot print path>"); |
| return; |
| } |
| char16_t* txt = ptt->ConvertDevicePathToText(path, false, false); |
| if (txt == NULL) { |
| printf("<cannot print path>"); |
| return; |
| } |
| puts16(txt); |
| printf("\n"); |
| bs->FreePool(txt); |
| } |
| |
| typedef struct { |
| efi_disk_io_protocol* io; |
| efi_handle h; |
| efi_boot_services* bs; |
| efi_handle img; |
| uint64_t first; |
| uint64_t last; |
| uint32_t blksz; |
| uint32_t id; |
| } disk_t; |
| |
| static efi_status disk_read(disk_t* disk, size_t offset, void* data, size_t length) { |
| uint64_t size = (disk->last - disk->first) * disk->blksz; |
| |
| if ((offset > size) || ((size - offset) < length)) { |
| return EFI_INVALID_PARAMETER; |
| } |
| |
| return disk->io->ReadDisk(disk->io, disk->id, (disk->first * disk->blksz) + offset, length, data); |
| } |
| |
| static void disk_close(disk_t* disk) { |
| disk->bs->CloseProtocol(disk->h, &DiskIoProtocol, disk->img, NULL); |
| } |
| |
| static int disk_find_boot(efi_handle img, efi_system_table* sys, |
| bool verbose, disk_t* disk) { |
| bool found = false; |
| efi_boot_services* bs = sys->BootServices; |
| efi_handle* list; |
| size_t count; |
| efi_status status; |
| efi_loaded_image_protocol* li; |
| |
| status = bs->OpenProtocol(img, &LoadedImageProtocol, (void**) &li, img, NULL, |
| EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL); |
| if (status != EFI_SUCCESS) { |
| return -1; |
| } |
| |
| efi_device_path_protocol* imgdevpath; |
| status = bs->OpenProtocol(li->DeviceHandle, &DevicePathProtocol, |
| (void**) &imgdevpath, img, NULL, |
| EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL); |
| if (status != EFI_SUCCESS) { |
| goto fail_open_devpath; |
| } |
| |
| if (verbose) { |
| printf("BootLoader Path: "); |
| print_path(bs, li->FilePath); |
| printf("BootLoader Device: "); |
| print_path(bs, imgdevpath); |
| } |
| |
| status = bs->LocateHandleBuffer(ByProtocol, &BlockIoProtocol, NULL, &count, &list); |
| if (status != EFI_SUCCESS) { |
| printf("find_boot_disk() - no block io devices found\n"); |
| goto fail_get_list; |
| } |
| |
| for (size_t n = 0; n < count; n++) { |
| efi_block_io_protocol* bio; |
| status = bs->OpenProtocol(list[n], &BlockIoProtocol, (void**) &bio, img, NULL, |
| EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL); |
| if (status != EFI_SUCCESS) { |
| continue; |
| } |
| |
| efi_device_path_protocol* path; |
| status = bs->OpenProtocol(list[n], &DevicePathProtocol, (void**) &path, img, NULL, |
| EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL); |
| if (status != EFI_SUCCESS) { |
| bs->CloseProtocol(list[n], &BlockIoProtocol, img, NULL); |
| continue; |
| } |
| |
| bool match = false; |
| |
| // if a non-logical partition, check for match |
| if (!bio->Media->LogicalPartition && bio->Media->MediaPresent) { |
| match = path_prefix_match(imgdevpath, path); |
| } |
| |
| if (verbose) { |
| printf("BlockIO Device: "); |
| print_path(bs, path); |
| printf(" : #%zu, %zuMB%s%s%s%s%s%s\n", n, |
| bio->Media->LastBlock * bio->Media->BlockSize / 1024 / 1024, |
| bio->Media->RemovableMedia ? " Removable" : "", |
| bio->Media->MediaPresent ? " Present" : "", |
| bio->Media->LogicalPartition ? " Logical" : "", |
| bio->Media->ReadOnly ? " RO" : "", |
| bio->Media->WriteCaching ? " WC" : "", |
| match ? " BootDevice" : ""); |
| } |
| |
| if (match && !found) { |
| status = bs->OpenProtocol(list[n], &DiskIoProtocol, (void**) &disk->io, img, NULL, |
| EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL); |
| if (status != EFI_SUCCESS) { |
| printf("find_boot_disk() - cannot get disk io protocol\n"); |
| } else { |
| disk->first = 0; |
| disk->last = bio->Media->LastBlock; |
| disk->id = bio->Media->MediaId; |
| disk->blksz = bio->Media->BlockSize; |
| disk->h = list[n]; |
| disk->img = img; |
| disk->bs = bs; |
| found = true; |
| } |
| } |
| |
| bs->CloseProtocol(list[n], &BlockIoProtocol, img, NULL); |
| bs->CloseProtocol(list[n], &DevicePathProtocol, img, NULL); |
| } |
| |
| bs->FreePool(list); |
| |
| fail_get_list: |
| bs->CloseProtocol(li->DeviceHandle, &DevicePathProtocol, img, NULL); |
| |
| fail_open_devpath: |
| bs->CloseProtocol(img, &LoadedImageProtocol, img, NULL); |
| |
| return found ? 0 : -1; |
| } |
| |
| static uint8_t GUID_ZIRCON_A[] = GUID_ZIRCON_A_VALUE; |
| static uint8_t GUID_ZIRCON_B[] = GUID_ZIRCON_B_VALUE; |
| |
| static int disk_find_kernel(disk_t* disk, bool verbose) { |
| gpt_header_t gpt; |
| efi_status status = disk_read(disk, disk->blksz, &gpt, sizeof(gpt)); |
| if (status != EFI_SUCCESS) { |
| return -1; |
| } |
| |
| if (gpt.magic != GPT_MAGIC) { |
| printf("gpt - bad magic!\n"); |
| return -1; |
| } |
| |
| if (verbose) { |
| printf("gpt: size: %u\n", gpt.size); |
| printf("gpt: current: %zu\n", gpt.current); |
| printf("gpt: backup: %zu\n", gpt.backup); |
| printf("gpt: first: %zu\n", gpt.first); |
| printf("gpt: last: %zu\n", gpt.last); |
| printf("gpt: entries: %zu\n", gpt.entries); |
| printf("gpt: e.count: %u\n", gpt.entries_count); |
| printf("gpt: e.size: %u\n", gpt.entries_size); |
| } |
| |
| if ((gpt.magic != GPT_MAGIC) || |
| (gpt.size != GPT_HEADER_SIZE) || |
| (gpt.entries_size != GPT_ENTRY_SIZE) || |
| (gpt.entries_count > 256)) { |
| printf("gpt - malformed header\n"); |
| return -1; |
| } |
| |
| gpt_entry_t* table; |
| size_t tsize = gpt.entries_count * gpt.entries_size; |
| |
| status = disk->bs->AllocatePool(EfiLoaderData, tsize, (void**) &table); |
| if (status != EFI_SUCCESS) { |
| printf("gpt - allocation failure\n"); |
| return -1; |
| } |
| |
| status = disk_read(disk, disk->blksz * gpt.entries, table, tsize); |
| if (status != EFI_SUCCESS) { |
| disk->bs->FreePool(table); |
| printf("gpt - io error\n"); |
| return -1; |
| } |
| |
| bool found = false; |
| for (unsigned n = 0; n < gpt.entries_count; n++) { |
| if ((table[n].first == 0) || |
| (table[n].last == 0) || |
| (table[n].last < table[n].first)) { |
| // ignore empty or bogus entries |
| continue; |
| } |
| |
| const char* type; |
| if (!memcmp(table[n].type, GUID_ZIRCON_A, sizeof(GUID_ZIRCON_A))) { |
| type = "zircon-a"; |
| disk->first = table[n].first; |
| disk->last = table[n].last; |
| found = true; |
| } else if (!memcmp(table[n].type, GUID_ZIRCON_B, sizeof(GUID_ZIRCON_B))) { |
| type = "zircon-b"; |
| } else { |
| type = "unknown"; |
| } |
| |
| if (verbose) { |
| char name[GPT_NAME_LEN/2]; |
| for (unsigned i = 0; i < GPT_NAME_LEN/2 ; i++) { |
| unsigned c = table[n].name[i*2 + 0] | (table[n].name[i*2 + 1] << 8); |
| if ((c != 0) && ((c < ' ') || (c > 127))) { |
| c = '.'; |
| } |
| name[i] = c; |
| } |
| name[GPT_NAME_LEN/2 - 1] = 0; |
| printf("#%03d %zu..%zu %zx name='%s' type='%s'\n", |
| n, table[n].first, table[n].last, table[n].flags, name, type); |
| } |
| } |
| disk->bs->FreePool(table); |
| |
| return found ? 0 : -1; |
| } |
| |
| void* image_load_from_disk(efi_handle img, efi_system_table* sys, size_t* _sz) { |
| static bool verbose = false; |
| static uint8_t sector[512]; |
| efi_boot_services* bs = sys->BootServices; |
| disk_t disk; |
| |
| if (disk_find_boot(img, sys, verbose, &disk) < 0) { |
| printf("Cannot find bootloader disk.\n"); |
| return NULL; |
| } |
| |
| if (disk_find_kernel(&disk, verbose)) { |
| printf("Cannot find ZIRCON partition on bootloader disk.\n"); |
| goto fail0; |
| } |
| |
| efi_status status = disk_read(&disk, 0, sector, 512); |
| if (status != EFI_SUCCESS) { |
| goto fail0; |
| } |
| |
| size_t sz = image_getsize(sector, 512); |
| if (sz == 0) { |
| printf("ZIRCON partition has no valid header\n"); |
| goto fail0; |
| } |
| |
| size_t pages = (sz + 4095) / 4096; |
| void* image; |
| status = bs->AllocatePages(AllocateAnyPages, EfiLoaderData, pages, (efi_physical_addr*) &image); |
| if (status != EFI_SUCCESS) { |
| printf("Failed to allocate %zu bytes to load ZIRCON image\n", sz); |
| goto fail0; |
| } |
| |
| status = disk_read(&disk, 0, image, sz); |
| if (status != EFI_SUCCESS) { |
| printf("Failed to read image from ZIRCON partition\n"); |
| goto fail1; |
| } |
| |
| if (identify_image(image, sz) != IMAGE_COMBO) { |
| printf("ZIRCON partition has no valid image\n"); |
| goto fail1; |
| } |
| |
| *_sz = sz; |
| return image; |
| |
| fail1: |
| bs->FreePages((efi_physical_addr) image, pages); |
| fail0: |
| disk_close(&disk); |
| return NULL; |
| } |