diff options
author | Matt Fleming <matt.fleming@intel.com> | 2013-07-08 17:03:53 +0100 |
---|---|---|
committer | Matt Fleming <matt.fleming@intel.com> | 2013-07-08 17:03:53 +0100 |
commit | 08d7f825a95e12c81a2ef0a1d9fec5520d44df35 (patch) | |
tree | 9e1709518844c44c86ce0c250993d89654657b5a | |
parent | 3aa295d25bda39afee12b015523f222ce7940079 (diff) | |
parent | 3dafb1427c5ba7d6f061a864ab0d72a95566cfa6 (diff) | |
download | syslinux-08d7f825a95e12c81a2ef0a1d9fec5520d44df35.tar.gz |
Merge branch 'kernel-loader' into for-hpa/elflink/firmware
Conflicts:
efi/efi.h
-rw-r--r-- | com32/include/syslinux/linux.h | 9 | ||||
-rw-r--r-- | efi/Makefile | 8 | ||||
-rw-r--r-- | efi/console.c | 27 | ||||
-rw-r--r-- | efi/efi.h | 11 | ||||
-rw-r--r-- | efi/i386/linux.S | 50 | ||||
-rw-r--r-- | efi/main.c | 542 | ||||
-rw-r--r-- | efi/x86_64/linux.S | 63 |
7 files changed, 476 insertions, 234 deletions
diff --git a/com32/include/syslinux/linux.h b/com32/include/syslinux/linux.h index a8c6f2f5..700ac9a8 100644 --- a/com32/include/syslinux/linux.h +++ b/com32/include/syslinux/linux.h @@ -69,6 +69,11 @@ struct setup_data { #define SETUP_E820_EXT 1 #define SETUP_DTB 2 +#define XLF_KERNEL_64 (1 << 0) +#define XLF_CAN_BE_LOADED_ABOVE_4G (1 << 1) +#define XLF_EFI_HANDOVER_32 (1 << 2) +#define XLF_EFI_HANDOVER_64 (1 << 3) + struct linux_header { uint8_t boot_sector_1[0x0020]; uint16_t old_cmd_line_magic; @@ -100,7 +105,8 @@ struct linux_header { uint32_t initrd_addr_max; uint32_t kernel_alignment; uint8_t relocatable_kernel; - uint8_t pad2[3]; + uint8_t min_alignment; + uint16_t xloadflags; uint32_t cmdline_max_len; uint32_t hardware_subarch; uint64_t hardware_subarch_data; @@ -109,6 +115,7 @@ struct linux_header { uint64_t setup_data; uint64_t pref_address; uint32_t init_size; + uint32_t handover_offset; } __packed; struct screen_info { diff --git a/efi/Makefile b/efi/Makefile index 551f02b6..b4b6cec1 100644 --- a/efi/Makefile +++ b/efi/Makefile @@ -42,7 +42,13 @@ LIB_OBJS = $(addprefix $(objdir)/com32/lib/,$(CORELIBOBJS)) CSRC = $(wildcard $(SRC)/*.c) OBJS = $(subst $(SRC)/,,$(filter-out %wrapper.o, $(patsubst %.c,%.o,$(CSRC)))) -OBJS += $(objdir)/core/codepage.o +OBJS += $(objdir)/core/codepage.o $(ARCH)/linux.o + +.PHONY: subdirs +subdirs: + mkdir -p $(ARCH) + +$(OBJS): subdirs # The targets to build in this directory BTARGET = syslinux.efi diff --git a/efi/console.c b/efi/console.c index cc493886..a01e14e8 100644 --- a/efi/console.c +++ b/efi/console.c @@ -1,8 +1,33 @@ #include <syslinux/linux.h> #include "efi.h" +#include <string.h> extern EFI_GUID GraphicsOutputProtocol; +static uint32_t console_default_attribute; +static bool console_default_cursor; + +/* + * We want to restore the console state when we boot a kernel or return + * to the firmware. + */ +void efi_console_save(void) +{ + SIMPLE_TEXT_OUTPUT_INTERFACE *out = ST->ConOut; + SIMPLE_TEXT_OUTPUT_MODE *mode = out->Mode; + + console_default_attribute = mode->Attribute; + console_default_cursor = mode->CursorVisible; +} + +void efi_console_restore(void) +{ + SIMPLE_TEXT_OUTPUT_INTERFACE *out = ST->ConOut; + + uefi_call_wrapper(out->SetAttribute, 2, out, console_default_attribute); + uefi_call_wrapper(out->EnableCursor, 2, out, console_default_cursor); +} + __export void writechr(char data) { efi_write_char(data, 0); @@ -276,6 +301,8 @@ out: void setup_screen(struct screen_info *si) { + memset(si, 0, sizeof(*si)); + if (!setup_gop(si)) setup_uga(si); } @@ -64,4 +64,15 @@ efi_setup_event(EFI_EVENT *ev, EFI_EVENT_NOTIFY func, void *ctx) extern void efi_derivative(enum syslinux_filesystem fs); +struct boot_params; +typedef void (handover_func_t)(void *, EFI_SYSTEM_TABLE *, + struct boot_params *, unsigned long); + +handover_func_t efi_handover_32; +handover_func_t efi_handover_64; +handover_func_t efi_handover; + +extern void efi_console_save(void); +extern void efi_console_restore(void); + #endif /* _SYSLINUX_EFI_H */ diff --git a/efi/i386/linux.S b/efi/i386/linux.S new file mode 100644 index 00000000..4049ad4d --- /dev/null +++ b/efi/i386/linux.S @@ -0,0 +1,50 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2013 Intel Corporation; author: Matt Fleming + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston MA 02110-1301, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + + .globl kernel_jump + .type kernel_jump,@function + .text +kernel_jump: + cli + movl 0x8(%esp), %esi + movl 0x4(%esp), %ecx + jmp *%ecx + + /* + * The default handover function should only be invoked for + * bzImage boot protocol versions < 2.12. + */ + .globl efi_handover + .type efi_handover,@function +efi_handover: + cli + popl %ecx /* discard return address */ + movl 0xc(%esp), %ecx + jmp *%ecx + + .globl efi_handover_32 + .type efi_handover_32,@function +efi_handover_32: + cli + popl %ecx /* discard return address */ + movl 0xc(%esp), %ecx + call *%ecx + + .globl efi_handover_64 + .type efi_handover_64,@function +efi_handover_64: + call 1f +1: + popl %eax + subl $1b, %eax + movl $38, errno(%eax) /* ENOSYS */ + ret @@ -417,74 +417,12 @@ struct boot_params { * 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" -static inline void kernel_jump(EFI_PHYSICAL_ADDRESS kernel_start, - struct boot_params *boot_params) -{ - asm volatile ("cli \n" - "movl %0, %%esi \n" - "movl %1, %%ecx \n" - "jmp *%%ecx \n" - :: "m" (boot_params), "m" (kernel_start)); -} - -static inline void handover_jump(EFI_HANDLE image, struct boot_params *bp, - EFI_PHYSICAL_ADDRESS kernel_start) -{ - /* handover protocol not implemented yet; the linux header needs to be updated */ -#if 0 - kernel_start += hdr->handover_offset; - - asm volatile ("cli \n" - "pushl %0 \n" - "pushl %1 \n" - "pushl %2 \n" - "movl %3, %%ecx \n" - "jmp *%%ecx \n" - :: "m" (bp), "m" (ST), - "m" (image), "m" (kernel_start)); -#endif -} #elif __SIZEOF_POINTER__ == 8 #define EFI_LOAD_SIG "EL64" -typedef void(*kernel_func)(void *, struct boot_params *); -typedef void(*handover_func)(void *, EFI_SYSTEM_TABLE *, struct boot_params *); -static inline void kernel_jump(EFI_PHYSICAL_ADDRESS kernel_start, - struct boot_params *boot_params) -{ - kernel_func kf; - - asm volatile ("cli"); - - /* The 64-bit kernel entry is 512 bytes after the start. */ - kf = (kernel_func)kernel_start + 512; - - /* - * The first parameter is a dummy because the kernel expects - * boot_params in %[re]si. - */ - kf(NULL, boot_params); -} - -static inline void handover_jump(EFI_HANDLE image, struct boot_params *bp, - EFI_PHYSICAL_ADDRESS kernel_start) -{ -#if 0 - /* handover protocol not implemented yet the linux header needs to be updated */ - - UINT32 offset = bp->hdr.handover_offset; - handover_func hf; - - asm volatile ("cli"); - - /* The 64-bit kernel entry is 512 bytes after the start. */ - kernel_start += 512; - - hf = (handover_func)(kernel_start + offset); - hf(image, ST, bp); -#endif -} #else #error "unsupported architecture" #endif @@ -771,71 +709,107 @@ void efree(EFI_PHYSICAL_ADDRESS memory, UINTN size) free_pages(memory, nr_pages); } -/* 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. +/* + * 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. * - * FIXME - * Can we move this allocation requirement to com32 linux loader in order - * to avoid double copying kernel image? + * 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. */ -int efi_boot_linux(void *kernel_buf, size_t kernel_size, - struct initramfs *initramfs, - struct setup_data *setup_data, - char *cmdline) +static void handover_boot(struct linux_header *hdr, struct boot_params *bp) { - EFI_MEMORY_DESCRIPTOR *map; - struct linux_header *hdr, *bhdr; - struct boot_params *bp; - struct boot_params *_bp; /* internal, in efi_physical below 0x3FFFFFFF */ - struct screen_info *si; - struct e820_entry *e820buf, *e; - EFI_STATUS status; - EFI_PHYSICAL_ADDRESS last, addr, pref_address, kernel_start = 0; - UINT64 setup_sz, init_size = 0; - UINTN i, nr_entries, key, desc_sz; - UINT32 desc_ver; - uint32_t e820_type; - addr_t irf_size; - char *_cmdline = NULL; /* internal, in efi_physical below 0x3FFFFFFF */ + 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; + } - hdr = (struct linux_header *)kernel_buf; - bp = (struct boot_params *)hdr; - /* - * We require a relocatable kernel because we have no control - * over free memory in the memory map. - */ - if (hdr->version < 0x20a || !hdr->relocatable_kernel) { - printf("bzImage version 0x%x unsupported\n", hdr->version); - goto bail; - } + 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); - goto bail; + return -1; } - 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; + return 0; +} + +static char *build_cmdline(char *str) +{ + EFI_PHYSICAL_ADDRESS addr; + EFI_STATUS status; + char *cmdline = NULL; /* internal, in efi_physical below 0x3FFFFFFF */ - /* - * 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 */ /* * The kernel expects cmdline to be allocated pretty low, * Documentation/x86/boot.txt says, @@ -845,71 +819,26 @@ int efi_boot_linux(void *kernel_buf, size_t kernel_size, */ addr = 0xA0000; status = allocate_pages(AllocateMaxAddress, EfiLoaderData, - EFI_SIZE_TO_PAGES(strlen(cmdline) + 1), + EFI_SIZE_TO_PAGES(strlen(str) + 1), &addr); if (status != EFI_SUCCESS) { printf("Failed to allocate memory for kernel command line, bailing out\n"); - goto bail; + return NULL; } - _cmdline = (char *)(UINTN)addr; - memcpy(_cmdline, cmdline, strlen(cmdline) + 1); - hdr->cmd_line_ptr = (UINT32)(UINTN)_cmdline; - memset((char *)&bp->screen_info, 0x0, sizeof(bp->screen_info)); - - 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. - */ - 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); - - /* 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 free_map; - } - - _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); - bhdr = (struct linux_header *)_bp; - bhdr->code32_start = (UINT32)((UINT64)kernel_start); + cmdline = (char *)(UINTN)addr; + memcpy(cmdline, str, strlen(str) + 1); + return cmdline; +} - 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); - si = &_bp->screen_info; - memset(si, 0, sizeof(*si)); - setup_screen(si); +static int build_gdt(void) +{ + EFI_STATUS status; - /* - * FIXME: implement handover protocol - * Use the kernel's EFI boot stub by invoking the handover - * protocol. - */ /* 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"); - goto free_map; + return -1; } memset(gdt.base, 0x0, gdt.limit); @@ -932,7 +861,26 @@ int efi_boot_linux(void *kernel_buf, size_t kernel_size, /* Task segment value */ gdt.base[4] = 0x0080890000000000; - dprintf("efi_boot_linux: setup_sects %d kernel_size %d\n", hdr->setup_sects, kernel_size); + 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. @@ -940,64 +888,73 @@ int efi_boot_linux(void *kernel_buf, size_t kernel_size, * <= hdr->initrd_addr_max, which fits the entire initramfs. */ irf_size = initramfs_size(initramfs); /* Handles initramfs == NULL */ - if (irf_size) { - struct initramfs *ip; - addr_t next_addr, len, pad; - - 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"); - goto free_map; - } + if (!irf_size) + return 0; - bhdr->ramdisk_image = (uint32_t)last; - bhdr->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; - } + 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; + } - if (ip->data_len) - memcpy((void *)(UINTN)last, ip->data, ip->data_len); + hdr->ramdisk_image = (uint32_t)last; + hdr->ramdisk_size = irf_size; - if (len > ip->data_len) - memset((void *)(UINTN)(last + ip->data_len), 0, - len - ip->data_len); + /* Copy initramfs into allocated memory */ + for (ip = initramfs->next; ip->len; ip = ip->next) { + len = ip->len; + next_addr = last + len; - last = next_addr; + /* + * 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) - goto free_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; + 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; + bp->efi.systab_hi = ((unsigned long)ST) >> 32; + bp->efi.memmap_hi = ((unsigned long)map) >> 32; #endif @@ -1007,14 +964,14 @@ int efi_boot_linux(void *kernel_buf, size_t kernel_size, * e820 map because it will most likely have changed in the * interim. */ - e = e820buf = _bp->e820_map; + 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); + map = get_mem_desc(bp->efi.memmap, desc_sz, i); e->start = map->PhysicalStart; e->len = map->NumberOfPages << EFI_PAGE_SHIFT; @@ -1061,20 +1018,139 @@ int efi_boot_linux(void *kernel_buf, size_t kernel_size, e++; } - _bp->e820_entries = e - e820buf; + bp->e820_entries = e - e820buf; - dprintf("efi_boot_linux: exit boot services\n"); status = uefi_call_wrapper(BS->ExitBootServices, 2, image_handle, key); if (status != EFI_SUCCESS) { printf("Failed to exit boot services: 0x%016lx\n", status); - goto free_map; + FreePool(map); + return -1; } - memcpy(&_bp->efi.load_signature, EFI_LOAD_SIG, sizeof(uint32_t)); + + 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); + + /* 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); + + if (handle_ramdisks(hdr, initramfs)) + goto free_map; + + 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); + kernel_jump(kernel_start, bp); /* NOTREACHED */ @@ -1083,13 +1159,12 @@ free_map: efree((EFI_PHYSICAL_ADDRESS)(unsigned long)_cmdline, strlen(_cmdline) + 1); - if (_bp) - efree((EFI_PHYSICAL_ADDRESS)(unsigned long)_bp, + if (bp) + efree((EFI_PHYSICAL_ADDRESS)(unsigned long)bp, BOOT_PARAM_BLKSIZE); if (kernel_start) efree(kernel_start, init_size); - FreePool(map); - if (irf_size) - free_addr(last, irf_size); + if (hdr->ramdisk_size) + free_addr(hdr->ramdisk_image, hdr->ramdisk_size); bail: return -1; } @@ -1180,6 +1255,8 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *table) image_handle = image; syslinux_register_efi(); + + efi_console_save(); init(); status = uefi_call_wrapper(BS->HandleProtocol, 3, image, @@ -1250,5 +1327,6 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *table) */ status = EFI_LOAD_ERROR; out: + efi_console_restore(); return status; } diff --git a/efi/x86_64/linux.S b/efi/x86_64/linux.S new file mode 100644 index 00000000..0a0e9965 --- /dev/null +++ b/efi/x86_64/linux.S @@ -0,0 +1,63 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2013 Intel Corporation; author: Matt Fleming + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston MA 02110-1301, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#define CR0_PG_FLAG 0x80000000 +#define MSR_EFER 0xc0000080 + + .globl kernel_jump + .type kernel_jump,@function + .code64 +kernel_jump: + cli + + /* + * Setup our segment selector (0x10) and return address (%rdi) + * on the stack in preparation for the far return below. + */ + mov $0x1000000000, %rcx + addq %rcx, %rdi + pushq %rdi + + .code32 +pm_code: + + /* Disable IA-32e mode by clearing IA32_EFER.LME */ + xorl %eax, %eax + xorl %edx, %edx + movl $MSR_EFER, %ecx + wrmsr + + /* Turn off paging to disable long mode */ + movl %cr0, %eax + andl $~CR0_PG_FLAG, %eax + movl %eax, %cr0 + + /* Far return */ + lret + + .code64 + .align 4 + .globl efi_handover_32 + .type efi_handover_32,@function +efi_handover_32: + movl $38, errno(%rip) /* ENOSYS */ + ret + + .globl efi_handover_64 + .globl efi_handover + .type efi_handover_64,@function + .type efi_handover,@function +efi_handover_64: +efi_handover: + add $512, %rcx + cli + jmp *%rcx |