/**
 * \file    : reset_elf_loader.c
 * \date    : August 2012
 * \author  : Cesar Fuguet
 *
 * This file defines an elf file loader which reads an executable .elf file
 * starting at a sector passed as argument on a disk and copy the different
 * ELF program segments in the appropriate memory address using as information
 * the virtual address read from the .elf file.
 */

#include <reset_ioc.h>
#include <elf-types.h>
#include <reset_tty.h>
#include <reset_utils.h>
#include <defs.h>

///////////////////////////////////////////////////////////////////////////////
void * reset_elf_loader(size_t lba)
///////////////////////////////////////////////////////////////////////////////
{
    size_t file_offset = lba * BLOCK_SIZE;

    reset_puts("\n[RESET] Start reset_elf_loader at cycle ");
    reset_putd( proctime() );
    reset_puts("\n");

    /*
     * Load ELF HEADER
     */
    Elf32_Ehdr elf_header;
    if (pread(file_offset, (void*)&elf_header, sizeof(Elf32_Ehdr), 0) < 0) {
        goto error;
    }
    check_elf_header(&elf_header);

    /*
     * Load ELF PROGRAM HEADER TABLE
     */
    Elf32_Phdr elf_pht[PHDR_ARRAY_SIZE];
    size_t phdr_nbyte = sizeof(Elf32_Phdr) * elf_header.e_phnum;
    size_t phdr_off = elf_header.e_phoff;
    if (pread(file_offset, (void*)&elf_pht, phdr_nbyte, phdr_off) < 0) {
        goto error;
    }

    /*
     * Search for loadable segments in the ELF file
     */
    int pseg;
    for (pseg = 0; pseg < elf_header.e_phnum; pseg++)
    {
        if(elf_pht[pseg].p_type != PT_LOAD) continue;

#if (RESET_DEBUG == 1)
        reset_puts("[RESET DEBUG] Loadable segment found:\n");
        reset_print_elf_phdr(&elf_pht[pseg]);
#endif

        addr_t p_paddr = elf_pht[pseg].p_paddr;
        size_t p_filesz = elf_pht[pseg].p_filesz;
        size_t p_memsz = elf_pht[pseg].p_memsz;
        size_t p_offset = elf_pht[pseg].p_offset;

        /*
         * Copy program segment from ELF executable into corresponding physical
         * address
         */
        if (pread(file_offset, (void*)p_paddr, p_filesz, p_offset) < 0) {
            goto error;
        }

        /*
         * Fill remaining bytes with zero (filesz < memsz)
         */
        char* pseg_ptr = (char*)p_paddr;
        memset((void*)&pseg_ptr[p_filesz], 0, (p_memsz - p_filesz));

        reset_puts("\n[RESET] Segment loaded : address = ");
        reset_putx(p_paddr);
        reset_puts(" / size = ");
        reset_putx(p_filesz);
        reset_puts("\n");
    }

    reset_puts("\n[RESET] Complete reset_elf_loader at cycle ");
    reset_putd( proctime() );
    reset_puts(" / boot entry = ");
    reset_putx( (addr_t)(elf_header.e_entry) );
    reset_puts("\n");

    return ((void *) elf_header.e_entry);

error:
    reset_puts("\n[RESET ERROR] Error while loading ELF file");
    reset_exit();
    return 0;
}

// vim: tabstop=4 : softtabstop=4 : shiftwidth=4 : expandtab
