aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatt Fleming <matt.fleming@intel.com>2013-07-08 17:03:53 +0100
committerMatt Fleming <matt.fleming@intel.com>2013-07-08 17:03:53 +0100
commit08d7f825a95e12c81a2ef0a1d9fec5520d44df35 (patch)
tree9e1709518844c44c86ce0c250993d89654657b5a
parent3aa295d25bda39afee12b015523f222ce7940079 (diff)
parent3dafb1427c5ba7d6f061a864ab0d72a95566cfa6 (diff)
downloadsyslinux-08d7f825a95e12c81a2ef0a1d9fec5520d44df35.tar.gz
Merge branch 'kernel-loader' into for-hpa/elflink/firmware
Conflicts: efi/efi.h
-rw-r--r--com32/include/syslinux/linux.h9
-rw-r--r--efi/Makefile8
-rw-r--r--efi/console.c27
-rw-r--r--efi/efi.h11
-rw-r--r--efi/i386/linux.S50
-rw-r--r--efi/main.c542
-rw-r--r--efi/x86_64/linux.S63
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);
}
diff --git a/efi/efi.h b/efi/efi.h
index 72d08e10..9e4a4432 100644
--- a/efi/efi.h
+++ b/efi/efi.h
@@ -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
diff --git a/efi/main.c b/efi/main.c
index 13b9403e..309fc49b 100644
--- a/efi/main.c
+++ b/efi/main.c
@@ -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