elilo/ia64/plain_loader.c

456 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;
VERB_PRT(1, Print(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;
if (bswap32(phdrs[i].p_flags) & PF_X)
flush_dcache ((CHAR8 *)phdrs[i].p_paddr, filesz);
/*
* 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
};