453 lines
11 KiB
C
453 lines
11 KiB
C
/*
|
|
* Copyright (C) 2001-2003 Hewlett-Packard Co.
|
|
* Contributed by Stephane Eranian <eranian@hpl.hp.com>
|
|
*
|
|
* Copyright (C) 2001 Silicon Graphics, Inc.
|
|
* Contributed by Brent Casavant <bcasavan@sgi.com>
|
|
*
|
|
* This file is part of the ELILO, the EFI Linux boot loader.
|
|
*
|
|
* ELILO 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; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* ELILO is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with ELILO; see the file COPYING. If not, write to the Free
|
|
* Software Foundation, 59 Temple Place - Suite 330, Boston, MA
|
|
* 02111-1307, USA.
|
|
*
|
|
* Please check out the elilo.txt for complete documentation on how
|
|
* to use this program.
|
|
*/
|
|
|
|
#include <efi.h>
|
|
#include <efilib.h>
|
|
|
|
#include "elilo.h"
|
|
#include "loader.h"
|
|
#include "elf.h"
|
|
#include "private.h"
|
|
|
|
#define LD_NAME L"plain_elf64"
|
|
|
|
#define PLAIN_MIN_BLOCK_SIZE sizeof(Elf64_Ehdr) /* see load_elf() for details */
|
|
|
|
#define SKIPBUFSIZE 2048 /* minimal default size of the skip buffer */
|
|
static CHAR8 *skip_buffer; /* used to skip over unneeded data */
|
|
static UINTN skip_bufsize;
|
|
static UINTN elf_is_big_endian; /* true if ELF file is big endian */
|
|
|
|
static inline UINT64
|
|
bswap64(UINT64 v)
|
|
{
|
|
if(elf_is_big_endian) v = __ia64_swab64(v);
|
|
return v;
|
|
}
|
|
|
|
static inline UINT32
|
|
bswap32(UINT32 v)
|
|
{
|
|
if(elf_is_big_endian) v = __ia64_swab32(v);
|
|
return v;
|
|
}
|
|
|
|
static inline UINT16
|
|
bswap16(UINT16 v)
|
|
{
|
|
if(elf_is_big_endian) v = __ia64_swab16(v);
|
|
return v;
|
|
}
|
|
|
|
static INTN
|
|
is_valid_header(Elf64_Ehdr *ehdr)
|
|
{
|
|
UINT16 type, machine;
|
|
|
|
if (ehdr->e_ident[EI_DATA] == ELFDATA2MSB) {
|
|
type = __ia64_swab16(ehdr->e_type);
|
|
machine = __ia64_swab16(ehdr->e_machine);
|
|
} else {
|
|
type = ehdr->e_type;
|
|
machine = ehdr->e_machine;
|
|
}
|
|
DBG_PRT((L"class=%d type=%d data=%d machine=%d\n",
|
|
ehdr->e_ident[EI_CLASS],
|
|
type,
|
|
ehdr->e_ident[EI_DATA],
|
|
machine));
|
|
|
|
return ehdr->e_ident[EI_MAG0] == 0x7f
|
|
&& ehdr->e_ident[EI_MAG1] == 'E'
|
|
&& ehdr->e_ident[EI_MAG2] == 'L'
|
|
&& ehdr->e_ident[EI_MAG3] == 'F'
|
|
&& ehdr->e_ident[EI_CLASS] == ELFCLASS64
|
|
&& type == ET_EXEC /* must be executable */
|
|
&& machine == EM_IA_64 ? 0 : -1;
|
|
}
|
|
|
|
static INTN
|
|
plain_probe(CHAR16 *kname)
|
|
{
|
|
Elf64_Ehdr ehdr;
|
|
EFI_STATUS status;
|
|
INTN ret = -1;
|
|
fops_fd_t fd;
|
|
UINTN size = sizeof(ehdr);
|
|
|
|
status = fops_open(kname, &fd);
|
|
if (EFI_ERROR(status)) return -1;
|
|
|
|
status = fops_read(fd, &ehdr, &size);
|
|
|
|
if (EFI_ERROR(status) || size != sizeof(ehdr)) goto error;
|
|
|
|
ret = is_valid_header(&ehdr);
|
|
error:
|
|
fops_close(fd);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* move skip bytes forward in the file
|
|
* this is required because we cannot assume fileops has
|
|
* seek() capabilities.
|
|
*/
|
|
static INTN
|
|
skip_bytes(fops_fd_t fd, UINTN curpos, UINTN newpos)
|
|
{
|
|
EFI_STATUS status;
|
|
UINTN n, skip;
|
|
|
|
skip = newpos - curpos;
|
|
/* check if seek capability exists */
|
|
|
|
status = fops_seek(fd, newpos);
|
|
if (status == EFI_SUCCESS) return 0;
|
|
|
|
if (status != EFI_UNSUPPORTED) goto error;
|
|
|
|
/* unsupported case */
|
|
|
|
if (skip_buffer == NULL) {
|
|
skip_bufsize = MAX(skip, SKIPBUFSIZE);
|
|
skip_buffer= (CHAR8 *)alloc(skip_bufsize, EfiLoaderData);
|
|
if (skip_buffer == NULL) return -1;
|
|
}
|
|
while (skip) {
|
|
n = skip > skip_bufsize? skip_bufsize : skip;
|
|
|
|
status = fops_read(fd, skip_buffer, &n);
|
|
if (EFI_ERROR(status)) goto error;
|
|
|
|
skip -=n;
|
|
}
|
|
return 0;
|
|
|
|
error:
|
|
ERR_PRT((L"%s : cannot skip %d bytes\n", LD_NAME, n));
|
|
return -1;
|
|
}
|
|
|
|
static INTN
|
|
load_elf(fops_fd_t fd, kdesc_t *kd)
|
|
{
|
|
Elf64_Ehdr ehdr;
|
|
Elf64_Phdr *phdrs;
|
|
EFI_STATUS status;
|
|
INTN ret = ELILO_LOAD_ERROR;
|
|
UINTN i, total_size = 0;
|
|
UINTN pages, size, bss_sz, osize;
|
|
UINTN offs = 0;
|
|
VOID *low_addr = (VOID *)~0;
|
|
VOID *max_addr = (VOID *)0;
|
|
UINTN load_offset = 0;
|
|
UINTN paddr, memsz, filesz, poffs;
|
|
UINT16 phnum;
|
|
|
|
Print(L"Loading Linux... ");
|
|
|
|
size = sizeof(ehdr);
|
|
|
|
status = fops_read(fd, &ehdr, &size);
|
|
if (EFI_ERROR(status) ||size < sizeof(ehdr)) return ELILO_LOAD_ERROR;
|
|
|
|
offs += size;
|
|
|
|
/*
|
|
* do some sanity checking on the file
|
|
*/
|
|
if (is_valid_header(&ehdr) == -1) {
|
|
ERR_PRT((L"%s : not an elf 64-bit file\n", LD_NAME));
|
|
return ELILO_LOAD_ERROR;
|
|
}
|
|
|
|
/* determine file endianess */
|
|
elf_is_big_endian = ehdr.e_ident[EI_DATA] == ELFDATA2MSB ? 1 : 0;
|
|
|
|
VERB_PRT(3, {
|
|
Print(L"ELF file is %s\n", elf_is_big_endian ? L"big endian" : L"little endian");
|
|
Print(L"Entry point 0x%lx\n", bswap64(ehdr.e_entry));
|
|
Print(L"%d program headers\n", bswap16(ehdr.e_phnum));
|
|
Print(L"%d segment headers\n", bswap16(ehdr.e_shnum));
|
|
});
|
|
|
|
phnum = bswap16(ehdr.e_phnum);
|
|
|
|
if (skip_bytes(fd, offs, bswap64(ehdr.e_phoff)) != 0) {
|
|
ERR_PRT((L"%s : skip tp %ld for phdrs failed", LD_NAME, offs));
|
|
return ELILO_LOAD_ERROR;
|
|
}
|
|
offs = bswap64(ehdr.e_phoff);
|
|
|
|
size = osize = phnum*sizeof(Elf64_Phdr);
|
|
|
|
DBG_PRT((L"%s : phdrs allocate %d bytes sizeof=%d entsize=%d\n", LD_NAME, size,sizeof(Elf64_Phdr), bswap16(ehdr.e_phentsize)));
|
|
|
|
phdrs = (Elf64_Phdr *)alloc(size, 0);
|
|
if (phdrs == NULL) {
|
|
ERR_PRT((L"%s : allocate phdrs failed", LD_NAME));
|
|
return ELILO_LOAD_ERROR;
|
|
}
|
|
|
|
status = fops_read(fd, phdrs, &size);
|
|
if (EFI_ERROR(status) || size != osize) {
|
|
ERR_PRT((L"%s : load phdrs failed", LD_NAME, status));
|
|
goto out;
|
|
}
|
|
offs += size;
|
|
/*
|
|
* First pass to figure out:
|
|
* - lowest physical address
|
|
* - total memory footprint
|
|
*/
|
|
for (i = 0; i < phnum; i++) {
|
|
|
|
paddr = bswap64(phdrs[i].p_paddr);
|
|
memsz = bswap64(phdrs[i].p_memsz);
|
|
|
|
DBG_PRT((L"Phdr %d paddr [0x%lx-0x%lx] offset %ld"
|
|
" filesz %ld memsz=%ld bss_sz=%ld p_type=%d\n",
|
|
1+i,
|
|
paddr,
|
|
paddr+bswap64(phdrs[i].p_filesz),
|
|
bswap64(phdrs[i].p_offset),
|
|
bswap64(phdrs[i].p_filesz),
|
|
memsz,
|
|
memsz - bswap64(phdrs[i].p_filesz), bswap32(phdrs[i].p_type)));
|
|
|
|
if (bswap32(phdrs[i].p_type) != PT_LOAD) continue;
|
|
|
|
|
|
if (paddr < (UINTN)low_addr) low_addr = (VOID *)paddr;
|
|
|
|
if (paddr + memsz > (UINTN)max_addr)
|
|
max_addr = (VOID *)paddr + memsz;
|
|
}
|
|
|
|
if ((UINTN)low_addr & (EFI_PAGE_SIZE - 1)) {
|
|
ERR_PRT((L"%s : kernel low address 0x%lx not page aligned\n", LD_NAME, low_addr));
|
|
goto out;
|
|
}
|
|
|
|
/* how many bytes are needed to hold the kernel */
|
|
total_size = (UINTN)max_addr - (UINTN)low_addr;
|
|
|
|
/* round up to get required number of pages */
|
|
pages = EFI_SIZE_TO_PAGES(total_size);
|
|
|
|
/* keep track of location where kernel ends for
|
|
* the initrd ramdisk (it will be put right after the kernel)
|
|
*/
|
|
kd->kstart = low_addr;
|
|
kd->kend = low_addr+ (pages << EFI_PAGE_SHIFT);
|
|
|
|
/*
|
|
* that's the kernel entry point (virtual address)
|
|
*/
|
|
kd->kentry = (VOID *)bswap64(ehdr.e_entry);
|
|
|
|
if (((UINTN)kd->kentry >> 61) != 0) {
|
|
ERR_PRT((L"%s: <<ERROR>> entry point is a virtual address 0x%lx : not supported anymore\n", LD_NAME, kd->kentry));
|
|
}
|
|
|
|
VERB_PRT(3, {
|
|
Print(L"Lowest PhysAddr: 0x%lx\nTotalMemSize:%d bytes (%d pages)\n",
|
|
low_addr, total_size, pages);
|
|
Print(L"Kernel entry @ 0x%lx\n", kd->kentry);
|
|
});
|
|
|
|
/*
|
|
* now allocate memory for the kernel at the exact requested spot
|
|
*/
|
|
if (alloc_kmem(low_addr, pages) == -1) {
|
|
VOID *new_addr;
|
|
|
|
ERR_PRT((L"%s : AllocatePages(%d, 0x%lx) for kernel failed\n", LD_NAME, pages, low_addr));
|
|
|
|
if (ia64_can_relocate() == 0) {
|
|
ERR_PRT((L"relocation is disabled, cannot load kernel"));
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* could not allocate at requested spot, try to find a
|
|
* suitable location to relocate the kernel
|
|
*
|
|
* The maximum sized Itanium TLB translation entry is 256 MB.
|
|
* If we relocate the kernel by this amount we know for sure
|
|
* that alignment constraints will be satisified, regardless
|
|
* of the kernel used.
|
|
*/
|
|
Print(L"Attempting to relocate kernel.\n");
|
|
if (find_kernel_memory(low_addr, max_addr, 256*MB, &new_addr) == -1) {
|
|
ERR_PRT((L"%s : find_kernel_memory(0x%lx, 0x%lx, 0x%lx, 0x%lx) failed\n", LD_NAME, low_addr, max_addr, 256*MB, &load_offset));
|
|
goto out;
|
|
}
|
|
/* unsigned arithmetic */
|
|
load_offset = (UINTN) (new_addr - ROUNDDOWN((UINTN) low_addr,256*MB));
|
|
|
|
VERB_PRT(3, Print(L"low_addr=0x%lx new_addr=0x%lx offset=0x%lx", low_addr, new_addr, load_offset));
|
|
|
|
/*
|
|
* correct various addesses for non-zero load_offset
|
|
*/
|
|
low_addr = (VOID*) ((UINTN) low_addr + load_offset);
|
|
max_addr = (VOID*) ((UINTN) max_addr + load_offset);
|
|
kd->kstart = (VOID *) ((UINTN) kd->kstart + load_offset);
|
|
kd->kend = (VOID *) ((UINTN) kd->kend + load_offset);
|
|
kd->kentry = (VOID *) ((UINTN) kd->kentry + load_offset);
|
|
|
|
/*
|
|
* try one last time to get memory for the kernel
|
|
*/
|
|
if (alloc_kmem(low_addr, pages) == -1) {
|
|
ERR_PRT((L"%s : AllocatePages(%d, 0x%lx) for kernel failed\n", LD_NAME, pages, low_addr));
|
|
ERR_PRT((L"Relocation by 0x%lx bytes failed.\n", load_offset));
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
VERB_PRT(1, Print(L"Press any key to interrupt\n"));
|
|
|
|
/* Second pass:
|
|
* Walk through the program headers
|
|
* and actually load data into physical memory
|
|
*/
|
|
for (i = 0; i < phnum; i++) {
|
|
|
|
/*
|
|
* Check for pure loadable segment; ignore if not loadable
|
|
*/
|
|
if (bswap32(phdrs[i].p_type) != PT_LOAD) continue;
|
|
|
|
poffs = bswap64(phdrs[i].p_offset);
|
|
|
|
size = poffs - offs;
|
|
|
|
VERB_PRT(3, Print(L"\noff=%ld poffs=%ld size=%ld\n", offs, poffs, size));
|
|
|
|
filesz = bswap64(phdrs[i].p_filesz);
|
|
/*
|
|
* correct p_paddr for non-zero load offset
|
|
*/
|
|
phdrs[i].p_paddr = (Elf64_Addr) ((UINTN) bswap64(phdrs[i].p_paddr) + load_offset);
|
|
|
|
/*
|
|
* Move to the right position
|
|
*/
|
|
if (size && skip_bytes(fd, offs, poffs) != 0) goto out_kernel;
|
|
|
|
/*
|
|
* Keep track of current position in file
|
|
*/
|
|
offs += size;
|
|
|
|
/*
|
|
* How many BSS bytes to clear
|
|
*/
|
|
bss_sz = bswap64(phdrs[i].p_memsz) - filesz;
|
|
|
|
VERB_PRT(4, {
|
|
Print(L"\nHeader #%d\n", i);
|
|
Print(L"offset %ld\n", poffs);
|
|
Print(L"Phys addr 0x%lx\n", phdrs[i].p_paddr); /* already endian adjusted */
|
|
Print(L"BSS size %ld bytes\n", bss_sz);
|
|
Print(L"skip=%ld offs=%ld\n", size, offs);
|
|
});
|
|
|
|
/*
|
|
* Read actual segment into memory
|
|
*/
|
|
ret = read_file(fd, filesz, (CHAR8 *)phdrs[i].p_paddr);
|
|
if (ret == ELILO_LOAD_ABORTED) goto load_abort;
|
|
if (ret == ELILO_LOAD_ERROR) goto out;
|
|
|
|
/*
|
|
* update file position
|
|
*/
|
|
offs += filesz;
|
|
|
|
/*
|
|
* Clear bss section
|
|
*/
|
|
if (bss_sz) Memset((VOID *) phdrs[i].p_paddr+filesz, 0, bss_sz);
|
|
}
|
|
|
|
free(phdrs);
|
|
|
|
Print(L"..done\n");
|
|
return ELILO_LOAD_SUCCESS;
|
|
|
|
load_abort:
|
|
Print(L"..Aborted\n");
|
|
ret = ELILO_LOAD_ABORTED;
|
|
out_kernel:
|
|
/* free kernel memory */
|
|
free_kmem();
|
|
out:
|
|
free(phdrs);
|
|
return ret;
|
|
}
|
|
|
|
static INTN
|
|
plain_load_kernel(CHAR16 *kname, kdesc_t *kd)
|
|
{
|
|
INTN ret;
|
|
fops_fd_t fd;
|
|
EFI_STATUS status;
|
|
|
|
/*
|
|
* Moving the open here simplifies the load_elf() error handling
|
|
*/
|
|
status = fops_open(kname, &fd);
|
|
if (EFI_ERROR(status)) return ELILO_LOAD_ERROR;
|
|
|
|
Print(L"Loading %s...", kname);
|
|
|
|
ret = load_elf(fd, kd);
|
|
|
|
fops_close(fd);
|
|
|
|
/*
|
|
* if the skip buffer was ever used, free it
|
|
*/
|
|
if (skip_buffer) {
|
|
free(skip_buffer);
|
|
/* in case we come back */
|
|
skip_buffer = NULL;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
loader_ops_t plain_loader={
|
|
NULL,
|
|
LD_NAME,
|
|
plain_probe,
|
|
plain_load_kernel
|
|
};
|