/**
 * \file
 * \brief Rudimentary ELF loader and handling routines.
 */

/*
 * Copyright (c) 2007, 2008, 2009, 2010, ETH Zurich.
 * All rights reserved.
 *
 * This file is distributed under the terms in the attached LICENSE file.
 * If you do not find this file, copies can be found by writing to:
 * ETH Zurich D-INFK, Universitaetstrasse 6, CH-8092 Zurich. Attn: Systems Group.
 */

/* Restricted includes, because this file is used in three environments:
 * in the preboot loader tool "elver", in 32-bit mode
 * in-kernel, 64-bit mode
 * userspace, 64-bit mode
 */
#include <stddef.h>
#include <stdint.h>
#include <barrelfish_kpi/paging_arch.h>
#include <barrelfish_kpi/types.h>
#include <errors/errno.h>
#include <elf/elf.h>

/**
 * \brief Load ELF binary image into memory
 *
 * This function loads an ELF binary image, based at 'base' and of size
 * 'size' into the memory provided by 'allocate'
 *
 * \param em_machine    ELF machine type.
 * \param allocate      Memory allocation function.
 * \param state         Pointer to state for allocation function.
 * \param base          Base address of ELF binary image in memory.
 * \param size          Size of ELF binary image in bytes.
 * \param retentry      Used to return entry point address
 * \param ret_tlsbase   Used to return TLS block base address
 * \param ret_tlssize   Used to return TLS block size
 */
errval_t elf_load_tls(uint16_t em_machine, elf_allocator_fn allocate_func,
                      void *state, lvaddr_t base,
                      size_t size, genvaddr_t *retentry,
                      genvaddr_t *ret_tlsbase, size_t *ret_tlsinitlen,
                      size_t *ret_tlstotallen)
{
    struct Elf64_Ehdr *head = (struct Elf64_Ehdr *)base;

    if(!IS_ELF(*head)) {
        return ELF_ERR_HEADER;
    }

    switch(head->e_ident[EI_CLASS]) {
    case ELFCLASS32:
        return elf32_load(em_machine, allocate_func, state, base, size,
                          retentry, ret_tlsbase, ret_tlsinitlen, ret_tlstotallen);

    case ELFCLASS64:
        return elf64_load(em_machine, allocate_func, state, base, size,
                          retentry, ret_tlsbase, ret_tlsinitlen, ret_tlstotallen);
    }

    return ELF_ERR_HEADER;
}

errval_t elf_load(uint16_t em_machine, elf_allocator_fn allocate_func,
                  void *state, lvaddr_t base,
                  size_t size, genvaddr_t *retentry)
{
    return elf_load_tls(em_machine, allocate_func, state, base, size,
                        retentry, NULL, NULL, NULL);
}

static size_t elf_virtual_size64(struct Elf64_Ehdr *ehead)
{
    struct Elf64_Phdr *phead =
        (struct Elf64_Phdr *)((uintptr_t)ehead + (uintptr_t)ehead->e_phoff);

    size_t retval = 0;
    int i;

    for (i = 0; i < ehead->e_phnum; i++) {
        struct Elf64_Phdr *p = &phead[i];
        if (p->p_type == PT_LOAD) {
            retval = p->p_vaddr + p->p_memsz;
        }
    }

    return retval - elf_virtual_base64(ehead);
}

static size_t elf_virtual_size32(struct Elf32_Ehdr *ehead)
{
    struct Elf32_Phdr *phead =
        (struct Elf32_Phdr *)((uintptr_t)ehead + (uintptr_t)ehead->e_phoff);

    size_t retval = 0;
    int i;

    for (i = 0; i < ehead->e_phnum; i++) {
        struct Elf32_Phdr *p = &phead[i];
        if (p->p_type == PT_LOAD) {
            retval = p->p_vaddr + p->p_memsz;
        }
    }

    return retval - elf_virtual_base32(ehead);
}

/**
 * \brief Calculates the size of the loadable portion of the elf image in
 * virtual memory. This is the amount of virtual memory required to load an
 * image.
 */
size_t elf_virtual_size(lvaddr_t base)
{
    size_t elfsize = 0;

    struct Elf64_Ehdr *ehead = (struct Elf64_Ehdr *)base;
    if (IS_ELF(*ehead)) {
        switch (ehead->e_ident[EI_CLASS]) {
          case ELFCLASS64:
            elfsize = elf_virtual_size64(ehead);
            break;

          case ELFCLASS32:
            elfsize = elf_virtual_size32((struct Elf32_Ehdr*)base);
            break;
        }
    }

    return elfsize;
}

