| /* |
| * Copyright 2011-2014 Intel Corporation - All Rights Reserved |
| */ |
| |
| #include <codepage.h> |
| #include <core.h> |
| #include <fs.h> |
| #include <com32.h> |
| #include <syslinux/memscan.h> |
| #include <syslinux/firmware.h> |
| #include <syslinux/linux.h> |
| #include <sys/ansi.h> |
| #include <setjmp.h> |
| |
| #include "efi.h" |
| #include "fio.h" |
| #include "version.h" |
| |
| __export uint16_t PXERetry; |
| __export char copyright_str[] = "Copyright (C) 2011-" YEAR_STR "\n"; |
| uint8_t SerialNotice = 1; |
| __export char syslinux_banner[] = "Syslinux " VERSION_STR " (EFI; " DATE_STR ")\n"; |
| char CurrentDirName[CURRENTDIR_MAX]; |
| struct com32_sys_args __com32; |
| |
| uint32_t _IdleTimer = 0; |
| char __lowmem_heap[32]; |
| uint32_t BIOS_timer_next; |
| uint32_t timer_irq; |
| __export uint8_t KbdMap[256]; |
| char aux_seg[256]; |
| |
| static jmp_buf load_error_buf; |
| |
| static inline EFI_STATUS |
| efi_close_protocol(EFI_HANDLE handle, EFI_GUID *guid, EFI_HANDLE agent, |
| EFI_HANDLE controller) |
| { |
| return uefi_call_wrapper(BS->CloseProtocol, 4, handle, |
| guid, agent, controller); |
| } |
| |
| struct efi_binding *efi_create_binding(EFI_GUID *bguid, EFI_GUID *pguid) |
| { |
| EFI_SERVICE_BINDING *sbp; |
| struct efi_binding *b; |
| EFI_STATUS status; |
| EFI_HANDLE protocol, child, *handles = NULL; |
| UINTN i, nr_handles = 0; |
| |
| b = malloc(sizeof(*b)); |
| if (!b) |
| return NULL; |
| |
| status = LibLocateHandle(ByProtocol, bguid, NULL, &nr_handles, &handles); |
| if (status != EFI_SUCCESS) |
| goto free_binding; |
| |
| for (i = 0; i < nr_handles; i++) { |
| status = uefi_call_wrapper(BS->OpenProtocol, 6, handles[i], |
| bguid, (void **)&sbp, |
| image_handle, handles[i], |
| EFI_OPEN_PROTOCOL_GET_PROTOCOL); |
| if (status == EFI_SUCCESS) |
| break; |
| |
| uefi_call_wrapper(BS->CloseProtocol, 4, handles[i], bguid, |
| image_handle, handles[i]); |
| } |
| |
| if (i == nr_handles) |
| goto free_binding; |
| |
| child = NULL; |
| |
| status = uefi_call_wrapper(sbp->CreateChild, 2, sbp, (EFI_HANDLE *)&child); |
| if (status != EFI_SUCCESS) |
| goto close_protocol; |
| |
| status = uefi_call_wrapper(BS->OpenProtocol, 6, child, |
| pguid, (void **)&protocol, |
| image_handle, sbp, |
| EFI_OPEN_PROTOCOL_GET_PROTOCOL); |
| if (status != EFI_SUCCESS) |
| goto destroy_child; |
| |
| b->parent = handles[i]; |
| b->binding = sbp; |
| b->child = child; |
| b->this = protocol; |
| |
| return b; |
| |
| destroy_child: |
| uefi_call_wrapper(sbp->DestroyChild, 2, sbp, child); |
| |
| close_protocol: |
| uefi_call_wrapper(BS->CloseProtocol, 4, handles[i], bguid, |
| image_handle, handles[i]); |
| |
| free_binding: |
| free(b); |
| return NULL; |
| } |
| |
| void efi_destroy_binding(struct efi_binding *b, EFI_GUID *guid) |
| { |
| efi_close_protocol(b->child, guid, image_handle, b->binding); |
| uefi_call_wrapper(b->binding->DestroyChild, 2, b->binding, b->child); |
| efi_close_protocol(b->parent, guid, image_handle, b->parent); |
| |
| free(b); |
| } |
| |
| #undef kaboom |
| void kaboom(void) |
| { |
| } |
| |
| void printf_init(void) |
| { |
| } |
| |
| __export void local_boot(uint16_t ax) |
| { |
| /* |
| * Inform the firmware that we failed to execute correctly, which |
| * will trigger the next entry in the EFI Boot Manager list. |
| */ |
| longjmp(load_error_buf, 1); |
| } |
| |
| void bios_timer_cleanup(void) |
| { |
| } |
| |
| char trackbuf[4096]; |
| |
| void __cdecl core_farcall(uint32_t c, const com32sys_t *a, com32sys_t *b) |
| { |
| } |
| |
| __export struct firmware *firmware = NULL; |
| __export void *__syslinux_adv_ptr; |
| __export size_t __syslinux_adv_size; |
| char core_xfer_buf[65536]; |
| struct iso_boot_info { |
| uint32_t pvd; /* LBA of primary volume descriptor */ |
| uint32_t file; /* LBA of boot file */ |
| uint32_t length; /* Length of boot file */ |
| uint32_t csum; /* Checksum of boot file */ |
| uint32_t reserved[10]; /* Currently unused */ |
| } iso_boot_info; |
| |
| uint8_t DHCPMagic; |
| uint32_t RebootTime; |
| |
| void pxenv(void) |
| { |
| } |
| |
| uint16_t BIOS_fbm = 1; |
| far_ptr_t InitStack; |
| far_ptr_t PXEEntry; |
| |
| void gpxe_unload(void) |
| { |
| } |
| |
| void do_idle(void) |
| { |
| } |
| |
| void pxe_int1a(void) |
| { |
| } |
| |
| uint8_t KeepPXE; |
| |
| struct semaphore; |
| mstime_t sem_down(struct semaphore *sem, mstime_t time) |
| { |
| /* EFI is single threaded */ |
| return 0; |
| } |
| |
| void sem_up(struct semaphore *sem) |
| { |
| /* EFI is single threaded */ |
| } |
| |
| __export volatile uint32_t __ms_timer = 0; |
| volatile uint32_t __jiffies = 0; |
| |
| void efi_write_char(uint8_t ch, uint8_t attribute) |
| { |
| SIMPLE_TEXT_OUTPUT_INTERFACE *out = ST->ConOut; |
| uint16_t c[2]; |
| |
| uefi_call_wrapper(out->SetAttribute, 2, out, attribute); |
| |
| /* Lookup primary Unicode encoding in the system codepage */ |
| c[0] = codepage.uni[0][ch]; |
| c[1] = '\0'; |
| |
| uefi_call_wrapper(out->OutputString, 2, out, c); |
| } |
| |
| static void efi_showcursor(const struct term_state *st) |
| { |
| SIMPLE_TEXT_OUTPUT_INTERFACE *out = ST->ConOut; |
| bool cursor = st->cursor ? true : false; |
| |
| uefi_call_wrapper(out->EnableCursor, 2, out, cursor); |
| } |
| |
| static void efi_set_cursor(int x, int y, bool visible) |
| { |
| SIMPLE_TEXT_OUTPUT_INTERFACE *out = ST->ConOut; |
| |
| uefi_call_wrapper(out->SetCursorPosition, 3, out, x, y); |
| } |
| |
| static void efi_scroll_up(uint8_t cols, uint8_t rows, uint8_t attribute) |
| { |
| efi_write_char('\n', 0); |
| efi_write_char('\r', 0); |
| } |
| |
| static void efi_get_mode(int *cols, int *rows) |
| { |
| SIMPLE_TEXT_OUTPUT_INTERFACE *out = ST->ConOut; |
| UINTN c, r; |
| |
| uefi_call_wrapper(out->QueryMode, 4, out, out->Mode->Mode, &c, &r); |
| *rows = r; |
| *cols = c; |
| } |
| |
| static void efi_erase(int x0, int y0, int x1, int y1, uint8_t attribute) |
| { |
| SIMPLE_TEXT_OUTPUT_INTERFACE *out = ST->ConOut; |
| int cols, rows; |
| |
| efi_get_mode(&cols, &rows); |
| |
| /* |
| * The BIOS version of this function has the ability to erase |
| * parts or all of the screen - the UEFI console doesn't |
| * support this so we just set the cursor position unless |
| * we're clearing the whole screen. |
| */ |
| if (!x0 && y0 == (cols - 1)) { |
| /* Really clear the screen */ |
| uefi_call_wrapper(out->ClearScreen, 1, out); |
| } else { |
| uefi_call_wrapper(out->SetCursorPosition, 3, out, y1, x1); |
| } |
| } |
| |
| static void efi_text_mode(void) |
| { |
| } |
| |
| static void efi_get_cursor(uint8_t *x, uint8_t *y) |
| { |
| SIMPLE_TEXT_OUTPUT_INTERFACE *out = ST->ConOut; |
| *x = out->Mode->CursorColumn; |
| *y = out->Mode->CursorRow; |
| } |
| |
| struct output_ops efi_ops = { |
| .erase = efi_erase, |
| .write_char = efi_write_char, |
| .showcursor = efi_showcursor, |
| .set_cursor = efi_set_cursor, |
| .scroll_up = efi_scroll_up, |
| .get_mode = efi_get_mode, |
| .text_mode = efi_text_mode, |
| .get_cursor = efi_get_cursor, |
| }; |
| |
| char SubvolName[2]; |
| static inline EFI_MEMORY_DESCRIPTOR * |
| get_memory_map(UINTN *nr_entries, UINTN *key, UINTN *desc_sz, |
| uint32_t *desc_ver) |
| { |
| return LibMemoryMap(nr_entries, key, desc_sz, desc_ver); |
| } |
| |
| |
| int efi_scan_memory(scan_memory_callback_t callback, void *data) |
| { |
| UINTN i, nr_entries, key, desc_sz; |
| UINTN buf, bufpos; |
| UINT32 desc_ver; |
| int rv = 0; |
| |
| buf = (UINTN)get_memory_map(&nr_entries, &key, &desc_sz, &desc_ver); |
| if (!buf) |
| return -1; |
| bufpos = buf; |
| |
| for (i = 0; i < nr_entries; bufpos += desc_sz, i++) { |
| EFI_MEMORY_DESCRIPTOR *m; |
| UINT64 region_sz; |
| enum syslinux_memmap_types type; |
| |
| m = (EFI_MEMORY_DESCRIPTOR *)bufpos; |
| region_sz = m->NumberOfPages * EFI_PAGE_SIZE; |
| |
| switch (m->Type) { |
| case EfiConventionalMemory: |
| type = SMT_FREE; |
| break; |
| default: |
| type = SMT_RESERVED; |
| break; |
| } |
| |
| rv = callback(data, m->PhysicalStart, region_sz, type); |
| if (rv) |
| break; |
| } |
| |
| FreePool((void *)buf); |
| return rv; |
| } |
| |
| static struct syslinux_memscan efi_memscan = { |
| .func = efi_scan_memory, |
| }; |
| |
| extern uint16_t *bios_free_mem; |
| void efi_init(void) |
| { |
| /* XXX timer */ |
| *bios_free_mem = 0; |
| syslinux_memscan_add(&efi_memscan); |
| mem_init(); |
| } |
| |
| char efi_getchar(char *hi) |
| { |
| SIMPLE_INPUT_INTERFACE *in = ST->ConIn; |
| EFI_INPUT_KEY key; |
| EFI_STATUS status; |
| |
| do { |
| status = uefi_call_wrapper(in->ReadKeyStroke, 2, in, &key); |
| } while (status == EFI_NOT_READY); |
| |
| if (!key.ScanCode) |
| return (char)key.UnicodeChar; |
| |
| /* |
| * We currently only handle scan codes that fit in 8 bits. |
| */ |
| *hi = (char)key.ScanCode; |
| return 0; |
| } |
| |
| int efi_pollchar(void) |
| { |
| SIMPLE_INPUT_INTERFACE *in = ST->ConIn; |
| EFI_STATUS status; |
| |
| status = WaitForSingleEvent(in->WaitForKey, 1); |
| return status != EFI_TIMEOUT; |
| } |
| |
| struct input_ops efi_iops = { |
| .getchar = efi_getchar, |
| .pollchar = efi_pollchar, |
| }; |
| |
| extern void efi_adv_init(void); |
| extern int efi_adv_write(void); |
| |
| struct adv_ops efi_adv_ops = { |
| .init = efi_adv_init, |
| .write = efi_adv_write, |
| }; |
| |
| struct efi_info { |
| uint32_t load_signature; |
| uint32_t systab; |
| uint32_t desc_size; |
| uint32_t desc_version; |
| uint32_t memmap; |
| uint32_t memmap_size; |
| uint32_t systab_hi; |
| uint32_t memmap_hi; |
| }; |
| |
| #define E820MAX 128 |
| #define E820_RAM 1 |
| #define E820_RESERVED 2 |
| #define E820_ACPI 3 |
| #define E820_NVS 4 |
| #define E820_UNUSABLE 5 |
| |
| #define BOOT_SIGNATURE 0xaa55 |
| #define SYSLINUX_EFILDR 0x30 /* Is this published value? */ |
| #define DEFAULT_TIMER_TICK_DURATION 500000 /* 500000 == 500000 * 100 * 10^-9 == 50 msec */ |
| #define DEFAULT_MSTIMER_INC 0x32 /* 50 msec */ |
| struct e820_entry { |
| uint64_t start; |
| uint64_t len; |
| uint32_t type; |
| } __packed; |
| |
| struct boot_params { |
| struct screen_info screen_info; |
| uint8_t _pad[0x1c0 - sizeof(struct screen_info)]; |
| struct efi_info efi; |
| uint8_t _pad2[8]; |
| uint8_t e820_entries; |
| uint8_t _pad3[0x2d0 - 0x1e8 - sizeof(uint8_t)]; |
| struct e820_entry e820_map[E820MAX]; |
| } __packed; |
| |
| /* Allocate boot parameter block aligned to page */ |
| #define BOOT_PARAM_BLKSIZE EFI_SIZE_TO_PAGES(sizeof(struct boot_params)) * EFI_PAGE_SIZE |
| |
| /* Routines in support of efi boot loader were obtained from |
| * http://git.kernel.org/?p=boot/efilinux/efilinux.git: |
| * kernel_jump(), handover_jump(), |
| * emalloc()/efree, alloc_pages/free_pages |
| * allocate_pool()/free_pool() |
| * memory_map() |
| */ |
| extern void kernel_jump(EFI_PHYSICAL_ADDRESS kernel_start, |
| struct boot_params *boot_params); |
| #if __SIZEOF_POINTER__ == 4 |
| #define EFI_LOAD_SIG "EL32" |
| #elif __SIZEOF_POINTER__ == 8 |
| #define EFI_LOAD_SIG "EL64" |
| #else |
| #error "unsupported architecture" |
| #endif |
| |
| struct dt_desc { |
| uint16_t limit; |
| uint64_t *base; |
| } __packed; |
| |
| struct dt_desc gdt = { 0x800, (uint64_t *)0 }; |
| struct dt_desc idt = { 0, 0 }; |
| |
| static inline EFI_MEMORY_DESCRIPTOR * |
| get_mem_desc(unsigned long memmap, UINTN desc_sz, int i) |
| { |
| return (EFI_MEMORY_DESCRIPTOR *)(memmap + (i * desc_sz)); |
| } |
| |
| EFI_HANDLE image_handle; |
| |
| static inline UINT64 round_up(UINT64 x, UINT64 y) |
| { |
| return (((x - 1) | (y - 1)) + 1); |
| } |
| |
| static inline UINT64 round_down(UINT64 x, UINT64 y) |
| { |
| return (x & ~(y - 1)); |
| } |
| |
| static void find_addr(EFI_PHYSICAL_ADDRESS *first, |
| EFI_PHYSICAL_ADDRESS *last, |
| EFI_PHYSICAL_ADDRESS min, |
| EFI_PHYSICAL_ADDRESS max, |
| size_t size, size_t align) |
| { |
| EFI_MEMORY_DESCRIPTOR *map; |
| UINT32 desc_ver; |
| UINTN i, nr_entries, key, desc_sz; |
| |
| map = get_memory_map(&nr_entries, &key, &desc_sz, &desc_ver); |
| if (!map) |
| return; |
| |
| for (i = 0; i < nr_entries; i++) { |
| EFI_MEMORY_DESCRIPTOR *m; |
| EFI_PHYSICAL_ADDRESS best; |
| UINT64 start, end; |
| |
| m = get_mem_desc((unsigned long)map, desc_sz, i); |
| if (m->Type != EfiConventionalMemory) |
| continue; |
| |
| if (m->NumberOfPages < EFI_SIZE_TO_PAGES(size)) |
| continue; |
| |
| start = m->PhysicalStart; |
| end = m->PhysicalStart + (m->NumberOfPages << EFI_PAGE_SHIFT); |
| if (first) { |
| if (end < min) |
| continue; |
| |
| /* What's the best address? */ |
| if (start < min && min < end) |
| best = min; |
| else |
| best = m->PhysicalStart; |
| |
| start = round_up(best, align); |
| if (start > max) |
| continue; |
| |
| /* Have we run out of space in this region? */ |
| if (end < start || (start + size) > end) |
| continue; |
| |
| if (start < *first) |
| *first = start; |
| } |
| |
| if (last) { |
| if (start > max) |
| continue; |
| |
| /* What's the best address? */ |
| if (start < max && max < end) |
| best = max - size; |
| else |
| best = end - size; |
| |
| start = round_down(best, align); |
| if (start < min || start < m->PhysicalStart) |
| continue; |
| |
| if (start > *last) |
| *last = start; |
| } |
| } |
| |
| FreePool(map); |
| } |
| |
| /** |
| * allocate_pages - Allocate memory pages from the system |
| * @atype: type of allocation to perform |
| * @mtype: type of memory to allocate |
| * @num_pages: number of contiguous 4KB pages to allocate |
| * @memory: used to return the address of allocated pages |
| * |
| * Allocate @num_pages physically contiguous pages from the system |
| * memory and return a pointer to the base of the allocation in |
| * @memory if the allocation succeeds. On success, the firmware memory |
| * map is updated accordingly. |
| * |
| * If @atype is AllocateAddress then, on input, @memory specifies the |
| * address at which to attempt to allocate the memory pages. |
| */ |
| static inline EFI_STATUS |
| allocate_pages(EFI_ALLOCATE_TYPE atype, EFI_MEMORY_TYPE mtype, |
| UINTN num_pages, EFI_PHYSICAL_ADDRESS *memory) |
| { |
| return uefi_call_wrapper(BS->AllocatePages, 4, atype, |
| mtype, num_pages, memory); |
| } |
| /** |
| * free_pages - Return memory allocated by allocate_pages() to the firmware |
| * @memory: physical base address of the page range to be freed |
| * @num_pages: number of contiguous 4KB pages to free |
| * |
| * On success, the firmware memory map is updated accordingly. |
| */ |
| static inline EFI_STATUS |
| free_pages(EFI_PHYSICAL_ADDRESS memory, UINTN num_pages) |
| { |
| return uefi_call_wrapper(BS->FreePages, 2, memory, num_pages); |
| } |
| |
| static EFI_STATUS allocate_addr(EFI_PHYSICAL_ADDRESS *addr, size_t size) |
| { |
| UINTN npages = EFI_SIZE_TO_PAGES(size); |
| |
| return uefi_call_wrapper(BS->AllocatePages, 4, |
| AllocateAddress, |
| EfiLoaderData, npages, |
| addr); |
| } |
| /** |
| * allocate_pool - Allocate pool memory |
| * @type: the type of pool to allocate |
| * @size: number of bytes to allocate from pool of @type |
| * @buffer: used to return the address of allocated memory |
| * |
| * Allocate memory from pool of @type. If the pool needs more memory |
| * pages are allocated from EfiConventionalMemory in order to grow the |
| * pool. |
| * |
| * All allocations are eight-byte aligned. |
| */ |
| static inline EFI_STATUS |
| allocate_pool(EFI_MEMORY_TYPE type, UINTN size, void **buffer) |
| { |
| return uefi_call_wrapper(BS->AllocatePool, 3, type, size, buffer); |
| } |
| |
| /** |
| * free_pool - Return pool memory to the system |
| * @buffer: the buffer to free |
| * |
| * Return @buffer to the system. The returned memory is marked as |
| * EfiConventionalMemory. |
| */ |
| static inline EFI_STATUS free_pool(void *buffer) |
| { |
| return uefi_call_wrapper(BS->FreePool, 1, buffer); |
| } |
| |
| static void free_addr(EFI_PHYSICAL_ADDRESS addr, size_t size) |
| { |
| UINTN npages = EFI_SIZE_TO_PAGES(size); |
| |
| uefi_call_wrapper(BS->FreePages, 2, addr, npages); |
| } |
| |
| /* cancel the established timer */ |
| static EFI_STATUS cancel_timer(EFI_EVENT ev) |
| { |
| return uefi_call_wrapper(BS->SetTimer, 3, ev, TimerCancel, 0); |
| } |
| |
| /* Check if timer went off and update default timer counter */ |
| void timer_handler(EFI_EVENT ev, VOID *ctx) |
| { |
| __ms_timer += DEFAULT_MSTIMER_INC; |
| ++__jiffies; |
| } |
| |
| /* Setup a default periodic timer */ |
| static EFI_STATUS setup_default_timer(EFI_EVENT *ev) |
| { |
| EFI_STATUS efi_status; |
| |
| *ev = NULL; |
| efi_status = uefi_call_wrapper( BS->CreateEvent, 5, EVT_TIMER|EVT_NOTIFY_SIGNAL, TPL_NOTIFY, (EFI_EVENT_NOTIFY)timer_handler, NULL, ev); |
| if (efi_status == EFI_SUCCESS) { |
| efi_status = uefi_call_wrapper(BS->SetTimer, 3, *ev, TimerPeriodic, DEFAULT_TIMER_TICK_DURATION); |
| } |
| return efi_status; |
| } |
| |
| /** |
| * emalloc - Allocate memory with a strict alignment requirement |
| * @size: size in bytes of the requested allocation |
| * @align: the required alignment of the allocation |
| * @addr: a pointer to the allocated address on success |
| * |
| * If we cannot satisfy @align we return 0. |
| */ |
| EFI_STATUS emalloc(UINTN size, UINTN align, EFI_PHYSICAL_ADDRESS *addr) |
| { |
| UINTN i, nr_entries, map_key, desc_size; |
| EFI_MEMORY_DESCRIPTOR *map_buf; |
| UINTN d; |
| UINT32 desc_version; |
| EFI_STATUS err; |
| UINTN nr_pages = EFI_SIZE_TO_PAGES(size); |
| |
| map_buf = get_memory_map(&nr_entries, &map_key, |
| &desc_size, &desc_version); |
| if (!map_buf) |
| goto fail; |
| |
| d = (UINTN)map_buf; |
| |
| for (i = 0; i < nr_entries; i++, d += desc_size) { |
| EFI_MEMORY_DESCRIPTOR *desc; |
| EFI_PHYSICAL_ADDRESS start, end, aligned; |
| |
| desc = (EFI_MEMORY_DESCRIPTOR *)d; |
| if (desc->Type != EfiConventionalMemory) |
| continue; |
| |
| if (desc->NumberOfPages < nr_pages) |
| continue; |
| |
| start = desc->PhysicalStart; |
| end = start + (desc->NumberOfPages << EFI_PAGE_SHIFT); |
| |
| /* Low-memory is super-precious! */ |
| if (end <= 1 << 20) |
| continue; |
| if (start < 1 << 20) |
| start = (1 << 20); |
| |
| aligned = (start + align -1) & ~(align -1); |
| |
| if ((aligned + size) <= end) { |
| err = allocate_pages(AllocateAddress, EfiLoaderData, |
| nr_pages, &aligned); |
| if (err == EFI_SUCCESS) { |
| *addr = aligned; |
| break; |
| } |
| } |
| } |
| |
| if (i == nr_entries) |
| err = EFI_OUT_OF_RESOURCES; |
| |
| free_pool(map_buf); |
| fail: |
| return err; |
| } |
| /** |
| * efree - Return memory allocated with emalloc |
| * @memory: the address of the emalloc() allocation |
| * @size: the size of the allocation |
| */ |
| void efree(EFI_PHYSICAL_ADDRESS memory, UINTN size) |
| { |
| UINTN nr_pages = EFI_SIZE_TO_PAGES(size); |
| |
| free_pages(memory, nr_pages); |
| } |
| |
| /* |
| * Check whether 'buf' contains a PE/COFF header and that the PE/COFF |
| * file can be executed by this architecture. |
| */ |
| static bool valid_pecoff_image(char *buf) |
| { |
| struct pe_header { |
| uint16_t signature; |
| uint8_t _pad[0x3a]; |
| uint32_t offset; |
| } *pehdr = (struct pe_header *)buf; |
| struct coff_header { |
| uint32_t signature; |
| uint16_t machine; |
| } *chdr; |
| |
| if (pehdr->signature != 0x5a4d) { |
| dprintf("Invalid MS-DOS header signature\n"); |
| return false; |
| } |
| |
| if (!pehdr->offset || pehdr->offset > 512) { |
| dprintf("Invalid PE header offset\n"); |
| return false; |
| } |
| |
| chdr = (struct coff_header *)&buf[pehdr->offset]; |
| if (chdr->signature != 0x4550) { |
| dprintf("Invalid PE header signature\n"); |
| return false; |
| } |
| |
| #if defined(__x86_64__) |
| if (chdr->machine != 0x8664) { |
| dprintf("Invalid PE machine field\n"); |
| return false; |
| } |
| #else |
| if (chdr->machine != 0x14c) { |
| dprintf("Invalid PE machine field\n"); |
| return false; |
| } |
| #endif |
| |
| return true; |
| } |
| |
| /* |
| * Boot a Linux kernel using the EFI boot stub handover protocol. |
| * |
| * This function will not return to its caller if booting the kernel |
| * image succeeds. If booting the kernel image fails, a legacy boot |
| * method should be attempted. |
| */ |
| static void handover_boot(struct linux_header *hdr, struct boot_params *bp) |
| { |
| unsigned long address = hdr->code32_start + hdr->handover_offset; |
| handover_func_t *func = efi_handover; |
| |
| dprintf("Booting kernel using handover protocol\n"); |
| |
| /* |
| * Ensure that the kernel is a valid PE32(+) file and that the |
| * architecture of the file matches this version of Syslinux - we |
| * can't mix firmware and kernel bitness (e.g. 32-bit kernel on |
| * 64-bit EFI firmware) using the handover protocol. |
| */ |
| if (!valid_pecoff_image((char *)hdr)) |
| return; |
| |
| if (hdr->version >= 0x20c) { |
| if (hdr->xloadflags & XLF_EFI_HANDOVER_32) |
| func = efi_handover_32; |
| |
| if (hdr->xloadflags & XLF_EFI_HANDOVER_64) |
| func = efi_handover_64; |
| } |
| |
| efi_console_restore(); |
| func(image_handle, ST, bp, address); |
| } |
| |
| static int check_linux_header(struct linux_header *hdr) |
| { |
| if (hdr->version < 0x205) |
| hdr->relocatable_kernel = 0; |
| |
| /* FIXME: check boot sector signature */ |
| if (hdr->boot_flag != BOOT_SIGNATURE) { |
| printf("Invalid Boot signature 0x%x, bailing out\n", hdr->boot_flag); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static char *build_cmdline(char *str) |
| { |
| EFI_PHYSICAL_ADDRESS addr; |
| EFI_STATUS status; |
| char *cmdline = NULL; /* internal, in efi_physical below 0x3FFFFFFF */ |
| |
| /* |
| * The kernel expects cmdline to be allocated pretty low, |
| * Documentation/x86/boot.txt says, |
| * |
| * "The kernel command line can be located anywhere |
| * between the end of the setup heap and 0xA0000" |
| */ |
| addr = 0xA0000; |
| status = allocate_pages(AllocateMaxAddress, EfiLoaderData, |
| EFI_SIZE_TO_PAGES(strlen(str) + 1), |
| &addr); |
| if (status != EFI_SUCCESS) { |
| printf("Failed to allocate memory for kernel command line, bailing out\n"); |
| return NULL; |
| } |
| cmdline = (char *)(UINTN)addr; |
| memcpy(cmdline, str, strlen(str) + 1); |
| return cmdline; |
| } |
| |
| static int build_gdt(void) |
| { |
| EFI_STATUS status; |
| |
| /* Allocate gdt consistent with the alignment for architecture */ |
| status = emalloc(gdt.limit, __SIZEOF_POINTER__ , (EFI_PHYSICAL_ADDRESS *)&gdt.base); |
| if (status != EFI_SUCCESS) { |
| printf("Failed to allocate memory for GDT, bailing out\n"); |
| return -1; |
| } |
| memset(gdt.base, 0x0, gdt.limit); |
| |
| /* |
| * 4Gb - (0x100000*0x1000 = 4Gb) |
| * base address=0 |
| * code read/exec |
| * granularity=4096, 386 (+5th nibble of limit) |
| */ |
| gdt.base[2] = 0x00cf9a000000ffff; |
| |
| /* |
| * 4Gb - (0x100000*0x1000 = 4Gb) |
| * base address=0 |
| * data read/write |
| * granularity=4096, 386 (+5th nibble of limit) |
| */ |
| gdt.base[3] = 0x00cf92000000ffff; |
| |
| /* Task segment value */ |
| gdt.base[4] = 0x0080890000000000; |
| |
| return 0; |
| } |
| |
| /* |
| * Callers use ->ramdisk_size to check whether any memory was |
| * allocated (and therefore needs free'ing). The return value indicates |
| * hard error conditions, such as failing to alloc memory for the |
| * ramdisk image. Having no initramfs is not an error. |
| */ |
| static int handle_ramdisks(struct linux_header *hdr, |
| struct initramfs *initramfs) |
| { |
| EFI_PHYSICAL_ADDRESS last; |
| struct initramfs *ip; |
| EFI_STATUS status; |
| addr_t irf_size; |
| addr_t next_addr, len, pad; |
| |
| hdr->ramdisk_image = 0; |
| hdr->ramdisk_size = 0; |
| |
| /* |
| * Figure out the size of the initramfs, and where to put it. |
| * We should put it at the highest possible address which is |
| * <= hdr->initrd_addr_max, which fits the entire initramfs. |
| */ |
| irf_size = initramfs_size(initramfs); /* Handles initramfs == NULL */ |
| if (!irf_size) |
| return 0; |
| |
| last = 0; |
| find_addr(NULL, &last, 0x1000, hdr->initrd_addr_max, |
| irf_size, INITRAMFS_MAX_ALIGN); |
| if (last) |
| status = allocate_addr(&last, irf_size); |
| |
| if (!last || status != EFI_SUCCESS) { |
| printf("Failed to allocate initramfs memory, bailing out\n"); |
| return -1; |
| } |
| |
| hdr->ramdisk_image = (uint32_t)last; |
| hdr->ramdisk_size = irf_size; |
| |
| /* Copy initramfs into allocated memory */ |
| for (ip = initramfs->next; ip->len; ip = ip->next) { |
| len = ip->len; |
| next_addr = last + len; |
| |
| /* |
| * If this isn't the last entry, extend the |
| * zero-pad region to enforce the alignment of |
| * the next chunk. |
| */ |
| if (ip->next->len) { |
| pad = -next_addr & (ip->next->align - 1); |
| len += pad; |
| next_addr += pad; |
| } |
| |
| if (ip->data_len) |
| memcpy((void *)(UINTN)last, ip->data, ip->data_len); |
| |
| if (len > ip->data_len) |
| memset((void *)(UINTN)(last + ip->data_len), 0, |
| len - ip->data_len); |
| |
| last = next_addr; |
| } |
| return 0; |
| } |
| |
| static int exit_boot(struct boot_params *bp) |
| { |
| struct e820_entry *e820buf, *e; |
| EFI_MEMORY_DESCRIPTOR *map; |
| EFI_STATUS status; |
| uint32_t e820_type; |
| UINTN i, nr_entries, key, desc_sz; |
| UINT32 desc_ver; |
| |
| /* Build efi memory map */ |
| map = get_memory_map(&nr_entries, &key, &desc_sz, &desc_ver); |
| if (!map) |
| return -1; |
| |
| bp->efi.memmap = (uint32_t)(unsigned long)map; |
| bp->efi.memmap_size = nr_entries * desc_sz; |
| bp->efi.systab = (uint32_t)(unsigned long)ST; |
| bp->efi.desc_size = desc_sz; |
| bp->efi.desc_version = desc_ver; |
| #if defined(__x86_64__) |
| bp->efi.systab_hi = ((unsigned long)ST) >> 32; |
| bp->efi.memmap_hi = ((unsigned long)map) >> 32; |
| #endif |
| |
| |
| /* |
| * Even though 'memmap' contains the memory map we provided |
| * previously in efi_scan_memory(), we should recalculate the |
| * e820 map because it will most likely have changed in the |
| * interim. |
| */ |
| e = e820buf = bp->e820_map; |
| for (i = 0; i < nr_entries && i < E820MAX; i++) { |
| struct e820_entry *prev = NULL; |
| |
| if (e > e820buf) |
| prev = e - 1; |
| |
| map = get_mem_desc(bp->efi.memmap, desc_sz, i); |
| e->start = map->PhysicalStart; |
| e->len = map->NumberOfPages << EFI_PAGE_SHIFT; |
| |
| switch (map->Type) { |
| case EfiReservedMemoryType: |
| case EfiRuntimeServicesCode: |
| case EfiRuntimeServicesData: |
| case EfiMemoryMappedIO: |
| case EfiMemoryMappedIOPortSpace: |
| case EfiPalCode: |
| e820_type = E820_RESERVED; |
| break; |
| |
| case EfiUnusableMemory: |
| e820_type = E820_UNUSABLE; |
| break; |
| |
| case EfiACPIReclaimMemory: |
| e820_type = E820_ACPI; |
| break; |
| |
| case EfiLoaderCode: |
| case EfiLoaderData: |
| case EfiBootServicesCode: |
| case EfiBootServicesData: |
| case EfiConventionalMemory: |
| e820_type = E820_RAM; |
| break; |
| |
| case EfiACPIMemoryNVS: |
| e820_type = E820_NVS; |
| break; |
| default: |
| continue; |
| } |
| |
| e->type = e820_type; |
| |
| /* Check for adjacent entries we can merge. */ |
| if (prev && (prev->start + prev->len) == e->start && |
| prev->type == e->type) |
| prev->len += e->len; |
| else |
| e++; |
| } |
| |
| bp->e820_entries = e - e820buf; |
| |
| status = uefi_call_wrapper(BS->ExitBootServices, 2, image_handle, key); |
| if (status != EFI_SUCCESS) { |
| printf("Failed to exit boot services: 0x%016lx\n", status); |
| FreePool(map); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| /* efi_boot_linux: |
| * Boots the linux kernel using the image and parameters to boot with. |
| * The EFI boot loader is reworked taking the cue from |
| * http://git.kernel.org/?p=boot/efilinux/efilinux.git on the need to |
| * cap key kernel data structures at * 0x3FFFFFFF. |
| * The kernel image, kernel command line and boot parameter block are copied |
| * into allocated memory areas that honor the address capping requirement |
| * prior to kernel handoff. |
| * |
| * FIXME |
| * Can we move this allocation requirement to com32 linux loader in order |
| * to avoid double copying kernel image? |
| */ |
| int efi_boot_linux(void *kernel_buf, size_t kernel_size, |
| struct initramfs *initramfs, |
| struct setup_data *setup_data, |
| char *cmdline) |
| { |
| struct linux_header *hdr; |
| struct boot_params *bp; |
| EFI_STATUS status; |
| EFI_PHYSICAL_ADDRESS addr, pref_address, kernel_start = 0; |
| UINT64 setup_sz, init_size = 0; |
| char *_cmdline; |
| |
| if (check_linux_header(kernel_buf)) |
| goto bail; |
| |
| /* allocate for boot parameter block */ |
| addr = 0x3FFFFFFF; |
| status = allocate_pages(AllocateMaxAddress, EfiLoaderData, |
| BOOT_PARAM_BLKSIZE, &addr); |
| if (status != EFI_SUCCESS) { |
| printf("Failed to allocate memory for kernel boot parameter block, bailing out\n"); |
| goto bail; |
| } |
| |
| bp = (struct boot_params *)(UINTN)addr; |
| |
| memset((void *)bp, 0x0, BOOT_PARAM_BLKSIZE); |
| /* Copy the first two sectors to boot_params */ |
| memcpy((char *)bp, kernel_buf, 2 * 512); |
| hdr = (struct linux_header *)bp; |
| |
| setup_sz = (hdr->setup_sects + 1) * 512; |
| if (hdr->version >= 0x20a) { |
| pref_address = hdr->pref_address; |
| init_size = hdr->init_size; |
| } else { |
| pref_address = 0x100000; |
| |
| /* |
| * We need to account for the fact that the kernel |
| * needs room for decompression, otherwise we could |
| * end up trashing other chunks of allocated memory. |
| */ |
| init_size = (kernel_size - setup_sz) * 3; |
| } |
| hdr->type_of_loader = SYSLINUX_EFILDR; /* SYSLINUX boot loader module */ |
| _cmdline = build_cmdline(cmdline); |
| if (!_cmdline) |
| goto bail; |
| |
| hdr->cmd_line_ptr = (UINT32)(UINTN)_cmdline; |
| |
| addr = pref_address; |
| status = allocate_pages(AllocateAddress, EfiLoaderData, |
| EFI_SIZE_TO_PAGES(init_size), &addr); |
| if (status != EFI_SUCCESS) { |
| /* |
| * We failed to allocate the preferred address, so |
| * just allocate some memory and hope for the best. |
| */ |
| if (!hdr->relocatable_kernel) { |
| printf("Cannot relocate kernel, bailing out\n"); |
| goto bail; |
| } |
| |
| status = emalloc(init_size, hdr->kernel_alignment, &addr); |
| if (status != EFI_SUCCESS) { |
| printf("Failed to allocate memory for kernel image, bailing out\n"); |
| goto free_map; |
| } |
| } |
| kernel_start = addr; |
| /* FIXME: we copy the kernel into the physical memory allocated here |
| * The syslinux kernel image load elsewhere could allocate the EFI memory from here |
| * prior to copying kernel and save an extra copy |
| */ |
| memcpy((void *)(UINTN)kernel_start, kernel_buf+setup_sz, kernel_size-setup_sz); |
| |
| hdr->code32_start = (UINT32)((UINT64)kernel_start); |
| |
| dprintf("efi_boot_linux: kernel_start 0x%x kernel_size 0x%x initramfs 0x%x setup_data 0x%x cmdline 0x%x\n", |
| kernel_start, kernel_size, initramfs, setup_data, _cmdline); |
| |
| if (handle_ramdisks(hdr, initramfs)) |
| goto free_map; |
| |
| /* Attempt to use the handover protocol if available */ |
| if (hdr->version >= 0x20b && hdr->handover_offset) |
| handover_boot(hdr, bp); |
| |
| setup_screen(&bp->screen_info); |
| |
| if (build_gdt()) |
| goto free_map; |
| |
| dprintf("efi_boot_linux: setup_sects %d kernel_size %d\n", hdr->setup_sects, kernel_size); |
| |
| efi_console_restore(); |
| |
| if (exit_boot(bp)) |
| goto free_map; |
| |
| memcpy(&bp->efi.load_signature, EFI_LOAD_SIG, sizeof(uint32_t)); |
| |
| asm volatile ("lidt %0" :: "m" (idt)); |
| asm volatile ("lgdt %0" :: "m" (gdt)); |
| |
| kernel_jump(kernel_start, bp); |
| |
| /* NOTREACHED */ |
| |
| free_map: |
| if (_cmdline) |
| efree((EFI_PHYSICAL_ADDRESS)(unsigned long)_cmdline, |
| strlen(_cmdline) + 1); |
| |
| if (bp) |
| efree((EFI_PHYSICAL_ADDRESS)(unsigned long)bp, |
| BOOT_PARAM_BLKSIZE); |
| if (kernel_start) efree(kernel_start, init_size); |
| if (hdr->ramdisk_size) |
| free_addr(hdr->ramdisk_image, hdr->ramdisk_size); |
| bail: |
| return -1; |
| } |
| |
| extern struct disk *efi_disk_init(EFI_HANDLE); |
| extern void serialcfg(uint16_t *, uint16_t *, uint16_t *); |
| |
| extern struct vesa_ops efi_vesa_ops; |
| |
| struct mem_ops efi_mem_ops = { |
| .malloc = efi_malloc, |
| .realloc = efi_realloc, |
| .free = efi_free, |
| }; |
| |
| struct firmware efi_fw = { |
| .init = efi_init, |
| .disk_init = efi_disk_init, |
| .o_ops = &efi_ops, |
| .i_ops = &efi_iops, |
| .get_serial_console_info = serialcfg, |
| .adv_ops = &efi_adv_ops, |
| .boot_linux = efi_boot_linux, |
| .vesa = &efi_vesa_ops, |
| .mem = &efi_mem_ops, |
| }; |
| |
| static inline void syslinux_register_efi(void) |
| { |
| firmware = &efi_fw; |
| } |
| |
| extern void init(void); |
| extern const struct fs_ops vfat_fs_ops; |
| extern const struct fs_ops pxe_fs_ops; |
| |
| char free_high_memory[4096]; |
| |
| extern char __bss_start[]; |
| extern char __bss_end[]; |
| |
| static void efi_setcwd(CHAR16 *dp) |
| { |
| CHAR16 *c16; |
| char *c8; |
| int i, j; |
| |
| /* Search for the start of the last path component */ |
| for (i = StrLen(dp) - 1; i >= 0; i--) { |
| if (dp[i] == '\\' || dp[i] == '/') |
| break; |
| } |
| |
| if (i < 0 || i > CURRENTDIR_MAX) { |
| dp = L"\\"; |
| i = 1; |
| } |
| |
| c8 = CurrentDirName; |
| c16 = dp; |
| |
| for (j = 0; j < i; j++) { |
| if (*c16 == '\\') { |
| *c8++ = '/'; |
| c16++; |
| } else |
| *c8++ = *c16++; |
| } |
| |
| *c8 = '\0'; |
| } |
| |
| EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *table) |
| { |
| EFI_PXE_BASE_CODE *pxe; |
| EFI_LOADED_IMAGE *info; |
| EFI_STATUS status = EFI_SUCCESS; |
| const struct fs_ops *ops[] = { NULL, NULL }; |
| unsigned long len = (unsigned long)__bss_end - (unsigned long)__bss_start; |
| static struct efi_disk_private priv; |
| SIMPLE_INPUT_INTERFACE *in; |
| EFI_INPUT_KEY key; |
| EFI_EVENT timer_ev; |
| |
| memset(__bss_start, 0, len); |
| InitializeLib(image, table); |
| |
| image_handle = image; |
| syslinux_register_efi(); |
| |
| efi_console_save(); |
| init(); |
| |
| status = uefi_call_wrapper(BS->HandleProtocol, 3, image, |
| &LoadedImageProtocol, (void **)&info); |
| if (status != EFI_SUCCESS) { |
| Print(L"Failed to lookup LoadedImageProtocol\n"); |
| goto out; |
| } |
| |
| status = uefi_call_wrapper(BS->HandleProtocol, 3, info->DeviceHandle, |
| &PxeBaseCodeProtocol, (void **)&pxe); |
| if (status != EFI_SUCCESS) { |
| /* |
| * Use device handle to set up the volume root to |
| * proceed with ADV init. |
| */ |
| if (EFI_ERROR(efi_set_volroot(info->DeviceHandle))) { |
| Print(L"Failed to locate root device to prep for "); |
| Print(L"file operations & ADV initialization\n"); |
| goto out; |
| } |
| |
| efi_derivative(SYSLINUX_FS_SYSLINUX); |
| ops[0] = &vfat_fs_ops; |
| } else { |
| efi_derivative(SYSLINUX_FS_PXELINUX); |
| ops[0] = &pxe_fs_ops; |
| } |
| |
| /* setup timer for boot menu system support */ |
| status = setup_default_timer(&timer_ev); |
| if (status != EFI_SUCCESS) { |
| Print(L"Failed to set up EFI timer support, bailing out\n"); |
| goto out; |
| } |
| |
| /* TODO: once all errors are captured in efi_errno, bail out if necessary */ |
| |
| priv.dev_handle = info->DeviceHandle; |
| |
| /* |
| * Set the current working directory, which should be the |
| * directory that syslinux.efi resides in. |
| */ |
| efi_setcwd(DevicePathToStr(info->FilePath)); |
| |
| fs_init(ops, (void *)&priv); |
| |
| /* |
| * There may be pending user input that wasn't processed by |
| * whatever application invoked us. Consume and discard that |
| * data now. |
| */ |
| in = ST->ConIn; |
| do { |
| status = uefi_call_wrapper(in->ReadKeyStroke, 2, in, &key); |
| } while (status != EFI_NOT_READY); |
| |
| if (!setjmp(load_error_buf)) |
| load_env32(NULL); |
| |
| /* load_env32() failed.. cancel timer and bailout */ |
| status = cancel_timer(timer_ev); |
| if (status != EFI_SUCCESS) |
| Print(L"Failed to cancel EFI timer: %x\n", status); |
| |
| /* |
| * Tell the firmware that Syslinux failed to load. |
| */ |
| status = EFI_LOAD_ERROR; |
| out: |
| efi_console_restore(); |
| return status; |
| } |