/*
 * elf.c - elf parser: find and map process CODE and DATA segments
 *
 * Authors   Alain Greiner    (2016)
 *
 * Copyright (c) UPMC Sorbonne Universites
 *
 * This file is part of ALMOS-MKH.
 *
 * ALMOS-MKH 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; version 2.0 of the License.
 *
 * ALMOS-MKH 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 ALMOS-MKH; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <almos_config.h>
#include <hal_types.h>
#include <hal_uspace.h>
#include <printk.h>
#include <process.h>
#include <vseg.h>
#include <kmem.h>
#include <vfs.h>
#include <elf.h>


///////////////////////////////////////////////////////////////////
// This static function checks the .elf header.
// - return true if legal header.
// - return false with an error message if illegal header.
///////////////////////////////////////////////////////////////////
static bool_t elf_isValidHeader(Elf32_Ehdr *header)
{
	if((header->e_ident[EI_CLASS] == ELFCLASS32) 
	   && (header->e_ident[EI_DATA] == ELFDATA2LSB) 
	   && (header->e_ident[EI_VERSION] == EV_CURRENT)
	   && (header->e_ident[EI_OSABI] == ELFOSABI_NONE)
	   && ((header->e_machine == EM_MIPS) || 
	       (header->e_machine == EM_MIPS_RS3_LE) ||
	       (header->e_machine == EM_386))
	   && (header->e_type == ET_EXEC))          return true;
   
	if( header->e_ident[EI_CLASS] != ELFCLASS32 )
    printk("\n[ERROR] in %s : Elf is not 32-Binary\n", __FUNCTION__ );

	if( header->e_ident[EI_DATA] != ELFDATA2LSB )
    printk("\n[ERROR] in %s : Elf is not 2's complement, little endian\n", __FUNCTION__ );

	if( header->e_ident[EI_VERSION] != EV_CURRENT ) 
    printk("\n[ERROR] in %s : Elf is not in Current Version\n", __FUNCTION__);

	if( header->e_ident[EI_OSABI] != ELFOSABI_NONE )
    printk("\n[ERROR] in %s : Unexpected Elf ABI, need UNIX System V ABI\n", __FUNCTION__ );

	if( (header->e_machine == EM_MIPS) || 
		(header->e_machine == EM_MIPS_RS3_LE) || 
        (header->e_machine == EM_386) ) 
	printk("\n[ERROR] in %s : unexpected core / accept only MIPS or 80386\n", __FUNCTION__ );

	if( header->e_type == ET_EXEC )
	printk("\n[ERROR] in %s : Elf is not executable binary\n", __FUNCTION__ );

	return false;
}

///////////////////////////////////////////////////////////////////////////////////////
// This function load the .elf header in the buffer allocated by the caller.
// @ file   : extended pointer on the remote file descriptor.
// @ buffer : pointer on buffer allocated by the caller.
// @ size   : number of bytes to read.
///////////////////////////////////////////////////////////////////////////////////////
static error_t elf_header_read( xptr_t   file_xp,
                                void   * buffer,
                                uint32_t size )
{ 
    uint32_t  count;

    // load .elf header 
	count = vfs_read( file_xp , buffer , size );

	if( count != size )
	{
		printk("\n[ERROR] in %s : failed to read ELF header\n", __FUNCTION__ );
		return -1;
	}

    Elf32_Ehdr * header = (Elf32_Ehdr *)buffer;
    
	if( (header->e_ident[EI_MAG0] != ELFMAG0) ||
	    (header->e_ident[EI_MAG1] != ELFMAG1) ||
	    (header->e_ident[EI_MAG2] != ELFMAG2) ||
	    (header->e_ident[EI_MAG3] != ELFMAG3) )
	{
		printk("\n[ERROR] in %s : file %s not in ELF format\n", __FUNCTION__ );
		return -1;
	}

	if( !(elf_isValidHeader( header ) ) )
	{
		printk("\n[ERROR] in %s : not supported Elf\n", __FUNCTION__ );
		return -1;
	}
	return 0;
}  // end elf_header_read()

///////////////////////////////////////////////////////////////////////////////////////
// This function registers in the process VMM the CODE and DATA segments.
// @ file      : extended pointer on the remote file descriptor.
// @ segs_base : local pointer on buffer containing the segments descriptors array
// @ segs_nr   : number of segments in segment descriptors array.
// @ process   : local pointer on process descriptor.
///////////////////////////////////////////////////////////////////////////////////////
static error_t elf_segments_load( xptr_t       file_xp,
				                  void       * segs_base,
                                  uint32_t     nb_segs,
				                  process_t  * process )
{
	error_t      error;
	uint32_t     index;
	uint32_t     file_size;
    uint32_t     mem_size;
	intptr_t     start;
	uint32_t     type;
	uint32_t     flags;
	uint32_t     offset;
    vseg_t     * vseg;

    Elf32_Phdr * seg_ptr = (Elf32_Phdr *)segs_base;

    // loop on segments
	for( index = 0 ; index < nb_segs ; index++ , seg_ptr++ ) 
	{   
		if( seg_ptr->p_type != PT_LOAD) continue;
    
        // get segment attributes
		start     = seg_ptr->p_vaddr;
        offset    = seg_ptr->p_offset;
        file_size = seg_ptr->p_filesz;
        mem_size  = seg_ptr->p_memsz;
        flags     = seg_ptr->p_flags;

        // check alignment
		if( start & CONFIG_PPM_PAGE_MASK )
		{
			printk("\n[WARNING] in %s : segment base not aligned = %x\n",
			       __FUNCTION__, start );
		}

        // check size
        if( file_size != mem_size )
		{
			printk("\n[WARNING] in %s : base = %x / mem_size = %x / file_size = %x\n",
			       __FUNCTION__, start , mem_size , file_size);
		}

        // set seek on segment base in file
		error = vfs_lseek( file_xp , offset , VFS_SEEK_SET , NULL );

		if( error )
		{
			printk("\n[ERROR] in %s : failed to seek\n", __FUNCTION__ );
			return -1;
		}

		if( flags & PF_X ) // found CODE segment
		{
            type                       = VSEG_TYPE_CODE;
			process->vmm.code_vpn_base = start >> CONFIG_PPM_PAGE_SHIFT;

			elf_dmsg("\n[INFO] %s found CODE vseg / base = %x / size = %x\n",
                     __FUNCTION__ , start , mem_size );
		}
		else                   // found DATA segment
		{
            type                       = VSEG_TYPE_DATA;
			process->vmm.data_vpn_base = start >> CONFIG_PPM_PAGE_SHIFT;

			elf_dmsg("\n[INFO] %s found DATA vseg / base = %x / size = %x\n",
                     __FUNCTION__, start , mem_size );
		}

        // register vseg in VMM
		vseg = (vseg_t *)vmm_create_vseg( process, 
					                      start,
					                      mem_size, 
					                      type );
		if( vseg == NULL )
		{
			printk("\n[ERROR] in %s : cannot map segment / base = %x / size = %x\n",
			       __FUNCTION__ , start , mem_size );
			return -1;
		}

		vfs_file_count_up( file_xp );
	}

	return 0;
} // end elf_load_segments()

///////////////////////////////////////////////
error_t elf_load_process( char      * pathname,
                          process_t * process)
{
	kmem_req_t   req;              // kmem request for program header
    char         path_copy[256];   // local copy of pathname
	uint32_t     length;           // actual path length
	Elf32_Ehdr   header;           // local buffer for .elf header
	void       * segs_base;        // pointer on buffer for segment descriptors array
    uint32_t     segs_size;        // size of buffer for segment descriptors array  
	xptr_t       file_xp;          // extended pointer on created file descriptor
    uint32_t     count;            // bytes counter
	error_t      error;

    // get path length from user space
	length = hal_strlen_from_uspace( pathname );

    if( length > 255 )
    {
        printk("\n[ERROR] in %s : pathname length too long\n", __FUNCTION__ );
        return -1;
    }

    // make a local copy for pathname
    hal_copy_from_uspace( path_copy , pathname , length+1 );

    // open file
    file_xp = XPTR_NULL;  // avoid GCC warning

	error = vfs_open( process->vfs_cwd_xp, 
                      path_copy,
                      VFS_O_RDONLY,
                      &file_xp );
	if( error )
	{
		printk("\n[ERROR] in %s : failed to open executable file %s\n",
               __FUNCTION__ , path_copy );
		return -1;
	}

    // load header in local buffer
	error = elf_header_read( file_xp , 
                             &header,
                             sizeof(Elf32_Ehdr) );
	if( error ) 
    {
        vfs_close( file_xp , &count );
        return -1;
    }

	elf_dmsg("\n[INFO] %s loaded elf header for %s\n", __FUNCTION__ , path_copy );

	if( header.e_phnum == 0 )
	{
		printk("\n[ERROR] in %s : no segments found\n", __FUNCTION__ );
        vfs_close( file_xp , &count );
		return -1;
	}

    // compute buffer size for segment descriptors array
	segs_size = sizeof(Elf32_Phdr) * header.e_phnum;
  
    // allocate memory for segment descriptors array
	req.type  = KMEM_GENERIC;
	req.size  = segs_size;
	req.flags = AF_KERNEL;
	segs_base = kmem_alloc( &req );

	if( segs_base == NULL )
    {
		printk("\n[ERROR] in %s : no memory for segment descriptors\n", __FUNCTION__ );
        vfs_close( file_xp , &count );
        return -1;
    }

    // set seek pointer in file descriptor to access segment descriptors array
	error = vfs_lseek( file_xp , header.e_phoff, VFS_SEEK_SET , NULL );

	if( error )
	{
		printk("\n[ERROR] in %s : cannot seek for descriptors array\n", __FUNCTION__ );
        vfs_close( file_xp , &count );
	    req.ptr = segs_base;
	    kmem_free( &req );
		return -1;
	}

    // load seg descriptors array to local buffer 
	count = vfs_read( file_xp, 
                      segs_base,
                      segs_size );

	if( count != segs_size )
	{
		printk("\n[ERROR] in %s : cannot read segments descriptors\n", __FUNCTION__ );
        vfs_close( file_xp , &count );
	    req.ptr = segs_base;
	    kmem_free( &req );
		return -1;
	}

	elf_dmsg("\n[INFO] %s loaded segments descriptors for %s \n", __FUNCTION__ , path_copy );

    // register loadable segments in process VMM
	error = elf_segments_load( file_xp, 
                               segs_base,
                               header.e_phnum,
                               process );
	if( error )
    {
        vfs_close( file_xp , &count );
	    req.ptr = segs_base;
	    kmem_free( &req );
		return -1;
    }

    // register process entry point in VMM
	process->vmm.entry_point = (intptr_t)header.e_entry;

    // register extended pointer on .elf file descriptor 
    process->vfs_bin_xp = file_xp;

    // release allocated memory for program header
	req.ptr = segs_base;
	kmem_free(&req);

	elf_dmsg("\n[INFO] %s successfully completed / entry point = %x for %s]\n", 
		 __FUNCTION__, (uint32_t) header.e_entry , path_copy );

	return 0;
}  // end elf_load_process()