/**
 * \brief Calculates the base of the loadable portion of the elf image in
 * virtual memory.
 */
genvaddr_t elf_virtual_base(lvaddr_t base)
{
    genvaddr_t elfbase = 0;

    struct Elf64_Ehdr *ehead = (struct Elf64_Ehdr *)base;
    if (IS_ELF(*ehead)) {
        switch (ehead->e_ident[EI_CLASS]) {
          case ELFCLASS64:
            elfbase = elf_virtual_base64(ehead);
            break;

          case ELFCLASS32:
            elfbase = elf_virtual_base32((struct Elf32_Ehdr*)base);
            break;
        }
    }

    return elfbase;
}

static errval_t elf32_get_eh_info(lvaddr_t elfbase, size_t elfsize,
                                  lvaddr_t *eh_frame, size_t *eh_frame_size,
                                  lvaddr_t *eh_frame_hdr, size_t *eh_frame_hdr_size)
{
    struct Elf32_Shdr *shdr;

    lvaddr_t addr = 0;
    size_t size = 0;

    shdr= elf32_find_section_header_name(elfbase, elfsize, ".eh_frame");
    if (shdr != NULL) {
        size = shdr->sh_size;
        addr = shdr->sh_addr;
    }

    if (eh_frame) {
        *eh_frame = addr;
    }
    if (eh_frame_size) {
        *eh_frame_size = size;
    }

    size = 0;
    addr = 0;

    shdr= elf32_find_section_header_name(elfbase, elfsize, ".eh_frame_hdr");
    if (shdr != NULL) {
        size = shdr->sh_size;
        addr = shdr->sh_addr;
    }

    if (eh_frame_hdr) {
        *eh_frame_hdr = addr;
    }
    if (eh_frame_hdr_size) {
        *eh_frame_hdr_size = size;
    }

    return SYS_ERR_OK;
}

static errval_t elf64_get_eh_info(lvaddr_t elfbase, size_t elfsize,
                                  lvaddr_t *eh_frame, size_t *eh_frame_size,
                                  lvaddr_t *eh_frame_hdr, size_t *eh_frame_hdr_size)
{
    struct Elf64_Shdr *shdr;

    lvaddr_t addr = 0;
    size_t size = 0;

    shdr= elf64_find_section_header_name(elfbase, elfsize, ".eh_frame");
    if (shdr != NULL) {
        size = shdr->sh_size;
        addr = shdr->sh_addr;
    }

    if (eh_frame) {
        *eh_frame = addr;
    }
    if (eh_frame_size) {
        *eh_frame_size = size;
    }

    size = 0;
    addr = 0;

    shdr= elf64_find_section_header_name(elfbase, elfsize, ".eh_frame_hdr");
    if (shdr != NULL) {
        size = shdr->sh_size;
        addr = shdr->sh_addr;
    }

    if (eh_frame_hdr) {
        *eh_frame_hdr = addr;
    }
    if (eh_frame_hdr_size) {
        *eh_frame_hdr_size = size;
    }

    return SYS_ERR_OK;
}

/**
 * \brief obtains the error handling frame information form the elf image
 *
 * \param elfbase            virtual base address of the mapped elf
 * \param elfsize            size of the elf in bytes
 * \param eh_frame           returns the virtual address of the eh_frame
 * \param eh_frame_size      returns the size of the eh_frame
 * \param eh_frame_hdr       returns the virtual address of the eh_frame_hdr
 * \param eh_frame_hdr_size  returns the size of the eh_frame_hdr
 *
 * \returns SYS_ERR_OK on success
 */
errval_t elf_get_eh_info(lvaddr_t elfbase, size_t elfsize,
                         lvaddr_t *eh_frame, size_t *eh_frame_size,
                         lvaddr_t *eh_frame_hdr, size_t *eh_frame_hdr_size)
{
    struct Elf64_Ehdr *ehead = (struct Elf64_Ehdr *)elfbase;
    if (IS_ELF(*ehead)) {
        switch (ehead->e_ident[EI_CLASS]) {
          case ELFCLASS64:
            return elf64_get_eh_info(elfbase, elfsize, eh_frame, eh_frame_size,
                                     eh_frame_hdr, eh_frame_hdr_size);
            break;
          case ELFCLASS32:
            return  elf32_get_eh_info(elfbase, elfsize, eh_frame, eh_frame_size,
                                      eh_frame_hdr, eh_frame_hdr_size);
            break;
        }
    }
    return ELF_ERR_HEADER;
}
