--- alloc.c | 10 +++ ia32/bzimage.c | 2 x86_64/bzimage.c | 160 +++++++++++++++++++++++++++++++++++++++++++++---------- x86_64/sysdeps.h | 5 + x86_64/system.c | 9 ++- 5 files changed, 152 insertions(+), 34 deletions(-) --- a/alloc.c +++ b/alloc.c @@ -217,7 +217,15 @@ INTN alloc_kmem_anywhere(VOID **start_addr, UINTN pgcnt) { void * tmp; - if ((tmp = alloc_pages(pgcnt, EfiLoaderData, AllocateAnyPages, *start_addr)) == 0) return -1; + /* + * During "AllocateAnyPages" *start_addr will be ignored. + * Therefore we can safely subvert it to reuse this function with + * an alloc_kmem_anyhwere_below() semantic... + */ + tmp = alloc_pages(pgcnt, EfiLoaderData, + (*start_addr) ? AllocateMaxAddress : AllocateAnyPages, + *start_addr); + if (tmp == NULL) return -1; kmem_addr = tmp; kmem_pgcnt = pgcnt; --- a/ia32/bzimage.c +++ b/ia32/bzimage.c @@ -169,7 +169,7 @@ bzImage_probe(CHAR16 *kname) kernel_start)); } - kernel_load_address = kernel_start; + kernel_load_address = NULL; /* allocate anywhere! */ if (alloc_kmem(kernel_start, EFI_SIZE_TO_PAGES(kernel_size)) != 0) { /* --- a/x86_64/bzimage.c +++ b/x86_64/bzimage.c @@ -36,6 +36,129 @@ UINTN param_size = 0; UINTN kernel_size = 0x800000; /* 8M (default x86_64 bzImage size limit) */ +static VOID * +bzImage_alloc() +{ + UINTN pages = EFI_SIZE_TO_PAGES(kernel_size); + int reloc_kernel = 0; + VOID *kla, *kend = kernel_start + kernel_size; + UINT32 kalign, kmask; + boot_params_t *ps = param_start; + + /* + * Get address for kernel from header, if applicable & available. + */ + if ((ps->s.hdr_major < 2) || + (ps->s.hdr_major == 2 && ps->s.hdr_minor < 5)) { + reloc_kernel = 0; + } else { + if (ps->s.kernel_start >= DEFAULT_KERNEL_START) + kernel_start = (void *)(UINT64)ps->s.kernel_start; + reloc_kernel = ps->s.relocatable_kernel; + kalign = ps->s.kernel_alignment; + kmask = kalign - 1; + VERB_PRT(3, Print(L"kernel header (%d.%d) suggests kernel " + "start at address "PTR_FMT" (%srelocatable!)\n", + ps->s.hdr_major, ps->s.hdr_minor, ps->s.kernel_start, + (reloc_kernel ? L"": L"not "))); + } + + /* + * Best effort for old (< 2.6.20) and non-relocatable kernels + */ + if (alloc_kmem(kernel_start, pages) == 0) { + VERB_PRT(3, Print(L"kernel_start: "PTR_FMT" kernel_size: %d\n", + kernel_start, kernel_size)); + return kernel_start; + } else if ( ! reloc_kernel ) { + /* + * Couldn't get desired address--just load it anywhere and + * (try to) move it later. It's the only chance for non- + * relocatable kernels, but it breaks occassionally... + */ + ERR_PRT((L"Kernel header (%d.%d) suggests kernel " + "start at address "PTR_FMT" (non relocatable!)\n" + "This address is not available, so an attempt" + "is made to copy the kernel there later on\n" + "BEWARE: this is unsupported and may not work. " + "Please update your kernel.\n", + ps->s.hdr_major, ps->s.hdr_minor, ps->s.kernel_start)); + kla = (VOID *)(UINT32_MAX - kernel_size); + /* NULL would preserve the "anywhere" semantic, */ + /* but it would not prevent allocation above 4GB! */ + + if (alloc_kmem_anywhere(&kla, pages) != 0) { + /* out of luck */ + return NULL; + } + VERB_PRT(3, Print(L"kernel_start: "PTR_FMT + " kernel_size: %d loading at: "PTR_FMT"\n", + kernel_start, kernel_size, kla)); + return kla; + } + + + /* Is 'ps->s.kernel_alignment' guaranteed to be sane? */ + if (kalign < EFI_PAGE_SIZE) { + kalign = EFI_PAGE_SIZE; + kmask = EFI_PAGE_MASK; + } + DBG_PRT((L"alignment: kernel=0x%x efi_page=0x%x : 0x%x\n", + ps->s.kernel_alignment, EFI_PAGE_SIZE, kalign)); + + /* + * Couldn't get the preferred address, but luckily it's + * a relocatable kernel, so ... + * + * 1. use 'find_kernel_memory()' (like Itanium) + * 2. try out the 16 lowest possible aligned addresses (> 0) + * 3. get enough memory to align "creatively" + * 4. forget alignment (and start praying)... + */ + + /* 1. */ + if ((find_kernel_memory(kernel_start, kend, kalign, &kla) != 0) || + (alloc_kmem(kla, pages) != 0)) { + kla = NULL; + } + + /* 2. */ + if ( ! kla && (UINT64)kernel_start < kalign ) { + int i; + for ( i = 1; i < 16 && !kla; i++ ) { + VOID *tmp = (VOID *)((UINT64)kalign * i); + if (alloc_kmem(tmp, pages) == 0) { + kla = tmp; + } + } + } + + /* 3. */ + if ( ! kla ) { + UINTN apages = EFI_SIZE_TO_PAGES(kernel_size + kmask); + kla = (VOID *)(UINT32_MAX - kernel_size - kmask); + + if (alloc_kmem_anywhere(&kla, apages) == 0) { + kla = (VOID *)(((UINT64)kla + kmask) & ~kmask); + } else { + kla = NULL; + } + } + + /* 4. last resort */ + if ( ! kla ) { + kla = (VOID *)(UINT32_MAX - kernel_size); + if (alloc_kmem_anywhere(&kla, pages) != 0) { + return NULL; + } + } + + kernel_start = kla; + VERB_PRT(1, Print(L"relocating kernel_start: "PTR_FMT + " kernel_size: %d\n", kernel_start, kernel_size)); + return kla; +} + static INTN bzImage_probe(CHAR16 *kname) { @@ -158,37 +281,16 @@ bzImage_probe(CHAR16 *kname) * Allocate memory for kernel. */ - /* - * Get correct address for kernel from header, if applicable & available. - */ - if ((param_start->s.hdr_major == 2) && - (param_start->s.hdr_minor >= 6) && - (param_start->s.kernel_start >= DEFAULT_KERNEL_START)) { - kernel_start = (void *)param_start->s.kernel_start; - VERB_PRT(3, Print(L"kernel header suggests kernel start at address "PTR_FMT"\n", - kernel_start)); - } - - kernel_load_address = kernel_start; - - if (alloc_kmem(kernel_start, EFI_SIZE_TO_PAGES(kernel_size)) != 0) { - /* - * Couldn't get desired address--just load it anywhere and move it later. - * (Easier than relocating kernel, and also works with non-relocatable kernels.) - */ - if (alloc_kmem_anywhere(&kernel_load_address, EFI_SIZE_TO_PAGES(kernel_size)) != 0) { - ERR_PRT((L"Could not allocate memory for kernel.")); - free(param_start); - param_start = NULL; - param_size = 0; - fops_close(fd); - return -1; - } + kernel_load_address = bzImage_alloc(); + if ( ! kernel_load_address ) { + ERR_PRT((L"Could not allocate memory for kernel.")); + free(param_start); + param_start = NULL; + param_size = 0; + fops_close(fd); + return -1; } - VERB_PRT(3, Print(L"kernel_start: "PTR_FMT" kernel_size: %d loading at: "PTR_FMT"\n", - kernel_start, kernel_size, kernel_load_address)); - /* * Now read the rest of the kernel image into memory. */ --- a/x86_64/sysdeps.h +++ b/x86_64/sysdeps.h @@ -285,7 +285,10 @@ typedef union x86_64_boot_params { /* 0x228 */ UINT32 cmdline_addr; /* LDR */ /* 0x22C */ UINT32 initrd_addr_max; /* BLD */ -/* 0x230 */ UINT32 pad_8[40]; +/* 0x230 */ UINT32 kernel_alignment; /* BLD */ +/* 0x234 */ UINT8 relocatable_kernel; /* BLD */ +/* 0x235 */ UINT8 pad_8[3]; +/* 0x238 */ UINT32 pad_9[38]; /* 0x2D0 */ UINT8 e820_map[2560]; } s; } boot_params_t; --- a/x86_64/system.c +++ b/x86_64/system.c @@ -105,10 +105,10 @@ UINTN high_base_mem = 0x90000; UINTN high_ext_mem = 32 * 1024 * 1024; /* This starting address will hold true for all of the loader types for now */ -VOID *kernel_start = (void *)DEFAULT_KERNEL_START; +VOID *kernel_start = (VOID *)DEFAULT_KERNEL_START; /* The kernel may load elsewhere if EFI firmware reserves kernel_start */ -VOID *kernel_load_address = DEFAULT_KERNEL_START; +VOID *kernel_load_address = (VOID *)DEFAULT_KERNEL_START; VOID *initrd_start = NULL; UINTN initrd_size = 0; @@ -631,6 +631,11 @@ sysdeps_create_boot_params( /* * Kernel entry point. */ + if ( (UINT64)kernel_start != (UINT32)(UINT64)kernel_start ) { + ERR_PRT((L"Start of kernel (will be) out of reach (>4GB).")); + free_kmem(); + return -1; + } bp->s.kernel_start = (UINT32)(UINT64)kernel_start; /*